สถาบันข้อมูลขนาดใหญ่ (องค์การมหาชน)

การแสดงกราฟด้วยภาษา DOT

Jan 29, 2021

หากเรามีข้อมูลที่มีความเชื่อมโยงกัน ยกตัวอย่างเช่น ผังองค์กร ผังงาน หรือ แผนที่ความคิด (Mind map) เราสามารถแสดงผลข้อมูลเป็นภาพเพื่อให้คนได้อ่านง่ายโดยใช้กราฟ การแสดงผลเป็นกราฟยังสามารถทำให้เห็นลำดับขั้นตอน ความหนาแน่นของความสัมพันธ์ สามารถเข้าใจและจดจำได้ง่ายขึ้น

เนื่องจากภาษา DOT นั้นมีความง่ายไม่ซับซ้อน ดังนั้นไม่ว่าข้อมูลตั้งต้นเราจะเป็นแบบใด การเขียนโปรแกรมเพื่อแปลงให้ออกมาเป็นผลลัพธ์ภาษา DOT นั้นก็สามารถทำได้ไม่ซับซ้อนจนเกินไปนักตามความคิดของผู้เขียน อีกทั้งภาษา DOT ยังสามารถจัด Layout ของกราฟให้โดยอัตโนมัติอีกด้วย

ไวยากรณ์ภาษา DOT

ไวยากรณ์หลักของภาษา DOT คือ

  • กราฟไม่ระบุทิศทาง (Undirected Graph) 
graph [ชื่อกราฟ] {
    [กำหนดค่าทั่วไป]
    [รายการโหนด]
    รายการความสัมพันธ์กราฟไม่ระบุทิศทาง
}

ผังงานแบบกราฟอวัฏจักรระบุทิศทาง

  • กราฟระบุทิศทาง (Directed Graph) 
digraph [ชื่อกราฟ] {
    [กำหนดค่าทั่วไป]
    [รายการโหนด]
    รายการความสัมพันธ์กราฟระบุทิศทาง
}

สิ่งที่อยู่ในวงเล็บก้ามปู [] เราจะใส่ค่าหรือไม่ก็ได้ หรือเป็น Optional นั่นเองส่วน รายการความสัมพันธ์กราฟไม่ระบุทิศทางนั้น ให้ผู้อ่านคิดเสมือนว่าเป็นการลากเส้นเชื่อมระหว่างโหนดหนึ่งกับอีกโหนดหนึ่ง เช่น

Undirected Graph

graph UndirectedGraph {
    a -- b
    b -- c
}

เป็นการลากเส้นระหว่างจุด a และจุด b และระหว่างจุด b และจุด cเราสามารถเขียนให้สั้นลงอีกเป็นบรรทัดเดียวได้เช่นกันเป็น

graph {
    a -- b -- c
}

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

Directed Graph

digraph DirectedGraph {
    a -> b
    b -> c
}

เป็นการลากเส้นจากจุด a ไปยังจุด b และจากจุด b ไปยังจุด cเราสามารถเขียนให้สั้นลงอีกเป็นบรรทัดเดียวได้เช่นกันเป็น

digraph {
    a -> b -> c
}

นิยามศัพท์ในภาษา DOT

นิยามส่วนต่าง ๆ ในกราฟ
  • โหนด (Node) คือจุดในกราฟ เช่นจากตัวอย่างด้านบนคือ โหนด a, โหนด b และ โหนด c
  • เส้นเชื่อม (Edge) คือเส้นที่ลากต่อระหว่างโหนดเพื่อเชื่อมความสัมพันธ์ ตามภาพด้านซ้ายมือ

สำหรับความแตกต่างระหว่างกราฟไม่ระบุทิศทางและกราฟระบุทิศทางนั้น ผู้เขียนจะบอกต่อไปด้านล่างบทความ

การใส่หมายเหตุ (Comment)

ในภาษา DOT นั้นการใส่หมายเหตุใช้เครื่องหมาย // นำหน้าประโยคที่ต้องการจะเขียนเป็นหมายเหตุ ลักษณะเหมือนตระกูลภาษา C เช่น

ผลลัพธ์การใส่หมายเหตุใน Code
digraph {
    // หมายเหตุ: เป็นการลากเส้นจากจุด a ไปยังจุด b และจากจุด b ไปยังจุด c
    a -> b -> c
}

โดยหมายเหตุจะไม่ถูกแสดงผลในผลลัพธ์ ดังภาพด้านซ้ายมือ

เครื่องมือเครื่องไม้ที่เราจะใช้

สำหรับเครื่องมือที่เราจะใช้เขียนภาษา DOT นั้นมีสองอย่างด้วยกันคือ

โดยหลังจากที่เราติดตั้ง Visual Studio Code และ Plugins ที่ระบุแล้วเราสามารถเขียนภาษา DOT ได้โดยสองวิธี

การใช้ไฟล์นามสกุล .gv

ไฟล์นามสกุล .gv เป็นไฟล์ของภาษา DOT เอง ดังนั้น Code ที่อยู่ในไฟล์นี้จะเป็นภาษา DOT ล้วน การเขียนภาษา DOT โดยใช้วิธีนี้มีขั้นตอนคือ

  1. สร้างไฟล์ใหม่
  1. เลือกประเภทไฟล์เป็น Graphviz (DOT)
  1. Render กราฟโดยใช้ Key ลัด ctrl+kv

การใช้ไฟล์นามสกุล .md

ไฟล์นามสกุล .md นั้นเป็นไฟล์ของภาษา Markdown ดังนั้นตัว Code ส่วนใหญ่ในไฟล์นี้ จึงเป็นภาษา Markdown ซึ่งแนบส่วนของ Code ภาษา DOT เข้าไป การเขียนภาษา DOT โดยใช้วิธีนี้สามารถทำได้โดย

  1. สร้างไฟล์ใหม่ และเลือกประเภทไฟล์เป็น Markdown
  2. Render ไฟล์ Markdown โดยใช้วิธีปกติ ctrl+shift+v ผลลัพธ์จะมีภาพของ Graph แทรกอยู่

ตัวอย่างภาษา DOT

ลักษณะข้อมูลที่ผู้เขียนพบเห็นบ่อย ๆ จะมีดังต่อไปนี้

โครงสร้างแบบต้นไม้

โครงสร้างแบบต้นไม้

ข้อมูลที่มีลักษณะเป็นโครงสร้างต้นไม้นั้น เราสามารถแสดงผลเป็นกราฟไม่ระบุทิศทาง (Undirected Graph) ตัวอย่างเช่นกราฟด้านซ้ายมือ สามารถเขียนออกมาเป็น DOT Language ได้สองแบบดังนี้

เขียนแบบปกติ

graph g {
    a -- b;
    a -- c;
    a -- d;
    a -- e;
    b -- h;
    b -- i;
    i -- f;
    e -- g;
}

เขียนแบบใช้ subgraph ช่วย

graph h {
    a -- { b c d e }; // subgraph
    b -- { h i }; // subgraph
    i -- f;
    e -- g;
}

ตัวอย่างต้นไม้ในโลกจริง ๆ ที่คุ้นเคยกันคือผังองค์กร

ผังองค์กร

จากภาพตัวอย่างด้านซ้ายมือ เราสามารถเขียนเป็น DOT Language ได้ดังนี้

graph g {
    // กำหนดค่าทั่วไป
    node [shape=box];

    // รายการโหนด
    CEO [label=
    <<table border="0" cellborder="1" cellspacing="0" cellpadding="4">
        <tr><td border="0">Mr. Jirapant Peerapant</td></tr>
        <tr><td border="0"><b>CEO</b></td></tr>
        <tr><td border="0"><i>Company President</i></td></tr>
    </table>>
    ];
    FINANCE_VP [label=
    <<table border="0" cellborder="1" cellspacing="0" cellpadding="4">
        <tr><td border="0">Ms. Roonnee Mitaowan</td></tr>
        <tr><td border="0"><b>Vice President</b></td></tr>
        <tr><td border="0"><i>Finance</i></td></tr>
    </table>>
    ]
    HR_VP [label=
    <<table border="0" cellborder="1" cellspacing="0" cellpadding="4">
        <tr><td border="0">Mr. Charkrich Nuurekha</td></tr>
        <tr><td border="0"><b>Vice President</b></td></tr>
        <tr><td border="0"><i>HR</i></td></tr>
    </table>>
    ]
    ACCOUNTS [label=
    <<table border="0" cellborder="1" cellspacing="0" cellpadding="4">
        <tr><td border="0">Mr. Karnjana Deelee</td></tr>
        <tr><td border="0"><b>Accounts</b></td></tr>
    </table>>
    ]

    BILLING [label=
    <<table border="0" cellborder="1" cellspacing="0" cellpadding="4">
        <tr><td border="0">Ms. Suyupajitr Kamruengsri</td></tr>
        <tr><td border="0"><b>Billing</b></td></tr>
    </table>>
    ]

    // รายการความสัมพันธ์
    CEO -- { FINANCE_VP HR_VP };
    FINANCE_VP -- { ACCOUNTS BILLING };
}

สังเกตได้ว่าเราสามารถใช้โครงสร้าง HTML table มาช่วยในการจัดรูปแบบของ label ภายใน node และอีกประเด็นหนึ่งก็คือ กราฟไม่ระบุทิศทางนั้น จะไม่มีลูกศรที่ปลายเส้นเชื่อมระหว่างโหนด ดังนั้นถือว่าผู้อ่านกราฟสามารถท่องไปตามเส้นที่เชื่อมต่อจากโหนดไปยังทิศทางใดก็ได้ ไม่ว่าจะเป็น จากล่างขึ้นบนหรือจากบนลงล่าง จากซ้ายไปขวาหรือขวาไปซ้าย สามารถเป็นไปได้ทั้งสิ้น

ผังงาน (Flowchart)

ในทางกลับกัน มีกราฟอีกรูปแบบหนึ่งที่ผู้อ่านกราฟสามารถท่องไปตามเส้นที่เชื่อมต่อจากโหนดไปยังทิศทางที่กำหนดโดยหัวลูกศรเท่านั้น กราฟที่มีลักษณะแบบนี้เรียกว่า กราฟระบุทิศทาง (Directed Graph) ตัวอย่างลักษณะข้อมูลที่สามารถแสดงผลออกมาเป็นกราฟระบุทิศทางได้คือผังงาน ไม่ว่าจะเป็นผังงานไม่ว่าจะเป็นผังงานแบบ กราฟอวัฏจักรระบุทิศทาง (Directed Acyclic Graph: DAG) หรือ ผังงานแบบกราฟวัฏจักรระบุทิศทาง (Directed Cyclic Graph: DCG)

ตัวอย่างผังงานแบบกราฟอวัฏจักรระบุทิศทาง จากบนลงล่าง

ผังงานแบบกราฟอวัฏจักรระบุทิศทาง
digraph {
    // รายการความสัมพันธ์
    a -> {
        rank = same;
        b -> c
    } [color=blue] // สีของเส้นเชื่อม
    c -> d;
}

ตัวอย่างผังงานแบบกราฟอวัฏจักรระบุทิศทางจากซ้ายไปขวา

ผังงานแบบกราฟอวัฏจักรระบุทิศทางจากซ้ายไปขวา

digraph {
    // กำหนดค่าทั่วไป
    rankdir=LR // กำหนดทิศทางกราฟจากซ้ายไปขวา
    node [shape=square] // วาดโหนดเป็นสี่เหลี่ยมจตุรัส

    // รายการความสัมพันธ์
    a -> b -> c
    b -> d -> e
}

ตัวอย่างผังงานแบบกราฟวัฏจักรระบุทิศทาง จากบนลงล่าง

ผังงานแบบกราฟวัฏจักรระบุทิศทาง จากบนลงล่าง

digraph {
    // กำหนดค่าทั่วไป    
    node [shape=square]
    // รายการโหนด
    a;
    b [label="b?" shape=diamond];
    c; d;
    e [label="counter > 10?" shape=parallelogram];    
    f;

    // รายการความสัมพันธ์
    a -> b
    b:w -> a:w [label="false"]
    b -> c [label="true"]
    c -> d
    d -> e
    e:w -> c:w [label="false;ncounter++"]
    e -> f [label="true;nexit loop"]
}

ผังงานในบริษัท

digraph {
    node [shape=box]

    drafter [label="ผู้ร่างเอกสาร"]
    supervisor [label="หัวหน้า"]
    department_manager [label="ผู้จัดการแผนก"]
    management_director [label="ผู้อำนวยการฝ่ายบริหาร"]
    ceo [label="ประธานกรรมการบริหาร"]
    end_process [label="จบกระบวนการ"]

    drafter -> supervisor [label="submit"]
    drafter -> end_process [label="cancel"]

    supervisor -> drafter [label="revise"]
    supervisor -> end_process [label="approven(low budget)"]
    supervisor -> department_manager [label="approven(medium budget)"]

    department_manager -> supervisor [label="revise"]
    department_manager -> end_process [label="approve"]
    department_manager -> management_director [label="approven(high budget)"]

    management_director -> department_manager [label="revise"]
    management_director -> end_process [label="approve"]
    management_director -> ceo [label="approven(high budget with high credit risk)"]

    ceo -> management_director [label="revise"]
    ceo -> end_process [label="approve"]
}

ตัวอย่างผังงานแบบกราฟวัฏจักรระบุทิศทาง จากซ้ายไปขวา

ผังงานแบบกราฟวัฏจักรระบุทิศทาง จากซ้ายไปขวา
digraph {
    rankdir=LR

    node [shape=box]

    client [label="User PC"]
    firewall [label="Firewall"]
    proxy [label="Proxy Server"]
    web_server [label="Web Server"]
    
    client -> firewall [label="(1) HTTP Request" color="blue" fontcolor="blue"]

    firewall -> client [label="(6)HTTP Response" color="green" fontcolor="green"]
    firewall -> client [label="(2) Reject" color="red" fontcolor="red"]
    
    firewall -> proxy [label="(2) Forward Request" color="blue" fontcolor="blue"]

    proxy -> web_server [label="(3) Forward Request" color="blue" fontcolor="blue"]
    proxy -> firewall [label="(5) Forward Response" color="green" fontcolor="green"]

    web_server -> proxy [label="(4) HTTP Response" color="green" fontcolor="green"]

}

ตัวอย่างการแปลงข้อมูลจากแหล่งอื่นเป็นภาษา DOT

SQL

ในฐานข้อมูลประเภท RDBMS นั้น เราสามารถจัดเก็บข้อมูลประเภทกราฟ และแปลงเป็นภาษา DOT ได้ด้วยการใช้ Table ประเภท linked-list ตัวอย่างเช่น หากเรามีข้อมูลตัวอย่างดังต่อไปนี้

-- สร้าง Table เพื่อเก็บข้อมูล
CREATE TABLE "orgchart" (
"node_id" VARCHAR NULL DEFAULT NULL,
"node_label" VARCHAR NULL DEFAULT NULL,
"parent_node_id" VARCHAR NULL DEFAULT NULL
);

-- ตัวอย่างข้อมูล
INSERT INTO "orgchart" VALUES
('CEO', 'Mr. Jirapant Peerapant', NULL),
('FINANCE_VP', 'Ms. Roonnee Mitaowan', 'CEO'),
('HR_VP', 'Mr. Charkrich Nuurekha', 'CEO'),
('ACCOUNTS', 'Mr. Karnjana Deelee', 'FINANCE_VP'),
('BILLING', 'Ms. Suyupajitr Kamruengsri', 'FINANCE_VP');

เราก็สามารถเขียนประโยค Select เพื่อสร้างเป็นคำสั่งภาษา DOT ได้ ตัวอย่างเช่น

SELECT *
FROM (
SELECT 0 AS ORD, 'graph {'
UNION

-- สร้าง Node
SELECT 1 AS ORD, o.node_id || ' [label="' || o.node_label || '"]' FROM "orgchart" o
UNION

-- สร้างความสัมพันธ์
SELECT 2 AS ORD, o.parent_node_id || '--' || o.node_id 
FROM "orgchart" o
WHERE parent_node_id IS NOT NULL
UNION

SELECT 3 AS ORD, '}'
) D
ORDER BY ORD;

ซึ่งจะให้ผลลัพธ์ดังนี้

ผลลัพธ์จากคำสั่ง SQL

graph {
    CEO [label="Mr. Jirapant Peerapant"]
    HR_VP [label="Mr. Charkrich Nuurekha"]
    FINANCE_VP [label="Ms. Roonnee Mitaowan"]
    ACCOUNTS [label="Mr. Karnjana Deelee"]
    BILLING [label="Ms. Suyupajitr Kamruengsri"]
    CEO--FINANCE_VP
    CEO--HR_VP
    FINANCE_VP--ACCOUNTS
    FINANCE_VP--BILLING
}

Python

ผลลัพธ์จากการรันทดสอบ Code Python ของผู้เขียน

สำหรับตัวอย่างการใช้ภาษา Python เพื่อใช้งานกับภาษา DOT นั้น ผู้อ่านสามารถดูรหัสต้นฉบับเต็มที่สามารถรันทดสอบได้จาก Repository นี้ของผู้เขียน โดยผู้เขียนขอแสดงเฉพาะผลลัพธ์ซึ่งเป็น .gif เนื่องจากรหัสต้นนั้นค่อนข้างยาวครับ

สรุป

จากทั้งหมดที่ผู้เขียนพยายามสื่อก็คือ DOT Language เป็นภาษาที่ค่อนข้างง่าย เพียงคำสั่งไม่กี่ชุดก็สามารถสร้างกราฟได้แล้ว ดังนั้นผู้เขียนหวังว่าผู้อ่านจะสามารถเก็บเกี่ยว แนวคิดที่อยู่ในบทความนี้รวมถึงจุดประกายแนวคิดใหม่ ๆ โดยใช้ DOT Language เพื่อสร้างสรรค์เป็นภาพกราฟ และสามารถสื่อให้บุคคลอื่นเข้าใจในข้อมูลของผู้อ่านโดยใช้กราฟ ในมิติที่ลึกซึ้งยิ่งขึ้น อย่างน้อยผู้เขียนหวังว่าผู้อ่านจะสามารถทำตามตัวอย่างที่ผู้เขียนได้พยายามเขียนให้สามารถอ่านได้ง่ายที่สุด หากผู้เขียนมีข้อเสนอแนะผู้เขียนขอน้อมรับมาปรับปรุงให้ดีขึ้น แล้วพบกันในบทความต่อไปครับ

Reference