01ภาพรวมของกราฟนี้
Node-link tree แสดงลำดับชั้นด้วยการวางโหนดเป็นชั้นตามความลึกและลากเส้นเชื่อมความสัมพันธ์พ่อแม่กับลูก ต่างจาก treemap ที่เน้นขนาด กราฟนี้เน้นโครงสร้างล้วน ๆ จึงตอบคำถามว่า "อะไรอยู่ใต้อะไร" ได้ตรงที่สุด
เปรียบเทียบให้เห็นภาพ
เหมือนผังองค์กรที่หัวหน้าอยู่บนและลูกน้องอยู่ล่าง เส้นบอกว่าใครรายงานตรงกับใคร
ใช้ที่ไหนบ้าง — ผังองค์กร โครงสร้างหมวดหมู่สินค้า โครงสร้างไฟล์และโฟลเดอร์ หรือแผนผังการตัดสินใจ
สร้างอย่างไร
- ขั้นที่ 1จัดข้อมูลเป็นความสัมพันธ์พ่อแม่กับลูก
- ขั้นที่ 2คำนวณความลึกของแต่ละโหนด
- ขั้นที่ 3กระจายใบให้ห่างเท่ากันแล้ววางพ่อแม่ไว้กึ่งกลางลูก
- ขั้นที่ 4ลากเส้นเชื่อมและติดชื่อโหนด
ตัวอย่างคลาสสิก · ผังองค์กรที่ใช้กันมาตั้งแต่ศตวรรษที่ 19
Daniel McCallum วาดผังองค์กรของบริษัทรถไฟนิวยอร์กแอนด์อีรีในปี 1855 ซึ่งถือเป็นผังองค์กรสมัยใหม่ชิ้นแรก เขาต้องการให้เห็นสายการบังคับบัญชาที่ชัดเจนเพื่อแก้ปัญหาการสื่อสารในองค์กรขนาดใหญ่ รูปแบบนี้แทบไม่เปลี่ยนเลยจนถึงวันนี้
02อธิบายภาษาคนเข้าใจง่าย ๆ
ข้อมูลหลายอย่างมีโครงสร้างเป็นลำดับชั้น เช่น ผังองค์กรหรือหมวดหมู่สินค้า สิ่งที่เราอยากรู้คือใครอยู่ใต้ใคร และโครงสร้างแตกสาขากว้างหรือลึก
Treemap และ sunburst ก็แสดงลำดับชั้นได้ แต่ทั้งคู่ใช้พื้นที่แทนขนาด ซึ่งเบียดโครงสร้างให้เป็นเรื่องรอง ถ้าเราสนใจโครงสร้างล้วน ๆ กราฟต้นไม้ตอบตรงกว่า
วิธีอ่านเป็นธรรมชาติที่สุดในบรรดากราฟลำดับชั้นทั้งหมด เพราะเส้นเชื่อมแสดงความสัมพันธ์โดยตรง ไม่ต้องอาศัยการซ้อนหรือการอยู่ในกรอบเดียวกัน
สิ่งที่อ่านได้ทันทีคือรูปร่างของโครงสร้าง ต้นไม้ที่กว้างและตื้นแปลว่ามีหลายสาขาแต่ไม่ลึก ส่วนต้นไม้ที่แคบและลึกแปลว่ามีสายบังคับบัญชายาว
ข้อจำกัดคือพื้นที่ จำนวนใบกำหนดความกว้างของภาพโดยตรง เมื่อมีใบเกินประมาณ 40 ใบ ชื่อจะชนกันจนอ่านไม่ได้ ทางแก้คือยุบสาขาที่ไม่ได้ดูอยู่ หรือเปลี่ยนไปใช้ radial tree
03ข้อมูลที่ต้องใช้
คอลัมน์ที่ต้องเตรียม
| คอลัมน์ | ทำหน้าที่ | ชนิด | เข้ารหัสด้วย | จำนวนค่าที่เหมาะ | หมายเหตุ |
|---|---|---|---|---|---|
id | ชื่อโหนด | โหนด | y | 5–60 โหนด | ต้องไม่ซ้ำกันทั้งชุด |
parent | โหนดแม่ | โหนด | path | อ้างถึง id ที่มีจริง | รากใช้ค่าว่าง และห้ามมีวงจร |
valueไม่บังคับ | ขนาด | ตัวเลขต่อเนื่อง | size | บวกเท่านั้น | ไม่บังคับ ใช้กำหนดขนาดของจุดถ้าต้องการ |
ต้องเตรียมข้อมูลอย่างไรก่อนวาด
- ตรวจว่าโครงสร้างไม่มีวงจรและมีรากเดียว
- ตรวจว่าทุก parent อ้างถึง id ที่มีจริง
- ตัดสินใจว่าจะวางต้นไม้แนวตั้งหรือแนวนอน
ค่าที่หายไป — โหนดที่อ้างถึงพ่อแม่ที่ไม่มีอยู่จริงจะหลุดออกจากต้นไม้เงียบ ๆ ต้องตรวจจำนวนโหนดที่วาดได้เทียบกับจำนวนแถวก่อนเสมอ
สเกลของแกน — ไม่มีสเกลค่า ตำแหน่งกำหนดจากโครงสร้างล้วน ๆ
กราฟจะเริ่มพังเมื่อไร
เกิน 60 โหนด ชื่อจะชนกันจนอ่านไม่ได้ — ยุบสาขาที่ไม่ได้ดู หรือใช้ radial-tree
หน้าตาไฟล์ที่ต้องเตรียม
id,parent,value
บริษัท,,0
สินค้าอุปโภค,บริษัท,0
…04อ่านแล้วเห็นอะไร
สิ่งที่เห็นได้จากภาพ
- ความลึกของโครงสร้าง
- สาขาที่แตกกว้างกับสาขาที่แคบ
- โหนดที่ไม่มีลูกซึ่งคือปลายสาย
- ความไม่สมดุลของโครงสร้าง
สิ่งที่กราฟนี้ตอบไม่ได้
- ไม่บอกขนาดหรือความสำคัญของแต่ละโหนด เว้นแต่จะเพิ่ม channel ขนาด
- ไม่บอกความสัมพันธ์ข้ามสาขาที่ไม่ใช่พ่อแม่ลูก
อ่านตามลำดับนี้
- หารากแล้วไล่ลงตามเส้น
- นับจำนวนชั้นเพื่อรู้ความลึก
- ดูว่าสาขาไหนมีลูกมากที่สุด
คำถามที่ตอบได้
- โหนดนี้อยู่ใต้ใคร
- โครงสร้างลึกกี่ชั้น
- สาขาไหนแตกย่อยมากที่สุด
สัญญาณว่ามีอะไรผิด
- จำนวนโหนดที่วาดได้น้อยกว่าจำนวนแถวในข้อมูล ซึ่งแปลว่ามีโหนดหลุด
- ชื่อโหนดชนกันจนอ่านไม่ได้
05เหมาะกับการนำเสนอแบบไหน
ทำอย่างไรให้คนอ่านได้ข้อสรุปโดยไม่ต้องบรรยาย
- ติดชื่อโหนดทุกโหนด เพราะชื่อคือข้อมูลหลัก
- ใช้สีบอกประเภทของโหนดถ้ามีความหมาย
- ยุบสาขาที่ไม่เกี่ยวกับประเด็นให้เหลือจุดเดียว
รูปแบบชื่อกราฟที่ควรใช้
โครงสร้างลึก <จำนวน> ชั้น โดย <สาขา> แตกย่อยมากที่สุดที่ <จำนวน> รายการ
06เอาไปใช้ต่อ
โค้ดทุกชิ้นอ่านไฟล์ node-link-tree-sample.csv ชุดเดียวกับที่ Playground ใช้ และตัวเลขในโค้ดเปลี่ยนตามค่าที่คุณปรับด้านล่าง
# ติดตั้ง: pip install "matplotlib==3.10.*" "pandas==2.*"
# ดาวน์โหลด node-link-tree-sample.csv จากหน้านี้ แล้ววางไว้ข้างสคริปต์
import matplotlib
matplotlib.use("Agg") # รันแบบไม่มีหน้าจอ ถ้ารันในเครื่องตัวเองลบบรรทัดนี้ได้
import matplotlib.pyplot as plt
import pandas as pd
import networkx as nx # pip install networkx
C1, C2, C3 = "#2a78d6", "#eb6834", "#1baf7a" # palette ของคลัง เปลี่ยนเป็นสีองค์กรได้
INK, SURFACE = "#0b0b0b", "#fcfcfb"
df = pd.read_csv("node-link-tree-sample.csv")
df["value"] = pd.to_numeric(df["value"], errors="coerce")
NOTE = "shape" # ← ค่าจาก Playground
fig, ax = plt.subplots(figsize=(7.6, 4.2), facecolor=SURFACE)
ax.set_facecolor(SURFACE)
G = nx.DiGraph()
for _, r in df.iterrows():
if r["parent"] and isinstance(r["parent"], str):
G.add_edge(r["parent"], r["id"])
root = df[df["parent"].isna() | (df["parent"] == "")]["id"].iloc[0]
pos = nx.nx_agraph.graphviz_layout(G, prog="dot") if False else nx.spring_layout(G, seed=42)
nx.draw_networkx_edges(G, pos, ax=ax, edge_color="#c9c6bd", width=1.6)
nx.draw_networkx_nodes(G, pos, ax=ax, node_color=C1, node_size=320)
nx.draw_networkx_labels(G, pos, ax=ax, font_size=8, font_color="white")
ax.axis("off")
print("ความลึกสูงสุด:", nx.dag_longest_path_length(G) + 1)
ax.spines[["top", "right"]].set_visible(False) # ลดเส้นที่ไม่ได้ให้ข้อมูล
fig.tight_layout()
fig.savefig("node-link-tree.png", dpi=160)
print("เขียน node-link-tree.png แล้ว ·", len(df), "แถว")07ลองเล่นเพื่อเข้าใจ
ดูข้อมูลเป็นตาราง (11 แถว)
| โหนด | พ่อแม่ | ค่า |
|---|---|---|
| บริษัท | (ราก) | — |
| สินค้าอุปโภค | บริษัท | — |
| อาหาร | บริษัท | — |
| บริการ | บริษัท | — |
| ของใช้ในบ้าน | สินค้าอุปโภค | 91.0 |
| ของใช้ส่วนตัว | สินค้าอุปโภค | 107 |
| เครื่องดื่ม | อาหาร | 87.0 |
| ขนม | อาหาร | 117 |
| อาหารแช่แข็ง | อาหาร | 41.0 |
| ซ่อมบำรุง | บริการ | 102 |
| จัดส่ง | บริการ | 87.0 |
ลองแล้วจะเห็นอะไร
- สังเกตอะไร: เทียบกับหน้า treemap ที่ใช้ข้อมูลชุดเดียวกัน จะเห็นว่า treemap บอกขนาดได้ดีแต่โครงสร้างต้องเดา ส่วนต้นไม้บอกโครงสร้างชัดแต่ไม่บอกขนาด
ข้อควรระวัง
- ไม่ตรวจว่ามีโหนดหลุดออกจากต้นไม้ เพราะโหนดที่อ้างพ่อแม่ผิดจะหายไปเงียบ ๆ
- ใส่โหนดมากเกินไปจนชื่อชนกัน
- ใช้ต้นไม้ทั้งที่ข้อมูลไม่ใช่ลำดับชั้นจริง เช่น มีโหนดที่มีพ่อแม่หลายคน
- เรียงลูกไม่คงที่ ทำให้ภาพเปลี่ยนทุกครั้งที่วาดใหม่
เมื่อไหร่ไม่ควรใช้กราฟนี้
- ขนาดของแต่ละโหนดสำคัญกว่าโครงสร้าง→ใช้แทน: treemap หรือ icicle-chart
- มีโหนดเกิน 60 โหนด→ใช้แทน: radial-tree หรือยุบสาขา
- โหนดมีพ่อแม่ได้หลายคน→ใช้แทน: force-directed-graph หรือ flow-diagram