Skip to content

Commit

Permalink
Allow forcing a fixed axis scale ratio to maintain quiver arrow lengt…
Browse files Browse the repository at this point in the history
…hs (#1197)

A new scaleratio parameter is added to the create_quiver function to allow forcing a fix scale ratio to the axis of the plot through the scaleratio option in the Layout. A distortion is introduced to the arrows length, angle and shape by having a scale ratio different than 1. In this pull request, this distortion is reversed by multiplying or dividing by scaleratio at key steps of the arrows construction.
  • Loading branch information
dabana authored and jonmmease committed Sep 26, 2018
1 parent d3d54be commit e0da828
Show file tree
Hide file tree
Showing 2 changed files with 96 additions and 16 deletions.
72 changes: 56 additions & 16 deletions plotly/figure_factory/_quiver.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@


def create_quiver(x, y, u, v, scale=.1, arrow_scale=.3,
angle=math.pi / 9, **kwargs):
angle=math.pi / 9, scaleratio = None, **kwargs):
"""
Returns data for a quiver plot.
Expand All @@ -21,6 +21,9 @@ def create_quiver(x, y, u, v, scale=.1, arrow_scale=.3,
:param (float in [0,1]) arrow_scale: value multiplied to length of barb
to get length of arrowhead. Default = .3
:param (angle in radians) angle: angle of arrowhead. Default = pi/9
:param (positive float) angle: the ratio between the scale of the y-axis
and the scale of the x-axis (scale_y / scale_x). Default = None, the
scale ratio is not fixed.
:param kwargs: kwargs passed through plotly.graph_objs.Scatter
for more information on valid kwargs call
help(plotly.graph_objs.Scatter)
Expand Down Expand Up @@ -80,33 +83,69 @@ def create_quiver(x, y, u, v, scale=.1, arrow_scale=.3,
# Add title to layout
fig['layout'].update(title='Quiver Plot')
# Plot
py.plot(fig, filename='quiver')
```
Example 4: Forcing a fix scale ratio to maintain the arrow length
```
import plotly.plotly as py
from plotly.figure_factory import create_quiver
import numpy as np
# Add data
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)
# Create quiver with a fix scale ratio
fig = create_quiver(x, y, u, v, scale = 1, scaleratio = 0.5)
# Plot
py.plot(fig, filename='quiver')
```
"""
utils.validate_equal_length(x, y, u, v)
utils.validate_positive_scalars(arrow_scale=arrow_scale, scale=scale)

barb_x, barb_y = _Quiver(x, y, u, v, scale,
arrow_scale, angle).get_barbs()
arrow_x, arrow_y = _Quiver(x, y, u, v, scale,
arrow_scale, angle).get_quiver_arrows()
quiver = graph_objs.Scatter(x=barb_x + arrow_x,
if scaleratio is None:
quiver_obj = _Quiver(x, y, u, v, scale, arrow_scale, angle)
else:
quiver_obj = _Quiver(x, y, u, v, scale, arrow_scale, angle, scaleratio)

barb_x, barb_y = quiver_obj.get_barbs()
arrow_x, arrow_y = quiver_obj.get_quiver_arrows()

quiver_plot = graph_objs.Scatter(x=barb_x + arrow_x,
y=barb_y + arrow_y,
mode='lines', **kwargs)

data = [quiver]
layout = graph_objs.Layout(hovermode='closest')
data = [quiver_plot]

return graph_objs.Figure(data=data, layout=layout)
if scaleratio is None:
layout = graph_objs.Layout(hovermode='closest')
else:
layout = graph_objs.Layout(
hovermode='closest',
yaxis=dict(
scaleratio = scaleratio,
scaleanchor = "x"
)
)

return graph_objs.Figure(data=data, layout=layout)

class _Quiver(object):
"""
Refer to FigureFactory.create_quiver() for docstring
"""
def __init__(self, x, y, u, v,
scale, arrow_scale, angle, **kwargs):
scale, arrow_scale, angle, scaleratio = 1, **kwargs):
try:
x = utils.flatten(x)
except exceptions.PlotlyError:
Expand All @@ -132,6 +171,7 @@ def __init__(self, x, y, u, v,
self.u = u
self.v = v
self.scale = scale
self.scaleratio = scaleratio
self.arrow_scale = arrow_scale
self.angle = angle
self.end_x = []
Expand All @@ -148,7 +188,7 @@ def scale_uv(self):
endpoints of the arrows so a smaller scale value will
result in less overlap of arrows.
"""
self.u = [i * self.scale for i in self.u]
self.u = [i * self.scale * self.scaleratio for i in self.u]
self.v = [i * self.scale for i in self.v]

def get_barbs(self):
Expand Down Expand Up @@ -188,13 +228,13 @@ def get_quiver_arrows(self):
point1, endpoint, point2 y_values separated by a None to create
the barb of the arrow.
"""
dif_x = [i - j for i, j in zip(self.end_x, self.x)]
dif_x = [i - j for i, j in zip(self.end_x, self.x)]
dif_y = [i - j for i, j in zip(self.end_y, self.y)]

# Get barb lengths(default arrow length = 30% barb length)
barb_len = [None] * len(self.x)
for index in range(len(barb_len)):
barb_len[index] = math.hypot(dif_x[index], dif_y[index])
barb_len[index] = math.hypot(dif_x[index] / self.scaleratio, dif_y[index])

# Make arrow lengths
arrow_len = [None] * len(self.x)
Expand All @@ -203,7 +243,7 @@ def get_quiver_arrows(self):
# Get barb angles
barb_ang = [None] * len(self.x)
for index in range(len(barb_ang)):
barb_ang[index] = math.atan2(dif_y[index], dif_x[index])
barb_ang[index] = math.atan2(dif_y[index], dif_x[index] / self.scaleratio)

# Set angles to create arrow
ang1 = [i + self.angle for i in barb_ang]
Expand Down Expand Up @@ -231,9 +271,9 @@ def get_quiver_arrows(self):

# Set coordinates to create arrow
for index in range(len(self.end_x)):
point1_x = [i - j for i, j in zip(self.end_x, seg1_x)]
point1_x = [i - j * self.scaleratio for i, j in zip(self.end_x, seg1_x)]
point1_y = [i - j for i, j in zip(self.end_y, seg1_y)]
point2_x = [i - j for i, j in zip(self.end_x, seg2_x)]
point2_x = [i - j * self.scaleratio for i, j in zip(self.end_x, seg2_x)]
point2_y = [i - j for i, j in zip(self.end_y, seg2_y)]

# Combine lists to create arrow
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2857,3 +2857,43 @@ def test_full_choropleth(self):
]

self.assertEqual(fig['data'][2]['x'][:50], exp_fig_head)

class TestQuiver(TestCase):

def test_scaleratio_param(self):
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, scaleratio = 0.5)

exp_fig_head = [(
0.5,
0.5883883476483185,
None,
1.0,
1.1118033988749896,
None,
1.5,
1.6185854122563141,
None,
2.0),
(0.5,
0.6767766952966369,
None,
0.5,
0.6118033988749895,
None,
0.5,
0.5790569415042095,
None,
0.5)]

fig_head = [fig['data'][0]['x'][:10], fig['data'][0]['y'][:10]]

self.assertEqual(fig_head, exp_fig_head)


0 comments on commit e0da828

Please sign in to comment.