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

Fix width of box plots with log-scale axis #4283

Merged
merged 5 commits into from
Oct 28, 2019
Merged
Show file tree
Hide file tree
Changes from 2 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
29 changes: 19 additions & 10 deletions src/plots/cartesian/autorange.js
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,8 @@ function doAutoRange(gd, ax) {
* (unless one end is overridden by tozero)
* tozero: (boolean) make sure to include zero if axis is linear,
* and make it a tight bound if possible
* vpadLinearized: (boolean) whether or not vpad (or vpadplus/vpadminus)
* is linearized (for log scale axes)
*
* @return {object}
* - min {array of objects}
Expand All @@ -320,6 +322,7 @@ function findExtremes(ax, data, opts) {
var tozero = opts.tozero && (ax.type === 'linear' || ax.type === '-');
var isLog = ax.type === 'log';
var hasArrayOption = false;
var vpadLinearized = opts.vpadLinearized || false;
var i, v, di, dmin, dmax, ppadiplus, ppadiminus, vmin, vmax;

function makePadAccessor(item) {
Expand Down Expand Up @@ -371,16 +374,22 @@ function findExtremes(ax, data, opts) {
if(!isNumeric(di)) return;
ppadiplus = ppadplus(i);
ppadiminus = ppadminus(i);
vmin = di - vpadminus(i);
vmax = di + vpadplus(i);
// special case for log axes: if vpad makes this object span
// more than an order of mag, clip it to one order. This is so
// we don't have non-positive errors or absurdly large lower
// range due to rounding errors
if(isLog && vmin < vmax / 10) vmin = vmax / 10;

dmin = ax.c2l(vmin);
dmax = ax.c2l(vmax);

if(vpadLinearized) {
dmin = ax.c2l(di) - vpadminus(i);
dmax = ax.c2l(di) + vpadplus(i);
} else {
vmin = di - vpadminus(i);
vmax = di + vpadplus(i);
// special case for log axes: if vpad makes this object span
// more than an order of mag, clip it to one order. This is so
// we don't have non-positive errors or absurdly large lower
// range due to rounding errors
// if(isLog && vmin < vmax / 10) vmin = vmax / 10;
etpinard marked this conversation as resolved.
Show resolved Hide resolved

dmin = ax.c2l(vmin);
dmax = ax.c2l(vmax);
}

if(tozero) {
dmin = Math.min(0, dmin);
Expand Down
4 changes: 2 additions & 2 deletions src/traces/box/cross_trace_calc.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ function setPositionOffset(traceType, gd, boxList, posAxis) {
for(i = 0; i < boxList.length; i++) {
calcTrace = calcdata[boxList[i]];
for(j = 0; j < calcTrace.length; j++) {
pointList.push(calcTrace[j].pos);
pointList.push(posAxis.c2l(calcTrace[j].pos, true));
shownPts += (calcTrace[j].pts2 || []).length;
}
}
Expand Down Expand Up @@ -125,7 +125,6 @@ function setPositionOffset(traceType, gd, boxList, posAxis) {
t.bPos = bPos;
t.bdPos = bdPos;
t.wHover = wHover;

etpinard marked this conversation as resolved.
Show resolved Hide resolved
// box/violin-only value-space push value
var pushplus;
var pushminus;
Expand Down Expand Up @@ -213,6 +212,7 @@ function setPositionOffset(traceType, gd, boxList, posAxis) {
padded: padded,
vpadminus: vpadminus,
vpadplus: vpadplus,
vpadLinearized: true,
Copy link
Contributor

Choose a reason for hiding this comment

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

So, could you explain why we need to pad in linear space (l) for log axes as opposed to in calc space (c)?

Padding in calc space on log axes seems to work ok in other scenarios like scatter-marker traces.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think paddings in scatter-marker traces are not based on values (vpad), but pixels (ppad). So the padding is not affected by axes types.
Another example that uses values as margins is box traces, but box traces should be based on c values rather than l values.

// N.B. SVG px-space positive/negative
ppadminus: {x: ppadminus, y: ppadplus}[axLetter],
ppadplus: {x: ppadplus, y: ppadminus}[axLetter],
Expand Down
2 changes: 1 addition & 1 deletion src/traces/box/hover.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ function hoverOnBoxes(pointData, xval, yval, hovermode) {
var boxDelta = t.bdPos;
var boxDeltaPos, boxDeltaNeg;
var posAcceptance = t.wHover;
var shiftPos = function(di) { return di.pos + t.bPos - pVal; };
var shiftPos = function(di) { return pAxis.c2l(di.pos) + t.bPos - pAxis.c2l(pVal); };

if(isViolin && trace.side !== 'both') {
if(trace.side === 'positive') {
Expand Down
23 changes: 12 additions & 11 deletions src/traces/box/plot.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,14 +86,14 @@ function plotBoxAndWhiskers(sel, axes, trace, t) {
paths.each(function(d) {
if(d.empty) return 'M0,0Z';

var pos = d.pos;
var posc = posAxis.c2p(pos + bPos, true) + bPosPxOffset;
var pos0 = posAxis.c2p(pos + bPos - bdPos0, true) + bPosPxOffset;
var pos1 = posAxis.c2p(pos + bPos + bdPos1, true) + bPosPxOffset;
var posw0 = posAxis.c2p(pos + bPos - wdPos, true) + bPosPxOffset;
var posw1 = posAxis.c2p(pos + bPos + wdPos, true) + bPosPxOffset;
var posm0 = posAxis.c2p(pos + bPos - bdPos0 * nw, true) + bPosPxOffset;
var posm1 = posAxis.c2p(pos + bPos + bdPos1 * nw, true) + bPosPxOffset;
var lcenter = posAxis.c2l(d.pos + bPos, true);
var posc = posAxis.l2p(lcenter) + bPosPxOffset;
var pos0 = posAxis.l2p(lcenter - bdPos0) + bPosPxOffset;
var pos1 = posAxis.l2p(lcenter + bdPos1) + bPosPxOffset;
var posw0 = posAxis.l2p(lcenter - wdPos) + bPosPxOffset;
var posw1 = posAxis.l2p(lcenter + wdPos) + bPosPxOffset;
var posm0 = posAxis.l2p(lcenter - bdPos0 * nw) + bPosPxOffset;
var posm1 = posAxis.l2p(lcenter + bdPos1 * nw) + bPosPxOffset;
var q1 = valAxis.c2p(d.q1, true);
var q3 = valAxis.c2p(d.q3, true);
// make sure median isn't identical to either of the
Expand Down Expand Up @@ -288,9 +288,10 @@ function plotBoxMean(sel, axes, trace, t) {
paths.exit().remove();

paths.each(function(d) {
var posc = posAxis.c2p(d.pos + bPos, true) + bPosPxOffset;
var pos0 = posAxis.c2p(d.pos + bPos - bdPos0, true) + bPosPxOffset;
var pos1 = posAxis.c2p(d.pos + bPos + bdPos1, true) + bPosPxOffset;
var lcenter = posAxis.c2l(d.pos + bPos, true);
var posc = posAxis.l2p(lcenter) + bPosPxOffset;
var pos0 = posAxis.l2p(lcenter - bdPos0) + bPosPxOffset;
var pos1 = posAxis.l2p(lcenter + bdPos1) + bPosPxOffset;
var m = valAxis.c2p(d.mean, true);
var sl = valAxis.c2p(d.mean - d.sd, true);
var sh = valAxis.c2p(d.mean + d.sd, true);
Expand Down
Binary file added test/image/baselines/box_log_scale.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
94 changes: 94 additions & 0 deletions test/image/mocks/box_log_scale.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
{
Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks very much for taking a shot a violin traces on log axes!

There seem to be a small problem with the position of the violin "box":

image

the tall line at the right of the box should be drawn where the shading from the one-side violin ends.

Copy link
Contributor

Choose a reason for hiding this comment

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

Actually this has nothing to do with your PR, all vertical one-sided violins have their box line shifted in the wrong direction e.g.:

https://codepen.io/etpinard/pen/RwwZZzj?editors=1010


No need to address this here. I'll make a PR in the next few minutes fixing that.

"data": [
{
"y": [
1.0,
1.1,
1.0,
0.9,
1.2,
2.0,
1.5,
2.3,
1.7,
2.2,
1.0,
1.1,
1.2,
0.9,
1.1
],
"x": [
1,
1,
1,
1,
1,
10,
10,
10,
10,
10,
100,
100,
100,
100,
100
],
"type": "box"
Copy link
Contributor

Choose a reason for hiding this comment

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

Have you tried plotting these samples as type: 'violin' traces?

Violin and box traces share a lot of the same code, so it might be nice to fix the log-position problems for violin traces in the same go.

@s417-lama please let us know if you're not interested in working on a fix for violin traces. We should be able to find some time to work on it before releasing v1.51.0.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh, I didn't consider violin traces.
I would be glad for you to work on violin plots. I appreciate it.

Copy link
Contributor

Choose a reason for hiding this comment

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

Great. Thanks!! The fix for violins should be very similar than the one for boxes.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed! 349ef64

},
{
"y": [
2.2,
2.3,
2.0,
2.5,
2.1,
0.1,
0.5,
0.8,
0.3,
0.3,
1.1,
1.2,
0.9,
1.0,
1.0,
2.4,
2.0,
1.5,
1.6,
1.9
],
"x": [
1,
1,
1,
1,
1,
10,
10,
10,
10,
10,
50,
50,
50,
50,
50,
100,
100,
100,
100,
100
],
"type": "box",
"boxmean": true
}
],
"layout": {
"xaxis": {
"type": "log"
}
}
}