Closed apalchys closed 6 years ago
This looks very useful @apalchys !
To make sure I'm understanding the PR correctly, this new hover mode would be enabled by setting:
layout.xaxis.showcrossline
and/or layout.yaxis.showcrossline
to true
?
The crosslinecolor
, crosslinethickness
, and crosslinedash
could also be optionally set in the axis object for styling?
@chriddyp and @cpsievert - Any opinions on this feature from a Dash and Shiny perspective?
In particular, from the PR description #2150
Thoughts @alexcjohnson ?
Seems like a reasonable addition to me.
Just curious @apalchys - in which financial software have you seen this feature? Looks like Highcharts has a similar hover line on the x-axis for example: https://www.highcharts.com/stock/demo/lazy-loading
This feature has already been requested a couple times by the dash community:
There is this built-in Plotly "Toggle Spike Lines" function in each Graph, that does nearly what I want and is pretty fast, but not customizable.
For the api, does it make sense to match the existing shapes
API for lines (https://plot.ly/python/reference/#layout-shapes-line)? that is, instead of The crosslinecolor
, crosslinethickness
, and crosslinedash
, just:
crossline: {
color: ...,
width: ...,
dash: ...
}
Note that this is pretty similar to spikemode: 'across'
The behavior of the line should be similar to the behavior of the spikes feature, but there are several differences:
It's similar enough that we should really try to extend the spikes feature rather than add a new competing one.
The line shouldn't depend on hovermode (as spikes do at the moment), moreover it can be used in 'x' or 'y' hovermode to compare data on hover.
TBH I'm not sure it's desirable for spikes to depend on hovermode
, the current behavior is probably just what was most convenient to implement. I guess there's a question of what to do with horizontal spikes when you're comparing multiple points at the same x value (my gut reaction: show only one, the closest to the pointer), and we currently can't show spikes if hovermode=false
because they're calculated after the hover data is determined, but that we can get around. So lets just define the behavior we want and make spikes do that.
This line should be always drawn over the nearest x/y point, regardless of whether this point is within the max hover distance or not.
Do you mean this to be linked to hover behavior (ie there's no limit to max hover distance as far as hover data AND spikes) or do you mean spikes should show up all the time (based on the nearest point) but hover data should only show up when you're within our predefined max distance? The former seems clearer to me (and notice that it would encompass spikes-only mode, if used with hovermode=false
) and also very easy to implement. But if there's a good reason we can do the latter instead. Seems like we should have a layout
parameter like hoverdistance
the defaults to the current range but can be set to any other value (including perhaps 0 to mean no cutoff). Then if the latter behavior is necessary we could make a spikedistance
as well, which normally inherits from hoverdistance
. Note that these are top-level layout attributes, not per-axis attributes, as in the general case that wouldn't make any sense.
For the api, does it make sense to match the existing
shapes
API for lines
In retrospect we probably should have put spike styling into a sub-container, but at this point I don't think we want to change the existing spike styling API until v2.0
If interested in another comparison it looks amcharts has a version of this feature:
it looks amcharts has a version of this feature
Interesting... per one of the pieces of #2026:
Allow hovering on arbitrary points on the plot, irrespective of whether there is data there or not.
In the amcharts version, the vertical spike is pinned to the data point while the horizontal is pinned to the cursor, even though the hover label is pinned to the data - to support that, we'd need spike positioning to be a per-axis configuration independent of hover label positioning.
I personally would love to have a feature like this implemented. @chriddyp already explained in #1847 the question I raised in the dash forum. Since it seems to be a general brainstorming here again, I would like to throw in another extension of this feature that I really would appreciate: It would be wonderful if this hovering line could be extended to reach over multiple stacked subplots. Here you can see an example. Here an example for what that might be useful. I e.g. have a model, that outputs a timeseries (e.g. of river discharge) that I want to plot against observed discharge. But then in subplots below, I would like to plot e.g. the state of the model internal snow storage, soil moisture storage etc. These usually are plotted in separate plots. But it would be very useful to have on hovering indicator line, that spans all these vertically stacked subplots and shows at each cross-section with on of the timeseries the value for this particular x-axis position.
I didn't take a into-depth look to this PR but I guess it's limited to one plot? Since this seems to be a more general discussion of where to go with this feature, I would really like to see this possibility supported as well.
@kratzert spikemode: 'across'
already works as you suggest for stacked coupled subplots.
@kratzert the second part of your question (labels across all subplots) is the subject of a separate issue https://github.com/plotly/plotly.js/issues/2114
@alexcjohnson
It's similar enough that we should really try to extend the spikes feature rather than add a new competing one.
It sounds reasonable, I think that we can do it this way and extend the existing spikes functionality by making it work in any hovermode
(except for false
one, of course)
Do you mean this to be linked to hover behavior (ie there's no limit to max hover distance as far as hover data AND spikes) or do you mean spikes should show up all the time (based on the nearest point) but hover data should only show up when you're within our predefined max distance? The former seems clearer to me (and notice that it would encompass spikes-only mode, if used with hovermode=false) and also very easy to implement.
At the moment, the second one is implemented. Crosslines are displayed all the time (based on the nearest point), but the hover data is displayed only when you're within predefined max distance (this is certainly discussable)
Seems like we should have a layout parameter like hoverdistance the defaults to the current range but can be set to any other value (including perhaps 0 to mean no cutoff). Then if the latter behavior is necessary we could make a spikedistance as well, which normally inherits from hoverdistance. Note that these are top-level layout attributes, not per-axis attributes, as in the general case that wouldn't make any sense.
The idea of hoverdistance
and spikedistance
parameters is definitely great and looks like a solution to this dilemma!
@jackparmer
To make sure I'm understanding the PR correctly, this new hover mode would be enabled by setting
Yes, you understood this correctly, in the current implementation this feature can be switched on by setting the corresponding axis showcrossline
attribute to true
(for example, layout.yaxis.showcrossline
), and then further optionally customized by crosslinecolor
, crosslinethickness
and crosslinedash
attributes.
@alexcjohnson
In the amcharts version, the vertical spike is pinned to the data point while the horizontal is pinned to the cursor, even though the hover label is pinned to the data - to support that, we'd need spike positioning to be a per-axis configuration independent of hover label positioning.
We can use one more parameter, for example mouseattached
, to handle this type of behavior.
The default value will be false
, and the spikes will be attached to the nearest datapoint, and on true
spikes will be attached to the current mouse position. What's your thoughts?
@jackparmer
in which financial software have you seen this feature? Looks like Highcharts has a similar hover line on the x-axis for example
Yes, highcharts/amcharts has this feature. Also many other less popular libraries provides this feature: e.g. http://techanjs.org or https://api.taucharts.com/basic/line.html
From my experience, users from financial area likes to have a vertical line for analyzing line/stock charts.
I second @alexcjohnson 's remark that we should try as much as possible to extend the existing spike
attributes as opposed to creating branch new attribute containers. Yes, we should've put the spike attributes under a container e.g. spike.line.color
instead of spikecolor
, but that must wait for v2.
To enable the proposed behavior, we could add addition spikemode
, but that attribute is already pretty crowded. Maybe a new attributes e.g. spikesnap: 'data' | 'cursor'
would be best.
TBH I'm not sure it's desirable for spikes to depend on hovermode
For backward-compatibility, we could add layout.hovermode: 'spikes'
.
I added support for spikedistance
, hoverdistance
, spikesnap
and moved functionality to the spikes feature in my branch:
https://github.com/apalchys/plotly.js/commit/253cdc74a1809b9844a505f1d334e796acd62aaf
Could someone please review it before I open PR?
Hmm. Before I dig deeper into your patch. Looks like the default spikes
behavior has changed.
Master: http://rickyreusser.com/plotly-mock-viewer/#stacked_coupled_subplots
your branch https://github.com/plotly/plotly.js/compare/master...apalchys:crossline
we can't allow that to happen.
Oh wait, I see why this is happening. You remove the fancy modebar logic that pinned hovermode to 'closest'
when toggling on the spikes. Nice :ok_hand:
Off your branch with hovermode: 'closest'
, we get back the current default behavior.
This functionality should be covered already by axis.spikemode
This functionality should be covered already by axis.spikemode
New functionality allows to work with spikes in the "compare" mode when there are several traces on the same plot and it does not depend on hoverData
when drawing the spikelines.
Just to summarize: In the current implementation, we take the first point from hoverData points for drawing spikes, and in the new implementation we specifically look for two separate points for drawing horizontal and vertical lines. In the "compare" mode, spikes are drawn across all the chart, since there may be several points on the horizontal / vertical line.
Also now we have the ability to stick the spikeline to the cursor, not to the data points, and to set the searching distance for points for drawing the hoverlabels and spikelines.
Here are some examples of new functionality: Demo: https://codepen.io/plotly-demo/pen/NwZKav
New functionality allows to work with spikes in the "compare" mode when there are several traces on the same plot and it does not depend on hoverData when drawing the spikelines.
Ok. Then why does this new functionality depend on hovermode
?
@etpinard are you asking why the feature depends on hovermode
but doesn't use hoverData?
If so, when hovermode
is compare
, hoverData has info about the points with the same closest X (or Y) but there is no information about the closest Y (or X) points. As result, we can't draw the second spikeline.
I decided not to change hoverData and implemented the separate logic for looking for the closest X and Y points.
Found another decent example of this feature in ChartIQ:
I like the vertical line aspect, but then cant we turn the horizontal line off. As the cursor moves horizontally the yvalues are reflected on the plots. The horizontal line to a certain extent is not giving out any information. My other question is can this be extended to subplots as mentioned in Hover labels across shared axes.
@deechiw In my branch, you can manually control the behavior of spikelines by editing corresponding properties in the config. In your example, you can set the configuration as follows:
{
"layout": {
"spikedistance": 0,
"xaxis": {
"showspikes": true,
...
},
...
},
...
}
By doing this, you turn on the vertical spikeline (spikeline to the shared xaxis) and set the range for drawing it to Infinity. And in the compare mode, it will be drawn by default across all subplots.
You can also set the spikesnap
attribute, as shown below, to bind a vertical line to the cursor, not to data points.
"xaxis": {
...
"spikesnap": "cursor",
...
}
Below are examples of this spikelines behavior for such settings. (here is the demo: https://codepen.io/plotly-demo/pen/BmgGMq)
As for the hover labels for other subplots, I agree with @alexcjohnson on his comment https://github.com/plotly/plotly.js/issues/2155#issuecomment-344924328:
labels across all subplots is the subject of a separate issue #2114
@apalchys
New functionality allows to work with spikes in the "compare" mode when there are several traces on the same plot and it does not depend on hoverData when drawing the spikelines.
That's great. That's for fixing that.
In the current implementation, we take the first point from hoverData points for drawing spikes, and in the new implementation we specifically look for two separate points for drawing horizontal and vertical lines. In the "compare" mode, spikes are drawn across all the chart, since there may be several points on the horizontal / vertical line.
I'm confused here. Now that hover and spike data are independent, why would the behavior depend on layout.hovermode
? Furthermore, how is the new behavior different from what the current spikemode
attribute allows?
Also now we have the ability to stick the spikeline to the cursor, not to the data points, and to set the searching distance for points for drawing the hoverlabels and spikelines.
That's very nice. Thanks for adding this :ok_hand:
Now that hover and spike data are independent, why would the behavior depend on
layout.hovermode
?
TL;DR Spike data and hover data are independent but I need to check hovermode in order to enable different spikeline points calculation logic:
compare
mode: looking for two nearest points - one with the nearest x
coordinate and one with y
closest
mode: search for the absolutely nearest point twice (for x
and y
spike points).Long explanation
I cannot say, my implementation strongly depends on the hovermode
, it's more a question of the correct way to update the existing functionality.
At the moment, spikelines are used in the toaxis
mode by default and can only be used in the closest
hovermode. In this mode, the same point is nearest to x
and y
coordinates.
In compare
hovermode (either x
or y
), we can have several points with the same nearest x
and several points with the same nearest y
coordinates.
Therefore, we simply cannot use the toaxis
mode, because we cannot select just one point for drawing both spikelines, all points with the nearest x
or y
must be covered by the corresponding spikelines.
That's why I used across
spikelines mode as default and only available mode while we use compare
hovermode. At the same time, I decided not to change the current behavior of the spikelines in the closest
hovermode and leave the spikeline toaxis
mode as default one.
Furthermore, how is the new behavior different from what the current spikemode attribute allows?
The answer to this question is closely related to the previous one.
The key point of the new implementation is a separate process of finding points for drawing horizontal and vertical spikelines to allow it to work in any hovermode
.
In the previous implementation, spikelines worked only in the closest
hovermode, because the closest datapoint was always the nearest x
and y
point, and since this was the first point in the hoverData
array, it was very easy to take it from there.
But, as I already mentioned, in compare
hovermode, we might not have the corresponding x
and y
points.
@apalchys I think this is making it too complicated, and somewhere in there it will lead to undesirable behavior. I haven't looked at it in enough detail to know where, though it seems like a) you've brought a bunch of scatter/hover
functionality into fx/hover
, that really needs to be left to the individual trace modules; and b) seems like you've added two new loops through the data, one for each axis - I think we can get away with 1 or in some cases 0 (see below).
I think as far as spikelines are concerned, we SHOULD just select one point for drawing both spikelines, and do this process completely independently from selecting hover data. Hover data makes one loop through the data, using hovermode
and hoverdistance
. Then spikelines, when spikesnap='data'
result in a second loop through the data, using a "hovermode" of closest
and a limit of spikedistance
.
I suppose when hovermode='closest'
and either hoverdistance === spikedistance
, hoverdistance < spikedistance
and we found a point, or hoverdistance > spikedistance
and we DIDN'T find a point, we could short-circuit the second loop and just use the results of the first loop. Might be a nice optimization but I'd make sure the behavior is correct and well-tested before getting into details like that.
we SHOULD just select one point for drawing both spikelines
To make sure we are on the same page:
I need to change code to use one point for drawing spikelines in 'compare'
mode but anyway it will require to find 2 data points, right?
Please see visualizations below:
'closest'
mode:
Looking for the nearest data point:
'compare'
mode:
Looking for data point (1st) with the x
coordinate nearest to the x
coordinate of the cursor and data point (2nd) with the y
coordinate closest to the y
coordinate of the cursor. After that we calculate the final point and draw 2 spikelines.
Do I understand it correctly?
@apalchys neat effect with the grey and red/green lines, really makes it easy to see what's going on!
I think spikelines should always behave like what you have for closest
mode, regardless of hovermode
. It seems weird to have the x and y spikes point to different points, so their crossing point is not a data point.
This will mean sometimes spikelines will appear or disappear independently of the hover labels - sometimes we'll have only hover labels, sometimes only spike lines - but especially with the distinction between spikedistance
and hoverdistance
that kind of decoupling is inevitable.
I think spikelines should always behave like what you have for closest mode, regardless of hovermode. It seems weird to have the x and y spikes point to different points, so their crossing point is not a data point.
Sorry if I look annoying, but could you please confirm that the following behaviour is expected? Legend:
spikedistance
hoverdistance
I am asking because I would expect hover labels and spikelines are drawn for the same points. (spikedistance
and hoverdistance
are the same)
For my case, the main idea of using spikelines in the 'compare'
mode is to allow users to compare data they hovered to, sometimes even without any labels.
Spikelines marks the closest axis points that contain data on it, rather than the nearest data point.
Summarizing all the facts and comments, what do you think about an alternative idea:
Make spikesnap
completely responsible for the behavior of the spikes with 'closest'
(default), 'compare'
and 'cursor'
values.
In this case, the spikelines functionality will be completely independent of hovermode
, and will be easily customizable for the desired behavior.
Sorry if I look annoying
Not at all! It's really important that we get the goals right before going any further.
I would expect hover labels and spikelines are drawn for the same points.
Yes, that's certainly desirable! I think we're pretty close though in your first gif above (with the green circle and grey rectangle). You really have to go looking for situations where the spikes and hover labels disagree. What about a small tweak to the behavior to cover that case:
spikesnap='data'
:Note that this way there is still no explicit coupling between hovermode and spike mode, just a coupling between the points chosen by each feature.
Hi, such a vertical assist line would be very helpful. Here are my illustration:
Currently, the line enabled by showspikes = TRUE & spikes = across
can only show in one chart, which would be useless while illustrating the data in reality.
@alexcjohnson Thank you for your ideas, I've reworked my code a bit according to them.
Now it works like this:
hovermode
).hoverData
, we check its content for a point that is close enough to the mouse (using the absolute distance, as in the closest
mode), and draw the spiklines to it, if any.hoverData
, we try to find the nearest point among all points (again using the absolute distance, as in the closest
mode, regardless of the current hovermode
) and draw spikelines to it, if it exists.https://github.com/apalchys/plotly.js/commit/8a2194f75c3fa671151cfba8f53db5fa50f5b210
What do you think about this behavior? Did I understand you correctly?
I am going to create a PR with my latest changes mentioned in https://github.com/plotly/plotly.js/issues/2155#issuecomment-353585476. Does it make sense?
I am going to create a PR
I'd say: go for it!
PR has been merged. Closing the issue.
Thanks for getting in this PR it has been super helpful. For anyone looking to achieve this functionality here is the layout that I used. I overlooked @apalchys comment a few times before I realized his codepen link. Hope this helps someone in the future :).
"layout": {
"spikedistance": 200,
"hoverdistance": 10,
"xaxis": {
"showspikes": true,
"spikemode": "across",
"spikedash": "solid",
"spikecolor": "#000000",
"spikethickness": 2
}
}
@apalchys How did you achieve this? What settings are required for it?
Wondering the same :) I'm also trying to find out how to add highlighted zones in the background (as of now I only found shapes which I could use to do it... but I'm not sure it is the intended use of shapes...)
@sleighsoft try this:
import numpy as np
import plotly.graph_objects as pl_go
import plotly.express as px
slug = np.random.random_sample((200,))
fig = pl_go.Figure()
fig.add_trace( # DYNAMIC
pl_go.Scatter(x=slug[:100],
y=slug[100:],
name='trace1_y1',
# line_width=0.9,
# marker_line_width=0,
marker_color='green', # or array,
# hovertemplate=None,
# hoverinfo='none',
mode='lines',
)
)
fig.add_hline(y=1,
line_width=0.5,
line_dash="dot",
line_color="cyan",
annotation_text="1BASE",
annotation_font_size=12,
annotation_position="bottom right")
fig.update_layout(
xaxis_zeroline=False,
yaxis_zeroline=False,
yaxis=dict(
tickfont=dict(size=14, color='#e6e6e6'),
# title='Quote',
# titlefont_size=16,
gridcolor='#283442',
linecolor='#283442',
spikecolor="white", spikethickness=1, spikedash='solid', spikemode='across',
spikesnap='cursor'
),
spikedistance=-1,
# hoverdistance=0,
hovermode='y',
font=dict(
color='white',
size=15
),
paper_bgcolor='rgb(17,17,17)',
plot_bgcolor='rgb(17,17,17)',
)
fig.show()
what bugs me is: how to disable the hover box on the chart; and always show the outer axis box with the coordinate of the cursor (not data point)?
How to easy use crossline in python?
It would be nice to have an option in plotly.js to toggle on a vertical or/and horizontal line across the entire plot area on hover.
The behavior of the line should be similar to the behavior of the spikes feature, but there are several differences:
This feature could be very useful for financial and stock charts
Related to #2026, #1959
PR #2150