เทคนิคที่ 080 · PYTHON / JAVASCRIPT

การมัดเส้นเชื่อมHierarchical Edge Bundling

ลดความรกของเส้นโดยมัดเส้นที่ไปทางเดียวกันเข้าด้วยกัน

5
เฉพาะทางควรรู้ก่อน · เข้าใจกราฟเครือข่ายและโครงสร้างลำดับชั้น
ลองเล่น

01ภาพรวมของกราฟนี้

Hierarchical edge bundling วางโหนดรอบวงตามลำดับชั้น แล้วดัดเส้นให้ลู่ตามโครงสร้างจนเส้นที่ไปทางเดียวกันรวมเป็นมัด ทำให้เห็นรูปแบบการเชื่อมระหว่างสาขาได้จากภาพรวม แลกกับการที่ตามเส้นทีละเส้นแทบไม่ได้เลย

เปรียบเทียบให้เห็นภาพ

เหมือนสายไฟในตู้ที่รัดเป็นมัด ซึ่งดูเป็นระเบียบและเห็นทิศทางรวม แต่ตามสายเส้นเดียวจากปลายหนึ่งไปอีกปลายได้ยากขึ้น

ใช้ที่ไหนบ้าง — การเรียกใช้ระหว่างโมดูลในโค้ดขนาดใหญ่ การอ้างอิงข้ามแผนก หรือความสัมพันธ์ข้ามหมวดหมู่ในโครงสร้างที่มีลำดับชั้นอยู่แล้ว

สร้างอย่างไร

  1. ขั้นที่ 1จัดโหนดเข้าโครงสร้างลำดับชั้นและวางรอบวง
  2. ขั้นที่ 2หาเส้นทางผ่านโครงสร้างสำหรับแต่ละเส้นเชื่อม
  3. ขั้นที่ 3ดัดเส้นให้ลู่เข้าหาเส้นทางนั้นตามความแรงที่กำหนด
  4. ขั้นที่ 4ใช้สีบอกทิศทางเข้าออก

ตัวอย่างคลาสสิก · Edge bundling ในการแสดงการพึ่งพากันของโค้ด

Danny Holten เสนอวิธีนี้ในปี 2006 เพื่อแสดงการพึ่งพากันของคลาสในซอฟต์แวร์ขนาดใหญ่ ซึ่งวาดด้วยเส้นตรงแล้วภาพเต็มไปด้วยเส้นตัดกัน เมื่อบังคับให้เส้นลู่ตามโครงสร้างแพ็กเกจ รูปแบบการพึ่งพาข้ามแพ็กเกจก็ปรากฏขึ้นชัดเจน

Wikipedia · Edge bundling

02อธิบายภาษาคนเข้าใจง่าย ๆ

เมื่อเครือข่ายมีเส้นหลายร้อยเส้น การวาดเส้นตรงทุกเส้นทำให้ภาพเต็มไปด้วยเส้นตัดกันจนไม่เห็นรูปแบบอะไรเลย

Edge bundling อาศัยข้อเท็จจริงว่าโหนดมักมีโครงสร้างลำดับชั้นอยู่แล้ว เช่น ไฟล์อยู่ในโฟลเดอร์ พนักงานอยู่ในแผนก แล้วบังคับให้เส้นเดินทางผ่านโครงสร้างนั้นแทนที่จะพุ่งตรง

ผลคือเส้นที่ไปทางเดียวกันจะทับกันเป็นมัดหนา ทำให้เห็นทันทีว่ามีการเชื่อมระหว่างสาขาไหนกับสาขาไหนมากผิดปกติ

ราคาที่จ่ายอย่างแรกคือตามเส้นทีละเส้นแทบไม่ได้ เพราะเมื่อเส้นทับกันแล้วก็แยกไม่ออกว่าเส้นไหนไปไหน กราฟนี้จึงตอบคำถามภาพรวมเท่านั้น

อย่างที่สองคือความแรงของการมัดเป็นค่าที่คนตั้ง มัดแรงไปภาพจะสวยแต่บิดเบือนความจริงมาก มัดเบาไปก็ไม่ต่างจากเส้นตรง ซึ่งแปลว่าภาพเดียวกันปรับให้เล่าเรื่องต่างกันได้

03ข้อมูลที่ต้องใช้

รูปทรงของข้อมูลรายการเส้นเชื่อม source–target–value
จำนวนมิติที่เข้ารหัสได้3 มิติ
จำนวนแถวที่เหมาะ50–400 เส้นเชื่อมขั้นต่ำ 8 แถว
เส้นฐานศูนย์ไม่เกี่ยวข้องไม่มีแกนค่า ตำแหน่งสื่อโครงสร้าง

คอลัมน์ที่ต้องเตรียม

คอลัมน์ทำหน้าที่ชนิดเข้ารหัสด้วยจำนวนค่าที่เหมาะหมายเหตุ
sourceโหนดต้นทางโหนดangle20–300 โหนดควรมีโครงสร้างลำดับชั้นกำกับ
targetโหนดปลายทางโหนดangleชุดเดียวกับ sourceเส้นเชื่อมลู่ตามโครงสร้าง
value
ไม่บังคับ
น้ำหนักตัวเลขต่อเนื่องsizeบวกเท่านั้นควบคุมความหนาของเส้น

ต้องเตรียมข้อมูลอย่างไรก่อนวาด

  1. จัดโหนดเข้าโครงสร้างลำดับชั้นให้ครบ
  2. เรียงโหนดรอบวงให้สาขาเดียวกันอยู่ติดกัน
  3. ตั้งค่าความแรงของการมัดและระบุไว้ในคำอธิบาย

ค่าที่หายไป — โหนดที่ไม่มีที่ในโครงสร้างลำดับชั้นต้องแยกเป็นกลุ่มอื่น ไม่ยัดเข้าสาขาใดสาขาหนึ่ง

สเกลของแกน — ความหนาของเส้นควรแปรตรงกับน้ำหนัก และใช้ความโปร่งใสเพื่อให้เห็นความหนาแน่นของมัด

กราฟจะเริ่มพังเมื่อไร

เกิน 800 เส้นเชื่อม มัดกลางภาพทึบจนไม่เห็นโครงสร้าง

หน้าตาไฟล์ที่ต้องเตรียม

source,target,value
เข้าชมเว็บ,ดูสินค้า,7975
เข้าชมเว็บ,ออกทันที,4078

04อ่านแล้วเห็นอะไร

สิ่งที่เห็นได้จากภาพ

  • คู่สาขาที่เชื่อมกันหนาแน่นผิดปกติ
  • โหนดที่มีเส้นออกไปทุกสาขา
  • สาขาที่แทบไม่เชื่อมกับใครเลย

สิ่งที่กราฟนี้ตอบไม่ได้

  • ★ ตามเส้นทีละเส้นแทบไม่ได้เมื่อเส้นถูกมัดรวม
  • นับจำนวนเส้นที่แน่นอนในมัดไม่ได้
  • ความแรงของการมัดเป็นค่าที่คนตั้ง จึงเทียบภาพสองภาพที่ตั้งต่างกันไม่ได้

อ่านตามลำดับนี้

  1. ดูมัดที่หนาที่สุดก่อนว่าเชื่อมสาขาไหนกับสาขาไหน
  2. ดูว่าเส้นส่วนใหญ่อยู่ภายในสาขาหรือข้ามสาขา
  3. ใช้การชี้เพื่อไฮไลต์เส้นของโหนดเดียวเมื่อต้องการดูรายตัว

คำถามที่ตอบได้

  • สาขาไหนเชื่อมกับสาขาไหนมากที่สุด
  • มีการเชื่อมข้ามสาขามากแค่ไหนเทียบกับภายในสาขา
  • โหนดไหนเป็นจุดเชื่อมหลัก

สัญญาณว่ามีอะไรผิด

  • มัดกลางภาพทึบจนไม่เห็นอะไร
  • ไม่ระบุความแรงของการมัด
  • สาขาเดียวกันกระจายอยู่คนละมุมของวง

05เหมาะกับการนำเสนอแบบไหน

ผู้ชมที่เหมาะผู้อ่านที่ต้องการเห็นรูปแบบการเชื่อมข้ามสาขาในภาพรวม
เวลาที่ผู้ชมต้องใช้ต้องอธิบาย
ขนาดเล็กสุดที่ยังอ่านได้440×440 px
สื่อสารกับคนทั่วไป
รายงานเชิงวิเคราะห์
สิ่งพิมพ์
สไลด์ผู้บริหารสวยแต่อ่านรายละเอียดไม่ได้ในเวลาสั้น
มือถือเส้นทับกันหมดเมื่อภาพเล็ก

ทำอย่างไรให้คนอ่านได้ข้อสรุปโดยไม่ต้องบรรยาย

  1. ตีกรอบสีของแต่ละสาขารอบวงเพื่อให้แยกกลุ่มได้
  2. ใช้สีต่างกันสำหรับเส้นเข้าและเส้นออกเมื่อชี้ที่โหนด
  3. ระบุความแรงของการมัดในคำอธิบาย

รูปแบบชื่อกราฟที่ควรใช้

การเชื่อมข้ามสาขาใน <ระบบ> โดย <สาขา> กับ <สาขา> เชื่อมกันมากที่สุด

06เอาไปใช้ต่อ

โค้ดทุกชิ้นอ่านไฟล์ edge-bundling-sample.csv ชุดเดียวกับที่ Playground ใช้ และตัวเลขในโค้ดเปลี่ยนตามค่าที่คุณปรับด้านล่าง

# ติดตั้ง: pip install "matplotlib==3.10.*" "pandas==2.*"
# ดาวน์โหลด edge-bundling-sample.csv จากหน้านี้ แล้ววางไว้ข้างสคริปต์
import matplotlib
matplotlib.use("Agg")   # รันแบบไม่มีหน้าจอ ถ้ารันในเครื่องตัวเองลบบรรทัดนี้ได้
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
import networkx as nx

C1, C2, C3 = "#2a78d6", "#eb6834", "#1baf7a"   # palette ของคลัง เปลี่ยนเป็นสีองค์กรได้
INK, SURFACE = "#0b0b0b", "#fcfcfb"

df = pd.read_csv("edge-bundling-sample.csv")
df["value"] = pd.to_numeric(df["value"], errors="coerce")
NOTE = "bundle"   # ← ค่าจาก Playground

fig, ax = plt.subplots(figsize=(7.6, 4.2), facecolor=SURFACE)
ax.set_facecolor(SURFACE)

G = nx.from_pandas_edgelist(df, "source", "target", edge_attr="value")
nodes = sorted(G)                                        # เรียงให้สาขาเดียวกันติดกัน
ang = {n: 2 * np.pi * i / len(nodes) - np.pi / 2 for i, n in enumerate(nodes)}
pt = {n: (np.cos(a), np.sin(a)) for n, a in ang.items()}
beta = 0.85                                              # ความแรงของการมัด 0 = เส้นตรง
for a_, b_ in G.edges():
    t = np.linspace(0, 1, 60)[:, None]
    straight = (1 - t) * np.array(pt[a_]) + t * np.array(pt[b_])
    curve = (1 - t) ** 2 * np.array(pt[a_]) + 2 * (1 - t) * t * np.zeros(2) + t ** 2 * np.array(pt[b_])
    path = beta * curve + (1 - beta) * straight
    ax.plot(path[:, 0], path[:, 1], color=C1, linewidth=1.2, alpha=0.55)
for n in nodes:
    ax.plot(*pt[n], "o", markersize=5, color=C1)
    ax.text(pt[n][0] * 1.08, pt[n][1] * 1.08, n, ha="center", va="center", fontsize=7.5)
ax.set_aspect("equal"); ax.axis("off")

ax.spines[["top", "right"]].set_visible(False)   # ลดเส้นที่ไม่ได้ให้ข้อมูล
fig.tight_layout()
fig.savefig("edge-bundling.png", dpi=160)
print("เขียน edge-bundling.png แล้ว ·", len(df), "แถว")

ติดตั้ง: pip install "matplotlib==3.10.*" "pandas==2.*" · ทดสอบกับเวอร์ชัน 3.10

07ลองเล่นเพื่อเข้าใจ

ข้อมูลตัวอย่าง · 8 แถว · seed 239● คำนวณใหม่ในเบราว์เซอร์ทันทีที่ปรับค่า
เส้นลู่เข้าศูนย์กลาง
เข้าชมเว็บเข้าชมเว็บดูสินค้าดูสินค้าออกทันทีออกทันทีใส่ตะกร้าใส่ตะกร้าออกกลางทางออกกลางทางกรอกที่อยู่กรอกที่อยู่ทิ้งตะกร้าทิ้งตะกร้าชำระเงินสำเชำระเงินสำเร็จชำระไม่สำเรชำระไม่สำเร็จ

การมัดเส้นทำให้เส้นแต่ละเส้นตามรอยยาก — ใช้เพื่อดูภาพรวมของทิศทาง ไม่ใช่เพื่อตามเส้นทีละเส้น

จำนวนโหนด9
เส้นเชื่อม8
ความแรงของการมัดสูง (ดึงเข้าศูนย์กลาง)
ดูข้อมูลเป็นตาราง (8 แถว)
hierarchical edge bundling 9 โหนด
จากไปน้ำหนัก
เข้าชมเว็บดูสินค้า7,975
เข้าชมเว็บออกทันที4,078
ดูสินค้าใส่ตะกร้า3,083
ดูสินค้าออกกลางทาง5,112
ใส่ตะกร้ากรอกที่อยู่1,952
ใส่ตะกร้าทิ้งตะกร้า1,166
กรอกที่อยู่ชำระเงินสำเร็จ1,418
กรอกที่อยู่ชำระไม่สำเร็จ457

ลองแล้วจะเห็นอะไร

  • สังเกตอะไร: ลองเลือกเส้นหนึ่งเส้นแล้วตามจากต้นทางไปปลายทาง จะพบว่าทำได้ยากมาก — นี่คือราคาที่แลกมากับความเป็นระเบียบของภาพ

ข้อควรระวัง

  1. พยายามตามเส้นทีละเส้น ทั้งที่กราฟนี้ออกแบบมาให้ดูภาพรวม
  2. ไม่ระบุความแรงของการมัด ทำให้เทียบสองภาพไม่ได้
  3. เรียงโหนดรอบวงไม่ตามโครงสร้าง ทำให้การมัดไม่มีความหมาย
  4. ใช้กับเครือข่ายที่ไม่มีโครงสร้างลำดับชั้นซึ่งไม่มีอะไรให้เส้นลู่ตาม

เมื่อไหร่ไม่ควรใช้กราฟนี้

  • ต้องตามเส้นทีละเส้นให้ได้ใช้แทน: adjacency-matrix หรือ arc-diagram
  • โหนดไม่มีโครงสร้างลำดับชั้นใช้แทน: force-directed-graph
  • เส้นเชื่อมน้อยกว่า 30 เส้นใช้แทน: chord-diagram

กราฟที่เกี่ยวข้อง

เทคนิคถัดไป · กราฟกลุ่มย่อย