plantuml 环境搭建及简单使用

plantuml 是底层用jave 实现的画图工具, 可以画流程图, 时序图, 状态机图等等各种各样的图,
早期的 plantuml 的某些图还依赖于 graphviz, 最新的plantuml 好像已经不再依赖 graphviz 了,
使用 plantuml 的新语法, 还能用if else 来画流程图

安装

plantuml release

1
cp plantumlxxx.jar ~/.doom.d/python-tools/

简单使用

test.plantuml:

 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
@startuml
group arrow-demo
        Bob -[#blue]->x Alice: helloo
        Bob -[#red]-> Alice : hello
        Bob ->> Alice
        Bob -\ Alice
        Bob \- Alice
        Bob //-- Alice
        Bob ->o Alice
        Bob o\-- Alice
        Bob <-> Alice
        Bob <->o Alice
        a -> b : ""-> ""
        a ->> b : ""->> ""
        a -\ b : ""-\ ""
        a -\ b : ""-\\""
        a -/ b : ""-/ ""
        a -// b : ""-// ""
        a ->x b : ""->x ""
        a x-> b : ""x-> ""
        a o-> b : ""o-> ""
        a ->o b : ""->o ""
        a o->o b : ""o->o ""
        a <-> b : ""<-> ""
        a o<->o b : ""o<->o""
        a x<->x b : ""x<->x""
        a ->>o b : ""->>o ""
        a -\o b : ""-\o ""
        a -\o b : ""-\\o""
        a -/o b : ""-/o ""
        a -//o b : ""-//o ""
        a x->o b : ""x->o ""
end
@enduml
1
java -jar .\plantumlxxx.jar -charset UTF-8 .\test.plantuml

这个命令会生成 test.png
效果如下:

emacs 配置

安装plantuml-mode

package.el

1
(package! plantuml-mode)
1
doom sync

restart doom

配置

config.el

  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
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
(use-package! plantuml-mode
  :config
  (setq browse-url-chrome-program "chrome")
  (when *is-windows*
    (setq browse-url-chrome-program "C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe"))
  (setq plantuml-mode-debug-enabled t)
  (setq plantuml-jar-path "~/.doom.d/python-tools/plantuml-1.2022.6.jar")
  (setq plantuml-default-exec-mode 'jar)
  ;; Enable plantuml-mode for PlantUML files
  (add-to-list 'auto-mode-alist '("\\.plantuml\\'" . plantuml-mode))
  
  (with-eval-after-load 'org
    (add-to-list 'org-src-lang-modes '("plantuml" . plantuml)))

  (map!
   (:map plantuml-mode-map
    "C-c g" #'my/graphviz-plantuml-preview-browser
    "C-c G" #'my/graphviz-plantuml-preview-pyqt
    "C-c k" #'my/kill-graphviz-plantuml-preview-process))

  (defcustom plantuml-participant-keywords
    '("actor" "boundary" "control" "entity" "database" "collections" "queue"
      "responseMessageBelowArrow" "skinparam" "autonumber" "title" "header" "footer")
    "*Keywords for participant "
    :type '(repeat (string :tag "Keyword"))
    :group 'plantuml)

  (defcustom plantuml-common-keywords
    '("scale" "skinparam" "title" "caption" "header" "footer" "legend" "left" "right" "center" "class" "object"
      "robust" "concise" "HorizontalAlignment" "FontSize" "FontColor" "BackGroundColor" "Margin" "Padding"
      "newpage" "alt" "else" "opt" "loop" "par" "break" "critical" "group" "over" "left" "right" "ref" "ParticipantPadding"
      "BoxPadding" "lifelineStrategy" "style" "highlight"
      )
    "*Keywords for participant "
    :type '(repeat (string :tag "Keyword"))
    :group 'plantuml)

  (defcustom plantuml-active-keywords
    '("detach" "swimlanes" "partition" "ArrowHeadColor" "start" "stop" "end" "split" "split again" "end split"
      "if" "else" "elseif" "endif" "floating note" "note" "note right" "note left" "end note"
      "arrow" "kill" "repeat" "repeat while" "break" "while" "endwhile" "fork" "fork again" "end fork"
      )
    "*Keywords for participant "
    :type '(repeat (string :tag "Keyword"))
    :group 'plantuml)


(defvar plantuml-color-keywords
  '("aliceblue" "antiquewhite" "antiquewhite1" "antiquewhite2"
    "antiquewhite3" "antiquewhite4" "aquamarine" "aquamarine1"
    "aquamarine2" "aquamarine3" "aquamarine4" "azure" "azure1"
    "azure2" "azure3" "azure4" "beige" "bisque" "bisque1" "bisque2"
    "bisque3" "bisque4" "black" "blanchedalmond" "blue" "blue1"
    "blue2" "blue3" "blue4" "blueviolet" "brown" "brown1" "brown2"
    "brown3" "brown4" "burlywood" "burlywood1" "burlywood2"
    "burlywood3" "burlywood4" "cadetblue" "cadetblue1"
    "cadetblue2" "cadetblue3" "cadetblue4" "chartreuse"
    "chartreuse1" "chartreuse2" "chartreuse3" "chartreuse4"
    "chocolate" "chocolate1" "chocolate2" "chocolate3" "chocolate4"
    "coral" "coral1" "coral2" "coral3" "coral4" "cornflowerblue"
    "cornsilk" "cornsilk1" "cornsilk2" "cornsilk3" "cornsilk4"
    "crimson" "cyan" "cyan1" "cyan2" "cyan3" "cyan4" "darkgoldenrod"
    "darkgoldenrod1" "darkgoldenrod2" "darkgoldenrod3"
    "darkgoldenrod4" "darkgreen" "darkkhaki" "darkolivegreen"
    "darkolivegreen1" "darkolivegreen2" "darkolivegreen3"
    "darkolivegreen4" "darkorange" "darkorange1" "darkorange2"
    "darkorange3" "darkorange4" "darkorchid" "darkorchid1"
    "darkorchid2" "darkorchid3" "darkorchid4" "darksalmon"
    "darkseagreen" "darkseagreen1" "darkseagreen2"
    "darkseagreen3" "darkseagreen4" "darkslateblue"
    "darkslategray" "darkslategray1" "darkslategray2"
    "darkslategray3"  "darkslategray4" "darkslategrey"
    "darkturquoise" "darkviolet" "deeppink" "deeppink1"
    "deeppink2" "deeppink3" "deeppink4" "deepskyblue"
    "deepskyblue1" "deepskyblue2" "deepskyblue3" "deepskyblue4"
    "dimgray" "dimgrey"  "dodgerblue" "dodgerblue1" "dodgerblue2"
    "dodgerblue3"  "dodgerblue4" "firebrick" "firebrick1"
    "firebrick2" "firebrick3" "firebrick4" "floralwhite"
    "forestgreen" "gainsboro" "ghostwhite" "gold" "gold1" "gold2"
    "gold3" "gold4" "goldenrod" "goldenrod1" "goldenrod2"
    "goldenrod3" "goldenrod4" "gray" "gray0" "gray1" "gray10" "gray100"
    "gray11" "gray12" "gray13" "gray14" "gray15" "gray16" "gray17"
    "gray18" "gray19" "gray2" "gray20" "gray21" "gray22" "gray23"
    "gray24" "gray25" "gray26" "gray27" "gray28" "gray29" "gray3"
    "gray30" "gray31" "gray32" "gray33" "gray34" "gray35" "gray36"
    "gray37" "gray38" "gray39" "gray4" "gray40" "gray41" "gray42"
    "gray43" "gray44" "gray45" "gray46" "gray47" "gray48" "gray49"
    "gray5" "gray50" "gray51" "gray52" "gray53" "gray54" "gray55"
    "gray56" "gray57" "gray58" "gray59" "gray6" "gray60" "gray61"
    "gray62" "gray63" "gray64" "gray65" "gray66" "gray67" "gray68"
    "gray69" "gray7" "gray70" "gray71" "gray72" "gray73" "gray74"
    "gray75" "gray76" "gray77" "gray78" "gray79" "gray8" "gray80"
    "gray81" "gray82" "gray83" "gray84" "gray85" "gray86" "gray87"
    "gray88" "gray89" "gray9" "gray90" "gray91" "gray92" "gray93"
    "gray94" "gray95" "gray96" "gray97" "gray98" "gray99" "green"
    "green1" "green2" "green3" "green4" "greenyellow" "grey" "grey0"
    "grey1" "grey10" "grey100" "grey11" "grey12" "grey13" "grey14"
    "grey15" "grey16" "grey17" "grey18" "grey19" "grey2" "grey20"
    "grey21" "grey22" "grey23" "grey24" "grey25" "grey26" "grey27"
    "grey28" "grey29" "grey3" "grey30" "grey31" "grey32" "grey33"
    "grey34" "grey35" "grey36" "grey37" "grey38" "grey39" "grey4"
    "grey40" "grey41" "grey42" "grey43" "grey44" "grey45" "grey46"
    "grey47" "grey48" "grey49" "grey5" "grey50" "grey51" "grey52"
    "grey53" "grey54" "grey55" "grey56" "grey57" "grey58" "grey59"
    "grey6" "grey60" "grey61" "grey62" "grey63" "grey64" "grey65"
    "grey66" "grey67" "grey68" "grey69" "grey7" "grey70" "grey71"
    "grey72" "grey73" "grey74" "grey75" "grey76" "grey77" "grey78"
    "grey79" "grey8" "grey80" "grey81" "grey82" "grey83" "grey84"
    "grey85" "grey86" "grey87" "grey88" "grey89" "grey9" "grey90"
    "grey91" "grey92" "grey93" "grey94" "grey95" "grey96" "grey97"
    "grey98" "grey99" "honeydew" "honeydew1" "honeydew2" "honeydew3"
    "honeydew4" "hotpink" "hotpink1" "hotpink2" "hotpink3" "hotpink4"
    "indianred" "indianred1" "indianred2" "indianred3" "indianred4"
    "indigo" "ivory" "ivory1" "ivory2" "ivory3" "ivory4" "khaki" "khaki1"
    "khaki2" "khaki3" "khaki4" "lavender" "lavenderblush"
    "lavenderblush1" "lavenderblush2" "lavenderblush3"
    "lavenderblush4" "lawngreen" "lemonchiffon" "lemonchiffon1"
    "lemonchiffon2" "lemonchiffon3" "lemonchiffon4" "lightblue"
    "lightblue1" "lightblue2" "lightblue3" "lightblue4"
    "lightcoral" "lightcyan" "lightcyan1" "lightcyan2" "lightcyan3"
    "lightcyan4" "lightgoldenrod" "lightgoldenrod1"
    "lightgoldenrod2" "lightgoldenrod3" "lightgoldenrod4"
    "lightgoldenrodyellow" "lightgray" "lightgrey" "lightpink"
    "lightpink1" "lightpink2" "lightpink3" "lightpink4"
    "lightsalmon" "lightsalmon1" "lightsalmon2" "lightsalmon3"
    "lightsalmon4" "lightseagreen" "lightskyblue" "lightskyblue1"
    "lightskyblue2" "lightskyblue3" "lightskyblue4"
    "lightslateblue" "lightslategray" "lightslategrey"
    "lightsteelblue" "lightsteelblue1" "lightsteelblue2"
    "lightsteelblue3" "lightsteelblue4" "lightyellow"
    "lightyellow1" "lightyellow2" "lightyellow3" "lightyellow4"
    "limegreen" "linen" "magenta" "magenta1" "magenta2" "magenta3"
    "magenta4" "maroon" "maroon1" "maroon2" "maroon3" "maroon4"
    "mediumaquamarine" "mediumblue"  "mediumorchid"
    "mediumorchid1" "mediumorchid2" "mediumorchid3"
    "mediumorchid4" "mediumpurple" "mediumpurple1"
    "mediumpurple2" "mediumpurple3" "mediumpurple4"
    "mediumseagreen" "mediumslateblue" "mediumspringgreen"
    "mediumturquoise" "mediumvioletred" "midnightblue"
    "mintcream" "mistyrose" "mistyrose1" "mistyrose2" "mistyrose3"
    "mistyrose4" "moccasin" "navajowhite" "navajowhite1"
    "navajowhite2" "navajowhite3" "navajowhite4" "navy" "navyblue"
    "oldlace" "olivedrab" "olivedrap" "olivedrab1" "olivedrab2"
    "olivedrap3" "oragne" "palegoldenrod" "palegreen" "palegreen1"
    "palegreen2" "palegreen3" "palegreen4" "paleturquoise"
    "paleturquoise1" "paleturquoise2" "paleturquoise3"
    "paleturquoise4" "palevioletred" "palevioletred1"
    "palevioletred2" "palevioletred3" "palevioletred4"
    "papayawhip" "peachpuff" "peachpuff1" "peachpuff2"
    "peachpuff3" "peachpuff4" "peru" "pink" "pink1" "pink2" "pink3"
    "pink4" "plum" "plum1" "plum2" "plum3" "plum4" "powderblue"
    "purple" "purple1" "purple2" "purple3" "purple4" "red" "red1" "red2"
    "red3" "red4" "rosybrown" "rosybrown1" "rosybrown2" "rosybrown3"
    "rosybrown4" "royalblue" "royalblue1" "royalblue2" "royalblue3"
    "royalblue4" "saddlebrown" "salmon" "salmon1" "salmon2" "salmon3"
    "salmon4" "sandybrown" "seagreen" "seagreen1" "seagreen2"
    "seagreen3" "seagreen4" "seashell" "seashell1" "seashell2"
    "seashell3" "seashell4" "sienna" "sienna1" "sienna2" "sienna3"
    "sienna4" "skyblue" "skyblue1" "skyblue2" "skyblue3" "skyblue4"
    "slateblue" "slateblue1" "slateblue2" "slateblue3" "slateblue4"
    "slategray" "slategray1" "slategray2" "slategray3" "slategray4"
    "slategrey" "snow" "snow1" "snow2" "snow3" "snow4" "springgreen"
    "springgreen1" "springgreen2" "springgreen3" "springgreen4"
    "steelblue" "steelblue1" "steelblue2" "steelblue3" "steelblue4"
    "tan" "tan1" "tan2" "tan3" "tan4" "thistle" "thistle1" "thistle2"
    "thistle3" "thistle4" "tomato" "tomato1" "tomato2" "tomato3"
    "tomato4" "transparent" "turquoise" "turquoise1" "turquoise2"
    "turquoise3" "turquoise4" "violet" "violetred" "violetred1"
    "violetred2" "violetred3" "violetred4" "wheat" "wheat1" "wheat2"
    "wheat3" "wheat4" "white" "whitesmoke" "yellow" "yellow1" "yellow2"
    "yellow3" "yellow4" "yellowgreen")
  "Possible color constants in the dot language.
The list of constant is available at http://www.research.att.com/~erg/graphviz\
/info/colors.html")
  ;; 
  ;; (add-to-list 'company-keywords-alist (cons 'plantuml-mode (append plantuml-common-keywords plantuml-participant-keywords
  ;;                                                                   plantuml-active-keywords plantuml-color-keywords)))
  )

预览

mytools.el

 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
(defun my/graphviz-plantuml-preview (&optional browser)
  " 使用 pyqt 或者浏览器 预览 plantuml "
  (interactive)
  (let* ((python-script-path "%HOME%/.doom.d/python-tools/graphviz_plantuml_preview.py")
         (graphviz-plantuml-preview-buffer "* graphviz-plantuml-preview-buffer *")
         (svg-file-name (file-name-concat (file-name-directory buffer-file-name) (concat (file-name-base buffer-file-name) ".svg"))))
    (message "plantuml-jar-path : %s" plantuml-jar-path)
    (message "svg-file-name : %s" svg-file-name)
    (async-shell-command (format "python3 %s  \"%s\" \"%s\" %s" python-script-path (buffer-file-name)
                                 (expand-file-name plantuml-jar-path) browser)  graphviz-plantuml-preview-buffer)
    ;;  当子线程状态改变的时候会调用 set-process-sentinel 的第二个参数指向的函数
    (set-process-sentinel (get-process (process-name (get-buffer-process graphviz-plantuml-preview-buffer)))
                          (lambda (p state)
                            (cond ((s-starts-with-p "finished" state)
                                   (message "graphviz-plantuml-preview finished!"))
                                  ((s-starts-with-p "exit" state)
                                   (message "graphviz-plantuml-preview fail")))))
    (when browser
      (while (not (file-exists-p svg-file-name))
          (sit-for 0.1))
      (browse-url-chrome (format "file:///%s" svg-file-name) nil))
    (delete-other-windows)))

(defun my/graphviz-plantuml-preview-pyqt ()
  " 通过 pyqt 预览 plantuml 生成的png 文件"
    (interactive)
    (my/graphviz-plantuml-preview))

(defun my/graphviz-plantuml-preview-browser ()
  " 通过浏览器预览 plantuml 生成的svg 文件
以及通过 pyqt 预览 plantuml 生成的png 文件"
  (interactive)
  (my/graphviz-plantuml-preview t))

(defun my/kill-graphviz-plantuml-preview-process ()
  " 杀掉 plantuml 的预览进程 "
  (interactive)
  (my/kill-buffer-process "* graphviz-plantuml-preview-buffer *"))

python

~/.doom.d/python-tools/graphviz_plantuml_preview.py

 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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# -*- coding: utf-8 -*-
#********************************************************************************
#Copyright © 2021 Wcq
#File Name: graphviz_preview.py
#Author: Wcq
#Email: wcq-062821@163.com
#Created: 2021-11-02 10:36:47 
#Last Update: 2022-09-05 17:33:31
#         By: Wcq
#Description: 
#********************************************************************************
import os, time
import sys
from PyQt6.QtWidgets import QApplication, QWidget, QLabel 
from PyQt6 import QtCore
from PyQt6.QtGui import QIcon, QPixmap, QGuiApplication
import hashlib

class App(QWidget):
    def __init__(self, src, jarFile, browser):
        super().__init__()
        self.left = 50
        self.top = 50
        self.width = 640
        self.height = 480
        self.srcFile = src
        self.jarFile = jarFile
        self.broswer = False if browser == 'nil' else True
        print('src : ', src)
        print('jarFile : ', jarFile)
        print('self.broswer : ', self.broswer)
        if self.srcFile.endswith('.dot'):
            self.dstFile = self.srcFile[:-3] + 'png'
            self.title = 'Graphviz Preview'
        elif self.srcFile.endswith('.plantuml'):
            self.dstFile = self.srcFile[:-8] + 'png'
            self.title = 'PlantUML Preview'
        else:
            Usage()
        print('self.dstFile : ', self.dstFile)
        self.preMd5 = None
        self.initUI()
        self.initTimer()

    def initUI(self):
        self.setWindowTitle(self.title)
        self.setGeometry(self.left, self.top, self.width, self.height)
        self.center()
        self.label = QLabel(self)

    def initTimer(self):
        timer = QtCore.QTimer(self)
        timer.timeout.connect(self.update_image)
        timer.start(100)    # 100ms 刷新一次
        self.update_image()

    def center(self):
        screen = QGuiApplication.primaryScreen().availableVirtualGeometry()
        size = self.geometry()
        self.move((screen.width() - size.width()) // 2,
                  (screen.height() - size.height()) // 2)

    def update_image(self):
        md5 = hashlib.md5(open(self.srcFile, 'rb').read()).hexdigest()
        if self.preMd5 != md5:
            self.preMd5 = md5
            # 文件有更新 重新生成 png 并显示
            if self.srcFile.endswith('.dot'):
                ret = os.system("dot -Tpng %s -o %s"%(self.srcFile, self.dstFile))
                if self.broswer:
                    ret = os.system("dot -Tsvg %s -o %s"%(self.srcFile, self.dstFile))
            elif self.srcFile.endswith('.plantuml'):
                ret = os.system("java -jar %s -charset UTF-8 -tpng %s"%(self.jarFile, self.srcFile))
                if self.broswer:
                    ret = os.system("java -jar %s -charset UTF-8 -tsvg %s"%(self.jarFile, self.srcFile))
            else:
                Usage()
            if (ret == 0):
                pixmap = QPixmap(self.dstFile)
                if not pixmap.isNull():
                    self.label.setPixmap(pixmap)
                    self.label.adjustSize()
                    self.resize(pixmap.size())
            else:
                print('result : ', ret)

def Usage():
    print('Usage: ')
    print('python3 %s dot-src 0 0'%sys.argv[0])
    print('Or python3 %s plantuml-src jar-file <nil/t>'%sys.argv[0])
    exit(-1)
if __name__ == '__main__':
    if len(sys.argv) != 4:
        Usage()
    app = QApplication(sys.argv)
    ex = App(sys.argv[1], sys.argv[2], sys.argv[3])
    ex.show()
    sys.exit(app.exec())

简单使用

新建后缀名为 .plantuml 的文件 并写入代码按 C-c g 即可预览, C-c k 杀掉预览

plantuml 生成SVG 矢量文件, 并在浏览器中实时预览

Chrome 安装 auto refresh 插件

进入扩展程序 => 打开Chrome 商店

搜索安装 Auto Refresh Plus

简单配置 Auto Refresh

选中需要刷新的svg 文件标签页, 只需要设置间隔时间, 然后点击开始即可

Licensed under CC BY-NC-SA 4.0