緣起
自己一直以來都是hackMD的重度使用者,無論是上課的筆記,還是自學知識的知識,基本上都是記錄在hackmd上面。
我會這麼喜歡hackMD的原因,就是因為他結合了很多十分方便的功能:例如像是用$$包住就可以打出$\LaTeX$的數學式,用```graphviz```就可以打出流程圖(Graphviz)。這種只需要文字就可以打出需要的圖/文字的設計,我真的覺得非常好用。
在打算開始建立個人部落格後,因為他一樣是使用markdown的型式來書寫,所以我也不想脫離上述兩個好用功能。而我使用的blog主題是基於Fluid的修改版,他也提供了內建的亮、暗色模式,所以我就發現了一個讓我看得不是很順眼的問題。
問題
我們先來看一個由$\LaTeX$語法寫出的數學式:
$$
\sum\limits_{i=0}^{n}a_i
$$
雖然$\LaTeX$能夠支援根據背影的顏色來調整現在字體的顏色(可以試著手動切換亮暗色模式試試看!),但是Graphviz就沒辦法支援這一點。因為原生的hexo並不支援用像是寫hackMD的方式來渲染Graphviz,所以我使用的套件是hexo-filter-viz
。我們來看一個用原本的hexo-filter-viz
渲染出來的SVG:
第一眼看到的問題就是它不是置中的!而且亮色模式下看起來沒什麼問題,但切到暗色模試就很痛苦了。那突兀的背景,還有不隨著主題色變化的線條,真的感覺需要好好修理一番😢。經過一連串的調整,最終我得到了以下的結果:
看起來非常棒!除了原本就有顏色的部分,其餘的部分都會隨著亮暗色主題變化,並且也沒有背景了!我在markdown檔裡面只需要像平常使用graphviz的方式,就可以生成支援暗色模式的流程圖了!接下來就讓我來介紹我到底做了哪些事吧!
開始工作
如何渲染
首先一開始要先感謝Chris 技術筆記的這篇文章。其實最一開始fluid主題是沒有支援使用像hackmd的方式產生Graphviz的,所以我一開始也是用他改寫好的套件來使用。如果大家有興趣的話記得點進去看看!
總之,點進他寫好的套件後,可以發現最重要的檔案就是lib/render.js
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| var reg = /(\s*)(```) *(graphviz) *\n?([\s\S]+?)\s*(\2)(\n+|$)/g;
function ignore(data) { var source = data.source; var ext = source.substring(source.lastIndexOf('.')).toLowerCase(); return ['.js', '.css', '.html', '.htm'].indexOf(ext) > -1; }
function getId(index) { return 'graphviz-' + index; }
exports.render = function(data) { if (!ignore(data)) {
var graphviz = [];
data.content = data.content .replace(reg, function(raw, start, startQuote, lang, content, endQuote, end) { var graphvizId = getId(graphviz.length); graphviz.push(content); return start + '<div id="' + graphvizId + '></div>' + end; });
if (graphviz.length) { var config = this.config.graphviz; data.content += '<script src="' + config.vizjs + '"></script>'; data.content += '<script src="' + config.render + '"></script>'; data.content += graphviz.map(function(code, index) { var graphvizId = getId(index); var codeId = graphvizId + '-code'; return '' + '{% raw %}' + '<textarea id="' + codeId + '" style="display: none">' + code + '</textarea>' + '<script>' + ' var viz = new Viz();' + ' var code = document.getElementById("' + codeId + '").value;' + ' viz.renderSVGElement(code)' + ' .then(function(element) {' + ' document.getElementById("' + graphvizId + '").append(element);' + ' });' + '</script>' + '{% endraw %}'; }).join(''); } } };
|
原來如此,所以其實就是把原本的程式碼塊進行拆解,然後用{% raw %}
來注入程式碼,並用Viz
來渲染整份程式碼。最後會把render好的element回傳到寫著<div id="graphvizID"></div>
的裡面。原來如此,大致了解了。
看起來這裡就是我該動手修改的地方。還在等什麼?直接fork一份開始改起來囉!
置中
但其實我一開始看到不順眼的地方是預設的結果並不是置中的,這讓我看的很痛苦。所以就讓我手動在<div id="graphvizID"></div>
這個tag裡面加上style="text-align:center;"
這個attribute吧!
移除背景和修改線條顏色
再來一個就是背景色,雖然可以在markdown書寫時手動寫bgcolor=transparent;
,但我覺得這樣做太不優雅了,所以還是打算直接修改這裡注入{% raw %}
裡的程式碼。首先我們要先來看他渲染出來的SVG架構長什麼樣子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| <svg width="337pt" height="184pt" viewBox="0.00 0.00 337.30 183.74" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 179.7361)"> <title>relax</title> <polygon fill="#ffffff" stroke="transparent" points="-4,4 -4,-179.7361 333.2956,-179.7361 333.2956,4 -4,4"></polygon>
<g id="node1" class="node"> <title>A</title> <ellipse fill="none" stroke="#b2dfee" cx="151.3691" cy="-18.0079" rx="18.0157" ry="18.0157"></ellipse> <text text-anchor="middle" x="151.3691" y="-13.8079" font-family="Times,serif" font-size="14.00" fill="#000000">A</text> <text text-anchor="middle" x="66.6806" y="-57.0157" font-family="Times,serif" font-size="14.00" fill="#000000">Updated</text> <text text-anchor="middle" x="66.6806" y="-40.2157" font-family="Times,serif" font-size="14.00" fill="#000000">Want to go to the cloest</text> </g>
<g id="edge3" class="edge"> <title>A->c</title> <path fill="none" stroke="#a52a2a" d="M167.0241,-27.0463C177.7195,-33.2213 192.1078,-41.5284 204.3,-48.5675"></path> <polygon fill="#a52a2a" stroke="#a52a2a" points="202.6875,-51.678 213.0978,-53.647 206.1876,-45.6158 202.6875,-51.678"></polygon> <text text-anchor="middle" x="182.162" y="-42.0069" font-family="Times,serif" font-size="14.00" fill="#000000">5</text> </g> </g> </svg>
|
雖然這裡我只列出幾個重要的部件,但由上述渲染出的結果我們就可以發現:最外層的svg
tag調整的是整個SVG的圖片大小,而進到第一層g
則是代表整個圖,其裡面第一層polygon
就是背景,而接下來幾個g
則是圖中的部件,像是表示形狀的node
,還有箭頭的edge
等等,裡面會再包幾個小的element來湊出需要的形狀。
而我這裡因為想處理的方式是透過調整fill
和stroke
來調整顏色:如果是希望看不到的話,就把顏色設成transparent
,而若是想變成因應主題的字體顏色的話,就改成var(--text-color)
。所以這裡我把背景還有其他polygon
一起處理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| var viz = new Viz(); var code = document.getElementById(" + codeId + ").value; viz.renderSVGElement(code) .then(function(element) { element.setAttribute("width", "100%"); polygons = element.getElementsByTagName("polygon"); for (var i = 0; i < polygons.length; i += 1) { if (polygons[i].parentElement.className.baseVal == "graph") { polygons[i].setAttribute("stroke", "transparent"); } else if (polygons[i].getAttribute("stroke") == "#000000") { polygons[i].setAttribute("stroke", "var(--text-color)"); } if (polygons[i].parentElement.className.baseVal == "graph") { polygons[i].setAttribute("fill", "transparent"); } else if (polygons[i].getAttribute("fill") == "#000000" && polygons[i].parentElement.className.baseVal == "edge") { polygons[i].setAttribute("fill", "var(--text-color)"); } } paths = element.getElementsByTagName("path"); for (var i = 0; i < paths.length; i += 1) { if (paths[i].getAttribute("fill") == "none" && paths[i].parentElement.className.baseVal == "edge") { paths[i].setAttribute("fill", "var(--text-color)"); } if (paths[i].getAttribute("stroke") == "#000000" && paths[i].parentElement.className.baseVal == "edge") { paths[i].setAttribute("stroke", "var(--text-color)"); } } ellipses = element.getElementsByTagName("ellipse"); for (var i = 0; i < ellipses.length; i += 1) { if (ellipses[i].getAttribute("stroke") == "#000000") { ellipses[i].setAttribute("stroke", "var(--text-color)"); } } texts = element.getElementsByTagName("text"); for (var i = 0; i < texts.length; i += 1) { if (texts[i].getAttribute("fill") == "#000000") { texts[i].setAttribute("fill", "var(--text-color)"); } texts[i].setAttribute("stroke", "none"); } document.getElementById(" + graphvizId + ").append(element); });
|
這裡在調整個element只有上述幾個我覺得比較常用的,至於其他我覺得應該用不太到的我就沒有放上去了。
這裡只是重點把回傳要注入{% raw %}
的JavaScript Code寫出來。可以看到就只是簡單地把fill和stroke為預設的值改為對應主題色的var(--text-color)
,而在Polygon內還會再特判背景的狀況這樣。
調整大小
最後的最後,在調整完才發現如果給定的圖太大張,其實會超過整個筆記的版面(往右邊超出界),因此這裡在第5行手動調整width為100%,這樣就不會出界了!
成品
Okay!在經過上述的調整後,看過一輪應該是沒什麼大問題了!於是就把修改好的結果存在hexo-filter-viz-customized
,這樣我用在github上的CI/CD也能夠下載到這個套件,正常地幫我渲染Graphviz啦!讚讚!
結論
其實這個修改大概是2023暑假就想做的了,只是因為太忙一直拖到了寒假,因為心血來潮就把這個小問題解掉了。原本在大二還沒修完軟體設計實驗對網頁內的JavaScript都還是敬而遠之的狀態,不過我想在經過這一次的實作,也發現自己開始有能力透過閱讀document,來完成自己需要的功能。我想這也算是一種成長吧XD。雖然做的東西很簡單,但是對整體的美觀性來說還是有很顯著的提升的,期待下次還可以繼續強化並讓這個部落格更完整😎。