Overview
Generates high-quality SVG diagrams to insert into business documents (PPT, Word, PDF). Supports automatic Mermaid syntax → SVG conversion + theme color customization.
Environment Note: Mermaid CLI(
mmdc) requires Puppeteer (Chrome). In the Cowork environment, Direct custom SVG generation(Method 2) first. If Chrome is installed on the user's local PC,mmdcalso works normally.
Supported Diagram Types
| Type | Description | Mermaid Support | Custom SVG |
|---|---|---|---|
| Flowchart | Business processes, decision flows | ✅ | ✅ |
| Org Chart | Team structure, reporting hierarchy | ✅ | ✅ |
| Sequence Diagram | API flows, system integration | ✅ | - |
| Gantt Chart | Project schedule | ✅ | - |
| Pie Chart | Ratio/distribution visualization | ✅ | ✅ |
| Timeline | History, milestones | ✅ | ✅ |
| KPI Card | Key metric visualization | - | ✅ |
| Process Arrow | Step-by-step progress diagram | - | ✅ |
| Comparison Table | Before/After, vs comparison | - | ✅ |
| Infographic | Statistics, data visualization | - | ✅ |
Theme Color System
Default Palette (ppt-design-system integration)
const THEME = {
primary: '#4A7BF7', // 메인 블루
secondary: '#00D4AA', // 시안 그린
accent: '#FFB020', // 골드 옐로우
danger: '#FF6B6B', // 레드
purple: '#8B5CF6', // 퍼플
dark: '#1A1F36', // 다크 네이비
text: '#4A5568', // 본문 그레이
light: '#F5F7FA', // 배경 그레이
white: '#FFFFFF',
};
Mermaid Official Syntax Reference (v11.x)
Supported Diagram List
| Diagram | Keyword | Purpose |
|---|---|---|
| Flowchart | flowchart LR/TD/BT/RL |
Business processes, decision-making |
| Sequence | sequenceDiagram |
API/system integration flows |
| Class | classDiagram |
System structure, relationship diagrams |
| State | stateDiagram-v2 |
State transitions, workflows |
| ER | erDiagram |
Database structure |
| Gantt | gantt |
Project schedule |
| Pie | pie |
Ratio/distribution |
| Mindmap | mindmap |
Idea organization, brainstorming |
| Timeline | timeline |
History, milestones |
| Git Graph | gitGraph |
Branch strategy |
| Block | block-beta |
System architecture |
| Sankey | sankey-beta |
Flow volume visualization |
Look & Layout Settings (v11 New Feature)
Starting with Mermaid v11, you can select the diagram's look and layout.
---
config:
look: handDrawn # classic | handDrawn
layout: elk # dagre | elk
theme: base
---
flowchart LR
A[요청] --> B[처리] --> C[완료]
- handDrawn: Sketch feel (informal meetings, idea stage)
- classic: Existing clean style (official documents, reports)
- ELK layout: Layout engine optimized for complex diagrams
Flowchart node shapes
flowchart LR
A[사각형] --> B(둥근사각형) --> C{다이아몬드}
C -->|조건1| D[[서브프로세스]]
C -->|조건2| E[(데이터베이스)]
C -->|조건3| F((원형))
D --> G>비대칭]
E --> H{{육각형}}
Styling (classDef)
flowchart TD
A[시작]:::primary --> B[처리]:::success --> C[완료]:::accent
classDef primary fill:#4A7BF7,color:#fff,stroke:#3A6BE7
classDef success fill:#00D4AA,color:#fff,stroke:#00B494
classDef accent fill:#FFB020,color:#1A1F36,stroke:#E09A10
classDef danger fill:#FF6B6B,color:#fff,stroke:#E05555
Sequence diagram syntax
sequenceDiagram
participant C as 고객
participant S as 서버
participant D as DB
C->>S: API 요청
activate S
S->>D: 쿼리 실행
D-->>S: 결과 반환
S-->>C: 응답
deactivate S
Note over C,S: 인증 필요 시
alt 인증 성공
S->>C: 200 OK
else 인증 실패
S->>C: 401 Unauthorized
end
Mindmap (v11)
mindmap
root((AI 업무 자동화))
문서 작성
보고서
이메일
제안서
데이터 분석
매출 분석
KPI 대시보드
디자인
PPT
SVG 도식화
번역
한영
영한
Timeline
timeline
title 프로젝트 마일스톤
2026-Q1 : 요구사항 분석
: 프로토타입 설계
2026-Q2 : 개발 착수
: 알파 테스트
2026-Q3 : 베타 출시
: 사용자 피드백
2026-Q4 : 정식 런칭
Method 1: Mermaid → SVG conversion
Installation and usage
# mermaid-cli 설치 (이미 설치됨)
npx mmdc --version
# Mermaid 파일 → SVG 변환
npx mmdc -i diagram.mmd -o diagram.svg -t dark -b transparent
Mermaid conversion script
const { execSync } = require('child_process');
const fs = require('fs');
const path = require('path');
/**
* Mermaid 구문을 SVG로 변환
* @param {string} mermaidCode - Mermaid 구문
* @param {string} outputPath - 출력 SVG 경로
* @param {object} options - 옵션 (theme, bgColor, width, height)
*/
function mermaidToSvg(mermaidCode, outputPath, options = {}) {
const tmpInput = '/tmp/mermaid_input.mmd';
const tmpConfig = '/tmp/mermaid_config.json';
// Mermaid 코드 저장
fs.writeFileSync(tmpInput, mermaidCode, 'utf8');
// 테마 설정 (한국형 비즈니스 색상)
const config = {
theme: 'base',
themeVariables: {
primaryColor: options.primaryColor || '#4A7BF7',
primaryTextColor: '#FFFFFF',
primaryBorderColor: '#3A6BE7',
secondaryColor: options.secondaryColor || '#00D4AA',
tertiaryColor: '#F5F7FA',
lineColor: '#4A5568',
textColor: '#1A1F36',
fontSize: '14px',
fontFamily: 'Pretendard, "ChosunilboNM", sans-serif',
// 노드 스타일
nodeBorder: '2px',
nodeTextColor: '#1A1F36',
mainBkg: '#EBF0FF',
// 간트 차트
gridColor: '#E2E8F0',
todayLineColor: '#FF6B6B',
// 시퀀스
actorBkg: '#4A7BF7',
actorTextColor: '#FFFFFF',
activationBkgColor: '#E8EEFF',
}
};
fs.writeFileSync(tmpConfig, JSON.stringify(config), 'utf8');
// 변환 실행
const cmd = [
'npx', 'mmdc',
'-i', tmpInput,
'-o', outputPath,
'-c', tmpConfig,
'-b', options.bgColor || 'transparent',
'-w', String(options.width || 1200),
'-H', String(options.height || 800),
].join(' ');
execSync(cmd, { timeout: 30000 });
// SVG 후처리: 한글 폰트 확보
let svg = fs.readFileSync(outputPath, 'utf8');
if (!svg.includes('Pretendard')) {
svg = svg.replace(
'<style>',
`<style>
@import url('https://cdn.jsdelivr.net/gh/orioncactus/pretendard/dist/web/static/pretendard.css');
`
);
fs.writeFileSync(outputPath, svg, 'utf8');
}
return outputPath;
}
Mermaid template examples
Business process flow
flowchart LR
A[📋 업무 요청] --> B{검토}
B -->|승인| C[🔨 작업 진행]
B -->|반려| D[📝 수정 요청]
D --> A
C --> E[✅ 완료 보고]
E --> F[📊 성과 기록]
style A fill:#4A7BF7,color:#fff
style C fill:#00D4AA,color:#fff
style E fill:#FFB020,color:#1A1F36
style F fill:#8B5CF6,color:#fff
Org chart
graph TD
CEO[대표이사] --> VP1[경영지원본부]
CEO --> VP2[사업본부]
CEO --> VP3[기술본부]
VP1 --> D1[인사팀]
VP1 --> D2[재무팀]
VP2 --> D3[영업팀]
VP2 --> D4[마케팅팀]
VP3 --> D5[개발팀]
VP3 --> D6[인프라팀]
Project Gantt chart
gantt
title 프로젝트 일정표
dateFormat YYYY-MM-DD
axisFormat %m/%d
section 기획
요구사항 분석 :a1, 2026-03-01, 7d
기획서 작성 :a2, after a1, 5d
section 개발
프론트엔드 :b1, after a2, 14d
백엔드 :b2, after a2, 14d
section 테스트
QA 테스트 :c1, after b1, 7d
배포 :milestone, after c1, 0d
Method 2: Creating custom SVG directly
Basic SVG structure
function createSvg(width, height, content) {
return `<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 ${width} ${height}"
width="${width}" height="${height}">
<defs>
<style>
@import url('https://cdn.jsdelivr.net/gh/orioncactus/pretendard/dist/web/static/pretendard.css');
text { font-family: 'Pretendard', sans-serif; }
.title { font-size: 24px; font-weight: 700; fill: #1A1F36; }
.subtitle { font-size: 16px; font-weight: 500; fill: #4A5568; }
.body { font-size: 14px; font-weight: 400; fill: #4A5568; }
.caption { font-size: 12px; font-weight: 400; fill: #718096; }
.accent { fill: #4A7BF7; }
.success { fill: #00D4AA; }
.warning { fill: #FFB020; }
.danger { fill: #FF6B6B; }
</style>
<!-- 그라데이션 -->
<linearGradient id="blueGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#4A7BF7;stop-opacity:1" />
<stop offset="100%" style="stop-color:#8B5CF6;stop-opacity:1" />
</linearGradient>
<!-- 그림자 -->
<filter id="shadow">
<feDropShadow dx="0" dy="2" stdDeviation="4" flood-opacity="0.1" />
</filter>
</defs>
${content}
</svg>`;
}
Process arrow (Step Arrow)
function createProcessArrow(steps, options = {}) {
const { width = 1200, stepHeight = 80, gap = 20 } = options;
const colors = ['#4A7BF7', '#00D4AA', '#FFB020', '#8B5CF6', '#FF6B6B'];
const stepWidth = (width - gap * (steps.length - 1)) / steps.length;
let content = '';
steps.forEach((step, i) => {
const x = i * (stepWidth + gap);
const color = colors[i % colors.length];
// 화살표 모양 배경
const arrowPoints = i < steps.length - 1
? `${x},0 ${x + stepWidth - 15},0 ${x + stepWidth},${stepHeight/2} ${x + stepWidth - 15},${stepHeight} ${x},${stepHeight} ${x + 15},${stepHeight/2}`
: `${x},0 ${x + stepWidth},0 ${x + stepWidth},${stepHeight} ${x},${stepHeight} ${x + 15},${stepHeight/2}`;
if (i > 0) {
content += `<polygon points="${x - 15},0 ${x},0 ${x + 15},${stepHeight/2} ${x},${stepHeight} ${x - 15},${stepHeight}" fill="${color}" opacity="0.15"/>`;
}
content += `<polygon points="${arrowPoints}" fill="${color}" rx="8"/>`;
content += `<text x="${x + stepWidth/2}" y="${stepHeight/2 - 8}" text-anchor="middle" class="body" fill="white" font-weight="600">STEP ${i + 1}</text>`;
content += `<text x="${x + stepWidth/2}" y="${stepHeight/2 + 12}" text-anchor="middle" class="caption" fill="white">${step}</text>`;
});
return createSvg(width, stepHeight, content);
}
KPI card
function createKpiCards(kpis, options = {}) {
const { width = 1200, cardHeight = 140, gap = 20 } = options;
const cardWidth = (width - gap * (kpis.length - 1)) / kpis.length;
const colors = ['#4A7BF7', '#00D4AA', '#FFB020', '#8B5CF6'];
let content = '';
kpis.forEach((kpi, i) => {
const x = i * (cardWidth + gap);
const color = colors[i % colors.length];
// 카드 배경
content += `<rect x="${x}" y="0" width="${cardWidth}" height="${cardHeight}" rx="12" fill="white" filter="url(#shadow)"/>`;
// 상단 accent 바
content += `<rect x="${x}" y="0" width="${cardWidth}" height="4" rx="2" fill="${color}"/>`;
// 라벨
content += `<text x="${x + cardWidth/2}" y="35" text-anchor="middle" class="caption">${kpi.label}</text>`;
// 수치
content += `<text x="${x + cardWidth/2}" y="80" text-anchor="middle" font-size="36" font-weight="800" fill="${color}" font-family="Pretendard">${kpi.value}</text>`;
// 변화량
if (kpi.change) {
const changeColor = kpi.change.startsWith('+') ? '#00D4AA' : '#FF6B6B';
content += `<text x="${x + cardWidth/2}" y="110" text-anchor="middle" font-size="14" fill="${changeColor}" font-family="Pretendard">${kpi.change}</text>`;
}
});
return createSvg(width, cardHeight, content);
}
Comparison table (Before/After)
function createComparison(before, after, options = {}) {
const { width = 1000, height = 400, title = '' } = options;
let content = '';
// 제목
if (title) {
content += `<text x="${width/2}" y="30" text-anchor="middle" class="title">${title}</text>`;
}
const startY = title ? 50 : 10;
const boxW = width / 2 - 30;
const boxH = height - startY - 10;
// Before 박스
content += `<rect x="10" y="${startY}" width="${boxW}" height="${boxH}" rx="12" fill="#FFF5F5" stroke="#FF6B6B" stroke-width="2"/>`;
content += `<text x="${boxW/2 + 10}" y="${startY + 30}" text-anchor="middle" font-size="18" font-weight="700" fill="#FF6B6B" font-family="Pretendard">BEFORE</text>`;
// After 박스
content += `<rect x="${width/2 + 20}" y="${startY}" width="${boxW}" height="${boxH}" rx="12" fill="#F0FFF4" stroke="#00D4AA" stroke-width="2"/>`;
content += `<text x="${width/2 + 20 + boxW/2}" y="${startY + 30}" text-anchor="middle" font-size="18" font-weight="700" fill="#00D4AA" font-family="Pretendard">AFTER</text>`;
// 화살표
content += `<text x="${width/2}" y="${startY + boxH/2}" text-anchor="middle" font-size="32" fill="#4A7BF7">→</text>`;
// 항목 리스트
before.forEach((item, i) => {
content += `<text x="30" y="${startY + 60 + i * 28}" class="body" fill="#4A5568">✗ ${item}</text>`;
});
after.forEach((item, i) => {
content += `<text x="${width/2 + 40}" y="${startY + 60 + i * 28}" class="body" fill="#1A1F36">✓ ${item}</text>`;
});
return createSvg(width, height, content);
}
Org chart (Org Chart)
function createOrgChart(data, options = {}) {
const { width = 1200, nodeW = 180, nodeH = 60, gap = 40 } = options;
const colors = { ceo: '#1A1F36', vp: '#4A7BF7', dept: '#00D4AA', team: '#FFB020' };
let content = '';
function drawNode(x, y, label, level) {
const color = level === 0 ? colors.ceo : level === 1 ? colors.vp : level === 2 ? colors.dept : colors.team;
const textColor = level <= 1 ? '#FFFFFF' : '#1A1F36';
content += `<rect x="${x}" y="${y}" width="${nodeW}" height="${nodeH}" rx="8" fill="${color}" filter="url(#shadow)"/>`;
content += `<text x="${x + nodeW/2}" y="${y + nodeH/2 + 5}" text-anchor="middle" font-size="14" font-weight="${level < 2 ? 700 : 500}" fill="${textColor}" font-family="Pretendard">${label}</text>`;
}
function drawLine(x1, y1, x2, y2) {
content += `<line x1="${x1}" y1="${y1}" x2="${x2}" y2="${y2}" stroke="#CBD5E0" stroke-width="2"/>`;
}
// 재귀적으로 트리 렌더링 (data: {label, children: []})
// 실제 사용 시 레벨별 x, y 계산 로직 필요
return createSvg(width, 500, content);
}
Horizontal timeline (Horizontal Timeline)
function createTimeline(items, options = {}) {
const { width = 1200, height = 200 } = options;
const colors = ['#4A7BF7', '#00D4AA', '#FFB020', '#8B5CF6', '#FF6B6B'];
const lineY = height / 2;
const step = (width - 120) / (items.length - 1);
let content = '';
// 중앙 가로선
content += `<line x1="60" y1="${lineY}" x2="${width - 60}" y2="${lineY}" stroke="#E2E8F0" stroke-width="3"/>`;
items.forEach((item, i) => {
const x = 60 + i * step;
const color = colors[i % colors.length];
// 포인트
content += `<circle cx="${x}" cy="${lineY}" r="10" fill="${color}" stroke="white" stroke-width="3"/>`;
content += `<circle cx="${x}" cy="${lineY}" r="4" fill="white"/>`;
// 날짜 (위)
content += `<text x="${x}" y="${lineY - 25}" text-anchor="middle" font-size="12" font-weight="600" fill="${color}" font-family="Pretendard">${item.date}</text>`;
// 이벤트 (아래)
content += `<text x="${x}" y="${lineY + 35}" text-anchor="middle" font-size="13" fill="#4A5568" font-family="Pretendard">${item.label}</text>`;
if (item.desc) {
content += `<text x="${x}" y="${lineY + 52}" text-anchor="middle" font-size="11" fill="#718096" font-family="Pretendard">${item.desc}</text>`;
}
});
return createSvg(width, height, content);
}
// 사용 예
// createTimeline([
// { date: '2026.01', label: '프로젝트 착수', desc: '킥오프 미팅' },
// { date: '2026.03', label: '프로토타입', desc: '1차 데모' },
// { date: '2026.06', label: '베타 런칭' },
// { date: '2026.09', label: '정식 출시' },
// ]);
Donut chart (Donut Chart)
function createDonutChart(data, options = {}) {
const { width = 400, height = 400, innerRadius = 70, outerRadius = 130 } = options;
const cx = width / 2, cy = height / 2;
const colors = ['#4A7BF7', '#00D4AA', '#FFB020', '#8B5CF6', '#FF6B6B', '#CBD5E0'];
const total = data.reduce((sum, d) => sum + d.value, 0);
let content = '';
let startAngle = -Math.PI / 2;
data.forEach((item, i) => {
const angle = (item.value / total) * 2 * Math.PI;
const endAngle = startAngle + angle;
const largeArc = angle > Math.PI ? 1 : 0;
const color = colors[i % colors.length];
const x1 = cx + outerRadius * Math.cos(startAngle);
const y1 = cy + outerRadius * Math.sin(startAngle);
const x2 = cx + outerRadius * Math.cos(endAngle);
const y2 = cy + outerRadius * Math.sin(endAngle);
const x3 = cx + innerRadius * Math.cos(endAngle);
const y3 = cy + innerRadius * Math.sin(endAngle);
const x4 = cx + innerRadius * Math.cos(startAngle);
const y4 = cy + innerRadius * Math.sin(startAngle);
content += `<path d="M${x1},${y1} A${outerRadius},${outerRadius} 0 ${largeArc},1 ${x2},${y2} L${x3},${y3} A${innerRadius},${innerRadius} 0 ${largeArc},0 ${x4},${y4} Z" fill="${color}"/>`;
// 레이블
const midAngle = startAngle + angle / 2;
const labelR = outerRadius + 25;
const lx = cx + labelR * Math.cos(midAngle);
const ly = cy + labelR * Math.sin(midAngle);
const pct = Math.round(item.value / total * 100);
content += `<text x="${lx}" y="${ly}" text-anchor="middle" font-size="12" fill="#4A5568" font-family="Pretendard">${item.label} ${pct}%</text>`;
startAngle = endAngle;
});
// 중앙 텍스트
content += `<text x="${cx}" y="${cy - 5}" text-anchor="middle" font-size="28" font-weight="800" fill="#1A1F36" font-family="Pretendard">${total.toLocaleString()}</text>`;
content += `<text x="${cx}" y="${cy + 18}" text-anchor="middle" font-size="12" fill="#718096" font-family="Pretendard">합계</text>`;
return createSvg(width, height, content);
}
SVG → PNG conversion (for document insertion)
const sharp = require('sharp');
async function svgToPng(svgPath, pngPath, options = {}) {
const { width = 1200, density = 150 } = options;
await sharp(svgPath, { density })
.resize(width)
.png({ quality: 95 })
.toFile(pngPath);
return pngPath;
}
// 사용 예
// await svgToPng('diagram.svg', 'diagram.png', { width: 1600 });
Usage workflow
1. 사용자 요청 분석
└→ 어떤 도식이 필요한지 파악
2. 다이어그램 유형 선택
├→ Mermaid 지원 유형 → mermaidToSvg() 사용
└→ 커스텀 필요 → SVG 직접 생성
3. 테마 색상 적용
└→ ppt-design-system의 COLORS 연동
4. SVG 생성 및 PNG 변환
└→ sharp로 고해상도 PNG 변환
5. 문서에 삽입
├→ PPT: slide.addImage({ path: 'diagram.png' })
├→ Word: docx 이미지 삽입
└→ PDF: pdf-lib 이미지 삽입
Precautions
- Korean font: SVG 내
<style>Must include Pretendard @import in - When converting to PNG:
sharpSecure high resolution with the density option (150+ DPI) - Mermaid Korean: Specify 'Pretendard' or 'ChosunilboNM' in fontFamily
- File size: SVG is text-based and lightweight (usually 10–50KB)
- Transparent background: When bgColor='transparent' is set, it can be placed naturally on PPT