-
-
Notifications
You must be signed in to change notification settings - Fork 404
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
Twin axis #396
Comments
I have a really uggly solution: %%opts Curve [finalize_hooks=[setglobal_hook], show_frame=True]
a = hv.Curve(([1,2], [3,21]), vdims=['A'])
b = hv.Curve(([1,2], [30,20]), vdims=['B'])
def setglobal_hook(plot, element):
global fig, el
fig = plot
el = element
a %%opts Curve [finalize_hooks=[overlay], show_frame=True]
b = hv.Curve(([1,2], [30,20]), vdims=['B'])
def overlay(plot, element):
ax = plot.handles['axis']
ax2 = ax.twinx()
ax2.set_yticklabels([t.get_text() for t in fig.handles['axis'].get_yticklabels()])
ax2.set_ylabel(fig.handles['axis'].get_ylabel())
ax2.plot(*fig.handles['axis'].lines[0].get_data())
ax2.lines[-1].set_color('r')
b Is there a better way to get grips to the plotted version of a? |
I see, yes it's problematic because you have to create a new axis to plot on. I'll have to think about it a bit, one quick workaround would be to add support not just for |
A slightly cleaner solution:
A small annoyance is that the x-axis is not set ok. Should go from 0 to 2, but has range 0,2.2. |
Here's what it could look like with a def twinx(plot, element):
ax = plot.handles['axis']
twinax = ax.twinx()
twinax.set_ylabel(str(element.last.get_dimension(1)))
plot.handles['axis'] = twinax
a = hv.Curve(([1,2], [3,21]), vdims=['A'])
b = hv.Curve(([1,2], [5, 0]), vdims=['B'])(plot=dict(init_hooks=[twinx]), style=dict(color='red'))
a * b Does that seem like a reasonable solution? |
This is a nice solution. It demonstrates again how powerfull holoviews is. But it show also how many undocumented gems are available. It might be good to add a cookbook like section on the website, where many of these things are demonstrated. Also on gitter many good solutions come along, but is is very hard to find them back. Is this already possible with the current version? |
No, I'm suggesting to add the |
Sounds like a very good idea, to allow people to do more customization themselves. |
I also agree that supporting initial hooks is a good idea. Just one minor gripe though - it should be Edit: Or would |
I think In any case, aren't these specifically meant for user extensions, and thus something that we expect and encourage people to specify in their own code? If so I'm not sure it's ok to change |
I would consider having an alias and eventually deprecating |
Not sure it's worth using that mechanism, it's mostly to do with if not self.final_hooks:
if self.finalize_hooks:
self.warning('Using deprecated finalize_hooks options, use final_hooks instead')
self.final_hooks = self.finalize_hooks
elif self.finalize_hooks:
raise ValueError('Set either final_hooks or deprecated finalize_hooks, not both.')
Let's do that for v1.5. |
The suggested code looks good and I'll open an issue about removing |
Sounds good. |
It is really super that you have such a fast response on feature request! Today I tried this with elements with a group, but that does not work yet: def twinx(plot, element):
ax = plot.handles['axis']
twinax = ax.twinx()
twinax.set_ylabel(str(element.last.get_dimension(1)))
plot.handles['axis'] = twinax
a = hv.Curve(([1,2], [3,21]), kdims=[dim_pos], vdims=[dim_itensity], group='test')
b = hv.Curve(([1,2], [5, 0]), kdims=[dim_pos], vdims=[dim_phase], group='test'
)plot=dict(initial_hooks=[twinx]))
a * b
|
Odd seems to work fine for me. If you just copy and paste what you put there into a new notebook do you still get an error? |
Another point is that the range of the original axis is adjusted to fit to data of the second axis in. This is different from plt.twinx and unwanted behaviour.
(Note that both axis have the same range) One can somehow overcome this by setting the extents for the first figure manually. These extents should be set by both the first and second element. Changing the extents for the second axis is not possible yet. |
You can disable HoloViews handling of ranges by setting |
The same works for the y-axis
|
This works great for the Is it possible to do the same with I cannot quite translate to the https://stackoverflow.com/questions/25199665/one-chart-with-two-different-y-axis-ranges-in-bokeh Specifically, in my hook function, how do I get the https://stackoverflow.com/a/30914348/1638996 That is, what is the Thank you. |
@timehaven This works for me with bokeh backend.
|
@ahuang11 I'm confused. The technique you show, using finalize_hooks=[apply_formatter] with the bokeh backend, seems to display the second axis, but it doesn't actually plot the curve on it. The curve you show seems to still be plotting on the left axis even though you have added the scale on the right. I'm getting a similar result with my attempts:
See that both curves are still plotting on the left axis, even after inserting the right axis on the bottom plot. Any ideas how to actually scale the figure to match the right axis? |
@chuard you could try running them through an "initialize_hooks" first to directly plot them into new axes at creation. |
The above example is almost complete. The only thing you need to do is to set Please find a modified snippet below:
|
@adamlansky took the liberty to add |
@poplarShift thank you, much appreciated! I also feel like this issue could probably be closed, as the hooks solution should be general enough to handle any secondary-axis related tasks. Please let me know if i should cross-post it to #3011 to make sure similar issues are solved as well. |
I don't think it fully works, or at least I'm missing something. Somehow the left axis gets modified if you have something like this:
You can see that the |
Also for the above, in bokeh, how would you add an overlay to the second axis
This creates another secondary axis (resulting in two secondary axis, instead of 1) |
Here is a modified example that works around both issues highlighted by @apuignav and @zeneofa This example is meant to be run in a jupyter notebook and it is using streaming dataframes to show how changing data also modifies axis ranges.
If you want to stop streaming dataframes, run this:
Please note that this code is hardcoded for specific case when your data column name is 'y'. As a bottom line, plots utilizing multiple axis from holoviews currently stay in "advanced territory" where you need to know bokeh object model good enough to work your way through it and there is no "plug and play" way to do it for N axis like you do in matplotlib. |
One way to go around the non supported twin axes is having 2 plots one above, second under sharing the same x-axis: plot_first = df_first.hvplot.line(height=800, width=3500, legend=False, value_label="first_y_label").opts(bgcolor="black")
plot_second = df_second.hvplot.scatter(height=800, width=3500, legend=False, value_label="second_y_label").opts(bgcolor="black")
(plot_first + plot_second).cols(1) IMHO this kind of example should be documented, especially since |
I think it would be very nice and intuitive to achieve this simply by overlaying two plots with different values of Example: hv.Curve(([1,2,3], [1,2,3])).opts(yaxis = 'left')*\
hv.Curve(([1,2,3], [4,5,6])).opts(yaxis = 'right') For the moment this returns a plot with yaxis on the left, but I think it is very clear from the code what the user would want to achieve: a single plot with the axis of the first curve on the left and the one of the second on the right |
Is there a good solution now for 2 y-axes? I try to create a plot with 5 curves, 3 of which need to be on the left y-axis and 2 on the right y-axis. It seems to me that @adamlansky 's solution would need to be modified for this ('y'), so ' y' would need to be passed in as a parameter to plot_secondary, but it exceeds my python skills to figure out how to do so. Has anyone done this? |
Saulo asks how to use a twin axis here https://discourse.holoviz.org/t/two-yaxis-in-one-plot/1829. |
I would like to know if is it possible to make something simple on this. Just like: plotA.opts(yaxis='left') plotA * plotB ?? |
I have just run into this wanted feature for my own work. I would add that making it possible to easily associate the curve color with the axis color and the axis label color is a great helper for reading the plot well (e.g. in bokeh |
I've no idea how to implement this feature, but if someone does, would be great to have a PR for this! |
I've tried all the codes above, none of which gives me a secondary y axis... ` class Plot:
` |
I tried to extend @adamlansky 's example by taking the data column name from the figure (not hardcoded to 'y') in this case: import numpy as np
import holoviews as hv
from bokeh.models import Range1d, LinearAxis
from bokeh.models.renderers import GlyphRenderer
from bokeh.plotting.figure import Figure
hv.extension('bokeh')
def plot_secondary(plot, element):
"""
Hook to plot data on a secondary (twin) axis on a Holoviews Plot with Bokeh backend.
More info:
- http://holoviews.org/user_guide/Customizing_Plots.html#plot-hooks
- https://docs.bokeh.org/en/latest/docs/user_guide/plotting.html#twin-axes
"""
fig: Figure = plot.state
glyph_first: GlyphRenderer = fig.renderers[0] # will be the original plot
glyph_last: GlyphRenderer = fig.renderers[-1] # will be the new plot
right_axis_name = "twiny"
# Create both axes if right axis does not exist
if right_axis_name not in fig.extra_y_ranges.keys():
# Recreate primary axis (left)
y_first_name = glyph_first.glyph.y
y_first_min = glyph_first.data_source.data[y_first_name].min()
y_first_max = glyph_first.data_source.data[y_first_name].max()
y_first_offset = (y_first_max - y_first_min) * 0.1
fig.y_range = Range1d(
start=y_first_min - y_first_offset,
end=y_first_max + y_first_offset
)
fig.y_range.name = glyph_first.y_range_name
# Create secondary axis (right)
y_last_name = glyph_last.glyph.y
y_last_min = glyph_last.data_source.data[y_last_name].min()
y_last_max = glyph_last.data_source.data[y_last_name].max()
y_last_offset = (y_last_max - y_last_min) * 0.1
fig.extra_y_ranges = {right_axis_name: Range1d(
start=y_last_min - y_last_offset,
end=y_last_max + y_last_offset
)}
fig.add_layout(LinearAxis(y_range_name=right_axis_name, axis_label=glyph_last.glyph.y), "right")
# Set right axis for the last glyph added to the figure
glyph_last.y_range_name = right_axis_name
# Define the data
x1 = np.arange(11)
x2 = np.arange(21) / 2
y1 = x1 + 1
y2 = x2**2 + 1
# Create individual curves
c1 = hv.Curve((x1, y1), kdims='x', vdims='y1').opts(width=400, show_grid=True, framewise=True, yaxis='left')
c2 = hv.Curve((x2, y2), kdims='x', vdims='y2').opts(width=400, show_grid=True, framewise=True, color='red', hooks=[plot_secondary])
# plot these maps on the same figure
twin_plot = (c1 * c2).opts(title='Twin y-axes demo')
twin_plot Some documentation that proved to be useful: Note:
|
@peterroelants thanks for the solution that actually works. But do users really need to be experts in order to plot 2 lines in the same window? Anyhow, would be great to have this in the docs at least until a better solution is found. Also doesn't work with |
@peterroelants I'm just learning python and stumbled across this as a solution to what I was trying to do. I have a data frame in pandas/jupyter notebook where I am trying to plot one y-axis with one value ($ value) and a second y-axis for two more columns (percent change), all over an x-axis that is the date (daily, several years). I'm trying to figure out where I would insert which data into your code to make that work. I can break the data frames up if that makes it work... would one data frame per y work better? Thank you! |
Twin axes is now (finally) supported! https://holoviews.org/user_guide/Customizing_Plots.html#twin-axes |
Thank you so much!
…On March 27, 2024 10:20:33 PM GMT+01:00, Demetris Roumis ***@***.***> wrote:
Twin axes is now (finally) supported! https://holoviews.org/user_guide/Customizing_Plots.html#twin-axes
--
Reply to this email directly or view it on GitHub:
#396 (comment)
You are receiving this because you commented.
Message ID: ***@***.***>
|
This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs. |
Is it possible to create overlays with two y-axis (a left and a right axis)? With matplotlib this is possible with ax1.twinx().
Twin axes are currently not supported bit yet. For now you could try to write a finalize_hook to do it.
The text was updated successfully, but these errors were encountered: