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

Stacked Area Charts #2960

Merged
merged 14 commits into from
Sep 7, 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
112 changes: 63 additions & 49 deletions src/plots/cartesian/autorange.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,13 @@ function getAutoRange(gd, ax) {
ax.autorange = true;
}

var rangeMode = ax.rangemode;
var toZero = rangeMode === 'tozero';
var nonNegative = rangeMode === 'nonnegative';
var axLen = ax._length;
// don't allow padding to reduce the data to < 10% of the length
var minSpan = axLen / 10;

var mbest = 0;
var minpt, maxpt, minbest, maxbest, dp, dv;

Expand All @@ -95,76 +102,83 @@ function getAutoRange(gd, ax) {
for(j = 0; j < maxArray.length; j++) {
maxpt = maxArray[j];
dv = maxpt.val - minpt.val;
dp = ax._length - getPad(minpt) - getPad(maxpt);
if(dv > 0 && dp > 0 && dv / dp > mbest) {
minbest = minpt;
maxbest = maxpt;
mbest = dv / dp;
if(dv > 0) {
dp = axLen - getPad(minpt) - getPad(maxpt);
if(dp > minSpan) {
if(dv / dp > mbest) {
minbest = minpt;
maxbest = maxpt;
mbest = dv / dp;
}
}
else if(dv / axLen > mbest) {
// in case of padding longer than the axis
// at least include the unpadded data values.
minbest = {val: minpt.val, pad: 0};
maxbest = {val: maxpt.val, pad: 0};
mbest = dv / axLen;
etpinard marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
}

function getMaxPad(prev, pt) {
return Math.max(prev, getPad(pt));
}

if(minmin === maxmax) {
var lower = minmin - 1;
var upper = minmin + 1;
if(ax.rangemode === 'tozero') {
newRange = minmin < 0 ? [lower, 0] : [0, upper];
} else if(ax.rangemode === 'nonnegative') {
newRange = [Math.max(0, lower), Math.max(0, upper)];
if(toZero) {
if(minmin === 0) {
// The only value we have on this axis is 0, and we want to
// autorange so zero is one end.
// In principle this could be [0, 1] or [-1, 0] but usually
// 'tozero' pins 0 to the low end, so follow that.
newRange = [0, 1];
}
else {
var maxPad = (minmin > 0 ? maxArray : minArray).reduce(getMaxPad, 0);
// we're pushing a single value away from the edge due to its
// padding, with the other end clamped at zero
// 0.5 means don't push it farther than the center.
var rangeEnd = minmin / (1 - Math.min(0.5, maxPad / axLen));
newRange = minmin > 0 ? [0, rangeEnd] : [rangeEnd, 0];
}
} else if(nonNegative) {
newRange = [Math.max(0, lower), Math.max(1, upper)];
} else {
newRange = [lower, upper];
}
}
else if(mbest) {
if(ax.type === 'linear' || ax.type === '-') {
if(ax.rangemode === 'tozero') {
if(minbest.val >= 0) {
minbest = {val: 0, pad: 0};
}
if(maxbest.val <= 0) {
maxbest = {val: 0, pad: 0};
}
else {
if(toZero) {
if(minbest.val >= 0) {
minbest = {val: 0, pad: 0};
}
else if(ax.rangemode === 'nonnegative') {
if(minbest.val - mbest * getPad(minbest) < 0) {
minbest = {val: 0, pad: 0};
}
if(maxbest.val < 0) {
maxbest = {val: 1, pad: 0};
}
if(maxbest.val <= 0) {
maxbest = {val: 0, pad: 0};
}
}
else if(nonNegative) {
if(minbest.val - mbest * getPad(minbest) < 0) {
minbest = {val: 0, pad: 0};
}
if(maxbest.val <= 0) {
maxbest = {val: 1, pad: 0};
}

// in case it changed again...
mbest = (maxbest.val - minbest.val) /
(ax._length - getPad(minbest) - getPad(maxbest));

}

// in case it changed again...
mbest = (maxbest.val - minbest.val) /
(axLen - getPad(minbest) - getPad(maxbest));

newRange = [
minbest.val - mbest * getPad(minbest),
maxbest.val + mbest * getPad(maxbest)
];
}

// don't let axis have zero size, while still respecting tozero and nonnegative
if(newRange[0] === newRange[1]) {
etpinard marked this conversation as resolved.
Show resolved Hide resolved
if(ax.rangemode === 'tozero') {
if(newRange[0] < 0) {
newRange = [newRange[0], 0];
} else if(newRange[0] > 0) {
newRange = [0, newRange[0]];
} else {
newRange = [0, 1];
}
}
else {
newRange = [newRange[0] - 1, newRange[0] + 1];
if(ax.rangemode === 'nonnegative') {
newRange[0] = Math.max(0, newRange[0]);
}
}
}

// maintain reversal
if(axReverse) newRange.reverse();

Expand Down
2 changes: 1 addition & 1 deletion src/plots/polar/layout_defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ function handleDefaults(contIn, contOut, coerce, opts) {
case 'radialaxis':
var autoRange = coerceAxis('autorange', !axOut.isValidRange(axIn.range));
axIn.autorange = autoRange;
if(autoRange) coerceAxis('rangemode');
if(autoRange && (axType === 'linear' || axType === '-')) coerceAxis('rangemode');
if(autoRange === 'reversed') axOut._m = -1;

coerceAxis('range');
Expand Down
Binary file modified test/image/baselines/autorange-tozero-rangemode.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified test/image/baselines/axes_range_type.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified test/image/baselines/contour_log.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 0 additions & 2 deletions test/image/mocks/scatter_fill_corner_cases.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,6 @@
{
"x": [1.5],
"y": [1.25],
"fill": "tonexty",
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Oh this change I think is actually required due to a change in the stacked area commit be38e93#diff-33c02cd37e7a4c951059a3c93221ac4eR175 - we were accidentally treating a length-1 trace as filling to itself (since its start and end points are the same!) but we shouldn't do that... therefore this trace, since it's the first on its subplot, should interpret 'tonexty' as 'tozeroy'.

"showlegend": false,
"yaxis": "y2"
},
Expand Down Expand Up @@ -111,7 +110,6 @@
{
"x": [1.5],
"y": [1.25],
"fill": "tonexty",
"line": {"shape": "spline"},
"xaxis": "x2",
"showlegend": false,
Expand Down
94 changes: 88 additions & 6 deletions test/jasmine/tests/axes_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1558,7 +1558,7 @@ describe('Test axes', function() {
expect(getAutoRange(gd, ax)).toEqual([7.5, 0]);
});

it('expands empty positive range to something including 0 with rangemode tozero', function() {
it('expands empty positive range to include 0 with rangemode tozero', function() {
gd = mockGd([
{val: 5, pad: 0}
], [
Expand All @@ -1567,7 +1567,7 @@ describe('Test axes', function() {
ax = mockAx();
ax.rangemode = 'tozero';

expect(getAutoRange(gd, ax)).toEqual([0, 6]);
expect(getAutoRange(gd, ax)).toEqual([0, 5]);
});

it('expands empty negative range to something including 0 with rangemode tozero', function() {
Expand All @@ -1579,7 +1579,63 @@ describe('Test axes', function() {
ax = mockAx();
ax.rangemode = 'tozero';

expect(getAutoRange(gd, ax)).toEqual([-6, 0]);
expect(getAutoRange(gd, ax)).toEqual([-5, 0]);
});

it('pads an empty range, but not past center, with rangemode tozero', function() {
gd = mockGd([
{val: 5, pad: 50} // this min pad gets ignored
], [
{val: 5, pad: 20}
]);
ax = mockAx();
ax.rangemode = 'tozero';

expect(getAutoRange(gd, ax)).toBeCloseToArray([0, 6.25], 0.01);

gd = mockGd([
{val: -5, pad: 80}
], [
{val: -5, pad: 0}
]);
ax = mockAx();
ax.rangemode = 'tozero';

expect(getAutoRange(gd, ax)).toBeCloseToArray([-10, 0], 0.01);
});

it('shows the data even if it cannot show the padding', function() {
gd = mockGd([
{val: 0, pad: 44}
], [
{val: 1, pad: 44}
]);
ax = mockAx();

// this one is *just* on the allowed side of padding
// ie data span is just over 10% of the axis
expect(getAutoRange(gd, ax)).toBeCloseToArray([-3.67, 4.67]);

gd = mockGd([
{val: 0, pad: 46}
], [
{val: 1, pad: 46}
]);
ax = mockAx();

// this one the padded data span would be too small, so we delete
// the padding
expect(getAutoRange(gd, ax)).toEqual([0, 1]);

gd = mockGd([
{val: 0, pad: 400}
], [
{val: 1, pad: 0}
]);
ax = mockAx();

// this one the padding is simply impossible to accept!
expect(getAutoRange(gd, ax)).toEqual([0, 1]);
});

it('never returns a negative range when rangemode nonnegative is set with positive and negative points', function() {
Expand Down Expand Up @@ -1614,17 +1670,43 @@ describe('Test axes', function() {
expect(getAutoRange(gd, ax)).toEqual([0, 1]);
});

it('expands empty range to something nonnegative with rangemode nonnegative', function() {
it('never returns a negative range when rangemode nonnegative is set with only nonpositive points', function() {
gd = mockGd([
{val: -5, pad: 0}
{val: -10, pad: 20},
{val: -8, pad: 0},
{val: -9, pad: 10}
], [
{val: -5, pad: 0}
{val: -5, pad: 20},
{val: 0, pad: 0},
{val: -6, pad: 10}
]);
ax = mockAx();
ax.rangemode = 'nonnegative';

expect(getAutoRange(gd, ax)).toEqual([0, 1]);
});

it('expands empty range to something nonnegative with rangemode nonnegative', function() {
[
[-5, [0, 1]],
[0, [0, 1]],
[0.5, [0, 1.5]],
[1, [0, 2]],
[5, [4, 6]]
].forEach(function(testCase) {
var val = testCase[0];
var expected = testCase[1];
gd = mockGd([
{val: val, pad: 0}
], [
{val: val, pad: 0}
]);
ax = mockAx();
ax.rangemode = 'nonnegative';

expect(getAutoRange(gd, ax)).toEqual(expected, val);
});
});
});

describe('findExtremes', function() {
Expand Down