We have to define how we apply style so a specific element of a UML diagram (for example, only for participant Bob or for a specific message between Alice and Bob).
To me, some style can be defined, but the style definition should not mention the specific element (otherwise it means that the style definition is specific to one diagram).
I know that this is different from what CSS allows.
Option 3
In Option 3 proposal, styles cannot inherit from each other. However, each element can have several styles (like in CSS) and there are a mecanism based on priority to solve multiple inheritance issues in property value.
Let's first focus on some elements (actor, boundary which can be used in several diagram type).
If you want use blue color for text everywhere:
@startuml
skinparam useBetaStyle true
title Styling example
<style>
root {
FontColor blue
}
</style>
Alice -> Bob : hello
@enduml
@startuml
skinparam useBetaStyle true
title Styling example
<style>
root {
FontColor blue
}
</style>
Alice -> Bob : hello
@enduml
Internally, there is a "plantuml.skin" file stored in the plantuml.jar library that store all settings (so no more hardcoded value in the code). The previous example only overrides the fontcolor of the root style.
So now, imagine that you want all UML elements (so not for title, footer...) in green:
@startuml
skinparam useBetaStyle true
title Styling example
<style>
root {
FontColor blue
}
element {
FontColor green
}
</style>
Alice -> Bob : hello
@enduml
@startuml
skinparam useBetaStyle true
title Styling example
<style>
root {
FontColor blue
}
element {
FontColor green
}
</style>
Alice -> Bob : hello
@enduml
The order of declaration is important: since "element" is defined after "root", the priority of green setting is higher than the priority of blue setting.
Now, you want also all actor printed in red:
@startuml
skinparam useBetaStyle true
title Styling example
<style>
root {
FontColor blue
}
element {
FontColor green
}
actor {
FontColor red
}
</style>
actor Alice
Alice -> Bob : hello
@enduml
@startuml
skinparam useBetaStyle true
title Styling example
<style>
root {
FontColor blue
}
element {
FontColor green
}
actor {
FontColor red
}
</style>
actor Alice
Alice -> Bob : hello
@enduml
Now, you want also sequence diagrams printed in purple:
@startuml
skinparam useBetaStyle true
title Styling example
<style>
root {
FontColor blue
}
element {
FontColor green
}
actor {
FontColor red
}
sequenceDiagram {
FontColor purple
}
</style>
' In that case, actor from sequence diagrams will be in purple, because purple is defined after red.
actor Alice
Alice -> Bob : hello
@enduml
@startuml
skinparam useBetaStyle true
title Styling example
<style>
root {
FontColor blue
}
element {
FontColor green
}
actor {
FontColor red
}
sequenceDiagram {
FontColor purple
}
</style>
' In that case, actor from sequence diagrams will be in purple, because purple is defined after red.
actor Alice
Alice -> Bob : hello
@enduml
@startuml
skinparam useBetaStyle true
title Styling example
<style>
root {
FontColor blue
}
element {
FontColor green
}
actor {
FontColor red
}
sequenceDiagram {
FontColor purple
}
' In that case, actor from sequence diagrams will be in purple, because purple is defined after red.
</style>
actor Alice
Alice -> Bob : hello
@enduml
Changing the order definition will only change the color of actor in sequence diagram:
@startuml
skinparam useBetaStyle true
title Styling example
<style>
root {
FontColor blue
}
element {
FontColor green
}
sequenceDiagram {
FontColor purple
}
actor {
FontColor red
}
</style>
' In that case, actor from sequence diagrams will be in red, because red is defined after purple.
actor Alice
Alice -> Bob : hello
@enduml
@startuml
skinparam useBetaStyle true
title Styling example
<style>
root {
FontColor blue
}
element {
FontColor green
}
sequenceDiagram {
FontColor purple
}
actor {
FontColor red
}
</style>
' In that case, actor from sequence diagrams will be in red, because red is defined after purple.
actor Alice
Alice -> Bob : hello
@enduml
Finally, there is also a way to change a setting for a element which have two or more precise styles:
@startuml
skinparam useBetaStyle true
title Styling example
<style>
sequenceDiagram+actor {
FontColor blue
}
</style>
' In that case, only actor from sequence diagram are printed in blue.
' Actors in other diagram and other sequence element are unchanged.
actor Alice
Alice -> Bob : hello
@enduml
@startuml
skinparam useBetaStyle true
title Styling example
<style>
sequenceDiagram+actor {
FontColor blue
}
</style>
' In that case, only actor from sequence diagram are printed in blue.
' Actors in other diagram and other sequence element are unchanged.
actor Alice
Alice -> Bob : hello
@enduml
You can also defined your own style. In that case, they could be used when needed with the stererotype notation :
@startuml
<style>
actor {
Padding 10
}
MorePadding {
Image <&check>
ImagePosition BeforeText
' or AfterText
' <img:> convention also supported
fontSize 24
Size 32
Padding 20
}
message {
fontSize 24
}
SmallFont {
fontSize 10
}
</style>
participant Bob
actor Alice <<MorePadding>>
'In that case Alice with use the style "root, element, sequence, actor, MorePadding"
participant Sally
Bob->Alice: Hello
Alice->Sally: Also Hello! <<SmallFont>>
' This is new: you can use stereotype on message
@enduml
Here is another example:
@startuml
<style>
message {
fontSize 24
}
BobAndAliceMessage {
fontSize 10
}
</style>
participant Bob
actor Alice
participant Sally
Bob->Alice: Hello <<BobAndAliceMessage>>
Alice->Sally: Also Hello!
@enduml
@startuml
<style>
message {
fontSize 24
}
.BobAndAliceMessage {
fontSize 10
}
</style>
participant Bob
actor Alice
participant Sally
Bob->Alice: Hello <<BobAndAliceMessage>>
Alice->Sally: Also Hello!
@enduml
Unfortunately here, you have to manually set <<BobAndAliceMessage>>.
Varying style
There is another limitation of current skinparam features: the parameter are defined once along the diagram and you cannot change values across the diagram.
The idea here is to allow a style to be different in some context (for example a package) or to change over the execution of the diagram.
For example:
@startuml
skinparam useBetaStyle true
style message {
FontColor blue
}
Alice -> Bob : this is printed in blue
style message {
FontColor red
}
Alice -> Bob : this is printed in red
@enduml
Or in some usecase diagram:
@startuml
style actor {
FontColor blue
}
'foo1 is printed in blue
actor foo1
package myPackage {
' Style modification in this package are local
style actor {
FontColor red
}
'foo2 is printed in red
actor foo2
}
' We left the package, so we're back to previous style definition
'foo3 is also printed in blue
actor foo3
@enduml
Mixing style and stereotype
Styles and stereotypes are going to be very close notions.
Stereotypes could be defined as style to change colors, font...
The only difference is that a stereotype is printed on diagrams using standard UML notation while a style is never printed on diagrams. So styles and stereotypes affect rendering in the same way.
For example, you can have:
@startuml
skinparam useBetaStyle true
stereotype foo1 {
FontColor green
}
style dummy1 {
FontColor red
}
participant Alice <<foo1>>
participant Bob <<dummy1>>
@enduml
Alice is going to be printed in green and Bob in red. However, <<foo1>> is going to be printed on the diagram while you won't see any dummy1 string.
Now, since stereotype are printed, you can change stereotype colors using a style named stereotype
@startuml
skinparam useBetaStyle true
stereotype foo1 {
FontColor red
}
style stereotype {
FontColor green
}
participant Bob <<foo1>>
@enduml
So Bob is going to be printed in red and <<dummy1>> is going to be printed in green.
Now you can also do complex stuff:
@startuml
skinparam useBetaStyle true
style dummy1 {
FontColor purple
FontStyle bold
BackgroundColor white
}
stereotype foo3 {
FontColor blue
FontStyle bold
}
style stereotype {
FontColor green
FontSize 8
}
style stereotype+foo3 {
FontSize 24
FontColor red
}
participant Bob <<dummy1>>
actor Alice <<foo2>>
actor Charlie <<foo3>>
actor David <<foo4>>
@enduml
Potential Use Extensions
[KJW]Have style Parameters equivalent to Skin Parameters applied conditionally
participant "EXternal API" as extapi
participant ""EXternal Service" as exts
end box</code>
<code>formatConnection("2", "ap", "extapi","<< MA-TLS >>")</code>
OR
<code>ap<<->>extapi : <style:sequenceArrowThickness 2> << MA-TLS >></code>
Result is only this sequence line is 2 the rest are the default 1
plantuml.skin file
This is the default file used by PlantUML.
Users will be able to modify this file or to create their own foo.skin file.
We keep here some previous discussions.
We do not delete them because they contain some interesting ideas.
Maybe we are going to reuse them.
First style proposal
A first idea is to use the same notion as CSS (cascading style sheet).
The cascading feature may be useful to avoid duplication in skinparam/style feature.
All elements will have one default (predefined) style, which may inherit for another default style.
Users will be able either:
to create a new style and to apply it to some element
to modify any predefined style.
Let's start by an simple example:
@startuml
actor Alice
boundary Bob
Alice -> Bob : hello
@enduml
By default, all elements uses the root style, so you can change the font name of all text of this diagram with:
Let's say we have the following style hierarchy:
@startuml
title Style hierarchy cascade
skinparam ranksep 30
hide circle
hide empty members
class root
class element extends root
class actor extends element
class boundary extends element
class message extends root
@enduml
@startuml
style root {
fontName Arial
}
style element {
fontName Courier
fontColor Red
}
syle actor {
fontSize 14
}
style boundary {
fontColor Blue
}
actor Alice
boundary Bob
Alice -> Bob : hello
@enduml
[RG] --
If the format of CSS is followed, the above example could be extended in a similar fashion for targeting specific elements perhaps:
@startuml
style root {
fontName Arial
}
style element {
fontName Courier
fontColor Red
}
style actor.Bob, boundary.Alice {
fontSize 14
}
style message {
fontSize 24
}
style message.Bob:Alice
{
BackgroundColor Black
}
actor Alice
boundary Bob
Alice -> Bob : hello
@enduml
The syntax for selecting the entity could take many forms. I've used : for simplicity. Implementing a full descendant selection matching like CSS is more powerful, but also likely more complex, i.e. ancestor > descendant. It seems most scenarios involve two entities at most, so a simple matching pair may be sufficient, with some minor additional flexibility for directional allowances. To continue the example above, it might look like:
@startuml
style message {
fontSize 24
}
style message.Bob:Alice
{
BackgroundColor Black
}
style message.Bob>Alice
{
BackgroundColor Blue
}
actor Alice
boundary Bob
Alice -> Bob : hello
'fontSize 24 && BackgroundColor Black
Bob o-> Alice : hi
'fontSize 24 && BackgroundColor Black && BackgroundColor Blue
'BackgroundColor would resolve as standard cascade to last defined value, "Blue"
'or precedence possibly, i.e. a specific relationship ">" is higher order than
'"any" relationship ":"
@enduml
It would nice to be mindful of some other outstanding requests that would like to see some form of metadata follow through into SVG output, for postprocessing with other tools. For example, I have opened the SVG in Sketch and used a tool to locate elements of a given name or type to make changes, which currently is quite hard. To incorporate that in a basic fashion:
[AR] Agreed on the need for SVG ouput. I propose to use exportedName property for this purpose
@startuml
style message {
styleName "DefaultMessage"
'default value, not required but processed by system if no styleName found? See example below for
'counter argument
fontSize 24
}
style message.Bob:Alice {
styleName "BobAndAliceMessage"
' provided by user. If not provided by user, perhaps system generate in output with basic
' convention like CamelCase to hyphens: Bob-Alice-Message
' But I think it's acceptable to simply state "if user wants a value here, they should supply it"
fontSize 10
}
participant Bob
actor Alice
participant Sally
Bob->Alice: Hello
Alice->Sally: Also Hello!
@enduml
I think the main drawback of doing the selectors in the style section is the dynamic nature of PlantUML, where new entities may be defined on the fly. But in many ways this is no different than HTML/CSS. To take a slightly different tact, focusing on style application happening in the UML itself:
[AR] Here, I don't like the idea of style targeting individual element of a specific diagram. According to me, it prevent the reuse of some existing style file several diagrams.
@startuml
style actor {
Padding 10
}
style actor {
styleName "MorePadding"
Image <&check>
ImagePosition BeforeText
' or AfterText
' <img:> convention also supported
fontSize 24
Size 32
Padding 20
}
style message {
styleName "DefaultMessage"
fontSize 24
}
style message {
styleName "SmallFont"
fontSize 10
}
participant Bob
actor Alice <style:"MorePadding">
' or actor <style:"MorePadding"> Alice
' or !style "MorePadding"
participant Sally
Bob->Alice: Hello
Alice->Sally: <style:"SmallFont"> Also Hello!
@enduml
It may be a personal choice, but I feel the cleaner the UML markup itself, the better. For example, to prevent having something like a Swimlane title, that must be repeated such as <&check><size:24><c:blue> \nSome\nName. With Macros or Variables this can be easier, but still is another layer of mental abstraction. None of this prevents doing that of course, to keep compatibility as is mentioned.
[AR] agreed on the cleaner the UML markup itseft, the better.
Targeting Specific Diagram types
It is important meet the goal of reusing a "style set", i.e. putting many definitions in one file for many diagram types. The common example of this is a "Design System", so that a common style may be followed across different diagrams.
Following the initial example, a convention could be followed such as:
would result in hierarchy:
@startuml
title Style hierarchy cascade per type Option1
skinparam ranksep 30
hide circle
hide empty members
class root
class message extends root
class element extends root
class actor extends element
class "SequenceDiagram.message" extends message
class "SequenceDiagram.actor" extends actor
@enduml
or alternately:
@startuml
title Style hierarchy cascade per type Option2
skinparam ranksep 30
hide circle
hide empty members
class root
class message extends root
class actor extends root
class SequenceDiagram extends root
class SequenceDiagram.message extends SequenceDiagram
class SequenceDiagram.actor extends SequenceDiagram
@enduml
It would seem Option1 makes more sense in an inheritance/parsing sense. Option2 would likely result in challenges in traversing/cascading the styles. I also note that this conflicts with my syntax above for style assignment (reusing the . operator), so perhaps one or the other may need to change if it was difficult to manage. Perhaps: style message.Bob:Alice in SequenceDiagram or style SequenceDiagram.message(Bob:Alice) or even style SequenceDiagram.message.Bob:Alice.
[AR] I fully agree on the need (except for targeting indivual element), and I will propose an Option3.