HyLiMo
HyLiMo is a textual DSL and hybrid editor for efficient modular diagramming. A deployed web-based version of our editor and documentation can be found at https://hylimo.github.io. This documentation is still under construction, and thus currently focuses on the usage aspects of the editor. For technical aspects, we refer to the code documentation.
Important features
- Hybrid graphical-textual approach
- Live-synced editing
- Graphical edits manipulate the textual definition
- Textual DSL for defining diagrams
- Programming language features, including custom functions and control-flow constructs
- Styling
- Theming
- Modular approach, with initial support for UML class diagrams
Example diagrams
classDiagram {
class("Movie")
class("Actor") layout {
pos = rpos(Movie, 600, 0)
}
Actor -- Movie with {
over = start(Position.Left).line(end(Position.Right))
}
}
Class diagram showcasing class, interface, package and comment
classDiagram {
package("Automotive") layout {
pos = apos(-213, -11)
width = 799.7542853820115
height = 323.71734670143684
}
class("Car") {
private {
weight : Double
}
public {
startEngine() : void
}
} layout {
pos = apos(-42, 96)
}
interface("CarPart") {
} layout {
pos = rpos(Car, 450, 0)
}
class("CarDoor") {
private {
color : Color
}
} layout {
pos = rpos(CarPart, 0, 150)
}
Car *-- CarPart with {
over = start(Position.Right).line(end(Position.Left))
label("1..*", 0.7424916682518469, 4.651791220118298)
label("+parts", 0.7167590181143503, -23.239473042820165)
}
CarDoor implements CarPart with {
over = start(Position.Top).line(end(Position.Bottom))
}
comment("Drives on roads, and sometimes not on roads") layout {
width = 177.0234375
pos = apos(33, 243)
} .. Car with {
over = start(0.8015633964429145).line(end(0.19))
}
}
Class diagram for the HyLiMo language server
classDiagram {
vdist = 190
hdist = 450
class("LanguageServer") layout {
height = 92.21163812100386
pos = apos(-315.26241546483385, -172.49258673881076)
}
class("DiagramImplementationManager", abstract = true) layout {
pos = rpos(LanguageServer, 0, vdist * 1.2)
}
class("LocalDiagramImplementationManager") layout {
pos = rpos(DiagramImplementationManager, hdist / -2, vdist)
}
class("RemoteDiagramImplementationManager") layout {
pos = rpos(DiagramImplementationManager, hdist / 2, vdist)
}
class("LocalDiagramImplementation") layout {
pos = rpos(LocalDiagramImplementationManager, 0, vdist)
}
class("RemoteDiagramImplementation") layout {
pos = rpos(RemoteDiagramImplementationManager, 0, vdist)
}
class("DiagramImplementation", abstract = true) layout {
pos = rpos(DiagramImplementationManager, 0, 3 * vdist)
}
class("TextDocument") layout {
pos = rpos(LanguageServer, hdist, 0)
}
class("DiagramServerManager") layout {
pos = rpos(TextDocument, 0, -(vdist))
}
class("DiagramServer") layout {
pos = rpos(DiagramServerManager, hdist, 0)
}
class("Diagram") layout {
pos = rpos(TextDocument, 0, vdist)
}
class("DiagramEngine") layout {
pos = rpos(LanguageServer, -(hdist), vdist / 2)
}
class("CompletionEngine") layout {
pos = rpos(LanguageServer, -(hdist), vdist / -2)
}
LanguageServer *--> Diagram with {
over = start(Position.BottomRight).line(end(Position.Left))
label("0..*", 0.7396783354052208, 3.3928302631897584, 39.132215797767685)
}
LanguageServer *--> TextDocument with {
over = start(Position.Right).line(end(Position.Left))
label("0..*", 0.6195860121844069, 2.0927187962815594)
}
LanguageServer *--> DiagramImplementationManager with {
over = start(Position.Bottom).line(end(Position.Top))
label("1", 0.6151205681523787, -9.593093876933835)
}
LocalDiagramImplementationManager extends DiagramImplementationManager with {
over = start(Position.Top).axisAligned(-0.3, end(Position.BottomLeft - 0.05))
}
RemoteDiagramImplementationManager extends DiagramImplementationManager with {
over = start(Position.Top).axisAligned(-0.3, end(Position.BottomRight + 0.05))
}
LocalDiagramImplementation extends DiagramImplementation with {
over = start(Position.Bottom).axisAligned(-0.3, end(Position.TopLeft + 0.05))
}
RemoteDiagramImplementation extends DiagramImplementation with {
over = start(Position.Bottom).axisAligned(-0.3, end(Position.TopRight - 0.05))
}
DiagramServerManager *--> DiagramServer with {
over = start(Position.Right).line(end(Position.Left))
label("0..*", 0.6281601945428326, 7.252088001319976)
}
LanguageServer *--> DiagramEngine with {
over = start(0.375).line(end(0))
label("1", 0.6996412414832724, -3.326981380696332)
}
LanguageServer *--> CompletionEngine with {
over = start(0.625).line(end(0))
label("1", 0.748341501858057, -2.879024374403271)
}
LanguageServer *--> DiagramServerManager with {
over = start(0.875).line(end(Position.Left))
label("1", 0.8538828359555786, 7.03902668189471)
}
DiagramServer !--> Diagram with {
over = start(Position.Bottom).axisAligned(0, end(Position.Right))
label("1", 0.9632605936444701, 19.754772181747512)
label("0..*", 0.36257150352053047, -16.761476983249963)
}
LocalDiagramImplementationManager *--> LocalDiagramImplementation with {
over = start(Position.Bottom).line(end(Position.Top))
label("{subsets implementations}\n+implementations", 0.39706318188121414, -100.58134608753261)
label("0..*", 0.562982175871534, 16.33695405927233)
}
RemoteDiagramImplementationManager *--> RemoteDiagramImplementation with {
over = start(Position.Bottom).line(end(Position.Top))
label("{subsets implementations}\n+implementations", 0.4027445627139059, -101.54023513002394)
label("0..*", 0.5770974319286689, 15.969074618100933)
}
Diagram -- DiagramImplementation with {
over = start(0.16262482083387972).axisAligned(0, end(Position.Right))
label("1", 0.33513676966510486, 11.602768327718366)
label("0..1", 0.9836919560546376, 22.135140605198103)
}
DiagramImplementationManager *--> DiagramImplementation with {
over = start(Position.Bottom).line(end(Position.Top))
label("+implementations", 0.8187474665442491, -8.243606353500638, -90)
label("0..*", 0.9147624004410975, 25.166060647386985, -90)
}
TextDocument <--! Diagram with {
over = start(Position.Bottom).line(end(Position.Top))
label("1", 0.1851708018039814, -10.540206060919928)
label("1", 0.7006360486994234, -10.14199564953438)
}
}
Package diagram of HyLiMo
classDiagram {
customPackage = {
this.package = package("") layout {
width = 200
}
package.content.contents.get(1).content = text(
contents = list(span(text = it, class = list("package-body"))),
vAlign = VAlign.Center,
hAlign = HAlign.Center
)
package
}
styles {
cls("package-body") {
fontSize = 15
fontWeight = "bold"
}
cls("title-wrapper") {
type("span") {
fontSize = 10
}
}
cls("package") {
width = 60
height = 25
marginTop = -25
}
cls("package-element") {
vAlign = VAlign.Bottom
hAlign = HAlign.Center
}
}
vdist = 150
hdist = 300
chevrotain = customPackage("Chevrotaion") styles {
class += "foreign"
}
vscodeLSP = customPackage("vscode-languageserver") styles {
class += "foreign"
} layout {
pos = rpos(chevrotain, 0, 2 * vdist)
}
core = customPackage("core") layout {
pos = rpos(chevrotain, hdist, 0)
}
diagram = customPackage("diagram") layout {
pos = rpos(core, 0, vdist)
}
diagramCommon = customPackage("diagram-common") layout {
pos = rpos(diagram, hdist, 0)
}
_fonts = customPackage("fonts") layout {
pos = rpos(core, hdist, 0)
} styles {
cls("package") {
marginTop = 0
}
}
languageServer = customPackage("language-server") layout {
pos = rpos(diagram, 0, vdist)
}
diagramProtocol = customPackage("diagram-protocol") layout {
pos = rpos(languageServer, hdist, 0)
}
diagramUI = customPackage("diagram-ui") layout {
pos = rpos(diagramProtocol, hdist, 0)
}
renderPdf = customPackage("diagram-render-pdf") layout {
pos = rpos(languageServer, 0, vdist)
}
renderSvg = customPackage("diagram-render-svg") layout {
pos = rpos(renderPdf, hdist, 0)
}
sprotty = customPackage("sprotty") layout {
pos = rpos(diagramUI, hdist, 0)
} styles {
class += "foreign"
}
pdfkit = customPackage("PDFKit") layout {
pos = rpos(renderPdf, 0, vdist)
} styles {
class += "foreign"
}
diagram --> core with {
over = start(Position.Top).line(end(Position.Bottom))
}
diagram --> diagramCommon with {
over = start(Position.Right).line(end(Position.Left))
}
diagram --> _fonts with {
over = start(Position.Top + 0.05).axisAligned(
-0.51,
end(Position.Bottom)
)
}
renderSvg --> diagramCommon with {
over = start(Position.Right).axisAligned(
-1,
rpos(diagramUI, 137, 0),
0,
end(Position.Right)
)
}
renderPdf --> renderSvg with {
over = start(Position.Right).line(end(Position.Left))
}
renderPdf --> diagram with {
over = start(Position.Left).axisAligned(
1,
rpos(languageServer, -149, 2),
0,
end(Position.Left)
)
}
languageServer --> diagram with {
over = start(Position.Top).line(end(Position.Bottom))
}
languageServer --> diagramProtocol with {
over = start(Position.Right).line(end(Position.Left))
}
diagramUI --> diagramProtocol with {
over = start(Position.Left).line(end(Position.Right))
}
diagramUI -- rpos(lpos(diagramCommon, 0), hdist - 100, 0) with {
over = start(Position.Top).line(end())
}
languageServer --> vscodeLSP with {
over = start(Position.Left).line(end(Position.Right))
}
core --> chevrotain with {
over = start(Position.Left).line(end(Position.Right))
}
renderPdf --> pdfkit with {
over = start(Position.Bottom).line(end(Position.Top))
}
diagramUI --> sprotty with {
over = start(Position.Right).line(end(Position.Left))
}
globe = {
target = it
element(
path(
path = "m210,15v390m195-195H15M59,90a260,260 0 0,0 302,0 m0,240 a260,260 0 0,0-302,0M195,20a250,250 0 0,0 0,382 m30,0 a250,250 0 0,0 0-382 M209,15a195,195 0 1,0 2,0z"
)
) layout {
pos = rpos(target, 76.80471820620753, -46.57664161215641)
width = 20
height = 20
} styles {
type("path") {
stroke = var("primary")
strokeWidth = 1.5
}
}
}
globe(diagramUI)
globe(sprotty)
styles {
cls("foreign") {
any {
strokeDash = 10
strokeDashSpace = 5
}
}
}
}