BloggerにMermaidを導入して、フローチャートを描く

<!-- markdown-mode-on --> <img alt="JT logo" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiun9GB33GNzIYOiICg1ujk8_gbZpE-ipnQ8BNlL5BjdfhCSzV86LsBLOXBmf3HuiktPcWqHQEE0zCoTFnDNHEQkjleNvR_5eFu1yJP790q4-HsguNiSIsmo4dR3mnsy6M4dMNhdZFEYo9npCerqMCWQO_CB7QCdBWmjY4Gp5922YQsaazfmuxCb0K8lrdY/s320/jettheme_2.png" style="display:none;"/> ## 概要 Mermaidとは <figure class="blogcard b-link"><a aria-label="記事詳細へ(別窓で開く)" href="https://mermaid.js.org/" rel="noopener noreferrer" target="_blank"><div class="blogcard-content"><div class="blogcard-image bi-link"><div class="blogcard-image-wrapper biw-link"><img alt="Mermaid" height="100" loading="lazy" src="https://mermaid.js.org/mermaid-logo-horizontal.svg" width="100"/></div></div><div class="blogcard-text"><p class="blogcard-title bt-link">Mermaid</p><p class="blogcard-description bd-link">Create diagrams and visualizations using text and code.</p></div></div><div class="blogcard-footer bf-link">  <img alt="ファビコン" height="16" loading="lazy" src="https://www.google.com/s2/favicons?domain=https://mermaid.js.org/" width="16"/>mermaid.js.org</div></a></figure> Mermaidは、Markdown風のテキスト記法を使って、フローチャートやシーケンス図、ER図などのダイアグラムを簡単に作成できるオープンソースで無料利用可能なツールです。JavaScriptベースで動作し、GitHubやQiitaなどのMarkdown対応プラットフォームで利用できます。 <a name="more"></a> これをはてなブログでは簡単に導入できたので、味をしめてこの Blogger/ JetTheme に導入してみます。まずは、サンプル図を載せます。 **フローチャート** <div class="mermaid"> graph TD; A-->B; </div> ```mermaid flowchart LR Start --> Stop ``` ```mermaid graph A["$$\rho \left( \frac{\partial v}{\partial t} + (v \cdot \nabla) v \right) = -\nabla p + \mu \nabla^2 v + \rho f$$"] B["$$ \frac{d}{dr} \left( r \frac{d u_z}{dr} \right) = -\frac{r}{\mu} \frac{dp}{dz}$$"] C["$$ u_z(r) = \frac{1}{4\mu} \left( \frac{dp}{dz} \right) (R^2-r^2) $$"] S((S)) --> A A --> B B --> C ``` **ガントチャート** ```mermaid gantt title A Gantt Diagram dateFormat YYYY-MM-DD section Section A task :a1, 2014-01-01, 30d Another task :after a1, 20d section Another Task in Another :2014-01-12, 12d another task :24d ``` **アーキテクチャチャート** ```mermaid architecture-beta group api(cloud)[API] service db(database)[Database] in api service disk1(disk)[Storage] in api service disk2(disk)[Storage] in api service server(server)[Server] in api db:L -- R:server disk1:T -- B:server disk2:T -- B:db ``` ## 導入手順 ### はてなブログの方法:失敗 はてなブログと同様に下記のスクリプトを `` の手前に書き入れたのですが、 ```javascript <!-- flowChart tool Marmaid --> <script type="module"> import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs'; mermaid.initialize({ startOnLoad: true }); </script> ``` フォローチャートを描く書き方をしたところ、 ````markdown ```mermaid flowchart LR Start --> Stop ``` ```` **mermaid のコードブロックが Markdown のコードハイライト対象として処理されてしまう**ため、結果はコードブロックの表示にしかなりません。 ### 解決策 **Mermaid の描画対象**として認識させるには、**Markdown のコードハイライトとの明確な切り分けが必要**です。 その方策は、Markdown で <code>```mermaid</code> と書いたときに、**Prism.jsではなく Mermaid が呼び出されるルール**を作ります。 既報の[「Markdown エディタを実装し、さらに Prism.js を適用する」](https://newprivatelibrary.blogspot.com/2025/05/markdown-prismjs.html) で作った `maked.js` のスクリプトを改良して、<code>```mermaid</code> だけを特別に `<pre class="mermaid">...</pre>` に変換させる `marked.js` の**カスタムレンダラー**を追加します。 変更箇所は、`code({ text, lang }) {...},` からの50行~59行です。 <details> <summary>Mermaid対応のMarkdown エディタスクリプト</summary> <pre class="line-numbers"><code class="language-javascript"> <b:comment>Markdown エディタ</b:comment> <b:with value='&quot;&lt;!-- markdown-mode-on --&gt;&quot;' var='md_comment'> <b:with value='data:post.body contains data:md_comment' var='md_enabled'> <b:comment>メッセージ</b:comment> <b:if cond='data:view.isPreview'> <div class='md-message'> <b:if cond='data:md_enabled'> <p>Markdown モードがオンになっています&#12290;オフにしたい場合は投稿から <input expr:value='data:md_comment.escaped' type='text'/> を削除してください&#12290;</p> <details class='md-convert'> <summary>変換後の HTML</summary> <textarea aria-label='変換後の HTML' class='md-output'/> <button aria-label='コードを全選択する' class='md-select'>コード全選択</button> </details> <b:else/> <p>Markdown モードをオンにしたい場合は投稿に <input expr:value='data:md_comment.escaped' type='text'/> を追加してください&#12290;</p> </b:if> </div> </b:if> <b:comment>投稿, ページ本文</b:comment> <div class='entry-text text-break mb-5' id='post-body'> <b:if cond='data:md_enabled'> <b:class cond='data:md_enabled' name='md-input'/> <data:post.body.escaped/> <b:else/> <data:post.body/> </b:if> </div> <b:comment>Markdown 変換</b:comment> <b:if cond='data:md_enabled'> <script src='https://cdn.jsdelivr.net/npm/marked/marked.min.js'/> <script> (() =&gt; { const comment = &#39;<data:md_comment/>&#39;; //<![CDATA[ const input = document.querySelector('div.md-input'); const output = document.querySelector('textarea.md-output'); const select = document.querySelector('button.md-select'); if(!input) return; const escapeHTML = (text) => { return text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;') } const renderer = { code({ text, lang }) { const validLanguage = lang ? `language-${lang}` : 'language-plaintext'; const code = escapeHTML(text); if (lang === 'mermaid') { return `<pre class="mermaid">${text}</pre>\n\n`; } return `\n<pre><code class="${validLanguage}">${code}</code></pre>\n\n`; }, blockquote({tokens}){ const body = this.parser.parse(tokens).replace(/\n$/, ''); return `\n<blockquote>${body}</blockquote>\n\n`; }, heading({tokens, depth}){ return `\n<h${depth}>${this.parser.parseInline(tokens)}</h${depth}>\n\n`; }, hr(token){ return '\n<hr/>\n\n'; }, list(token){ const ordered = token.ordered; const start = token.start; let body = ''; token.items.forEach(item => { body += ' ' + this.listitem(item); }) const type = ordered ? 'ol' : 'ul'; const startAttr = (ordered && start !== 1) ? (' start="' + start + '"') : ''; return '\n<' + type + startAttr + '>\n' + body + '</' + type + '>\n\n'; }, paragraph({tokens}){ const text = this.parser.parseInline(tokens); const regex = /^\s*(<a name=["']more['"]>).*?(<\/a>)\s*$/; if(regex.test(text)){ return `\n${text.replace(regex, '$1$2')}\n`; }else{ return `<p>${text}</p>\n`; } } } marked.use({ renderer }); const converted = marked.parse(input.textContent.replace(comment, '')); input.innerHTML = converted; input.classList.remove('md-input'); if(output){ output.value = converted.replace(/<a name=["']more["']>.*?<\/a>/, '<!--more-->').replace(/\n{3,}/g, '\n\n').trim(); } if(!select) return; select.addEventListener('click', () => { output.focus(); output.setSelectionRange(0, output.value.length); }); })(); //]]></script> </b:if> </b:with> </b:with> </code></pre></details> 続いて、チャートを描く背景を装飾がないようにCSSで定義します。ついでにダークモードで線が見えなくなることにも対応しました。 <details> <summary>Mermaid対応のCSS</summary> <pee><code class="languege-css"> pre.mermaid { background: none; border: none; padding: 0; overflow: auto; } pre.code.mermaid[data-lang="mermaid"]:before { content: none; } /* ダークモード時の線の色 */ .dark-mode svg path, .dark-mode svg line, .dark-mode svg polyline { stroke: #cccccc !important; stroke-width: 1.5 !important; opacity: 1 !important; } </code> </pee></details> ## 番外編 前項でこのように説明しました。 **mermaid のコードブロックが Markdown のコードハイライト対象として処理されてしまう**ため、結果はコードブロックの表示にしかなりません。 言い換えれば、Markdown記法を使わず、HTML で書くならば `marked.js` の**カスタムレンダラー**は必要ありません。`<div>`タグと、`<pre>`タグが使えます。 ```markdown <div class="mermaid"> graph TD; A-->B; </div> ``` <div class="mermaid"> graph TD; A-->B; </div> ```markdown <pre class="mermaid"> graph TD; A-->B; </pre> ``` <pre class="mermaid"> graph TD; A-->B; </pre> ## レイアウト Mermaidで作成したチャートを**画面の中央に配置**するには、**CSSを使う方法**が一般的です。 HTML書式を使うと、テキストや図形と同じにレイアウトを設定できます。しかし、<code>\`\`\`mermaid</code> 書式ではそれができません。`marked.js` での <code>```mermaid</code> から `<pre class="mermaid">` へのDOM変換が失敗しているようです。解決策は今のところ見つかっていません。 ### 方法1: CSSで中央配置 Mermaidのチャートを囲む`div`要素に**CSSの`text-align: center;`を適用**すると、中央に配置できます。 ```html <div style="text-align: center;"> <pre class="mermaid"> graph TD; A-->B; B-->C; </pre> </div> ``` <div style="text-align: center;"> <pre class="mermaid"> graph TD; A-->B; B-->C; </pre> </div> ````html <div style="text-align: center;"> ```mermaid graph TD; A-->B; B-->C; ``` </div> ```` <b>失敗</b> <div style="text-align: center;"> ```mermaid graph TD; A-->B; B-->C; ``` </div> ### 方法2: `display: flex;` を使う `display: flex;` を使うことで、より柔軟に中央配置できます。 ```html <div style="display: flex; justify-content: center;"> <pre class="mermaid"> graph TD; A-->B; B-->C; </pre> </div> ``` <div style="display: flex; justify-content: center;"> <pre class="mermaid"> graph TD; A-->B; B-->C; </pre> </div> ### 方法3: Mermaidの設定を変更 Mermaidの設定で**`themeVariables`を使う**ことで、チャートの配置を調整できます。 これはテーマXMLでの変更になるので、使えません。 ```html <script> mermaid.initialize({ themeVariables: { nodeTextAlign: "center" } }); </script> ``` ## 関連リンク <figure class="blogcard b-link"><a aria-label="記事詳細へ(別窓で開く)" href="https://qiita.com/opengl-8080/items/a275119c5ff3012ff23a#%E5%8F%82%E8%80%83" rel="noopener noreferrer" target="_blank"><div class="blogcard-content"><div class="blogcard-image bi-link"><div class="blogcard-image-wrapper biw-link"><img alt="Mermaid使い方メモ - Qiita" height="100" loading="lazy" src="https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F28302%2Fdbddd92b-c7b2-c7f3-a421-5701b2d48272.png?ixlib=rb-4.0.0&auto=format&gif-q=60&q=75&w=1400&fit=max&s=04ca7e4b20659581dc9c6e8c46e304a0" width="100"/></div></div><div class="blogcard-text"><p class="blogcard-title bt-link">Mermaid使い方メモ - Qiita</p><p class="blogcard-description bd-link">Mermaid とは UML やフローチャートなどの図をテキストで記述できるツール JavaScript 製 似たものとして PlantUML がある こっちはJava製 PlantUML は名前の通り UML に特化している Mermaid は UML だけでなくガ...</p></div></div><div class="blogcard-footer bf-link">  <img alt="ファビコン" height="16" loading="lazy" src="https://www.google.com/s2/favicons?domain=https://qiita.com/opengl-8080/items/a275119c5ff3012ff23a#%E5%8F%82%E8%80%83" width="16"/>qiita.com</div></a></figure> </pre></pre></div>
Next Post Previous Post