01ภาพรวมของกราฟนี้
Radial tree วางรากไว้ตรงกลางแล้วให้สาขาแผ่ออกเป็นรัศมี ข้อดีคือเส้นรอบวงโตตามรัศมี จึงมีที่ให้ใบมากกว่าต้นไม้แนวตรงที่ความกว้างจำกัด ข้อเสียคือชั้นนอกดูห่างกันมากกว่าความจริงและอ่านชื่อยากเพราะต้องหมุนตาม
เปรียบเทียบให้เห็นภาพ
เหมือนมองรากไม้จากด้านบนที่แผ่ออกทุกทิศ แทนที่จะมองต้นไม้จากด้านข้างที่กิ่งแผ่ได้แค่สองทาง
ใช้ที่ไหนบ้าง — โครงสร้างหมวดหมู่ที่มีใบจำนวนมาก ต้นไม้วิวัฒนาการของสิ่งมีชีวิต หรือโครงสร้างไฟล์ที่ลึกและกว้าง
สร้างอย่างไร
- ขั้นที่ 1คำนวณมุมของแต่ละใบให้กระจายรอบวง
- ขั้นที่ 2คำนวณรัศมีจากความลึก
- ขั้นที่ 3ลากเส้นโค้งเชื่อมพ่อแม่กับลูก
- ขั้นที่ 4หมุนป้ายชื่อให้อ่านตามแนวรัศมี
ตัวอย่างคลาสสิก · ต้นไม้วิวัฒนาการของสิ่งมีชีวิตในงานชีววิทยา
ต้นไม้วิวัฒนาการที่ครอบคลุมสิ่งมีชีวิตหลายพันชนิดวาดเป็นแนวตรงไม่ได้เลยเพราะความกว้างเกินหน้ากระดาษ การดัดเป็นวงทำให้ใส่สปีชีส์ได้มากขึ้นหลายเท่า และยังสื่อแนวคิดว่าทุกสิ่งมีบรรพบุรุษร่วมกันที่จุดศูนย์กลาง
02อธิบายภาษาคนเข้าใจง่าย ๆ
ต้นไม้แนวตรงมีข้อจำกัดชัดเจน คือความกว้างของภาพถูกกำหนดโดยจำนวนใบ ถ้ามี 100 ใบ ภาพก็ต้องกว้าง 100 ช่อง ซึ่งเกินหน้าจอแน่นอน
Radial tree แก้ด้วยเรขาคณิต เมื่อดัดให้แผ่เป็นวง ที่ว่างสำหรับใบก็คือเส้นรอบวง ซึ่งโตตามรัศมี ดังนั้นยิ่งต้นไม้ลึกยิ่งมีที่ให้ใบมาก
ผลคือใส่ใบได้มากกว่าต้นไม้แนวตรงหลายเท่าในพื้นที่สี่เหลี่ยมจัตุรัสเท่ากัน ซึ่งเป็นข้อได้เปรียบที่ชัดเจนเมื่อโครงสร้างใหญ่
ราคาที่จ่ายอย่างแรกคือการรับรู้ระยะ โหนดที่อยู่ชั้นนอกดูห่างกันมากกว่าโหนดชั้นในที่อยู่ห่างกันเท่ากันในเชิงโครงสร้าง เพราะเส้นรอบวงยาวกว่า
อย่างที่สองคือป้ายชื่อ ชื่อที่วางตามรัศมีจะเอียงไปทุกทิศ ผู้อ่านต้องหมุนหัวตาม ซึ่งช้ากว่าการอ่านชื่อที่เรียงแนวนอนอย่างมาก
03ข้อมูลที่ต้องใช้
คอลัมน์ที่ต้องเตรียม
| คอลัมน์ | ทำหน้าที่ | ชนิด | เข้ารหัสด้วย | จำนวนค่าที่เหมาะ | หมายเหตุ |
|---|---|---|---|---|---|
id | ชื่อโหนด | โหนด | angle | 15–200 โหนด | รองรับโหนดได้มากกว่าต้นไม้แนวตรง |
parent | โหนดแม่ | โหนด | path | อ้างถึง id ที่มีจริง | รากอยู่ตรงกลางวง |
valueไม่บังคับ | ขนาด | ตัวเลขต่อเนื่อง | size | บวกเท่านั้น | ไม่บังคับ |
ต้องเตรียมข้อมูลอย่างไรก่อนวาด
- ตรวจโครงสร้างว่าไม่มีวงจร
- เรียงใบให้สาขาเดียวกันอยู่ติดกันรอบวง
- เตรียมมุมหมุนของป้ายชื่อแต่ละใบ
ค่าที่หายไป — โหนดที่หลุดจากโครงสร้างจะหายไปเงียบ ๆ ต้องตรวจจำนวนที่วาดได้เทียบกับจำนวนแถว
สเกลของแกน — รัศมีแปรตามความลึก และควรให้ชั้นห่างเท่ากันเพื่อไม่ให้ชั้นใดดูสำคัญกว่า
กราฟจะเริ่มพังเมื่อไร
เกิน 250 โหนด ใบจะเบียดกันรอบวงจนชื่อชนกัน
หน้าตาไฟล์ที่ต้องเตรียม
id,parent,value
บริษัท,,0
สินค้าอุปโภค,บริษัท,0
…04อ่านแล้วเห็นอะไร
สิ่งที่เห็นได้จากภาพ
- ความลึกของโครงสร้างจากจำนวนวง
- สาขาที่กินมุมกว้างซึ่งคือสาขาที่มีใบมาก
- ความสมมาตรหรือไม่สมมาตรของโครงสร้าง
สิ่งที่กราฟนี้ตอบไม่ได้
- ★ ระยะที่เห็นระหว่างโหนดชั้นนอกดูห่างกว่าความจริงเพราะเส้นรอบวงยาวกว่า
- อ่านชื่อได้ช้ากว่าต้นไม้แนวตรงมากเพราะป้ายเอียงไปทุกทิศ
อ่านตามลำดับนี้
- เริ่มที่รากตรงกลางแล้วไล่ออกนอก
- ดูว่าสาขาไหนกินมุมกว้างที่สุด
- นับจำนวนวงเพื่อรู้ความลึก
คำถามที่ตอบได้
- โครงสร้างลึกกี่ชั้น
- สาขาไหนใหญ่ที่สุดเมื่อวัดจากจำนวนใบ
- โหนดนี้อยู่ใต้สาขาไหน
สัญญาณว่ามีอะไรผิด
- ชื่อใบชนกันรอบวง
- ชั้นห่างไม่เท่ากันโดยไม่มีเหตุผล
- ใบของสาขาเดียวกันกระจายอยู่คนละมุม
05เหมาะกับการนำเสนอแบบไหน
ทำอย่างไรให้คนอ่านได้ข้อสรุปโดยไม่ต้องบรรยาย
- หมุนป้ายชื่อให้อ่านตามแนวรัศมีและกลับด้านเมื่ออยู่ครึ่งซ้าย
- ใช้สีตามสาขาระดับบนเพื่อให้แยกสาขาได้
- ติดชื่อเฉพาะใบที่สำคัญเมื่อมีใบเยอะ
รูปแบบชื่อกราฟที่ควรใช้
โครงสร้าง <จำนวน> โหนด ลึก <จำนวน> ชั้น โดยสาขา <สาขา> ใหญ่ที่สุด
06เอาไปใช้ต่อ
โค้ดทุกชิ้นอ่านไฟล์ radial-tree-sample.csv ชุดเดียวกับที่ Playground ใช้ และตัวเลขในโค้ดเปลี่ยนตามค่าที่คุณปรับด้านล่าง
# ติดตั้ง: pip install "matplotlib==3.10.*" "pandas==2.*"
# ดาวน์โหลด radial-tree-sample.csv จากหน้านี้ แล้ววางไว้ข้างสคริปต์
import matplotlib
matplotlib.use("Agg") # รันแบบไม่มีหน้าจอ ถ้ารันในเครื่องตัวเองลบบรรทัดนี้ได้
import matplotlib.pyplot as plt
import pandas as pd
import networkx as nx
import numpy as np
C1, C2, C3 = "#2a78d6", "#eb6834", "#1baf7a" # palette ของคลัง เปลี่ยนเป็นสีองค์กรได้
INK, SURFACE = "#0b0b0b", "#fcfcfb"
df = pd.read_csv("radial-tree-sample.csv")
df["value"] = pd.to_numeric(df["value"], errors="coerce")
NOTE = "space" # ← ค่าจาก Playground
fig, ax = plt.subplots(figsize=(7.6, 4.2), facecolor=SURFACE)
ax.set_facecolor(SURFACE)
fig.clf()
ax = fig.add_subplot(projection="polar")
G = nx.DiGraph()
for _, r in df.iterrows():
if isinstance(r["parent"], str) and r["parent"]:
G.add_edge(r["parent"], r["id"])
root = [n for n in G if G.in_degree(n) == 0][0]
depth = nx.shortest_path_length(G, root)
leaves = [n for n in G if G.out_degree(n) == 0]
angle = {n: 2 * np.pi * i / len(leaves) for i, n in enumerate(leaves)}
for n in reversed(list(nx.topological_sort(G))): # พ่อแม่อยู่กึ่งกลางลูก
kids = list(G.successors(n))
if kids: angle[n] = np.mean([angle[k] for k in kids])
for a, b in G.edges():
ax.plot([angle[a], angle[b]], [depth[a], depth[b]], color="#c9c6bd", linewidth=1.4)
for n in G:
ax.plot(angle[n], depth[n], "o", markersize=7, color=C1)
ax.set_yticklabels([]); ax.set_xticklabels([])
ax.spines[["top", "right"]].set_visible(False) # ลดเส้นที่ไม่ได้ให้ข้อมูล
fig.tight_layout()
fig.savefig("radial-tree.png", dpi=160)
print("เขียน radial-tree.png แล้ว ·", len(df), "แถว")07ลองเล่นเพื่อเข้าใจ
ชั้นนอกมีเส้นรอบวงยาวกว่า ทำให้โหนดชั้นนอกดูห่างกันมากกว่าความจริง
ดูข้อมูลเป็นตาราง (11 แถว)
| โหนด | พ่อแม่ |
|---|---|
| บริษัท | (ราก) |
| สินค้าอุปโภค | บริษัท |
| อาหาร | บริษัท |
| บริการ | บริษัท |
| ของใช้ในบ้าน | สินค้าอุปโภค |
| ของใช้ส่วนตัว | สินค้าอุปโภค |
| เครื่องดื่ม | อาหาร |
| ขนม | อาหาร |
| อาหารแช่แข็ง | อาหาร |
| ซ่อมบำรุง | บริการ |
| จัดส่ง | บริการ |
ลองแล้วจะเห็นอะไร
- สังเกตอะไร: สังเกตว่าโหนดในวงนอกดูห่างกันมากกว่าวงใน ทั้งที่ในเชิงโครงสร้างอยู่ห่างกันเท่ากัน — นี่คือการบิดที่แลกมากับพื้นที่ที่มากขึ้น
ข้อควรระวัง
- ลืมว่าโหนดชั้นนอกดูห่างกันมากกว่าความจริง
- ไม่หมุนป้ายชื่อให้อ่านได้ ทำให้ชื่อครึ่งซ้ายกลับหัว
- ใช้กับต้นไม้เล็ก ซึ่งต้นไม้แนวตรงอ่านง่ายกว่ามาก
- เรียงใบไม่ให้สาขาเดียวกันอยู่ติดกัน ทำให้สาขากระจายรอบวง
เมื่อไหร่ไม่ควรใช้กราฟนี้
- มีโหนดน้อยกว่า 20 โหนด→ใช้แทน: node-link-tree ที่อ่านชื่อง่ายกว่า
- ชื่อโหนดยาว→ใช้แทน: icicle-chart ที่เขียนชื่อแนวนอนได้
- ขนาดของโหนดสำคัญ→ใช้แทน: circle-packing หรือ treemap