-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
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
Rangeslider allow zoom on oppaxis #2364
Rangeslider allow zoom on oppaxis #2364
Conversation
Thanks for the PR. Impressive work and clear explanation! But I have to say I'm not 100% sure the current behavior is incorrect. It might just be a matter of preference. Perhaps we can add an attribute e.g. |
Just added a new attributes |
Not really sure why the last test is failling, any hint from your side ? |
@@ -89,5 +89,17 @@ module.exports = { | |||
'If visible, perpendicular axes will be set to `fixedrange`' | |||
].join(' ') | |||
}, | |||
perpendicularaxesinitialrange: { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is probably a little to verbose for our needs. Attribute names rarely have more than two words smashed together.
The best option that comes to my mind is xaxis.rangeslider.fixedanchor: false || true
, but anchor
is used already elsewhere to declare a different thing.
Looking for opinions from @alexcjohnson @cldougl and @chriddyp
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I find it also too verbose but I prefered to use a clear name until we have a better proposal (since anchor is already used).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
given that the range slider is always an x axis, we could use something like fixedyrange
- and if we ever make a y axis range slider it can get a fixedxrange
attribute.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It does makes sense, I was not quite sure rangeslider was for x-axis only.
We've been fighting some intermittent test failures lately. Your latest commit is now ✅ |
referencing also #2172 - I love the effect here @TomDemulierChevret, just want to make sure we have a path forward when we add potentially stacked multiple axes. I guess it just means we'd end up with multiple clear and shaded regions stacked up, which should be fine. |
By stacked multiple axes, you're referencing multiple x-axis right ? edit : just saw the issue you're referencing. |
src/plots/cartesian/axes.js
Outdated
@@ -389,6 +389,10 @@ axes.saveRangeInitial = function(gd, overwrite) { | |||
ax._rangeInitial = ax.range.slice(); | |||
hasOneAxisChanged = true; | |||
} | |||
// store the initial range for the rangeslider if we zoom on oppaxis | |||
if((isNew && ax.fixedrange === false) || (overwrite && hasChanged)) { | |||
ax._rangesliderInitialRange = ax.range.slice(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, one issue with this: _rangesliderInitialRange
isn't part of the figure JSON... so if you zoom in on the y axis, then you decide you like the plot and you save it, or you snapshot it even, the rangeslider y axis will not have the same range in the snapshot or when you reopen the plot.
That seems to me to mean that we need a real attribute for the range of each y axis on the rangeslider, and perhaps even an attribute for whether to autorange that y axis on the range slider - it would be cool if the y axis could be told to autorange in the range slider even if you start out zoomed in on the main plot, wouldn't it?
BTW I came to this realization as I was thinking "what tests should we have for this feature", noting that a big part of this is drawing code so it should have an image test, but then realizing as it stands we can't make an image test because we have no mechanism to force _rangesliderInitialRange
to be different from the y axis range in an image test, therefore we can't make the opp axis masks appear there.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, I didn't think about that.
I'll look into it.
Added new attributes to the figure JSON as suggested by @alexcjohnson.
Not sure exactly what id did broke for the |
Good call putting the new attributes into the y axis - originally I was thinking that they belonged in the
I'll try out |
// style constants | ||
|
||
maskColor: 'rgba(0,0,0,0.4)', | ||
maskOppColor: 'rgba(0,0,0,0.2)', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just noticed this. Why pick a different opacity value here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like this effect - you see that the y axis is constrained, but it's not shaded as darkly as the x axis constraint, which aligns with the fact that you can only affect the x axis constraint with the range slider handles.
But a different thought occurred to me on looking at this: this is semitransparent black, so does that mean range sliders don't work on a black background? That's a totally separate issue, doesn't need to be addressed in this PR, but we should look into it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed, that's what I wanted to show with the 2 different values of opacity.
And yeah, the black backgrond will be an issue.
Hmm. I think people will inevitably confuse Perhaps, it would have been best to make range sliders a root layout component? 😡 Oh well, for now I can think of 3 options: // option 1
xaxis: {
rangeslider: {
// ...
}
},
yaxis: {
// finding a good concise name for this thing is very hard
oppositerangeslider: {
mode: ' ',
range: [/* */],
}
}
// option 2
xaxis: {
rangeslider: {
yrangemode: ' ',
yrange: [/* */],
y2rangemode: ' ',
y2range: [/* */],
// ...
}
},
// option 3 - which would be more similar to other
// attributes with axis counters
xaxis: {
rangeslider: {
rangemodey: ' ',
rangey: [/* */],
rangemodey2: ' ',
rangey2: [/* */],
// ....
} After writing the 3 options down, I'd say I prefer option 3 by far. It keeps range sliders attributes contained in their base x-axis object. While axis counters (e.g. 'y', 'y2', 'y3', ...) aren't great, they are already a pretty big part of the plotly.js api. Thoughts? |
I was hoping to avoid something like that, but I just realized there's a use case where we need to do it this way. Kinda convoluted, but imagine you have two side-by-side x axes sharing a y axis, each x axis has its own range slider, and you want different range settings for the y axes in the two rangesliders. Would be weird, as the y axes on the main plots are one axis so they must share a range... but I guess I can imagine wanting to restrict the range of the y axes in each range slider to just the data on the respective subplot. This is not possible with Option 3 is pretty good, but let me add one more to the mix: xaxis: {
rangeslider: {
yaxis: {range: [/* */], rangemode: ' '},
yaxis2: {range: [/* */], rangemode: ' '},
}
} |
(@TomDemulierChevret sorry for the back-and-forth here, attribute specs can be really tricky to get right, especially when we start thinking of all the possible edge cases involved) |
@alexcjohnson oh right, true. A given y axis can be linked to multiple x range sliders in general. Nice catch!
I like it, but I still prefer my option 3 by a slight margin (mainly due to possible confusion between We could also do something like: xaxis: {
rangeslider: {
oppaxis: {
rangemode: ' ',
range: [/* */]
},
oppaxis2: {
}
}
} that way, the same But really, maybe we should thinking about pulling layout: {
xaxis: {},
yaxis: {},
yaxis2: {},
rangesliders: [{
xref: 'x',
yref: ['y', 'y2'],
xaxis: {
range: [/* */], // equivalent to current `xaxis.rangeslider.range`
/// style attributes ...
},
yaxis: {
rangemode: ' ',
range: [/* */]
}
}] |
@TomDemulierChevret would if be ok if we start pushing a few commits to this branch directly? We're very close to merging your PR. Thanks again for all your efforts! |
Yes of course, no problem. |
yaxis3: {} | ||
}); | ||
|
||
expect(out).toBeUndefined(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As promised in https://github.com/plotly/plotly.js/pull/2364/files#r171957082
Please note, the _isSubplotObj
flag in the range slider opposite axis attributes is very important. Without it, this test would spit out:
that is Plotly.validate
would think that rangeslider.yaxis2
and rangeslider.yaxis3
aren't part of the schema.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
…ulierChevret/plotly.js into rangeslider-allow-zoom-on-oppaxis
// the logic is complicated to figure this out later, particularly for y axes since | ||
// the settings can be spread out in the x axes... so instead we'll collect them | ||
// during supplyDefaults | ||
containerOut._rangesliderAutorange = false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very clear comment. Thanks!
rangemodeDflt = 'fixed'; | ||
} | ||
|
||
var rangeMode = coerceRange(rangeContainerIn, rangeContainerOut, 'rangemode', rangemodeDflt); | ||
if(rangeMode !== 'match') { | ||
coerceRange(rangeContainerIn, rangeContainerOut, 'range', layoutOut[yName].range.slice()); | ||
coerceRange(rangeContainerIn, rangeContainerOut, 'range', yAxOut.range.slice()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok. Looks like rangeslider.yaxis?.range
is still getting coerced when rangemode: 'auto'
though this appears standard for subplot xaxis
and yaxis
range
under autorange: true
, so this sounds fine 👌 .
That said, it would be nice in the future to make:
Plotly.validate([], {
xaxis: {
autorange: true,
range: [0, 1]
}
})
spit out an unused
log message for xaxis.range
}, | ||
"title": "Rangeslider Y2 rangemode fixed" | ||
}, | ||
"yaxis": {"type": "log", "range": [2, 6], "title": "Y explicit range"}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice mock!
values: ['auto', 'fixed', 'match'], | ||
dflt: 'match', | ||
role: 'style', | ||
editType: 'calc', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@alexcjohnson we should be able to change these to editType: 'plot'
, correct?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh hmm... actually I don't think we can, it's like ax.autorange
which is still calc
, because this affects _rangesliderAutorange
which determines whether we even bother calculating ax._min/_max
during the calc
step.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok. I tried switching them to 'plot'
, but that didn't break any test 😕
.then(done); | ||
}); | ||
|
||
it('should update rangeslider x/y ranges when data changes even if main axes are not autoranged', function(done) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for testing this case!
All right. Time to merge to thing 💃 I'll let @alexcjohnson do the honors after a failing test case for #2364 (comment) is added.
Thanks a million to @TomDemulierChevret for first bringing this issue and persevering through our numerous iterations at solving this problem 💪 I'd like to make this PR an early nominee for community PR of the year 🏆 |
this test fails if rangeslider.yaxis.rangemode or rangeslider.(auto)range doesn't have editType: 'calc'
So how do we make the range slider keep a constant y axis with the latest version of plotly? What's the configuration? |
By default we can't currently zoom on the y-axis if we set a rangeslider on the x-axis.
If we write
fixedrange: false
in our y-axis config, it will override the lock put by the rangeslider and allow us to zoom both in x-and y-axis.The issue is that the rangeslider is kinda broken if we zoom on y-axis since it will not keep the initial range on y-axis (like it does for the x-axis).
This PR :
Here is a set of screenshot to illustrate the issue and the PR :
Without zoom
With zoom on y-axis before PR
With zoom on y-axis after PR
If you have multiple y-axis and not all of them are zoomable, then you must add
anchor: 'y'
in your x-axis config (withy
replaced with the name of one of the zoomable y-axis).