joinstyle & capstyle

  • 오늘은 매우 사소한 글입니다.
  • Matplotlib에서 그려지는 선의 꼭지점과 끝점 표현입니다.
  • 사소하지만 신경을 거스르는 일을 해결합시다.

1. motivation: Pie chart

  • 다른 그림에 비해 자주 그리는 그림은 아닙니다.
  • 그런 만큼 손에 익히기 쉽지 않은데, 정리를 한번 하겠습니다.
  • 아보카도, 바나나, 체리 판매량이 각기 40, 70, 10이라고 하고,
  • 상대적인 판매량을 그림으로 그린다고 칩시다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
%matplotlib inline

import numpy as np
import matplotlib.pyplot as plt

# 판매량
data = [40, 70, 10]
# 레이블
labels = ["Abocado", "Banana", "Cherry"]

fig, axs = plt.subplots(ncols=3, figsize=(12, 4), constrained_layout=True)

# (A) 색과 글자 설정
axs[0].pie(data, labels=labels, labeldistance=0.7,
colors=["g", "y", "r"],
textprops={"color":"w", "fontsize":"14", "fontweight":"bold", "ha":"center"})

# (B) 부채꼴에 윤곽선 추가
axs[1].pie(data, labels=labels, labeldistance=0.7,
colors=["g", "y", "r"],
textprops={"color":"w", "fontsize":"14", "fontweight":"bold", "ha":"center"},
wedgeprops={"ec":"w"})

# (C) 부채꼴 윤곽선 두껍게
axs[2].pie(data, labels=labels, labeldistance=0.7,
colors=["g", "y", "r"],
textprops={"color":"w", "fontsize":"14", "fontweight":"bold", "ha":"center"},
wedgeprops={"ec":"w", "lw":6})

titles = ["(A)", "(B): (A) + wedgeprops", "(C): (B) + lw"]
for title, ax in zip(titles, axs):
ax.set_title(title, fontsize=16)
ax.set_aspect(1)


  • colors 매개변수로 색을 조정하고 textprops 매개변수로 글꼴만 좀 조정한 것이 (A)입니다.

  • (B)는 부채꼴에 경계선을 넣어서 틈을 살짝 벌렸습니다. wedgeprops를 사용합니다.

  • 만들고 보니 좀 두꺼우면 좋겠다는 생각이 듭니다. wedgepropslw를 넣어 line width를 두껍게 했습니다.

  • 그런데 cherry가 banana를 찌르고 있습니다.

  • 사소하다면 사소한건데, 영 성가십니다.

  • 원인을 파악하고 제거합시다.

2. 별 그리기

  • 문제를 일반화하기 위해 별을 그리고 이 그림을 바꿔봅니다.
  • 지난 글에서 다룬 PathPathPatch를 사용합니다.

2.1. 별 만들기

  • 오각형 기반으로 별을 만들겠습니다.
  • 어릴 때부터 별을 그리라면 이렇게 그렸죠. :)
1
2
3
4
5
6
7
from matplotlib.path import Path
from matplotlib.patches import PathPatch

xs = [np.cos(2*np.pi*x/5 + 0.5*np.pi) for x in range(5)]
ys = [np.sin(2*np.pi*x/5 + 0.5*np.pi) for x in range(5)]
ps = np.array(list(zip(xs, ys)))
ps
  • 실행 결과: 다섯 지점의 좌표를 만들었습니다.
1
2
3
4
5
array([[ 6.12323400e-17,  1.00000000e+00],
[-9.51056516e-01, 3.09016994e-01],
[-5.87785252e-01, -8.09016994e-01],
[ 5.87785252e-01, -8.09016994e-01],
[ 9.51056516e-01, 3.09016994e-01]])
  • 이제 점따라 이어서 별을 그립니다.
  • 어릴 때 그리던 그 순서대로 점 번호를 찍습니다.
  • 마지막에 원점으로 돌아가는 것까지 잊지 말아야 합니다.
1
2
ps_star = ps[[0,2,4,1,3,0]]
ps_star
  • 실행 결과: 순서대로 점이 배열됩니다.
1
2
3
4
5
6
array([[ 6.12323400e-17,  1.00000000e+00],
[-5.87785252e-01, -8.09016994e-01],
[ 9.51056516e-01, 3.09016994e-01],
[-9.51056516e-01, 3.09016994e-01],
[ 5.87785252e-01, -8.09016994e-01],
[ 6.12323400e-17, 1.00000000e+00]])
  • PathPathPatch를 차례대로 적용해서 별을 그립니다.
1
2
3
4
5
6
7
path_star = Path(ps_star)
patch_star = PathPatch(path_star, fc="yellow", ec="k")

fig, ax = plt.subplots(figsize=(4, 4), constrained_layout=True)
ax.add_artist(patch_star)
ax.set_xlim(-1.2, 1.2)
ax.set_ylim(-1.2, 1.2)


  • 맘에 듭니다.
  • 선을 굵게 합시다. PathPatch에 lw 매개변수를 추가합니다.
1
2
3
4
5
6
7
path_star = Path(ps_star)
patch_star = PathPatch(path_star, fc="yellow", ec="k", lw=10)

fig, ax = plt.subplots(figsize=(4, 4), constrained_layout=True)
ax.add_artist(patch_star)
ax.set_xlim(-1.2, 1.2)
ax.set_ylim(-1.2, 1.2)


  • 시작점이자 끝점인 맨 위가 부러진 듯 보입니다.
  • 끝점(caps)과 중간점(joints) 표현 방식이 달라서 그렇습니다.
  • 일단 저 끝점을 중간점으로 만들어서 일관성을 줍시다.
  • 한 포인트 더 붙이면 됩니다.
1
2
3
4
5
6
7
8
ps_star = ps[[0,2,4,1,3,0,2]]  # 맨 마지막에 2 추가
path_star = Path(ps_star)
patch_star = PathPatch(path_star, fc="yellow", ec="k", lw=10)

fig, ax = plt.subplots(figsize=(4, 4), constrained_layout=True)
ax.add_artist(patch_star)
ax.set_xlim(-1.2, 1.2)
ax.set_ylim(-1.2, 1.2)


2.2. joinstyle

matplotlib._enums.Joinstyle

  • Matplotlib에서 꺾은선 그래프처럼 여러 선이 꺾이는 관절 표현은 joinstyle 매개변수로 결정합니다.

  • miter, round, bevel 세 가지의 선택지가 있습니다.


  • 우리 별 그림에 이 셋을 적용합니다.

1
2
3
4
5
6
7
8
9
joinstyles = ["miter", "round", "bevel"]

fig, axs = plt.subplots(ncols=3, figsize=(12, 4), constrained_layout=True)

for js, ax in zip(joinstyles, axs):
patch_star = PathPatch(path_star, fc="yellow", ec="k", lw=10, joinstyle=js)
ax.add_artist(patch_star)
ax.set_xlim(-1.2, 1.2)
ax.set_ylim(-1.2, 1.2)


  • 왼쪽부터 뾰족, 둥글, 판판입니다.
  • 기본값은 뾰족한 miter로 각도가 작을수록 먼 곳까지 꼭지점이 연장된다는 특징이 있습니다.
  • 맨 위 pie chart 예제에서도 체리 판매량이 다른 둘보다 적어 꼭지점이 바나나를 찔렀습니다.
  • roundbevel을 사용하면 그럴 일이 없을 겁니다.

3. 원 그리기

  • 이번에는 조금 다른 예제를 확인합니다.
  • 시작과 끝이 동일한 원을 점선으로 표현해서 짧은 선을 많이 그립니다.

3.1. 원 만들기

  • 이번엔 PathPatch말고 Circle을 사용합니다.
  • Circle로 원을 만들고 add_artist()로 붙입니다.
1
2
3
4
5
6
7
8
from matplotlib.patches import Circle

circle = Circle((0, 0), 1, ls=":", fc="beige", ec="k", lw=10)

fig, ax = plt.subplots(figsize=(4, 4), constrained_layout=True)
ax.add_artist(circle)
ax.set_xlim(-1.2, 1.2)
ax.set_ylim(-1.2, 1.2)


3.2. joinstyle? capstyle!

  • 별 그림과 똑같이 joinstyle을 적용합니다.
1
2
3
4
5
6
7
fig, axs = plt.subplots(ncols=3, figsize=(12, 4), constrained_layout=True)

for j, ax in zip(joinstyles, axs):
circle = Circle((0, 0), 1, ls=":", fc="beige", ec="k", lw=10, joinstyle=j)
ax.add_artist(circle)
ax.set_xlim(-1.2, 1.2)
ax.set_ylim(-1.2, 1.2)


  • 별 그림과는 달리 변화가 없습니다.

  • joinstyle은 선이 꺾이는 부분에 적용되는 매개변수입니다.

  • 원은 꺾이는 점이 없고 점선도 꺾인 곳이 없기에 아무런 변화가 없습니다.

  • 선 끝에는 capstyle이 적용됩니다.

  • 선택지는 joinstyle처럼 세 개지만 이름이 다릅니다.

  • butt, round, projecting입니다.


  • 확인합니다.

1
2
3
4
5
6
7
8
9
capstyles = ["butt", "round", "projecting"]

fig, axs = plt.subplots(ncols=3, figsize=(12, 4), constrained_layout=True)

for cs, ax in zip(capstyles, axs):
circle = Circle((0, 0), 1, ls=":", fc="beige", ec="k", lw=10, capstyle=cs)
ax.add_artist(circle)
ax.set_xlim(-1.2, 1.2)
ax.set_ylim(-1.2, 1.2)


  • 이제 차이가 보입니다.
  • joinstyle과 capstyle을 함께 표현합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
fig, axs = plt.subplots(ncols=3, figsize=(12, 5), constrained_layout=True)

for js, cs, ax in zip(joinstyles, capstyles, axs):
circle = Circle((0, 0), 1, ls=":", fc="beige", ec="k", lw=10, capstyle=cs)
ax.add_artist(circle)
patch_star = PathPatch(path_star, fc="yellow", ec="r", lw=10, joinstyle=js)
ax.add_artist(patch_star)
ax.set_xlim(-1.2, 1.2)
ax.set_ylim(-1.2, 1.2)
ax.set_title(f"joinstyle= '{js}'\ncapstype= '{cs}'", pad=12, fontsize="xx-large")
ax.set_xticks([])
ax.set_yticks([])
ax.set_aspect(1)


4. 다시 Pie chart

  • 애초 우리 목적은 Pie chart였습니다.
  • 맨 위 코드에 joinstyleround로 적용합니다.
1
2
3
4
5
6
7
8
9
10
data = [40, 70, 10]
labels = ["Abocado", "Banana", "Cherry"]

fig, ax = plt.subplots(figsize=(4, 4), constrained_layout=True)
ax.pie(data, labels=labels, labeldistance=0.7,
colors=["g", "y", "r"],
textprops={"color":"w", "fontsize":"14", "fontweight":"bold", "ha":"center"},
wedgeprops={"ec":"w", "lw":6, "joinstyle":"round"})

ax.set_aspect(1)


  • 짜잔. 이제 깔끔합니다.


도움이 되셨나요? 카페인을 투입하시면 다음 포스팅으로 변환됩니다

Share