Issue
I would like to create custom thick stripes within circles (angled, vertical, or horizontal). The goal is to reproduce images like the tokens here.
One key point is that the output must be in PNG with a transparent background. Furthermore, there must be no artist / patch / etc. outside the main circle.
Here is what I have so far (without the stripes):
import matplotlib.pyplot as plt
import matplotlib.font_manager as fm
from matplotlib import rcParams
from matplotlib.patches import Ellipse,Rectangle
prop = fm.FontProperties(fname="mydirectory/JUST Sans ExBold.otf")
class Team:
def __init__(self):
self.color = '#8bb5da'
self.edge_type = 'thin'
self.edge_color = 'black'
self.name = 'man-city'
self.font_color = 'black'
def define_jerseys():
jerseys = []
mancity = Team()
arsenal = Team()
arsenal.color = '#EF0107'
arsenal.edge_type = 'full'
arsenal.edge_color = 'white'
arsenal.name = 'arsenal'
arsenal.font_color = 'white'
jerseys = [mancity,arsenal]
return jerseys
def draw_token(jersey,n,save=True):
plt.close('all')
fig= plt.figure('token',figsize=(10,10))
ax = fig.add_subplot(111)
ax.grid(False)
ax.axis('off')
x0, y0 = ax.transAxes.transform((0, 0)) # lower left in pixels
x1, y1 = ax.transAxes.transform((1, 1)) # upper right in pixes
dx = x1 - x0
dy = y1 - y0
maxd = max(dx, dy)
width = 0.4* maxd / dx
height = 0.4* maxd / dy
if jersey.edge_type=='full':
ax.add_artist(Ellipse((.45, .45), width, height,fc=jersey.edge_color))
ax.add_artist(Ellipse((.45, .45), 0.9*width, 0.9*height,fc=jersey.color))
if jersey.edge_type=='thin':
ax.add_artist(Ellipse((.45, .45), width, height,fc=jersey.color))
ax.add_artist(Ellipse((.45, .45), width*0.9, height*0.9,ec=jersey.edge_color,fc=None,fill=False,lw=3))
plt.text(0.444,0.426,str(n),fontproperties=prop,fontsize=170,ha='center',va='center',color=jersey.font_color)
if n == 6:
ax.add_artist(Rectangle((.41,.31),0.08,0.01,color=jersey.font_color))
if save:
plt.savefig('./'+jersey.name+'/'+jersey.name+'_'+str(n)+'.png',transparent=True,dpi=96/5)
else:
plt.show()
def create_one(jersey):
for i in range(1,12):
draw_token(jersey,i)
def create_all(jerseys):
for jersey in jerseys:
create_one(jersey)
if __name__=='__main__':
jerseys = define_jerseys()
arsenal = jerseys[1]
create_one(arsenal)
Which produces images such as the following.
My next steps would be to mess around with trigonometry, circle segments and rectangles, but if this is overkill I'd prefer not to re-invent the wheel.
Solution
import matplotlib.pyplot as plt
import matplotlib.patches as patches
fig, ax = plt.subplots()
r_out, r_in = 25, 21
outer = patches.Circle((0, 0), radius=r_out, transform=ax.transData,
color='k')
inner = patches.Circle((0, 0), radius=r_in, transform=ax.transData)
ax.add_patch(outer)
width = height = 2*r_in
#################################
nstripes = 4
ratio_stripes_bg = 1.6
#################################
width_stripe = width/(nstripes+(nstripes-1)/ratio_stripes_bg)
delta_stripe = width_stripe*(1+1/ratio_stripes_bg)
for stripe in range(nstripes):
x0 = -r_in+stripe*delta_stripe
rect = patches.Rectangle((x0,-r_in), width_stripe, height, color="#050050")
rect.set_clip_path(inner)
ax.add_patch(rect)
ax.set_aspect(1)
ax.autoscale_view()
ax.axis('off')
plt.show()
UPDATE
import matplotlib.pyplot as plt
import matplotlib.patches as patches
from math import cos, pi, sin
r_out, r_inn = 25, 22
fig, axs = plt.subplots(1, 5, figsize=(10.0, 2), layout='constrained')
width = height = 2*r_inn
nstripes = 4
ratio_stripes_bg = 1.618
width_stripe = width/(nstripes+(nstripes-1)/ratio_stripes_bg)
delta_stripe = width_stripe*(1+1/ratio_stripes_bg)
for ax, angle in zip(axs, (0.0, 22.5, 45.0, 77.5, 90.0)):
outer = patches.Circle((0, 0), radius=r_out, color='k')
ax.add_patch(outer)
inner = patches.Circle((0, 0), radius=r_inn, transform=ax.transData)
theta = angle*pi/180
s, c = sin(theta), cos(theta)
x0, y0 = r_inn*(s-c), -r_inn*(s+c)
dx, dy = delta_stripe*c, delta_stripe*s
for stripe in range(nstripes):
x = x0+stripe*dx
y = y0+stripe*dy
rect = patches.Rectangle((x, y), width_stripe, height,
angle=angle, color="#120857")
rect.set_clip_path(inner)
ax.add_patch(rect)
ax.set_aspect(1)
ax.set_xlim(-r_out, +r_out)
ax.set_ylim(-r_out, +r_out)
ax.autoscale_view()
ax.axis('off')
plt.show()
2nd UPDATE
I have introduced an offset and I have defined a function that draws a circle in a pre-existing Axes
import matplotlib.pyplot as plt
import matplotlib.patches as patches
from math import cos, pi, sin
def tick_stripes(ax, r_out, r_inn, n_stripes, ratio, angle, bgc, fgc, offset=0):
width = height = 2*r_inn
width_stripe = (width-2*offset)/(n_stripes+(n_stripes-1)/ratio)
delta_stripe = width_stripe*(1+1/ratio)
outer = patches.Circle((0, 0), radius=r_out, color=bgc)
ax.add_patch(outer)
inner = patches.Circle((0, 0), radius=r_inn, transform=ax.transData)
theta = angle*pi/180
s, c = sin(theta), cos(theta)
x0, y0 = r_inn*(s-c)+offset*c, -r_inn*(s+c)+offset*s
dx, dy = delta_stripe*c, delta_stripe*s
for i_stripe in range(n_stripes):
x = x0+i_stripe*dx
y = y0+i_stripe*dy
rect = patches.Rectangle((x, y), width_stripe, height,
angle=angle, color=fgc)
rect.set_clip_path(inner)
ax.add_patch(rect)
ax.set_aspect(1)
ax.set_xlim(-r_out, +r_out)
ax.set_ylim(-r_out, +r_out)
ax.axis('off')
fig, axs = plt.subplots(1, 5, figsize=(10.0, 2), layout='constrained')
for ax, angle in zip(axs, range(0, 121, 30)):
tick_stripes(ax, 40, 38, 5, 0.9, angle, '#600030', '#ffb000', 10)
fig, axs = plt.subplots(1, 5, figsize=(10.0, 2), layout='constrained')
for ax, angle in zip(axs, range(0, 121, 30)):
tick_stripes(ax, 40, 38, 5, 0.9, angle, '#600030', '#ffb000', -7)
plt.show()
3rd UPDATE
Looking attentively at the circles, you may notice that there is a small clipping, that may be avoided increasing the Axes dimensions by a tiny little bit — I won't post images or full code any more, suffices to say
...
axlim = r_out*1.01
ax.set_xlim(-axlim, axlim)
ax.set_ylim(-axlim, axlim)
...
Answered By - gboffi
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.