Skip to content

The length of the arrows are not maintained by the 2D quiver plot in figure factory. #1187

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

Closed
dabana opened this issue Sep 19, 2018 · 9 comments

Comments

@dabana
Copy link
Contributor

dabana commented Sep 19, 2018

Bug report

Thanks to the Plotly community for this great implementation of 2D quiver plots in the figure factory. The issue is that the length of the arrows are not maintained by the quiver plot.

Bug summary

The length of the arrows in a quiver plot carries relevant information in most applications (wind speed, force field intensity, etc.). This seem to be the case with the quiver plot in figure factory if both axis have the same scale (for example if quiver plot is plotted on a map: x and y coordinates have obviously the same scale). However, as soon as the scales of both axis are different, the apparent length of the arrows is distorted. The barb angle is also affected. This should not be the case.

Code for reproduction

This is an instance of a quiver plot where the scales of both axis are different but where the length of each arrow is intended to be the same (using the norm and an angle to produce u an v).

import plotly.plotly as py
import plotly.figure_factory as ff
import plotly
print(plotly.__version__)
import numpy as np

x,y = np.meshgrid(np.arange(0.5, 3.5, .5), np.arange(0.5, 4.5, .5))
u = x
v = y
angle = np.arctan(v / u)
norm = 0.25
u = norm * np.cos(angle)
v = norm * np.sin(angle)

fig = ff.create_quiver(x, y, u, v, scale = 1)

Actual outcome

The above code produces arrows with apparent lengths changing depending on their angle. This should not be the case.

fig1

Expected outcome

This is an instance of the above quiver plot but where the scales of both axis are forced to be the same using specifications on the layout.

x,y = np.meshgrid(np.arange(0.5, 3.5, .5), np.arange(0.5, 4.5, .5))
u = x
v = y
angle = np.arctan(v / u)
norm = 0.25
u = norm * np.cos(angle)
v = norm * np.sin(angle)

fig = ff.create_quiver(x, y, u, v, scale = 1)
fig.layout = go.Layout(
    yaxis=dict(
        scaleratio = 1,
        scaleanchor = "x"
    ))

fig2

It would be nice to have nicely scaled arrows as the above plot but with axis of different scales.

WORKAROUND: To maintain an aspect ratio similar the original plot, we can force a scale ratio different than 1 and apply an inverse transformation on u (or v) to counter the effect of the stretching.

x,y = np.meshgrid(np.arange(0.5, 3.5, .5), np.arange(0.5, 4.5, .5))
u = x
v = y
angle = np.arctan(v / u)
norm = 0.25
scale_ratio = 0.5
u = norm * np.cos(angle) * scale_ratio
v = norm * np.sin(angle)

fig = ff.create_quiver(x, y, u, v, scale = 1)
fig.layout = go.Layout(
    yaxis=dict(
        scaleratio = scale_ratio,
        scaleanchor = "x"
    ))

fig3

This workaround is not perfect though. Despite the fact that the length of the arrows are maintained, the barbs of the arrows are still distorted and they don't look good.

Plotly version

  • Operating system: Ubuntu 16.04 Bash on Windows10
  • Plotly version: 3.2.1
  • Python version: 3.5.6
    plotly installed from pip
@dabana
Copy link
Contributor Author

dabana commented Sep 19, 2018

Fun fact, the plotly.js team was working actively on a 3D quiver plot (see closed issue #861) earlier this year. This issue maybe a good time for the plotly community to give a little love to their 2D quiver plot. Just saying ;)

@dabana dabana changed the title The length of the arrows are not maintained by the quiver plot in figure factory. The length of the arrows are not maintained by the 2D quiver plot in figure factory. Sep 19, 2018
@jonmmease
Copy link
Contributor

Hi @dabana ,

Thanks for the report, this is a great point regarding scale distortion. What if we added scaleratio as an input parameter (optional for now for backwards compatibility) that would cause the arrow length and arrowhead shape to be computed with respect to this ratio, and would set the scaleanchor and scaleratio attributes in the resulting figure as you do above.

Do you have any interest in working on this?

@dabana
Copy link
Contributor Author

dabana commented Sep 20, 2018

Hi Jon,

Yes I am interested in working on this. I will submit a pull request once I have a proper fix figured out.

The addition of scaleratio as an input parameter is also the solution I was looking into. But the problem is when fixing the scale ratio, the range of the axis are adjusted to satisfy this new constrain instead of being adjusted to fill the layout nicely. This is why in figure 2 and 3 there are blank spaces left and right of the plot. The scaleratio parameter would have to be adjusted manually for the figure to fit the screen nicely.

It would be nice if we could "read" the scale ratio out of the final figure and then apply the inverse transform on the axis, no new input parameter needed. Any idea how to do this?

@jonmmease
Copy link
Contributor

Yeah, I was picturing that the user would use the scaleratio to adjust the viewport onto the data. But you're right that this would take some manual tweaking to get right.

Another option that comes to mind is that the user could specify the figure width/height that they want. Then we could subtract off the margins to get the pixel width/height of the axis extents. Then we could also manually compute the axis range extents based on the data itself to compute the scaleratio that way.

To go a step further, we could add an option to return the figure as a FigureWidget, with per-installed callback functions that would dynamically recompute the scale ratio and the quiver lines whenever the plot resizes or the user zooms in. This would be someone similar to the DataShader example here: http://nbviewer.jupyter.org/github/jonmmease/plotly_ipywidget_notebooks/blob/master/notebooks/DataShaderExample.ipynb.

@dabana
Copy link
Contributor Author

dabana commented Sep 21, 2018

I like the callback function approach a lot. I will look into it. Thanks!

@dabana
Copy link
Contributor Author

dabana commented Sep 24, 2018

I have opened a pull request regarding only the scaling of the arrows for now by passing a scaleratio parameter to the create_quiver function.

I have not worked on the widget callback option yet. I still have problems making widgets work on my dev environment.

Here is the result with scaleratio = 0.5:

fig3

@dabana
Copy link
Contributor Author

dabana commented Sep 25, 2018

@jonmmease I am having trouble making the FigureWidget callback example work.

I get a 404 reponse when I run the following cell for the first time

f = go.FigureWidget(data=[{'x': x_range, 
                           'y': y_range, 
                           'mode': 'markers',
                           'marker': {'opacity': 0}}], # invisible trace to init axes and to support autoresize
                    layout={'width': plot_width, 'height': plot_height})
f

Then nothing and the plot does not show. This is the error

 404 GET /static/plotlywidget.js?v=20180925174518 (::1) 3.99ms referer=http://localhost:8888/notebooks/DataShaderExample.ipynb 

Maybe there is something I do wrong...

@jonmmease
Copy link
Contributor

Hi @dabana , could you try upgrading plotly and all of the other packages to match the current versions in the README instructions and try again?

@jonmmease
Copy link
Contributor

Let's close this issue as your initial solution was merged and included in version 3.3.0. Why don't you open a new issue to discuss the FigureWidget approach when you have time if you're still interested in perusing that. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants