상세 컨텐츠

본문 제목

파이썬 rader chart 작성법(spider chart)

IT/코딩-고민과 결과(python)

by 후즈테크 2022. 9. 15. 01:12

본문

반응형

게임을 하다 보면, 아래 같은 차트를 보게되곤 한다.

한눈에 봐도 어떤 성향을 가졌는지 어떤 분야에 뛰어난지 등을 알 수 있는데,

 

위의 차트를 rader chart 또는 spider chart 라고 부른다.

 

해당 차트를 그리기 위해 구글링을 진행했으나, 몇가지 문제점을 찾게 되었고,

똑같은 고민을 하시는 분들에게 도움을 드리고자 해당 내용을 작성했다.

 

먼저 rader charct를 그리기 위해 참고한 꽁양이님의 블로그 이다.

Matplotlib을 이용하여 레이더 차트(Radar chart) 그리기! (tistory.com)

 

Matplotlib을 이용하여 레이더 차트(Radar chart) 그리기!

안녕하세요~ 꽁냥이에요. 보통 게임 속 캐릭터의 능력치를 나타낼 때 레이더 차트(Radar chart)를 많이 사용합니다. 여러분들도 많이 보셨을 거예요. 레이더 차트는 스파이더 차트(Spider chart)라고도

zephyrus1111.tistory.com

 

친절하고 세세하게 설명을 해주셔서 코드를 이해 하는데에는 큰 어려움이 없으나,

 

몇가지 문제가 발생하게 되는데 

 

1. 똑같은 코드를 사용해도 내부의 도형이 원으로 나타나는 것이고,

2. 한글을 정상적으로 출력하지 못한다는 것

3. 데이터를 row별이 아닌 column별로 입력이 되어 가시성이 떨어진다는 것이었고,

 

4. 추가로 필요했던 기능은 투명을 포함한 png 파일로 해당 데이터를 저장하는 것이었다.

 

 

먼저 꽁냥이님이 작성해주신 따로 표기하는 차트의 전체 코드를 보면 아래와 같다. 

import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
 
from math import pi
from matplotlib.path import Path
from matplotlib.spines import Spine
from matplotlib.transforms import Affine2D
 
## 데이터 준비
df = pd.DataFrame({
'Character': ['Barbarian','Amazon','Necromancer','Sorceress','Paladin'],
'Strength': [10, 5, 3, 2, 7],
'Dexterity': [4, 10, 3, 3, 8],
'Vitality': [9, 9, 7, 7, 8],
'Energy': [4, 4, 10, 10, 6],
'Wisdom': [2, 6, 8, 9, 8]
})

## 하나로 합치기 - 폴리곤
labels = df.columns[1:]
num_labels = len(labels)
    
angles = [x/float(num_labels)*(2*pi) for x in range(num_labels)] ## 각 등분점
angles += angles[:1] ## 시작점으로 다시 돌아와야하므로 시작점 추가
    
my_palette = plt.cm.get_cmap("Set2", len(df.index))
 
fig = plt.figure(figsize=(8,8))
fig.set_facecolor('white')
ax = fig.add_subplot(polar=True)
for i, row in df.iterrows():
    color = my_palette(i)
    data = df.iloc[i].drop('Character').tolist()
    data += data[:1]
    
    ax.set_theta_offset(pi / 2) ## 시작점
    ax.set_theta_direction(-1) ## 그려지는 방향 시계방향
    
    plt.xticks(angles[:-1], labels, fontsize=13) ## x축 눈금 라벨
    ax.tick_params(axis='x', which='major', pad=15) ## x축과 눈금 사이에 여백을 준다.
    ax.set_rlabel_position(0) ## y축 각도 설정(degree 단위)
    plt.yticks([0,2,4,6,8,10],['0','2','4','6','8','10'], fontsize=10) ## y축 눈금 설정
    plt.ylim(0,10)
    
    ax.plot(angles, data, color=color, linewidth=2, linestyle='solid', label=row.Character) ## 레이더 차트 출력
    ax.fill(angles, data, color=color, alpha=0.4) ## 도형 안쪽에 색을 채워준다.
    
for g in ax.yaxis.get_gridlines(): ## grid line 
    g.get_path()._interpolation_steps = len(labels)
 
spine = Spine(axes=ax,
          spine_type='circle',
          path=Path.unit_regular_polygon(len(labels)))
 
## Axes의 중심과 반지름을 맞춰준다.
spine.set_transform(Affine2D().scale(.5).translate(.5, .5)+ax.transAxes)
           
ax.spines = {'polar':spine} ## frame의 모양을 원에서 폴리곤으로 바꿔줘야한다.
 
plt.legend(loc=(0.9,0.9))
plt.show()

위 코드를 실행하면 아래와 같은 이쁘지만 겉과 속이 다른 도형 나타난다.....

49라인의 코드를 통해 내부 도형도 다각형이 그려져야 하는데, 그대로 원으로 남아있다.

for g in ax.yaxis.get_gridlines(): ## grid line 
    g.get_path()._interpolation_steps = len(labels)

 

위와 관련된 내용을 stackoverflow 에서 발견하였고, 2019년 작성된 내용이라는 것을 확인하고, 

matplotlib 의 과거 version을 설치하였다. 

기존의 작업환경과 구별을 위해 가상환경을 구성했고,

혹시 가상환경을 구성하는 방법을 모른다면 아래 내용을 따라 하거나

matplotlib을 삭제 후 해당 버전으로 설치하여 진행할 수도 있다.

1. 내부 도형 변경(원->다각형)

 1-1. 특정 버전의 matplotlib을 설치하기 위해 가상환경을 구성한다. venv의 경우, 파이썬 3.3 부터는 내장되어 있으므로 별도의 설치가 필요없다.

  1-2. 원하는 경로에서 python -m venv .venv_rader 를 입력한다.(마지막 venv_rader 대신 venv만 사용해도 무관하다.)

 1-3. 실행이 완료되면 해당 폴더에 venv_rader 폴더가 생성되며, 아래에 Scripts 및 기타 파일들이 생성된다.

 1-4. 코드를 작성할 main.py 파일을 만들고, vs code를 사용할 경우, [Ctrl + Shift + ` ] 을 눌러주면 실행되는 터미널에 아래와 같은 가상환경에 대한 표기가 나타난다.

1-5 사용할 코드를 넣어보면 사용할 모듈들이 인식되지 못하는 것을 볼 수 있다.

1-6. 이제 필요한 모듈을 pip를 이용하여 설치하면 되는데, 가장 중요한 부분은 matplotlib의 경우 꼭 해당 버전을 설치해야한다는 것이다. * numpy의 경우, matplogtlib에 포함되어 있어 별도로 설치를 진행하지 않아도 된다.

 - pip install matplotlib==3.2.2

 - pip install pandas

 

해당 버전이 정상적으로 설치되었음을 확인할 수 있다.

1-7. 위 과정을 완료한 후, 코드를 실행해보면 내부의 도형도 깔끔한 오각형으로 나타나는 것을 확인할 수 있다.

좌 - matplotlib 최신 버전                                         /                                         우 - matplotlib 3.2.2 버전

 

 

2. 한글 출력 제한 사항

 - 각 항목별 내용을 확인해보면 캐릭터가 Babarian, Amazon 등 영어로 표기되어 있고, 능력치 구별 또한 영문임을 알 수 있다.

 - 한글로 바꿔 볼 경우, 아래와 같이 글자가 깨지는 것을 확인할 수 있는데

위와 같이 항목이나 캐릭터를 구별할 수 없다.

 

처음에는 encoding 문제로 생각했으나, 실제 한글 폰트 인식에 대한 문제였다. 

한글 font에 맞게 해당 코드를 추가해주면

# 한글 폰트 사용을 위해서 세팅
from matplotlib import font_manager, rc
font_path = "C:/Windows/Fonts/NGULIM.TTF"
font = font_manager.FontProperties(fname=font_path).get_name()
rc('font', family=font)

## 데이터 준비
df = pd.DataFrame({
'Character': ['바바리안','아마존','네크로멘서','소서리스','팔라딘'],
'힘': [10, 5, 3, 2, 7],
'민첩': [4, 10, 3, 3, 8],
'체력': [9, 9, 7, 7, 8],
'마나': [4, 4, 10, 10, 6],
'지혜': [2, 6, 8, 9, 8]
})

원하는 형태의 한글을 포함한 차트가 나타나게된다.

 

3. 데이터를 row별이 아닌 column별로 입력이 되어 가시성이 떨어지는 문제

  예제코드의 데이터를 보면 row별이 아닌 column별로 입력되어 있는 것을 볼 수 있다.

이 코드에 어쎄신을 추가해 보도록 하자.

뭔가 가시성이 떨어지고 입력할 때에도 많은 항목을 수정해야한다.

큰 의미는 없지만 네크로멘서의 능력치를 확인하려면 해당 항목에서 각각 3번째 항목을 찾아서 봐야 하는데

이러한 부분을 column 단위가 아닌 row단위로 data를 입력하여 가시성을 높였다.

 

변경전 column으로 읽어 들이던 정보를

## 데이터 준비
df = pd.DataFrame({
'Character': ['바바리안','아마존','네크로멘서','소서리스','팔라딘','어쎄신'],
'힘'  : [10, 5, 3, 2, 7, 5],
'민첩': [4, 10, 3, 3, 8, 10],
'체력': [9, 9, 7, 7, 8, 7],
'마나': [4, 4, 10, 10, 6, 5],
'지혜': [2, 6, 8, 9, 8, 5]
})

 

row 단위로 입력한다.

# 데이터 준비
# 변경 후 row 단위
data = [
['바바리안',10,4,9,4,2],
['아마존',5,10,9,4,6],
['네크로멘서',3,3,7,10,8],
['소서리스',2,3,7,10,9],
['팔라딘',7,8,8,10,9],
['어쎄신',5,10,7,5,5]
]

df = pd.DataFrame(data, columns=['Character','힘','민첩','체력','마나','지혜'] )

# 변경 전 column 단위
# df = pd.DataFrame({
# 'Character': ['바바리안','아마존','네크로멘서','소서리스','팔라딘','어쎄신'],
# '힘'  : [10, 5, 3, 2, 7, 5],
# '민첩': [4, 10, 3, 3, 8, 10],
# '체력': [9, 9, 7, 7, 8, 7],
# '마나': [4, 4, 10, 10, 6, 5],
# '지혜': [2, 6, 8, 9, 8, 5]
# })

 

입력된 Data를 확인할 때에도 좀 더 깔끔하게 해당 내용을 확인할 수 있다.

 

4. 그림으로 저장

 

마지막으로 해당 파일을 그림파일 형태로 저장하려 하는데, 다른 이미지와 합칠 경우 배경등을 사용하기 위해 png와 배경을 투명으로 하는 옵션을 추가하였다.

 # 그림파일로 저장 
# file name 중복을 피하기 위해 시간값으로 file name 생성
from datetime import datetime
now = datetime.now()
# 배경 투명 옵션
plt.savefig(str(now.strftime('%Y%m%d%H%M%S'))+'.png', transparent = True)

실행 후, 시간값으로 파일이 생성된 것과 격자무늬(투명)으로 파일이 생성되었다.

 

 

전체 코드

import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
 
from math import pi
from matplotlib.path import Path
from matplotlib.spines import Spine
from matplotlib.transforms import Affine2D


# 한글 폰트 사용을 위해서 세팅
from matplotlib import font_manager, rc
font_path = "C:/Windows/Fonts/NGULIM.TTF"
font = font_manager.FontProperties(fname=font_path).get_name()
rc('font', family=font)

## 데이터 준비
data = [
['바바리안',10,4,9,4,2],
['아마존',5,10,9,4,6],
['네크로멘서',3,3,7,10,8],
['소서리스',2,3,7,10,9],
['팔라딘',7,8,8,10,9],
['어쎄신',5,10,7,5,5]
]

df = pd.DataFrame(data, columns=['Character','힘','민첩','체력','마나','지혜'] )

## 하나로 합치기 - 폴리곤
labels = df.columns[1:]
num_labels = len(labels)
    
angles = [x/float(num_labels)*(2*pi) for x in range(num_labels)] ## 각 등분점
angles += angles[:1] ## 시작점으로 다시 돌아와야하므로 시작점 추가
    
my_palette = plt.cm.get_cmap("Set2", len(df.index))
 
fig = plt.figure(figsize=(8,8))
fig.set_facecolor('white')
ax = fig.add_subplot(polar=True)
for i, row in df.iterrows():
    color = my_palette(i)
    data = df.iloc[i].drop('Character').tolist()
    data += data[:1]
    
    ax.set_theta_offset(pi / 2) ## 시작점
    ax.set_theta_direction(-1) ## 그려지는 방향 시계방향
    
    plt.xticks(angles[:-1], labels, fontsize=13) ## x축 눈금 라벨
    ax.tick_params(axis='x', which='major', pad=15) ## x축과 눈금 사이에 여백을 준다.
    ax.set_rlabel_position(0) ## y축 각도 설정(degree 단위)
    plt.yticks([0,2,4,6,8,10],['0','2','4','6','8','10'], fontsize=10) ## y축 눈금 설정
    plt.ylim(0,10)
    
    ax.plot(angles, data, color=color, linewidth=2, linestyle='solid', label=row.Character) ## 레이더 차트 출력
    ax.fill(angles, data, color=color, alpha=0.4) ## 도형 안쪽에 색을 채워준다.
    
for g in ax.yaxis.get_gridlines(): ## grid line 
    g.get_path()._interpolation_steps = len(labels)
 
spine = Spine(axes=ax,
          spine_type='circle',
          path=Path.unit_regular_polygon(len(labels)))
 
## Axes의 중심과 반지름을 맞춰준다.
spine.set_transform(Affine2D().scale(.5).translate(.5, .5)+ax.transAxes)
           
ax.spines = {'polar':spine} ## frame의 모양을 원에서 폴리곤으로 바꿔줘야한다.


# # 그림파일로 저장 
# file name 중복을 피하기 위해 시간값으로 file name 생성
from datetime import datetime
now = datetime.now()
# 배경 투명 옵션
plt.savefig(str(now.strftime('%Y%m%d%H%M%S'))+'.png', transparent = True)

plt.legend(loc=(0.9,0.9))
plt.show()
반응형

'IT > 코딩-고민과 결과(python)' 카테고리의 다른 글

pyautogui step1 자동화 업무 분석  (0) 2021.07.29
python 업무자동화(매크로) 란?  (0) 2021.07.01
시작  (0) 2021.07.01

관련글 더보기

댓글 영역