PlusMagi's Blog By Pitt Phunsanit Backend Mermaid.js: Dynamic Render

Mermaid.js: Dynamic Render

ในโลกของการพัฒนาเว็บแอปพลิเคชันสมัยใหม่ ข้อมูลที่แสดงผลไม่ได้มาจากโค้ดคงที่ (Static) อีกต่อไป แต่ต้องถูกสร้างขึ้นแบบไดนามิก (Dynamic) จากการเรียกใช้ API หรือการดึงข้อมูลจากฐานข้อมูล การสร้างแผนภาพ (Diagram) ที่ซับซ้อนด้วยไลบรารีอย่าง Mermaid.js จึงเป็นสิ่งที่นักพัฒนาต้องเจอเป็นประจำ

บทความนี้จะเจาะลึกวิธีการทำ Dynamic Rendering ของ Mermaid.js โดยเน้นที่หลักการทำงานตั้งแต่การดึงข้อมูลดิบจากระบบหลังบ้าน (Backend) เช่น PostgreSQL จนถึงการแปลงเป็น SVG ที่สวยงามบนหน้าไคลเอนต์ (Client-side) ได้อย่างสมบูรณ์แบบ

Understanding Dynamic Rendering: จากข้อมูลดิบสู่แผนภาพ

เมื่อเราพูดถึง Dynamic Rendering ของ Mermaid.js หมายถึงการที่เราไม่ได้ป้อนโค้ด Mermaid Markup เข้าไปในโค้ด HTML โดยตรง แต่เราจะสร้างสตริง (String) ของ Markup นั้นขึ้นมาในหน่วยความจำของแอปพลิเคชัน (เช่น ใน JavaScript) โดยมีข้อมูลที่มาจากการประมวลผลข้อมูลจริง

ขั้นตอนหลักมีดังนี้:

  1. Data Retrieval (Backend): ดึงข้อมูลโครงสร้าง (Nodes และ Edges) จากฐานข้อมูล PostgreSQL
  2. Markup Generation (Backend/Service Layer): แปลงข้อมูลโครงสร้างเหล่านั้นให้เป็น Mermaid Syntax String (เช่น `A –> B`)
  3. Client Rendering (Frontend): ส่ง Markup String ไปยัง JavaScript และใช้ฟังก์ชัน `mermaid.render()` เพื่อแปลงเป็น SVG

การใช้งาน Mermaid.js ในการ Render แบบ Dynamic

หัวใจสำคัญของการทำ Dynamic Render คือการใช้ฟังก์ชัน mermaid.render() ซึ่งเป็นฟังก์ชัน Asynchronous ที่รับ Markup String เข้าไป และคืนค่าเป็น Promise ที่มี Object { svg: string, alt: string } ที่เราสามารถนำไปใช้ได้ทันที

ในทางปฏิบัติ เราจะต้องรอให้ Promise นี้ Resolve ก่อนจึงจะสามารถนำ SVG ไปใส่ใน DOM ได้อย่างถูกต้อง นี่คือตัวอย่างโค้ด JavaScript ที่สมบูรณ์สำหรับการจัดการกระบวนการนี้:

JavaScript

// 1. Import Mermaid Library (ต้องใช้ type="module")
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';

// 2. Initialize Mermaid (สำคัญ: ต้องตั้งค่า startOnLoad: false เพื่อควบคุมการ Render เอง)
mermaid.initialize({ startOnLoad: false });

// 3. ฟังก์ชันจำลองการรับ Markup จาก API (สมมติว่า Backend ส่งมา)
async function renderDiagramFromApi(mermaidMarkupString) {
    const previewSVG = document.getElementById('previewSVG');
    previewSVG.innerHTML = 'กำลังโหลดแผนภาพ...';

    try {
        // 4. ใช้ mermaid.render() ซึ่งเป็น Promise
        const { svg, alt } = await mermaid.render('diagram-id', mermaidMarkupString);

        // 5. เมื่อได้ SVG แล้ว ให้นำไปใส่ใน DOM
        previewSVG.innerHTML = svg;
        console.log('Diagram rendered successfully. Alt text:', alt);

    } catch (error) {
        // 6. จัดการข้อผิดพลาดหาก Markup ผิดไวยากรณ์
        previewSVG.innerHTML = '<p style="color: red;">เกิดข้อผิดพลาดในการสร้างแผนภาพ: ' + error.message + '</p>
<!-- /wp:paragraph -->';
        console.error('Mermaid rendering error:', error);
    }
}

// ตัวอย่างการเรียกใช้ฟังก์ชันด้วยข้อมูลจำลอง
const dynamicMarkup = "graph TD\n    A[Start] --> B(Process Data)\n    B --> C{Decision Point}\n    C --> D[End];";

// เรียกใช้เมื่อหน้าเว็บโหลดเสร็จ (หรือเมื่อข้อมูลจาก API มาถึง)
renderDiagramFromApi(dynamicMarkup);

การเชื่อมโยงกับ PostgreSQL (Backend Flow)

ในสถานการณ์จริง ข้อมูลที่ใช้สร้าง dynamicMarkup จะไม่ได้มาจากการ Hardcode แต่มาจาก PostgreSQL โดยเราต้องเขียน Service Layer เพื่อทำหน้าที่นี้:

  1. Query Data: ดึงข้อมูลโครงสร้างที่ต้องการจากตาราง (เช่น SELECT node_id, node_label, relationship_type, target_node_id FROM diagram_data WHERE diagram_id = 'X')
  2. Process Data: วนลูป (Loop) ข้อมูลที่ได้มา และสร้างสตริงตามไวยากรณ์ Mermaid (เช่น ใช้ Template Literals ในภาษา Backend อย่าง Node.js หรือ Python)
  3. API Response: ส่งสตริง Mermaid Markup ที่สมบูรณ์แล้วกลับไปยัง Frontend ในรูปแบบ JSON

ตัวอย่างแนวคิดการสร้าง Markup (Pseudo-Code):

SQL

-- PostgreSQL Query (ดึงข้อมูลดิบ)
SELECT node_id, node_label FROM nodes WHERE diagram_id = 1;
SELECT start_id, end_id, relationship_type FROM relationships WHERE diagram_id = 1;

-- Backend Logic (สร้างสตริง)
let markup = "graph TD\n";

// 1. เพิ่ม Nodes
// FOR node IN nodes: markup += `${node.id}['${node.label}']\n`;

// 2. เพิ่ม Edges
// FOR rel IN relationships: markup += `${rel.start} --> ${rel.end} (${rel.type})\n`;

// ส่ง 'markup' string นี้ไปให้ Frontend

บทสรุปและข้อควรระวัง

การทำ Dynamic Rendering ด้วย Mermaid.js เป็นเทคนิคที่ทรงพลังอย่างยิ่งในการสร้าง User Experience ที่ทันสมัย อย่างไรก็ตาม สิ่งที่ต้องระวังคือ การจัดการ Asynchronous เสมอ เนื่องจาก mermaid.render() เป็น Promise คุณต้องใช้ await หรือ `.then()` เพื่อให้แน่ใจว่า SVG จะถูก Render และแสดงผลใน DOM อย่างสมบูรณ์ก่อนที่โค้ดส่วนถัดไปจะทำงาน

การแยกหน้าที่ (Separation of Concerns) ระหว่าง Backend (ที่รับผิดชอบการแปลงข้อมูลเป็น Markup String) และ Frontend (ที่รับผิดชอบการเรียกใช้ mermaid.render()) จะช่วยให้โค้ดของคุณสะอาด มีประสิทธิภาพ และง่ายต่อการบำรุงรักษาในระยะยาว