plantuml / plantuml

Generate diagrams from textual description
https://plantuml.com
Other
9.73k stars 881 forks source link

Add support for `MetaClass` and `Stereotype` in class diagram #1159

Closed The-Lum closed 1 year ago

The-Lum commented 1 year ago

Requested by:

Even if:

The main question is how many new keywords will be necessary for the future. Perhaps the best solution will be to manage a more generic solution possible used by the user, without create new keywords on PlantUML.

Awaiting for another solution, here is a simple proposal to support MetaClass and Stereotype in class diagram.

[Special credit to #1056 and especially #1028]

arnaudroques commented 1 year ago

Many thanks for your contribution!

Even if we have merged it, we are not sure that this is really a good idea: now there are some confusion about Stereotype because we have now two different things called "Stereotype":

Maybe we should merge the two notions?

The-Lum commented 1 year ago

Hello @PlantUML team, @arnaudroques,

We are not sure that this is really a good idea: now there are some confusion about Stereotype because we have now two different things called "Stereotype

Maybe but the 2 can maybe live together, see a possible example here:

predefined _Taken from: https://sparxsystems.com/enterprise_architect_user_guide/15.2/modeling/addingelementsandmetaclass.html_

or

profile-stereotype-tag-definitions Taken from: https://www.uml-diagrams.org/profile-metaclass.html

I just simple answered to the initial request:


Then:

Also would it be possible to change the S CircledChar of the new Stereotype:

But that would take 2 characters and not 1! On: https://github.com/plantuml/plantuml/blob/5c4b6b7fef3c2d20d53b4685ba68ac8028efd696/src/net/sourceforge/plantuml/svek/image/EntityImageClassHeader.java#L232-L233

Happy to improve PlantUML tool. Regards.

grivo commented 1 year ago

first of all - thanks for crucial update!

since plantuml forum suffers from infinite browser security checks that don't allow me to post anything - can i ask more common questions here, probbaly, someone has an information on following

  1. how ofter master commits from this repo are promoted to plantuml-server repo?
  2. if this is quite irregular and unpredicted process - how latest snapshot may be ported to plantuml server version run by jetty?
  3. i have noticed some bugs related to stereotypes, interfaces, etc - will it be fine to post issues directly to this issue tracker?
The-Lum commented 1 year ago

Hello @grivo, and all,

Regards.

grivo commented 1 year ago

Many thanks for your contribution!

Even if we have merged it, we are not sure that this is really a good idea: now there are some confusion about Stereotype because we have now two different things called "Stereotype":

  • the stereotype which is printed using << and >> and which can be used to change color
  • this new Stereotype (which can have stereotype BTW :-) )

Maybe we should merge the two notions?

well, i have created some diagram that freezes some current bugs related with metaclass/stereotype/interface usage

it contains some proposal of new layout as well, that fixes usage conflicts staying more UML 2.x compatible and same time lightweight

probably, more bugs and feature tasks should be created according my proposal

@startuml
namespace myOldProfile {
    class MyMetaclass <<metaclass>>
    note left
        =preamble
        metaclass is a special
        profile entity extended
        by stereotypes, it may
        have detailed attributes and
        operations (yet usually
        it is so abstract that it
        has no fields at all)

        =bugs
        * metaclasse's head compartment
        isn't marked with //italic//,
        however it should since it's
        highlevel abstraction

        * body compartment is visible
        by default

        * when displayed, //metaclass// type
        of PlantUML //class// entity isn't marked
        by //<<metaclass>>// stereotype by default
        despite it should

        * //<<metaclass>>// stereotype can be
        applied on PlantUML //class// entity:
        **class X <<metaclass>>**

        =solutions
        * metaclasse's head compartment
        should be marked with //italic//

        * attribute/operation compartments
        should be hidden in case there are
        no attributes/operations - i know,
        that it may be done with **hide**
        command, yet this behaviour should
        be forced for //metaclass// imo

        * once entity is type of //metaclass//
        then //<<metaclass>>// stereotype should
        be appended to the list of stereotypes
        entity has when displaying entity

        * forbid user to apply //<<metaclass>>//
        stereotype onto any PlantUML //class//
        entity
    end note

    class MyStereotype <<stereotype>> {
        - stereotype_property : str
        - stereotype_property_boolean : bool
        # stereotype_operation() : void
    }
    note right
        =preamble
        stereotype is a thing that
        brings metaclasse's abstractions
        closer to implementation: it's
        like average class entity, it
        may have detailed attributes and
        operations, however it stays
        abstract by itself

        depending on implementation,
        stereotype applies on a target,
        injects own functionality to
        target, and that's important
        since it is an opposite of classic
        inheritance concept

        =bugs
        * you may mention that
        every field of //stereotype//
        type of entity is marked
        as abstract (//italic//
        formatted), but it's not always
        true, for example:

        ** JavaScript has a
        concept of prototype classes,
        and inheritance is implemented
        via injection here... and prototype
        may have not only declarations of
        properties and methods signatures,
        but methods bodies and so on

        ** Python has more excplicit
        injection engine: once class is
        in initialization process, special
        type of class (called metaclass, lol)
        injects some properties and methods
        to target class (depending on target
        class name, import context,
        application configuration, etc)

        * when displayed, //stereotype// type
        of PlantUML //class// entity isn't marked
        by //<<stereotype>>// stereotype by default
        despite it should

        * //<<stereotype>>// stereotype can be
        applied on PlantUML //class// entity:
        **class X <<stereotype>>**

        =solutions
        * header of //stereotype//
        should stay //italic//, however,
        body should remain styled with
        normal font - since there is a chance
        that some fields of //stereotype// may
        be not abstract

        * once entity is type of //stereotype//
        then //<<stereotype>>// stereotype should
        be appended to the list of stereotypes
        entity has when displaying entity

        * forbid user to apply //<<stereotype>>//
        stereotype onto any PlantUML //class//
        entity
    end note
    note left of MyStereotype::stereotype_property
        =note
        let's assume this field is the only abstract
        feature of stereotype
    end note
    note left of MyStereotype::stereotype_operation
        =note
        this is just method description sample

        =method
        def stereotype_operation(self):
            assert(True)
    end note
    MyStereotype ---|> MyMetaclass
    note on link
        =preamble
        it might be said that
        //stereotype// inherits
        //metaclass//, but in fact it
        **clarifies** metaclass
        manifestation

        =bug
        most accurate way to explain this
        relation is to use //extension//
        type of arrow, however, most
        of UML guides recommend
        same link type and arrow type,
        but with filled arrowhead

        =solution
        fill arrowhead of --|> relation
        with color when it is an //extension//
        from //stereotype// to //metaclass//
    end note
}
note top of myOldProfile
    =preamble
    new stereotypes //<<metaclass>>//
    and //<<stereotype>>// aren't
    used here

    =bugs
    * stereotype can not be applied on
    //package/namespace//, for example,
    there is a lack of //<<profile>>//
    stereotype applied on namespace

    =solution
    syntax similar to class stereotype
    declaration should be accessible,
    sort of:
    **package myPackage <<stereotype>> {}**
    or
    **namespace myNamespace <<stereotype>> {}**.
    thus it should end up with tltle labels fomatted
    in following manner:
    //<<profile>>// myPackage
    or
    //<<profile>>// myNamespace
end note

namespace myOldClasses {
    interface MyInterface {
        + interface_property
        + interface_operation()
    }
    note left
        =preamble
        //interface// is most used
        stereotype, but it has some 
        differences

        * it may be declared outside
        of //<<profile>>// package

        * still it may refer to some
        metaclasses located at
        //profile packages//

        * //interface// may use
        two main notations for
        relations: arrow-like and
        joint-like

        * depending on
        programming language,
        //interface// allows
        //abstract// attributes
        or operations, but it never
        allows non-abstract features

        =bugs
        * despite head compartment is
        marked //italic//, body
        compartments aren't

        * when displayed, //interface// type
        of PlantUML //class// entity isn't marked
        by //<<interface>>// stereotype by default
        despite it should

        * //<<interface>>// stereotype can be
        applied on PlantUML //class// entity:
        **class X <<interface>>**

        =solutions
        * force //italic// to be applied
        on entity ищвн once it's
        an //interface// type of entity

        * once entity is type of //interface//
        then //<<interface>>// stereotype should
        be appended to the list of stereotypes
        entity has when displaying entity

        * forbid user to apply //<<interface>>//
        stereotype onto any PlantUML //class//
        entity
    end note
    MyInterface ---|> myOldProfile.MyMetaclass
    note on link
        =preamble
        //interface// may refer to
        //metaclass// since it's
        special case of //stereotype//

        =bug
        same as with //stereotype// to
        //metaclass// relation

        =solution
        same as with //stereotype// to
        //metaclass// relation
    end note

    class myClass <<mystereotype, myinterface>> {
        + y : int = 2
        # qwerty(arg : int) : long
        - x : int = 1
        ==//<<mystereotype>>//==
        # stereotype_operation(f : float) : void
        - stereotype_property = "my string"
        ==//<<myinterface>>//==
        + interface_property : int = 2
        + interface_operation(i : int) : float
    }
    note top
        =preamble
        //stereotype// injected features may be
        presented within //class body compartment//

        =bugs
        once user adds custom compartment

        * no auto-arrange of properties and
        operations performed anymore

        * class properties and operations
        aren't scoped to separate compartments
        anymore

        =solutions
        * broken arrangement of classe's own
        features should be restored

        * once //stereotype// is applied on the
        class - //stereotype's// compartment
        should be created for class

        * custom compartments should list
        properties prior to operations

        * in case of auto-compartments - some
        auto-linebreak is vital

        =optional yet still useful
        * despite it is not necessary, since
        //interface// is a special case of
        //stereotype//

        ** //interface's// stereotype may be
        listed among stereotypes applied onto
        class

        ** those features
        which are implemented by a class
        that applied an //interface// onto
        itself... they may be declared through
        separate compartment 
    end note
    note left of myClass::stereotype_operation
        def stereotype_operation(self, f : float):
            assert(f > 0)
    end note
    note left of myClass::stereotype_property
        once attribute is declared at //stereotype//,
        //class// may set default value only unless
        //class// redefines attribute or clarifies it
    end note
    note left of myClass::interface_property
        abstract attribute of //stereotype/interface// should be
        fully declared once //class// isn't marked as an
        abstract entity
    end note
    note left of myClass::interface_operation
        from typing import Callable

        interface_operation : Callable[[int], float] = lambda i: float(i) * 5
    end note
    myClass ..|> MyInterface

    class "{stereotype_property_boolean}\nmyAnotherClass" as myAnotherClass <<mystereotype>> {
        + b : bool = false
        # qazzaq(arg : long) : int        
        - e : float = 3.4
        ==//<<mystereotype>>//==
        + stereotype_property = "public data"
    }
    note top
        =preamble
        if //stereotype// applied on //class// has
        boolean attributes - they may be listed
        comma separated in curly brackets at
        head section of a //class// in case of being
        set to //true// by default for //class//

        =solutions
        it's possible, yet solution looks ugly, so
        it's up to team whether to implement it
        or not, but, frankly i find that formatting
        pattern bit awkward
    end note

    class myAnotherSpecialClass <<mystereotype>> {
        + b : bool = false
        # qazzaq(arg : long) : int        
        - e : float = 3.4
    }
    note top
        //<<mystereotype>>//
        ~ stereotype_property_boolean = false
        - stereotype_property = "protected chars"

        ---

        =preamble
        note-style notation for //stereotype//
        provided features is legal according UML 2.x
        standards, no action required, it's up to
        user to stick to that layout option
    end note

}
myOldClasses ..> myOldProfile : <<apply>>
note on link
    =bug
    //package/namespace// linking
    looks weird

    =solution
    relation points should be class-like
end note

namespace  myNewProfile {
    hide empty members
    class MyMetaclass <<(M, #CCCCCC) metaclass>>

    class MyStereotype <<(S, #FF77FF) stereotyp>> {
        - stereotype_property : str {abstract}
        - stereotype_property_boolean : bool
        # stereotype_operation() : void
    }
    note right
        =bug
        had to set //stereotyp// not
        //stereotype// to avoid whole
        entity body to be formatted
        italic
    end note
    MyStereotype --|> MyMetaclass
    note on link
        =bug
        unfortunately,
        can't paint
        arrowhead black
    end note
}
note top of myNewProfile
    =bugs
    * stereotypes //<<metaclass>>//
    and //<<stereotype>>// weren't
    used here since my local server
    is bit outdated. i have tried to
    apply some ad-hocs to show
    what effects will bring solutions
    mentioned at this diagram

    * failed to have title set to
    **<<profile>> myNewProfile**
    due to namespace name
    formatting restrictions
end note
namespace myNewClasses {
    interface MyInterface {
        + interface_property {abstract}
        + interface_operation() {abstract}
    }
    MyInterface --|> myNewProfile.MyMetaclass
    note on link
        =bug
        unfortunately,
        can't paint
        arrowhead black
    end note

    class myClass <<mystereotype, myinterface>> {
        - x : int = 1
        + y : int = 2
        ___
        # qwerty(arg : int) : long

        ==//<<mystereotype>>//==
        - stereotype_property = "my string"
        - stereotype_property_boolean = true
        ___
        # stereotype_operation(f : float) : void

        ==//<<myinterface>>//==
        + interface_property : int = 2
        ___
        + interface_operation(i : int) : float
    }
    note right
        following auto-compartments generation
        and features layout seem to me as quite
        elegant and optimal
    end note
    myClass ..|> MyInterface
}
myNewClasses ..> myNewProfile : <<apply>>
@enduml

you may see diagram as image as well since it's too heavy for demo plantuml online server

svg class

png image

example of stereotype usage (taken from here) image

grivo commented 1 year ago

also: how can i change class or custom stereotype cirlce char and spot colour with <style></style> block but not with skinparam?

grivo commented 1 year ago

any thoughts?

grivo commented 1 year ago

created https://github.com/plantuml/plantuml/issues/1173 for metaclass / stereotype related issues and https://github.com/plantuml/plantuml/issues/1174 for spotchars

grivo commented 1 year ago

@The-Lum - thanks a lot for bringing on metaclass / stereotype keywords into plantuml core, it is real game changer!

nevertheless, yet there are some questions regarding css/style

  1. spot char can be set now by skinparam spotChar only
  2. css spot block is bit out of classDiagram block context

my proposal example is

classDiagram {
    class { ' custom stereotype name '.xyz' may be used here as well
        spot {
            BackgroundColor #СССССС ' spot background colour
            LineColor #СССССС ' spot line colour
            LineThickness 1.0 ' spot line thickness
            FontColor #000000 ' spot char colour
            FontStyle normal ' spot char style
            Char 'C' ' spot char content
        }
    }
}

"<<(X, #64BDF9) xyz>>" - this inline style override works fine and should be left as it is for future use, however, in case of multiple stereotypes applied - it does not: "<<(Z, #CCCCCC) interface, xyz>>"

there are additional things that will make metaclass / stereotype / interface usage more correct, however, small steps are easier to accomplish, imo

grivo commented 3 months ago

classDiagram { class { ' custom stereotype name '.xyz' may be used here as well spot { BackgroundColor #СССССС ' spot background colour LineColor #СССССС ' spot line colour LineThickness 1.0 ' spot line thickness FontColor #000000 ' spot char colour FontStyle normal ' spot char style Char 'C' ' spot char content } } }

i'm not sure if my proposal from above is available, can you confirm, please?

The-Lum commented 3 months ago

Hello @grivo, and all,

I'm not sure if my proposal from above is available, can you confirm, please?

Not yet. It is a wanted feature. Currently only this:

<style>
spot {
  BackgroundColor pink
  LineColor blue
  FontColor green
  FontStyle plain
}
</style>

class C
entity E

And (individually and globally) for:

Regards, Th.

grivo commented 3 months ago

@The-Lum - and stereotypes-driven custom styling isn't available yet as well?

i have tried multiple patterns - none of them works... or, maybe, i'm just wrong with syntax?

@startuml
<style>
    spotClass {
        BackgroundColor orange
    }

    spotEntity {
        .q {
            BackgroundColor pink
        }
    }

    .w {
        spot {
        BackgroundColor pink
        LineColor blue
        FontColor green
        FontStyle plain
        }
    }

    spotE {
        BackgroundColor pink
    }
</style>

class A

entity B <<q>>

entity C <<w>>

entity D <<e>>
@enduml