Rotating Rectangles around point with matplotlib - python

I wanted to rotate Rectangles with matplotlib, but the normal patch always rotates around the lower left corner of the Rectangle. Is there a way to describe more general transformations? For Example I want to rotate about the mid point on the shorter side of the rectangle, so that the result looks like the motion of a clock hand.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle
height = 0.1
width = 1
fig, ax = plt.subplots()
ax.set_aspect('equal')
ax.set_xlim([-width * 1.2, width * 1.2])
ax.set_ylim([-width * 1.2, width * 1.2])
ax.plot(0, 0, color='r', marker='o', markersize=10)
for deg in range(0, 360, 45):
rec = Rectangle((0, 0), width=width, height=height,
angle=deg, color=str(deg / 360), alpha=0.9)
ax.add_patch(rec)

Same as the other answer, just without subclassing and using private attributes.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.transforms import Affine2D
height = 0.1
width = 1
fig, ax = plt.subplots()
ax.set_aspect('equal')
ax.set_xlim([-width * 1.2, width * 1.2])
ax.set_ylim([-width * 1.2, width * 1.2])
ax.plot(0, 0, color='r', marker='o', markersize=10)
point_of_rotation = np.array([0, height/2]) # A
# point_of_rotation = np.array([width/2, height/2]) # B
# point_of_rotation = np.array([width/3, height/2]) # C
# point_of_rotation = np.array([width/3, 2*height]) # D
for deg in range(0, 360, 45):
rec = plt.Rectangle(-point_of_rotation, width=width, height=height,
color=str(deg / 360), alpha=0.9,
transform=Affine2D().rotate_deg_around(*(0,0), deg)+ax.transData)
ax.add_patch(rec)
plt.show()

I created a custom Rectangle class which has as additional argument the relative point of rotation - relative means measured in the frame defined by the original lower left coordinates of the rectangle (x0, y0) and the angle.
rec = RotatingRectangle((0, 0), width=1, height=0.1, rel_point_of_rot=(0, 0.05), angle=45)
creates an rectangle where the the relative point (0, 0.05) [the midpoint of the left side] is shifted to the center (0, 0) and than a rotation of 45 deg is performed around this point.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle
class RotatingRectangle(Rectangle):
def __init__(self, xy, width, height, rel_point_of_rot, **kwargs):
super().__init__(xy, width, height, **kwargs)
self.rel_point_of_rot = rel_point_of_rot
self.xy_center = self.get_xy()
self.set_angle(self.angle)
def _apply_rotation(self):
angle_rad = self.angle * np.pi / 180
m_trans = np.array([[np.cos(angle_rad), -np.sin(angle_rad)],
[np.sin(angle_rad), np.cos(angle_rad)]])
shift = -m_trans # self.rel_point_of_rot
self.set_xy(self.xy_center + shift)
def set_angle(self, angle):
self.angle = angle
self._apply_rotation()
def set_rel_point_of_rot(self, rel_point_of_rot):
self.rel_point_of_rot = rel_point_of_rot
self._apply_rotation()
def set_xy_center(self, xy):
self.xy_center = xy
self._apply_rotation()
height = 0.1
width = 1
fig, ax = plt.subplots()
ax.set_aspect('equal')
ax.set_xlim([-width * 1.2, width * 1.2])
ax.set_ylim([-width * 1.2, width * 1.2])
ax.plot(0, 0, color='r', marker='o', markersize=10)
point_of_rotation = np.array([0, height/2]) # A
# point_of_rotation = np.array([width/2, height/2]) # B
# point_of_rotation = np.array([width/3, height/2]) # C
# point_of_rotation = np.array([width/3, 2*height]) # D
for deg in range(0, 360, 45):
rec = RotatingRectangle((0, 0), width=width, height=height,
rel_point_of_rot=point_of_rotation,
angle=deg, color=str(deg / 360), alpha=0.9)
ax.add_patch(rec)

Related

Is it possible with matplotlib to create a bar plot with custom width bars with vertical gradients and borders for the columns?

I am trying to create a specific bar plot with ALL the following characteristics:
Custom width bars
Colored borders for the bars
Vertical gradient
So far, I've been able to get (1 and 2) with horizontal (instead of vertical) gradient or (1 and 3) but I wasn't able to get (1 and 2 and 3) at the same time.
To illustrate, here is 1 & 2:
chart.py script:
import matplotlib.pyplot as plt
from colour import Color
height=[..]
width=[..]
x_pos=[..]
colors=[]
for i in list(Color("gray").range_to(Color('#00C1D1'),len(data))):
colors.append(i.hex_l)
plt.bar(x_pos, height, width=width, align='edge', color=colors, edgecolor="white", linewidth=0.3)
plt.show()
Here is 1 & 3:
chart.py script:
import matplotlib.pyplot as plt
from colour import Color
from gradient_chart import visualize_data
height=[..]
width=[..]
x_pos=[..]
visualize_data(x_pos,height,width)
and gradient_chart.py:
import argparse
import numpy as np
from matplotlib import pyplot as plt
def gradient_image(ax, extent, direction=0.05, cmap_range=(0, 1), **kwargs):
phi = direction * np.pi / 2
v = np.array([np.cos(phi), np.sin(phi)])
X = np.array([[v # [1, 0], v # [1, 1]], [v # [0, 0], v # [0, 1]]])
a, b = cmap_range
X = a + (b - a) / X.max() * X
im = ax.imshow(X, extent=extent, interpolation="bicubic", vmin=0, vmax=1, **kwargs)
return im
def gradient_bar(ax, x, y, widths, bottom=0):
for left, top, width in zip(x, y, widths):
right = left + width
gradient_image(
ax,
extent=(left, right, bottom, top),
cmap="winter",
cmap_range=(0.25, 0.85),
)
def visualize_data(x_pos: list, height: list, width: list):
xlim = (0, sum(width))
ylim = (0, max(height))
fig, ax = plt.subplots()
ax.set(xlim=xlim, ylim=ylim, autoscale_on=False)
gradient_bar(ax, x_pos, height, width)
ax.set_aspect("auto")
plt.savefig("bar.png", dpi=72)
plt.show()
And here is the data:
heigth=[0.7071068,0.7071068,0.7071068,0.7071068,0.7071068,0.7071068,0.7071068,0.7071068,0.7071068,0.7071068,0.7071068,0.7071068,0.7071068,0.7071068,0.7071068,0.7071068,0.7071068,0.7071068,0.7071068,0.7071068,0.7071068,0.7071068,0.7071068,0.7071068,0.7071068,0.7071068,0.7071068,0.7071068,0.7071068,0.7071068,0.7071068,0.7071068,0.7071068,0.7071068,0.7071068,0.7071068,0.7071068,0.7071068,0.7071068,0.7071068,0.7071068,0.7071068,0.7071068,0.7071068,0.7071068,0.7071068,0.7071068,0.7071068,0.7071068,0.7071068,0.7071068,0.7071068,0.7071068,0.7071068,0.7071068,0.7071068,0.7071068,0.802301919,1.43246706,1.46461545,1.4928755,1.511782365,1.531091129,1.548707097,1.558339896,1.56545997,1.568193925,1.572500504,1.583429484,1.592396845,1.593307081,1.594706608,1.600956011,1.608112212,1.610486687,1.613005881,1.615324641,1.616097991,1.616728799,1.617822862,1.618928751,1.620501458,1.622433491,1.696794387,1.77077331,1.778544896,1.786565616,1.78846728,1.790230358,1.791287388,1.844274481,1.897942907,1.925275234,1.958241303,1.960365732,1.961356058,1.962014556,1.987720812,2.01560191,2.018330288,2.018658981,2.032033526,2.044623053,2.045398536,2.299888844,2.555846549,2.578067639,2.601988556,2.607561473,2.628311647,2.647418882,2.648329588,2.650077731,2.65164762,2.655918331,2.660828199,2.662871086,2.693536103,2.724338055,2.727244302,2.729618015,2.731246355,2.733277265,2.735314175,2.737347934,2.740301515,2.74445905,2.747687147,2.74968592,2.753016422,2.756339416,2.783700215,2.811034297,2.820786426,2.847210253,2.900088807,2.943898451,2.954369814,2.957200311,2.974859109,3.000084325,3.010163061,3.016808672,3.025924214,3.036304836,3.044443602,3.085190926,3.125650947,3.135215527,3.159005478,3.182423556,3.192006899,3.216510108,3.240563814,3.249799537,3.259639796,3.372965407,3.491304188,3.50358988,3.517936679,3.534232106,3.542717124,3.548344732,3.56965051,3.846702914,4.110474979,4.152541934,4.192988337,4.198258769,4.210454226,4.210823244,4.211915977,4.216868357,4.227898419,4.240197705,4.240546229,4.244998213,4.247661057,4.249872364,4.251048986,4.253720457,4.25742323,4.259447864,4.260370021,4.260370752,4.262028226,4.266064978,4.275414765,4.280905751,4.286387362,4.290579301,4.292025296,4.293490029,4.294443729,4.297698826,4.302864339,4.304464046,4.305720479,4.306940858,4.309734398,4.318669215,4.322737597,4.324582847,4.326799166,4.333613484,4.336715871,4.339796589,4.340661794,4.341530005,4.342988362,4.344529087,4.345678432,4.346729198,4.350688634,4.354681223,4.357246524,4.35977953,4.360679556,4.363012497,4.373756545,4.383063474,4.384540191,4.386025534,4.386753829,4.387648878,4.388571979,4.389394581,4.390188649,4.391419862,4.393004617,4.3940818,4.394818851,4.396973648,4.399891825,4.401919827,4.403712968,4.406412211,4.40920005,4.411143159,4.430251797,4.449357524,4.451850591,4.462206008,4.472026492,4.473475602,4.559723163,4.64641236,4.652303126,4.658381088,4.660277561,4.662855189,4.665405014,4.669597116,4.676298465,4.686328952,4.693878455,4.698078497,4.702579866,4.70651582,4.710114215,4.71379437,4.717505582,4.719269285,4.723070527,4.727626149,4.729295146,4.749597537,4.922862686,4.941152789,4.95477639,4.956167444,4.957569989,4.958972172,4.960273514,4.968301975,4.976309772,4.977876867,4.979571304,4.982891603,4.987712396,4.990619387,4.991947092,4.993271909,4.996455537,4.999638352,5.003208546,5.008682862,5.015029646,5.022636024,5.027068747,5.028460625,5.029853924,5.03128473,5.039655593,5.047869379,5.049516847,5.057573552,5.094520348,5.125060634,5.126323418,5.1275643,5.129176939,5.131409014,5.133274266,5.134519243]
width=[209.9541399,141.4213525,141.4213525,28284.27049,14002.12811,180.1708031,151.8441062,706.3996556,271.3027226,1487.243511,151.9006747,567.0289127,141.9304693,984.7734458,865.4986771,838.5579095,141.4213525,151.5895477,1781.527204,393.1513599,151.5895477,367.8227957,212.1320287,141.4213525,178.8414423,255.972648,148.4924201,141.4213525,3802.579752,282.1355982,707.1067624,183.8477582,212.1320287,1524.564606,314.3796666,2121.320287,282.8427049,459.3789792,141.4213525,3142.198604,202.232534,4631.549294,142.835566,3051.349527,237.5878722,20890.76219,2820.861007,1434.521631,141.4213525,144.6740436,227.6883775,15157.54056,1408.556671,249.4672658,7666.53637,2833.433365,13714.37808,272.5033992,3577.338805,2852.339159,2799.670837,981.7021515,2880.05065,643.1429172,1283.417055,140.5976545,406.1933858,455.1222708,1730.673849,62.79841629,119.2488267,160.6565112,1089.224181,342.0159338,132.8790866,370.9595899,92.79249272,61.877436,64.28412736,154.5286606,66.64901092,247.8924027,138.5141525,14733.66496,62.11975264,1492.197361,111.94663,268.3862352,84.22938385,127.1766895,10470.24193,263.4431195,5203.022313,297.358655,127.5272241,70.53793186,61.16162575,5080.089687,496.1297145,49.54590466,115.2844548,2460.589324,57.31618834,97.78045522,50800.28119,391.2597962,4052.958054,731.2253529,383.3581721,3766.676608,54.7703278,127.3708535,222.2576316,91.72033198,762.4217871,219.5519426,189.0252978,5943.978245,216.4122029,364.8371359,109.905488,215.7623017,190.4197597,216.96228,189.7895381,400.9266842,430.5802996,215.0390377,184.715642,481.3847057,183.214011,5288.945958,177.870473,1772.555325,3512.209887,7063.500936,1698.42815,395.8441474,170.2556293,3361.503733,1683.539345,332.2079169,996.9143975,826.193858,1249.930493,377.8227323,7771.642201,320.3620676,1592.553991,3165.436106,1518.179436,398.4891137,4502.152803,308.5882758,1538.556438,429.4953086,22235.6268,1432.12958,1025.008669,1844.350991,1414.734474,282.2692202,843.2523405,3417.903228,51992.57766,2432.808873,24434.11809,1397.809755,4763.879765,239.2853469,474.9665052,1172.862903,562.9865101,7607.562154,247.6299628,470.2224412,758.9850073,941.6947225,470.6023684,280.8718516,1425.316981,939.5354383,353.5387797,235.4161716,235.8832267,351.9451117,2930.100705,2338.954359,1167.977127,2332.966938,344.3008266,579.2137344,356.2672767,252.8336773,2331.768792,462.4826263,559.203184,243.2438439,536.1763897,2320.328604,2315.528118,282.8277157,895.6794533,2311.177297,252.560595,1728.842798,238.720866,313.8599745,240.6409719,690.7686022,293.2469721,440.8057407,230.2880981,2298.486709,251.4627234,1386.919461,230.8373607,343.9830835,1145.997176,5715.91028,228.1509282,714.9848932,233.6602904,231.4809628,340.1593978,249.3977552,275.9742779,231.1745761,555.1644062,456.9719759,230.9925136,239.7413945,1136.463486,727.2906078,567.9340148,577.2923936,1134.71,657.715678,583.2909764,11620.83384,581.4322598,1010.815594,5602.61,669.7187517,255.7854567,54827.89,538.0495329,3224.209514,657.6061387,553.615094,1092.639551,535.8591575,2141.512373,2138.443488,4267.732847,553.9129368,2128.529782,746.3626562,1767.41571,530.7726917,1819.633893,550.6087814,575.8158384,1851.926612,1057.613238,1057.24,421088.31,4256.36288,10118.28659,588.8419114,504.4220213,597.8735563,504.136727,518.6205948,5791.147588,502.3803008,729.2386086,602.4615006,2007.045466,1781.73465,502.9435838,540.5325718,500.6737157,2001.418791,500.0361674,2305.868303,1996.532876,2991.563572,2986.479595,497.3077007,596.6040552,498.4240175,626.0826348,5952.787735,502.631865,792.1549964,5539.810684,23497.59974,504.7745158,487.6789457,487.5609263,779.8522156,974.3912416,491.5576042,486.9005026]
x_pos=[0,209.9541399,351.3754924,492.7968449,28777.06733,42779.19544,42959.36625,43111.21035,43817.61001,44088.91273,45576.15624,45728.05692,46295.08583,46437.0163,47421.78975,48287.28842,49125.84633,49267.26769,49418.85723,51200.38444,51593.5358,51745.12534,52112.94814,52325.08017,52466.50152,52645.34296,52901.31561,53049.80803,53191.22938,56993.80914,57275.94473,57983.0515,58166.89925,58379.03128,59903.59589,60217.97556,62339.29584,62622.13855,63081.51753,63222.93888,66365.13748,66567.37002,71198.91931,71341.75488,74393.1044,74630.69228,95521.45447,98342.31547,99776.83711,99918.25846,100062.9325,100290.6209,115448.1614,116856.7181,117106.1854,124772.7217,127606.1551,141320.5332,141593.0366,145170.3754,148022.7146,150822.3854,151804.0875,154684.1382,155327.2811,156610.6982,156751.2958,157157.4892,157612.6115,159343.2853,159406.0837,159525.3326,159685.9891,160775.2133,161117.2292,161250.1083,161621.0679,161713.8604,161775.7378,161840.0219,161994.5506,162061.1996,162309.092,162447.6062,177181.2711,177243.3909,178735.5882,178847.5349,179115.9211,179200.1505,179327.3272,189797.5691,190061.0122,195264.0345,195561.3932,195688.9204,195759.4583,195820.62,200900.7097,201396.8394,201446.3853,201561.6697,204022.259,204079.5752,204177.3557,254977.6369,255368.8967,259421.8547,260153.0801,260536.4383,264303.1149,264357.8852,264485.256,264707.5137,264799.234,265561.6558,265781.2077,265970.233,271914.2113,272130.6235,272495.4606,272605.3661,272821.1284,273011.5482,273228.5105,273418.3,273819.2267,274249.807,274464.846,274649.5617,275130.9464,275314.1604,280603.1063,280780.9768,282553.5321,286065.742,293129.2429,294827.6711,295223.5152,295393.7709,298755.2746,300438.814,300771.0219,301767.9363,302594.1301,303844.0606,304221.8834,311993.5256,312313.8876,313906.4416,317071.8777,318590.0572,318988.5463,323490.6991,323799.2873,325337.8438,325767.3391,348002.9659,349435.0955,350460.1041,352304.4551,353719.1896,354001.4588,354844.7112,358262.6144,410255.1921,412688.0009,437122.119,438519.9288,443283.8085,443523.0939,443998.0604,445170.9233,445733.9098,453341.472,453589.1019,454059.3244,454818.3094,455760.0041,456230.6065,456511.4783,457936.7953,458876.3307,459229.8695,459465.2857,459701.1689,460053.114,462983.2147,465322.1691,466490.1462,468823.1131,469167.414,469746.6277,470102.895,470355.7287,472687.4975,473149.9801,473709.1833,473952.4271,474488.6035,476808.9321,479124.4602,479407.2879,480302.9674,482614.1447,482866.7053,484595.5481,484834.2689,485148.1289,485388.7699,486079.5385,486372.7855,486813.5912,487043.8793,489342.366,489593.8287,490980.7482,491211.5856,491555.5686,492701.5658,498417.4761,498645.627,499360.6119,499594.2722,499825.7532,500165.9126,500415.3103,500691.2846,500922.4592,501477.6236,501934.5956,502165.5881,502405.3295,503541.793,504269.0836,504837.0176,505414.31,506549.02,507206.7357,507790.0266,519410.8605,519992.2927,521003.1083,526605.7183,527275.4371,527531.2225,582359.1125,582897.1621,586121.3716,586778.9777,587332.5928,588425.2324,588961.0915,591102.6039,593241.0474,597508.7802,598062.6932,600191.2229,600937.5856,602705.0013,603235.774,605055.4079,605606.0167,606181.8325,608033.7591,609091.3724,610148.6124,1031236.922,1035493.285,1045611.572,1046200.414,1046704.836,1047302.709,1047806.846,1048325.467,1054116.614,1054618.995,1055348.233,1055950.695,1057957.74,1059739.475,1060242.418,1060782.951,1061283.625,1063285.043,1063785.08,1066090.948,1068087.481,1071079.044,1074065.524,1074562.832,1075159.436,1075657.86,1076283.942,1082236.73,1082739.362,1083531.517,1089071.328,1112568.927,1113073.702,1113561.381,1114048.942,1114828.794,1115803.185,1116294.743]
Any idea on how to get (1 & 2 & 3) would be super helpful!
You could add a white border around the bars, e.g. via plt.Rectangle((x,y), width, height).
Here is an extended version of the loop in gradient_bar():
def gradient_bar(ax, x, y, widths, bottom=0):
for left, top, width in zip(x, y, widths):
right = left + width
gradient_image(
ax,
extent=(left, right, bottom, top),
cmap="winter",
cmap_range=(0.25, 0.85),
)
ax.add_patch(plt.Rectangle((left, bottom), width, top,
facecolor='none', edgecolor='white', linewidth=0.3))

aligning axes of different plots in matplotlib

I am trying to align these plots so the x axis of the top plot perfectly aligns with the x axis values of the imshow. I'm able to do this by setting the aspect to auto, but then my image is warped. is there a way to do this?
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(-10, 10, 1200)
y = np.linspace(-20, 20, 1600)
xv, yv = np.meshgrid(x, y)
w = 3
xpos = 0
ypos = 5
z = np.exp(-((xv - xpos)**2 + (yv - ypos)**2) / w**2)
xh = np.linspace(0, 2)
yh = np.sin(xh)
sumvertical = np.sum(z, 0)
xvert = range(np.shape(z)[1])
sumhoriz = np.sum(z, 1)
yhoriz = range(np.shape(z)[0])
# definitions for the axes
left, width = 0.1, 0.65
bottom, height = 0.1, 0.65
bottom_h = left_h = left + width + 0.02
rect_scatter = [left, bottom, width, height]
rect_x = [left, bottom_h, width, 0.2]
rect_y = [left_h, bottom, 0.2, height]
plt.figure(1, figsize=(8, 8))
axCenter = plt.axes(rect_scatter)
axhoriz = plt.axes(rect_x)
axvert = plt.axes(rect_y)
axCenter.imshow(z, origin='lower', cmap='jet') #aspect='auto')
axhoriz.plot(xvert, sumvertical)
axvert.plot(sumhoriz, yhoriz)
plt.show()
I would recommend using the tools from mpl_toolkits.axes_grid1, namely make_axes_locatable to divide the center axes to leave room for the marginal axes.
You should then also set the margins to 0 along the shared direction to have the ranges match up.
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import make_axes_locatable
x = np.linspace(-10, 10, 1200)
y = np.linspace(-20, 20, 1600)
xv, yv = np.meshgrid(x, y)
w = 3
xpos = 0
ypos = 5
z = np.exp(-((xv - xpos)**2 + (yv - ypos)**2) / w**2)
xh = np.linspace(0, 2)
yh = np.sin(xh)
sumvertical = np.sum(z, 0)
xvert = range(np.shape(z)[1])
sumhoriz = np.sum(z, 1)
yhoriz = range(np.shape(z)[0])
fig, axCenter = plt.subplots(figsize=(8, 8))
fig.subplots_adjust(.05,.1,.95,.95)
divider = make_axes_locatable(axCenter)
axvert = divider.append_axes('right', size='30%', pad=0.5)
axhoriz = divider.append_axes('top', size='20%', pad=0.25)
axCenter.imshow(z, origin='lower', cmap='jet')
axhoriz.plot(xvert, sumvertical)
axvert.plot(sumhoriz, yhoriz)
axhoriz.margins(x=0)
axvert.margins(y=0)
plt.show()

combining patches to obtain single transparency in matplotlib

I calculate ellipse-like polygons which are drawn over another image. The polygons together form one shape and must have a single transparency. Unfortunately, if I draw one polygon after another, the stack gets different transparencies. In the example below I have 3 polygons, creating 3 transparencies, resulting in different grey areas instead of one. Does anyone know how to obtain a single transparency?
Thank you
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Polygon, Rectangle
def get_ellipse_coords(a=0.0, b=0.0, x=0.0, y=0.0, angle=0.0, k=2):
pts = np.zeros((360*k+1, 2))
beta = -angle * np.pi/180.0
sin_beta = np.sin(beta)
cos_beta = np.cos(beta)
alpha = np.radians(np.r_[0.:360.:1j*(360*k+1)])
sin_alpha = np.sin(alpha)
cos_alpha = np.cos(alpha)
pts[:, 0] = x + (a * cos_alpha * cos_beta - b * sin_alpha * sin_beta)
pts[:, 1] = y + (a * cos_alpha * sin_beta + b * sin_alpha * cos_beta)
return pts
if __name__ == '__main__':
fig = plt.figure()
ax = fig.add_subplot(111, aspect='equal')
plt.xlim([-5,5])
plt.ylim([-5,5])
c='0.5'
alp=0.5
ax.add_patch(Rectangle((1.5, -4.0),0.5,8.0,
color='lightblue', zorder=1))
pts = get_ellipse_coords(a=4.0, b=1.0)
ax.add_patch(Polygon(pts, closed=True, lw=0.5,
color=c, alpha=alp,zorder=2))
pts = get_ellipse_coords(a=4.0, b=1.0, x=1.0, angle=30)
ax.add_patch(Polygon(pts, closed=True, lw=0.5,
color=c, alpha=alp, zorder=2))
pts = get_ellipse_coords(a=2.0, b=0.25, x=2.0, y=-2.0, angle=250)
ax.add_patch(Polygon(pts, closed=True, lw=0.5,
color=c, alpha=alp, zorder=2))
plt.show()
np.concatenate, suggested in the first answer, does the trick I need:
[...]
ax.add_patch(Rectangle((1.5, -4.0),0.5,8.0,
color='lightblue', zorder=1))
pts1 = get_ellipse_coords(a=4.0, b=1.0)
pts2 = get_ellipse_coords(a=4.0, b=1.0, x=1.0, angle=30)
pts3 = get_ellipse_coords(a=2.0, b=0.25, x=2.0, y=-2.0, angle=250)
stoppoint = np.array([[np.nan,np.nan]])
pts = np.concatenate((pts1, stoppoint, pts2))
pts = np.concatenate((pts, stoppoint, pts3))
ax.add_patch(Polygon(pts, closed=True, lw=0.0,
color=c, alpha=alp, zorder=2))
Like this:
pts1 = get_ellipse_coords(a=4.0, b=1.0)
pts2 = get_ellipse_coords(a=4.0, b=1.0, x=1.0, angle=30)
stoppoint = np.array([[nan,nan]])
pts = np.concatenate((pts1, stoppoint, pts2))
ax.add_patch(Polygon(pts, closed=True, lw=0.5,
color=c, alpha=alp,zorder=2))
The stoppoint prevents a line connecting the ellipses.
The accepted solution did not work for me, I had to build my compound path with matplotlib.path.Path.make_compound_path_from_polys and then pass its vertices to my matplotlib.patches.Polygon class instance.
The gist of it as documented is as follows:
data = np.random.randn(1000)
n, bins = np.histogram(data, 50)
# get the corners of the rectangles for the histogram
left = np.array(bins[:-1])
right = np.array(bins[1:])
bottom = np.zeros(len(left))
top = bottom + n
# we need a (numrects x numsides x 2) numpy array for the path helper
# function to build a compound path
XY = np.array([[left, left, right, right], [bottom, top, top, bottom]]).T
# get the Path object
barpath = path.Path.make_compound_path_from_polys(XY)
# make a patch out of it
patch = patches.PathPatch(barpath)
ax.add_patch(patch)

how to make arrow that loops in matplotlib?

what is the right way to draw an arrow that loops back to point to its origin in matplotlib? i tried:
plt.figure()
plt.xlim([0, 1])
plt.ylim([0, 1])
plt.annotate("", xy=(0.6, 0.9),
xycoords="figure fraction",
xytext = (0.6, 0.8),
textcoords="figure fraction",
fontsize = 10, \
color = "k",
arrowprops=dict(edgecolor='black',
connectionstyle="angle,angleA=-180,angleB=45",
arrowstyle = '<|-',
facecolor="k",
linewidth=1,
shrinkA = 0,
shrinkB = 0))
plt.show()
this doesn't give the right result:
the connectionstyle arguments are hard to follow from this page (http://matplotlib.org/users/annotations_guide.html).
i'm looking for is something like this or this:
update: the answer linked to does not show how do this with plt.annotate which has other features i want to use. the proposal to use $\circlearrowleft$ marker is not a real solution.
It seems the easiest way to create an easily modifiable looping arrow is to use patches. I've pasted code to do this below. Change the variables in the variables section and things should all rotate and scale together. You can play around with the patch that creates the arrow head to make a different shape though I suspect that this triangle will be the easiest one.
%matplotlib inline
# from __future__ import division #Uncomment for python2.7
import matplotlib.pyplot as plt
from matplotlib.patches import Arc, RegularPolygon
import numpy as np
from numpy import radians as rad
fig = plt.figure(figsize=(9,9))
ax = plt.gca()
def drawCirc(ax,radius,centX,centY,angle_,theta2_,color_='black'):
#========Line
arc = Arc([centX,centY],radius,radius,angle=angle_,
theta1=0,theta2=theta2_,capstyle='round',linestyle='-',lw=10,color=color_)
ax.add_patch(arc)
#========Create the arrow head
endX=centX+(radius/2)*np.cos(rad(theta2_+angle_)) #Do trig to determine end position
endY=centY+(radius/2)*np.sin(rad(theta2_+angle_))
ax.add_patch( #Create triangle as arrow head
RegularPolygon(
(endX, endY), # (x,y)
3, # number of vertices
radius/9, # radius
rad(angle_+theta2_), # orientation
color=color_
)
)
ax.set_xlim([centX-radius,centY+radius]) and ax.set_ylim([centY-radius,centY+radius])
# Make sure you keep the axes scaled or else arrow will distort
drawCirc(ax,1,1,1,0,250)
drawCirc(ax,2,1,1,90,330,color_='blue')
plt.show()
I find no way to make a loop using plt.annotate only once, but using it four times works :
import matplotlib.pyplot as plt
fig,ax = plt.subplots()
# coordinates of the center of the loop
x_center = 0.5
y_center = 0.5
radius = 0.2
# linewidth of the arrow
linewidth = 1
ax.annotate("", (x_center + radius, y_center), (x_center, y_center + radius),
arrowprops=dict(arrowstyle="-",
shrinkA=10, # creates a gap between the start point and end point of the arrow
shrinkB=0,
linewidth=linewidth,
connectionstyle="angle,angleB=-90,angleA=180,rad=10"))
ax.annotate("", (x_center, y_center - radius), (x_center + radius, y_center),
arrowprops=dict(arrowstyle="-",
shrinkA=0,
shrinkB=0,
linewidth=linewidth,
connectionstyle="angle,angleB=180,angleA=-90,rad=10"))
ax.annotate("", (x_center - radius, y_center), (x_center, y_center - radius),
arrowprops=dict(arrowstyle="-",
shrinkA=0,
shrinkB=0,
linewidth=linewidth,
connectionstyle="angle,angleB=-90,angleA=180,rad=10"))
ax.annotate("", (x_center, y_center + radius), (x_center - radius, y_center),
arrowprops=dict(arrowstyle="-|>",
facecolor="k",
linewidth=linewidth,
shrinkA=0,
shrinkB=0,
connectionstyle="angle,angleB=180,angleA=-90,rad=10"))
plt.show()
Try this:
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_subplot(1,1,1)
ax.set_xlim(1,3)
ax.set_ylim(1,3)
ax.plot([2.5],[2.5],marker=r'$\circlearrowleft$',ms=100)
plt.show()
My suggestion uses just the plot command
import matplotlib.pyplot as plt
import numpy as np
def circarrowdraw(x0, y0, radius=1, aspect=1, direction=270, closingangle=-330,
arrowheadrelativesize=0.3, arrowheadopenangle=30, *args):
"""
Circular arrow drawing. x0 and y0 are the anchor points.
direction gives the angle of the circle center relative to the anchor
in degrees. closingangle indicates how much of the circle is drawn
in degrees with positive being counterclockwise and negative being
clockwise. aspect is important to make the aspect of the arrow
fit the current figure.
"""
xc = x0 + radius * np.cos(direction * np.pi / 180)
yc = y0 + aspect * radius * np.sin(direction * np.pi / 180)
headcorrectionangle = 5
if closingangle < 0:
step = -1
else:
step = 1
x = [xc + radius * np.cos((ang + 180 + direction) * np.pi / 180)
for ang in np.arange(0, closingangle, step)]
y = [yc + aspect * radius * np.sin((ang + 180 + direction) * np.pi / 180)
for ang in np.arange(0, closingangle, step)]
plt.plot(x, y, *args)
xlast = x[-1]
ylast = y[-1]
l = radius * arrowheadrelativesize
headangle = (direction + closingangle + (90 - headcorrectionangle) *
np.sign(closingangle))
x = [xlast +
l * np.cos((headangle + arrowheadopenangle) * np.pi / 180),
xlast,
xlast +
l * np.cos((headangle - arrowheadopenangle) * np.pi / 180)]
y = [ylast +
aspect * l * np.sin((headangle + arrowheadopenangle) * np.pi / 180),
ylast,
ylast +
aspect * l * np.sin((headangle - arrowheadopenangle) * np.pi / 180)]
plt.plot(x, y, *args)
To test it:
plt.figure()
plt.plot(np.arange(10)**2, 'b.')
bb = plt.gca().axis()
asp = (bb[3] - bb[2]) / (bb[1] - bb[0])
circarrowdraw(6, 36 , radius=0.4, aspect=asp, direction=90)
plt.grid()
plt.show()
Another possibility is to use tikz to generate the figure:
\documentclass {minimal}
\usepackage {tikz}
\begin{document}
\usetikzlibrary {arrows}
\begin {tikzpicture}[scale=1.8]
\draw[-angle 90, line width=5.0mm, rounded corners=20pt]
(0.25,0)-- (1.0, 0.0) -- (1.0, -3.0) -- (-3.0, -3.0) -- (-3.0, 0) --(-1,0);
\end{tikzpicture}
\end{document}
This is the result:
there is a pgf/tikz backend in matplotlib that you could generate your matplotlib output to tikz code that pdflatex or lualatex can process.
So this way, I think, you could insert seamlessly the looparrow figure in
your matplotlib figure.
See for ex:
http://matplotlib.org/users/whats_new.html#pgf-tikz-backend
#Aguy's answer is useful if you want a smooth arc instead of a complete circle. In Aguy's answer an arrow head is drawn line by line, but instead a FancyArrowPatch can be used. This gives a full arrow head, which might be more suitable. Below gives the code with the FancyArrowPatch arrow head.
def circarrowdraw(x0, y0, radius=1, aspect=1, direction=270, closingangle=-330, rotate_head = 0.0, color='b', *args):
"""
Circular arrow drawing. x0 and y0 are the anchor points.
direction gives the angle of the circle center relative to the anchor
in degrees. closingangle indicates how much of the circle is drawn
in degrees with positive being counterclockwise and negative being
clockwise. aspect is important to make the aspect of the arrow
fit the current figure. rotate_head is used to rotate the arrow head
by increasing the y value of the arrow's tail coordinate.
"""
# Center of circle
xc = x0 + radius * np.cos(direction * np.pi / 180)
yc = y0 + aspect * radius * np.sin(direction * np.pi / 180)
# Draw circle
if closingangle < 0:
step = -1
else:
step = 1
x = [xc + radius * np.cos((ang + 180 + direction) * np.pi / 180)
for ang in np.arange(0, closingangle, step)]
y = [yc + aspect * radius * np.sin((ang + 180 + direction) * np.pi / 180)
for ang in np.arange(0, closingangle, step)]
plt.plot(x, y, *args, color=color)
# Draw arrow head
arc_arrow_head = patches.FancyArrowPatch((x[-1], y[-1] + rotate_head),
(x[0], y[0]),
arrowstyle="Simple,head_width=10,head_length=10,tail_width=0.01",
color = color,
zorder = 10)
plt.gca().add_patch(arc_arrow_head)
To test it:
plt.plot([0, 0, 1, 1, 0], [0, 1, 1, 0, 0])
circarrowdraw(1.0, 1.0 , radius=0.1, aspect=0.3, direction=90, closingangle=-345, rotate_head = 0.003)
circarrowdraw(0.0, 1.0 , radius=0.1, aspect=1, direction=-90, closingangle=-345, rotate_head = 0.0)
circarrowdraw(0.0, 0.0 , radius=0.1, aspect=3.0, direction=90, closingangle=-345, rotate_head = 0.01)
circarrowdraw(1.0, 0.0 , radius=0.1, aspect=0.3, direction=-90, closingangle=-345)
plt.show()
Picture of image (I don't have a high enough reputation to embed the image in my answer)

Overlay rotated Images on plot with Matplotlib

I've currently constructed a plot using rectangle Patches to display a sequence of positions.
EDIT: Code used to generate this (built off of the RLPy library)-
def visualize_trajectory(self, trajectory=[[0,0,0,0], [0.1,0.1,0,0]]):
domain_fig = plt.figure()
for i, s in enumerate(trajectory):
x, y, speed, heading = s[:4]
car_xmin = x - self.REAR_WHEEL_RELATIVE_LOC
car_ymin = y - self.CAR_WIDTH / 2.
car_fig = matplotlib.patches.Rectangle(
[car_xmin,
car_ymin],
self.CAR_LENGTH,
self.CAR_WIDTH,
alpha=(0.8 * i) / len(trajectory) )
rotation = Affine2D().rotate_deg_around(
x, y, heading * 180 / np.pi) + plt.gca().transData
car_fig.set_transform(rotation)
plt.gca().add_patch(car_fig)
Is there any way to overlay each of these patches with images? Ideally, there would be a car image instead of a rectangle at each of the positions.
I've played around with AnnotationBbox and TransformedBbox, but both seem to be inflexible when dealing with rotations.
Take a look at
demo_affine_image
from the matplotlib gallery. It shows how
to rotate an image.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.transforms as mtransforms
import matplotlib.cbook as cbook
def get_image():
fn = cbook.get_sample_data("necked_tensile_specimen.png")
arr = plt.imread(fn)
# make background transparent
# you won't have to do this if your car image already has a transparent background
mask = (arr == (1,1,1,1)).all(axis=-1)
arr[mask] = 0
return arr
def imshow_affine(ax, z, *args, **kwargs):
im = ax.imshow(z, *args, **kwargs)
x1, x2, y1, y2 = im.get_extent()
im._image_skew_coordinate = (x2, y1)
return im
N = 7
x = np.linspace(0, 1, N)
y = x**1.1
heading = np.linspace(10, 90, N)
trajectory = list(zip(x, y, heading))
width, height = 0.3, 0.3
car = get_image()
fig, ax = plt.subplots()
for i, t in enumerate(trajectory, start=1):
xi, yi, deg = t
im = imshow_affine(ax, car, interpolation='none',
extent=[0, width, 0, height], clip_on=True,
alpha=0.8*i/len(trajectory))
center_x, center_y = width//2, height//2
im_trans = (mtransforms.Affine2D()
.rotate_deg_around(center_x, center_y, deg)
.translate(xi, yi)
+ ax.transData)
im.set_transform(im_trans)
ax.set_xlim(-0.5, 1.5)
ax.set_ylim(-0.5, 1.7)
plt.show()

Categories

Resources