01ภาพรวมของกราฟนี้
Circle packing บรรจุวงกลมของโหนดลูกไว้ภายในวงกลมของพ่อแม่ ทำให้ความสัมพันธ์แบบ "อยู่ใน" เห็นได้ทันทีโดยไม่ต้องอาศัยเส้นขอบหรือสีเหมือน treemap แลกกับพื้นที่ที่เสียไปกับช่องว่างระหว่างวงประมาณหนึ่งในสี่
เปรียบเทียบให้เห็นภาพ
เหมือนกล่องใส่ของที่มีกล่องเล็กซ้อนอยู่ข้างใน เราเห็นทันทีว่าอะไรอยู่ในอะไรโดยไม่ต้องอ่านป้าย
ใช้ที่ไหนบ้าง — โครงสร้างองค์กรที่มีขนาดทีมต่างกัน โครงสร้างโค้ดที่แต่ละโมดูลมีขนาดต่างกัน หรือหมวดหมู่ที่ต้องเห็นทั้งการซ้อนและขนาด
สร้างอย่างไร
- ขั้นที่ 1จัดข้อมูลเป็นลำดับชั้นพร้อมค่าที่ใบ
- ขั้นที่ 2คำนวณรัศมีจากรากที่สองของค่า
- ขั้นที่ 3บรรจุวงลูกไว้ในวงพ่อแม่ให้แน่นที่สุด
- ขั้นที่ 4ใช้สีบอกระดับหรือสาขา
ตัวอย่างคลาสสิก · Circle packing ในการแสดงโครงสร้างโค้ดของโปรแกรม
เครื่องมือวิเคราะห์โค้ดใช้ circle packing แสดงโครงสร้างโมดูลโดยขนาดวงแทนจำนวนบรรทัด การซ้อนของวงตรงกับการซ้อนของโฟลเดอร์พอดี ทำให้นักพัฒนาเห็นได้ทันทีว่าโมดูลไหนใหญ่ผิดปกติและอยู่ลึกแค่ไหนในโครงสร้าง
02อธิบายภาษาคนเข้าใจง่าย ๆ
Treemap แสดงลำดับชั้นด้วยการซ้อนสี่เหลี่ยม แต่เพราะสี่เหลี่ยมเรียงต่อกันสนิท เราจึงต้องพึ่งเส้นขอบและสีเพื่อบอกว่าอะไรอยู่ในอะไร ซึ่งบางครั้งก็ยังสับสน
Circle packing ใช้วงกลมแทน และเพราะวงกลมไม่เรียงต่อกันสนิท ช่องว่างที่เหลือจึงกลายเป็นตัวบอกขอบเขตที่ชัดเจนโดยไม่ต้องมีเส้นขอบเลย
ผลคือความสัมพันธ์แบบ "อยู่ใน" อ่านได้ทันทีและแทบไม่มีทางเข้าใจผิด ซึ่งเป็นจุดแข็งที่ชัดเจนเมื่อโครงสร้างลึกหลายชั้น
ราคาที่จ่ายคือพื้นที่ วงกลมบรรจุในวงกลมเหลือช่องว่างประมาณหนึ่งในสี่เสมอ ต่างจาก treemap ที่ใช้พื้นที่เต็มร้อยเปอร์เซ็นต์
ผลที่ตามมาคือความแม่นในการเทียบขนาดลดลงอีก เพราะนอกจากพื้นที่จะอ่านได้หยาบอยู่แล้ว วงที่อยู่คนละชั้นยังถูกย่อลงตามพ่อแม่ของมัน จึงเทียบข้ามชั้นไม่ได้เลย
03ข้อมูลที่ต้องใช้
คอลัมน์ที่ต้องเตรียม
| คอลัมน์ | ทำหน้าที่ | ชนิด | เข้ารหัสด้วย | จำนวนค่าที่เหมาะ | หมายเหตุ |
|---|---|---|---|---|---|
id | ชื่อโหนด | โหนด | text | 10–150 โหนด | ต้องไม่ซ้ำกัน |
parent | โหนดแม่ | โหนด | row | อ้างถึง id ที่มีจริง | รากเป็นวงนอกสุด |
value | ขนาด | ตัวเลขต่อเนื่อง | area | บวกเท่านั้น | ★ ใส่ค่าเฉพาะที่ใบ และรัศมีต้องแปรตามรากที่สอง |
ต้องเตรียมข้อมูลอย่างไรก่อนวาด
- ตรวจโครงสร้างว่าไม่มีวงจร
- ใส่ค่าเฉพาะที่ใบ
- คำนวณรัศมีจากรากที่สองของค่า
ค่าที่หายไป — ใบที่ไม่มีค่าจะหายไปจากภาพ ต้องตรวจจำนวนวงที่วาดได้เทียบกับจำนวนใบในข้อมูล
สเกลของแกน — พื้นที่แปรตรงกับค่า ซึ่งแปลว่ารัศมีแปรตามรากที่สอง
กราฟจะเริ่มพังเมื่อไร
เกิน 200 โหนด วงในสุดจะเล็กจนกดและอ่านไม่ได้
หน้าตาไฟล์ที่ต้องเตรียม
id,parent,value
บริษัท,,0
สินค้าอุปโภค,บริษัท,0
…04อ่านแล้วเห็นอะไร
สิ่งที่เห็นได้จากภาพ
- โครงสร้างการซ้อนที่อ่านได้ทันที
- กลุ่มที่ใหญ่ที่สุดจากขนาดวง
- กลุ่มที่มีสมาชิกเยอะแต่แต่ละตัวเล็ก เทียบกับกลุ่มที่มีสมาชิกน้อยแต่ตัวใหญ่
สิ่งที่กราฟนี้ตอบไม่ได้
- ★ เทียบขนาดข้ามชั้นไม่ได้ เพราะวงลูกถูกย่อลงตามขนาดของพ่อแม่
- อ่านค่าที่แม่นยำไม่ได้เพราะเข้ารหัสด้วยพื้นที่
อ่านตามลำดับนี้
- ดูวงใหญ่ก่อนเพื่อรู้ว่ามีกี่กลุ่มหลัก
- ดูภายในวงที่สนใจว่ามีวงย่อยกี่วง
- เทียบขนาดเฉพาะวงที่อยู่ในชั้นเดียวกัน
คำถามที่ตอบได้
- อะไรอยู่ในอะไร
- กลุ่มไหนใหญ่ที่สุด
- โครงสร้างภายในกลุ่มนี้เป็นอย่างไร
สัญญาณว่ามีอะไรผิด
- วงในสุดเล็กจนมองไม่เห็น
- ใช้รัศมีแปรตรงกับค่าแทนรากที่สอง
- เทียบขนาดข้ามชั้นโดยไม่รู้ว่าทำไม่ได้
05เหมาะกับการนำเสนอแบบไหน
ทำอย่างไรให้คนอ่านได้ข้อสรุปโดยไม่ต้องบรรยาย
- ติดชื่อในวงที่ใหญ่พอ และใช้ tooltip สำหรับวงเล็ก
- ใช้สีอ่อนสำหรับวงชั้นนอกและสีเข้มสำหรับชั้นใน
- เขียนยอดรวมไว้ในคำอธิบาย
รูปแบบชื่อกราฟที่ควรใช้
<กลุ่ม> ใหญ่ที่สุดและประกอบด้วย <จำนวน> รายการย่อย
06เอาไปใช้ต่อ
โค้ดทุกชิ้นอ่านไฟล์ circle-packing-sample.csv ชุดเดียวกับที่ Playground ใช้ และตัวเลขในโค้ดเปลี่ยนตามค่าที่คุณปรับด้านล่าง
# ติดตั้ง: pip install "matplotlib==3.10.*" "pandas==2.*"
# ดาวน์โหลด circle-packing-sample.csv จากหน้านี้ แล้ววางไว้ข้างสคริปต์
import matplotlib
matplotlib.use("Agg") # รันแบบไม่มีหน้าจอ ถ้ารันในเครื่องตัวเองลบบรรทัดนี้ได้
import matplotlib.pyplot as plt
import pandas as pd
import circlify # pip install circlify
C1, C2, C3 = "#2a78d6", "#eb6834", "#1baf7a" # palette ของคลัง เปลี่ยนเป็นสีองค์กรได้
INK, SURFACE = "#0b0b0b", "#fcfcfb"
df = pd.read_csv("circle-packing-sample.csv")
df["value"] = pd.to_numeric(df["value"], errors="coerce")
NOTE = "nesting" # ← ค่าจาก Playground
fig, ax = plt.subplots(figsize=(7.6, 4.2), facecolor=SURFACE)
ax.set_facecolor(SURFACE)
leaves = df[df["value"] > 0]
groups = leaves.groupby("parent")["value"].sum().sort_values(ascending=False)
data = [{"id": g, "datum": v,
"children": [{"id": r["id"], "datum": r["value"]}
for _, r in leaves[leaves["parent"] == g].iterrows()]}
for g, v in groups.items()]
circles = circlify.circlify(data, show_enclosure=False, target_enclosure=circlify.Circle(x=0, y=0, r=1))
for c in circles:
depth_color = C1 if c.level == 1 else "#9ec5f4"
ax.add_patch(plt.Circle((c.x, c.y), c.r, fill=c.level > 1, alpha=0.6,
facecolor=depth_color, edgecolor=C1, linewidth=1.8))
if c.r > 0.12:
ax.text(c.x, c.y, c.ex["id"], ha="center", va="center", fontsize=8)
ax.set_xlim(-1.05, 1.05); ax.set_ylim(-1.05, 1.05); ax.set_aspect("equal"); ax.axis("off")
ax.spines[["top", "right"]].set_visible(False) # ลดเส้นที่ไม่ได้ให้ข้อมูล
fig.tight_layout()
fig.savefig("circle-packing.png", dpi=160)
print("เขียน circle-packing.png แล้ว ·", len(df), "แถว")07ลองเล่นเพื่อเข้าใจ
พื้นที่ว่างระหว่างวงไม่ได้มีความหมาย แต่กินที่ประมาณ 20% ของภาพ
ดูข้อมูลเป็นตาราง (7 แถว)
| กลุ่ม | รายการ | ค่า |
|---|---|---|
| สินค้าอุปโภค | ของใช้ในบ้าน | 91.0 |
| สินค้าอุปโภค | ของใช้ส่วนตัว | 107 |
| อาหาร | เครื่องดื่ม | 87.0 |
| อาหาร | ขนม | 117 |
| อาหาร | อาหารแช่แข็ง | 41.0 |
| บริการ | ซ่อมบำรุง | 102 |
| บริการ | จัดส่ง | 87.0 |
ลองแล้วจะเห็นอะไร
- สังเกตอะไร: เทียบกับหน้า treemap ที่ใช้ข้อมูลชุดเดียวกัน จะเห็นว่าการซ้อนของวงอ่านง่ายกว่า แต่ treemap ใช้พื้นที่คุ้มกว่า
ข้อควรระวัง
- ใช้รัศมีแปรตรงกับค่าแทนรากที่สอง ทำให้ขนาดโกหก
- เทียบขนาดวงข้ามชั้น ทั้งที่วงลูกถูกย่อตามพ่อแม่แล้ว
- ใส่โหนดมากเกินไปจนวงในสุดเล็กจนมองไม่เห็น
- เลือกกราฟนี้ทั้งที่พื้นที่จำกัด ซึ่ง treemap ใช้พื้นที่คุ้มกว่าหนึ่งในสี่
เมื่อไหร่ไม่ควรใช้กราฟนี้
- พื้นที่จำกัดและต้องใช้ให้คุ้ม→ใช้แทน: treemap
- ต้องเทียบขนาดข้ามกลุ่มให้แม่น→ใช้แทน: horizontal-bar
- ขนาดไม่สำคัญ สนใจแค่โครงสร้าง→ใช้แทน: node-link-tree