emacs在org模式画图

org模式画简单的图,可以用ditaa这种工具,小巧够用,可以满足大部分的需求。 配合emacs的picture-mode和artis-mode,可以实现文本画大部分的图。生成的 图形效果还不错。

但要画比较复杂的图,比如脑图,时序图等,ditaa就有点不够用了。就需要 plantuml这种更强大的工具。

plantuml

介绍

PlantUML 是基于 Java 语言的开源 UML(Unified Modeling Language)图形绘 制工具,因为其可以单独绘图,又可以嵌入 Markdown 以及 HTML,因而被不少 开发文档采用。PlantUML 可以看作是著名绘图工具 Graphviz 的封装。

PlantUML 支持的图形可简单分为三种类型

  • 基本类:流程图、时序图、甘特图
  • 工程类:类图、实体关系图、用例图、组件图
  • 导图类:思维导图、组织结构导图

其他的支持类型,可以到官网上查看。

PlantUML 内置了 30 多种主题,可以通过 help themes 命令查看内置主题列表。 https://plantuml.com/zh/

安装

mac下先安装graphviz,是plantuml的依赖组件。

:: brew install graphviz

后从官网下载plantuml的jar包,放到emacs的插件目录下。

配置

emacs中,相关的配置

(org-babel-do-load-languages
 'org-babel-load-languages
 '((emacs-lisp . t)
   (ditaa . t)
   (python . t)
   (shell . t)
   (latex . t)
   (plantuml . t)
   (dot . t)
   (lisp . t)
   (org . t)
   (java . t)))

(setq org-plantuml-jar-path
      (expand-file-name "~/.emacs.d/plugins/plantuml-1.2023.8.jar"))
(setq org-confirm-babel-evaluate nil)   ;picture 不提示
(add-hook 'org-babel-after-execute-hook 'bh/display-inline-images 'append)

(setq org-babel-results-keyword "results")

(defun bh/display-inline-images ()
  (condition-case nil
      (org-display-inline-images)
    (error nil)))

(define-skeleton skel-org-block-plantuml
    "Insert a org plantuml block, querying for filename."
  "File (no extension): "
  "#+begin_src plantuml :file " str ".png :cach yes :cmdline -charset UTF-8\n"
  "title " @ - \n
  "#+end_src\n")

(define-abbrev org-mode-abbrev-table "spuml" "" 'skel-org-block-plantuml)

语法

颜色,基本组件

  • ->表示消息传递走向,默认箭头类型是单向直线,PlantUML手册中有很多样式可以通过代码自定义颜色和样式
  • –>是单向虚直线箭头,一般是用来传递消息返回信息
  • scale=2 设置图形大小
  • 使用 as关键字重命名参与者
  • 使用 title 增加标题
  • legend 和 end legend 作为关键词,用于配置一个图例(legend). 支持可选 地使用left,right,center为这个图例指定对齐方式.
  • caption显示在图片下方的标题
  • together 有时候,默认布局并不完美,你可以使用 together 关键词将某些类进行分组: 布局引擎会尝试将它们捆绑在一起
  • package 可以定义不同的样式,你可以通过以下的命令来设置默认样式 : skinparam packageStyle,或者对包使用对应的模板
  • 如果需要调整布局,学会使用a -[hidden]d- c , A -[hidden]left- B 类似 的方式
  • skinparam nodesep x will increase the horizontal margin
  • skinparam ranksep x affects the vertical margin

时序图

  • 常用对象

    actor :: 实体对象,比如人、操作员等 participant:: 参与者,可以是类、服务名等 database :: 数据库 queue :: 队列,比如kafka队列就可以用这个表示 boundary :: 边界 control :: 控制 entity :: 实体 collections :: 集合

  • 常用操作

    • 使用activate和deactivate来激活、销毁生命周期
    • destroy 表示一个参与者的生命线的终结
    • 用 alt-else-end 表示分支
    • 用 loop-end表示循环
    • 使用 order 来自定义参与者的顺序,值越大的越靠后
    • autonumber 自动对消息编号
    • 使用 note left 或 note right 给消息添加注释,可以用 end note 添加多 行注释。
    • 用 == 将图标分隔成多个逻辑步骤
    • 使用… 表示延迟,并且可以给延迟添加注释;如….5min later….

思维导图

  • 以@startmindmap开始,以@endmindmap结束。
  • 用+或-新建分支,用符号数量表示层级,+表示分支向右边生长,-表示分支向 左边生长。也可以用*建立分支
  • 使用:开始多行内容,;结束多行内容。目前如果要使用: ;只能用*新建分支
  • 思维导图中定义颜色

      @startmindmap
      <style>
      mindmapDiagram {
        .green {
          BackgroundColor lightgreen
        }
        .rose {
          BackgroundColor #FFBBCC
        }
        .your_style_name {
          BackgroundColor lightblue
        }
      }
      </style>
      * Colors
      ** Green <<green>>
      ** Rose <<rose>>
      ** Blue <<your_style_name>>
    @endmindmap
    

流程图

架构图

plantuml支持Archimate画架构图,也可以通过c4的插件,画c4风格的架构图。

Archimate是一种架构建模语言,顾名思义,是专业架构师(Archi)的好伴侣 (Mate)。

ArchiMate的建模概念元素虽然种类繁多,但究其性质而言不外乎结构元素 (Structure Element)和行为元素(Behavior Element)两种(也可称为静态 元素和动态元素),前者代表了各种结构化的实体(如参与者、应用组件以及数 据等),而后者则用来描述结构化元素所能执行的各种行为(如业务活动、应用 功能以及服务等)。此外,根据与行为元素之间的关系进一步划分,结构元素又 可被分为主动性结构元素(Active Structure Element)和被动性结构元素 (Passive Structure Element),前者表示发起动作的结构元素(例如参与者、 应用组件等),而后者则被用来描述行为元素的各种目标(例如业务数据、信息 数据等)。由此可见,ArchiMate所使用的描述方式与很多标准化的描述方式 (例如RDF语言)有着异曲同工之妙,基本采用“主动对象(主语)、行为(谓 语)、被动对象(宾语)+相互之间的关系”进行建模描述。

ArchiMate 3.0可以完全覆盖自身所在行业架构工作建模的方方面面,但它只有 59个元素 、13种关联关系(relationship) ,如此简洁的要素和关系却可以描 述如复杂的架构范围,堪称建模语言的翘楚。

示例

流程图

打开一个org文档,加入一个planduml代码块,代码需要以BEGIN_SRC开头 plantuml开头如下:

start

if (Graphviz installed?) then (yes)
  :process all\ndiagrams;
else (no)
  :process only
  __sequence__ and __activity__ diagrams;
endif

stop

显示效果如下:

架构图

uml代码

title
 <u>Simple</u> communication example
 on <i>several</i> lines and using <back:cadetblue>creole tags</back>
end title
node "note " {
rectangle GO #lightgreen
rectangle STOP #red
rectangle WAIT #orange
!define Junction_Or circle #black
!define Junction_And circle #whitesmoke
Junction_And JunctionAnd
Junction_Or JunctionOr
archimate #Technology "VPN Server" as vpnServerA <<technology-device>>
GO -up-> JunctionOr
STOP -up-> JunctionOr
STOP -down-> JunctionAnd
WAIT -down-> JunctionAnd
}

package和颜色

title comtest
skinparam rectangle<<behavior>> {
        roundCorner 25
}
sprite $bProcess jar:archimate/business-process
sprite $aService jar:archimate/application-service
sprite $aComponent jar:archimate/application-component
rectangle user #lightyellow;line.dotted;text:red {
rectangle "Handle claim"  as HC <<$bProcess>><<behavior>> #Business
rectangle "Capture Information"  as CI <<$bProcess>><<behavior>> #Business
rectangle "Notify\nAdditional Stakeholders" as NAS <<$bProcess>><<behavior>> #Business
rectangle "Validate" as V <<$bProcess>><<behavior>> #Business
rectangle "Investigate" as I <<$bProcess>><<behavior>> #Business
rectangle "Pay" as P <<$bProcess>><<behavior>> #Business

HC *-down- CI
HC *-down- NAS
HC *-down- V
HC *-down- I
HC *-down- P

V -right->> I
I -right->> P
}
package "severicenow" #lightgreen {
rectangle "Scanning" as scanning <<$aService>><<behavior>> #Application
rectangle "Customer admnistration" as customerAdministration <<$aService>><<behavior>> #Application
rectangle "Claims admnistration" as claimsAdministration <<$aService>><<behavior>> #Application
rectangle Printing <<$aService>><<behavior>> #Application
rectangle Payment <<$aService>><<behavior>> #Application
}
scanning -up-> CI
customerAdministration  -up-> CI
claimsAdministration -up-> NAS
claimsAdministration -up-> I
Payment -up-> P

Printing -up-> V
Printing -up-> P
rectangle #lightgray;line:lightgray {
rectangle "Document\nManagement\nSystem" as DMS <<$aComponent>> #Application
rectangle "General\nCRM\nSystem" as CRM <<$aComponent>>  #Application
rectangle "Home & Away\nPolicy\nAdministration" as HAPA <<$aComponent>> #Application
rectangle "Home & Away\nFinancial\nAdministration" as HFPA <<$aComponent>>  #Application
}
DMS .up.|> scanning
CRM .up.|> customerAdministration
HAPA .up.|> claimsAdministration
HFPA .up.|> Payment

legend left
Example from the "Archisurance case study" (OpenGroup).
See
====
<$bProcess> :business process
====
<$aService> : application service
====
<$aComponent> : application component
endlegend

可以嵌套的元素

'left to right direction
top to bottom direction
rectangle "rc1" as rc1{
artifact artifact {
}
card card {
}
cloud cloud {
}
component component {
}
database database {
}
}
rectangle  "rc2" as rc2 #lightgreen {
file file {
}
folder folder {
}
frame frame {
}
hexagon hexagon {
}
node node {
}
}
package "rc3" as rc3{
package package {
}
queue queue {
}
rectangle rectangle {
}
stack stack {
}
storage storage {
}
}
rc1 -[hidden]-> rc2
rc2-[hidden]->rc3

package 可以指定种类

scale 750 width
package foo1 <<Node>> {
  class Class1
}

package foo2 <<Rectangle>> {
  class Class2
}

package foo3 <<Folder>> {
  class Class3
}

package foo4 <<Frame>> {
  class Class4
}

package foo5 <<Cloud>> {
  class Class5
}

package foo6 <<Database>> {
  class Class6
}

plantuml支持ditaa

plantuml中,可以支持ditaa图。以@startditaa开始以@endditaa之间的内容, 会按ditaa的语法来处理。plantuml代码块里的代码如下:

生成的图片:

plantuml 画c4图

!include <C4/C4_Container>
AddElementTag("microService", $shape=EightSidedShape(), $bgColor="CornflowerBlue", $fontColor="white", $legendText="micro service\neight sided")
AddElementTag("storage", $shape=RoundedBoxShape(), $bgColor="lightSkyBlue", $fontColor="white")

SHOW_PERSON_OUTLINE()

Person(customer, Customer, "A customer")

System_Boundary(c1, "Customer Information") {
    Container(app, "Customer Application", "Javascript, Angular", "Allows customers to manage their profile")
    Container(customer_service, "Customer Service", "Java, Spring Boot", "The point of access for customer information", $tags = "microService")
    Container(message_bus, "Message Bus", "RabbitMQ", "Transport for business events")
    Container(reporting_service, "Reporting Service", "Ruby", "Creates normalised data for reporting purposes", $tags = "microService")
    Container(audit_service, "Audit Service", "C#/.NET", "Provides organisation-wide auditing facilities", $tags = "microService")
    ContainerDb(customer_db, "Customer Database", "Oracle 12c", "Stores customer information", $tags = "storage")
    ContainerDb(reporting_db, "Reporting Database", "MySQL", "Stores a normalized version of all business data for ad hoc reporting purposes", $tags = "storage")
    Container(audit_store, "Audit Store", "Event Store", "Stores information about events that have happened", $tags = "storage")
}

Rel_D(customer, app, "Uses", "HTTPS")

Rel_D(app, customer_service, "Updates customer information using", "async, JSON/HTTPS")

Rel_U(customer_service, app, "Sends events to", "WebSocket")
Rel_U(customer_service, message_bus, "Sends customer update events to")
Rel(customer_service, customer_db, "Stores data in", "JDBC")

Rel(message_bus, reporting_service, "Sends customer update events to")
Rel(message_bus, audit_service, "Sends customer update events to")

Rel(reporting_service, reporting_db, "Stores data in")
Rel(audit_service, audit_store, "Stores events in")

Lay_R(reporting_service, audit_service)

SHOW_LEGEND()