Customizing Diagrams

Hello! I’m trying to customize diagrams so I can include them in the documentation. Below is an example model that I’ve been using and the diagram that I’m able to generate from it.

What I would like to customize is:

  1. Hide the attributes field of every element shown in the diagram (I’ll show those in a table format next to the diagram)
  2. Hide the elements with attribute “software_library”
  3. What are the “ownedPorts” that appear on the diagram? I don’t have them in the model?
  4. Show the port types (for some reason they are not being shown?)

Are there any options on customizing aesthetics? General layout, colors, etc..

Thanks!

Tom

package Example {

    package Ontology {

        enum def SoftwareItemType{
            TBD;
            software_process;
            software_library;
        }
        
        abstract part def SoftwareItem {
            attribute id : ScalarValues::String default "TBD";
            attribute type : SoftwareItemType default SoftwareItemType::TBD;
        }

        abstract part def ComputingNode;
        abstract port def SoftwareInterfacePort;
    }


    package SoftwareItems {

        part def SoftwareItem1 :> Ontology::SoftwareItem {
            attribute :>> id = "1";
            attribute :>> type = Ontology::SoftwareItemType::software_process;

            part softwareItem3 : SoftwareItem3;
            part softwareItem4 : SoftwareItem4;

            port port1 : SoftwareInterfaces::Port1;
            port port2 : SoftwareInterfaces::Port2;
        }

        part def SoftwareItem2 :> Ontology::SoftwareItem {
            attribute :>> id = "2";
            attribute :>> type = Ontology::SoftwareItemType::software_process;

            part softwareItem3 : SoftwareItem3;

            port port2 : SoftwareInterfaces::Port2;
            port port3 : SoftwareInterfaces::Port3;
        }

        part def SoftwareItem3 :> Ontology::SoftwareItem {
            attribute :>> id = "3";
            attribute :>> type = Ontology::SoftwareItemType::software_library;
        }

        part def SoftwareItem4 :> Ontology::SoftwareItem {
            attribute :>> id = "4";
            attribute :>> type = Ontology::SoftwareItemType::software_library;
        }

    }


    package SoftwareInterfaces {
        port def Port1 :> Ontology::SoftwareInterfacePort;
        port def Port2 :> Ontology::SoftwareInterfacePort;
        port def Port3 :> Ontology::SoftwareInterfacePort;
    }


    package ComputingNodes {

        part def ComputingNode1 :> Ontology::ComputingNode {
            part softwareItem1 : SoftwareItems::SoftwareItem1;
            part softwareItem2 : SoftwareItems::SoftwareItem2;

            port port1 : SoftwareInterfaces::Port1;
            port port3 : SoftwareInterfaces::Port3;

            binding bind port1 = softwareItem1.port1;
            binding bind softwareItem2.port3 = port3;

            interface connect softwareItem1.port2 to softwareItem2.port2;
        }

    }


    package MyViews {
    
        view nodeRuntimeView {
            attribute fileType = "png";
            attribute depth = -1;
            expose ComputingNodes::ComputingNode1;
        }

    }


}

Hi Tom!

  1. Currently there is no way in Tom Sawyer to toggle which compartments are shown (e.g. attributes comparment).

  2. The expression that goes into filter works on the metamodel level according to the spec. Therefore, it is not able to check what attributes the element has and what their values are.
    However, you could still do this kind of filtering if you changed the type attribute to instead be a metadata tag (@) or keyword (#).
    We are looking into ways to make filtering more powerful and expressive, but cannot give any timelines at the moment.

  3. ownedPorts come from the standard library. This is a bug and we are working towards fixing it.

  4. By “port types” do you mean seeing e.g. port1 : SoftwareInterfaces::Port1 in the diagram instead of just port1?

1 Like

Yes, exactly!

According to the specification showing the port type in the diagrams is optional.

During diagram generation, we are providing Tom Sawyer SysMLv2 Viewer with all the required information to show the type. But since it is optional to show it, the Tom Sawyer SysMLv2 Viewer chooses not to display it.

I will raise it with them and see if we can get it on by default or get this as a configurable option.

1 Like

I’ve updated my example model according to your suggestion 2 (using metadata for filtering).

I must be something wrong, the filter is removing all exposed elements and I get this message when running the CLI:

View Example::MyViews::nodeRuntimeView has no exposed elements. Skipping.

Updated Model:

package Example {

    package Ontology {

        enum def SoftwareItemType{
            TBD;
            software_process;
            software_library;
        }
        metadata def SoftwareItem {
            attribute id : ScalarValues::String default "TBD";
            attribute type : SoftwareItemType default SoftwareItemType::TBD;
        }
    }


    package SoftwareItems {

        part def SoftwareItem1 {

            @Ontology::SoftwareItem {
                id = "1";
                type = Ontology::SoftwareItemType::software_process;
            }

            part softwareItem3 : SoftwareItem3;
            part softwareItem4 : SoftwareItem4;

            port port1 : SoftwareInterfaces::Port1;
            port port2 : SoftwareInterfaces::Port2;
        }

        part def SoftwareItem2 {

            @Ontology::SoftwareItem {
                id = "2";
                type = Ontology::SoftwareItemType::software_process;
            }

            part softwareItem3 : SoftwareItem3;

            port port2 : SoftwareInterfaces::Port2;
            port port3 : SoftwareInterfaces::Port3;
        }

        part def SoftwareItem3 {

            @Ontology::SoftwareItem {
                id = "3";
                type = Ontology::SoftwareItemType::software_library;
            }
            part softwareItem4 : SoftwareItem4;
        }

        part def SoftwareItem4 {

            @Ontology::SoftwareItem {
                id = "4";
                type = Ontology::SoftwareItemType::software_library;
            }
            part softwareItem3 : SoftwareItem3;
        }

        part def SoftwareItem5 {

            @Ontology::SoftwareItem {
                id = "5";
                type = Ontology::SoftwareItemType::software_library;
            }
        }
    }


    package SoftwareInterfaces {
        port def Port1;
        port def Port2;
        port def Port3;
    }


    package ComputingNodes {

        part def ComputingNode1 {
            part softwareItem1 : SoftwareItems::SoftwareItem1;
            part softwareItem2 : SoftwareItems::SoftwareItem2;

            port port1 : SoftwareInterfaces::Port1;
            port port3 : SoftwareInterfaces::Port3;

            binding bind port1 = softwareItem1.port1;
            binding bind softwareItem2.port3 = port3;

            interface connect softwareItem1.port2 to softwareItem2.port2;
        }

    }

    package MyViews {
    
        view nodeRuntimeView {
            attribute fileType = "png";
            attribute depth = -1;
            expose ComputingNodes::ComputingNode1;
            filter (as Ontology::SoftwareItem).type == Ontology::SoftwareItemType::software_process;
        }

    }

}

Okay, this answer will be quite a long one since there are multiple things combining into one that makes it all break down in the end. I will try to tackle each issue one by one.

Issue #1: @ Metadata is not inherited according to SysMLv2 spec

The SysMLv2 spec says that metadata is an annotation (similar to doc and comment elements). Annotations are not inherited through specializations or defined by or other relationships. For example

part def A { // This element will have the @MyMetadata on it
    @MyMetadata;
}

part b : A; // This element will not have the @MyMetadata even though its type has it.

This essentially means, that if you want to filter a view according to metadata, you need to attach the metadata tags on the part softwareItemN in each place it is being used, not on the part def SoftwareItemN. Of course, this means that these metadata tags are not really useful in your use case, where you want to define a type once and reuse it in many different parts of the model.

Issue #2: What about SemanticMetadata??

While it is true that SemanticMetadata (example below) creates implicit specializations when used on an element, filtering by these implicit specializations would require filters to work on the model-level and not metamodel-level. As mentioned in the previous reply, this is not supported now by the spec and we are looking for solutions.

action def UserAction;
action userActions : UserAction[*] nonunique;

metadata def CommandMetadata :> SemanticMetadata {
    // The meta-cast operation "userAction meta SysML::Usage" has
    // type Usage, which conforms to the type KermL::Type of baseType.
    // Since userActions is an ActionUsage, the expression evaluates
   // at model level to a value of type SysML::ActionUsage.
   :>> baseType = userActions meta SysML::Usage;
}

// Save implicitly subclassifies UserAction
// (which is the definition of userActions).
action def Save {
    @CommandMetadata;
}

// previousAction implicitly subsets userActions.
action previousAction[1] {
    @CommandMetadata;
}

Issue #3: Okay, I want to create explicit metadata on each softwareItemN usage

view nodeRuntimeView {
    expose ComputingNodes::ComputingNode1;
    filter (as Ontology::SoftwareItem).type == Ontology::SoftwareItemType::software_process;
}

Above is the way you have currently defined your view. However, according to the expose statement, you are saying “Only expose the element ComputingNode1 without its children”. This means that when we apply the filter defined, we have no elements to show, as ComputingNode1 does not have a Metadata tag.

A way to fix it would be to do the following:

view nodeRuntimeView {
    expose ComputingNodes::ComputingNode1::*;
    filter (as Ontology::SoftwareItem).type == Ontology::SoftwareItemType::software_process;
}

This would expose ComputingNode1s children: softwareItem1, softwareItem2, port1, port2, and the interface. However, it would not expose the ComputingNode1 itself. After applying the filter, we would be left with softwareItem1 and softwareItem2 only.

If you want ComputingNode1 to show up as well, you would need to do something like:

view nodeRuntimeView {
    expose ComputingNodes::ComputingNode1;
    expose ComputingNodes::ComputingNode1::*;
    filter (as Ontology::SoftwareItem).type == Ontology::SoftwareItemType::software_process;
}

However, in this case, the filter would apply to both exposes, and ComputingNode1 would be filtered out. The correct way would be to do inline filters:

view nodeRuntimeView {
    expose ComputingNodes::ComputingNode1; // Leave this without a filter
    expose ComputingNodes::ComputingNode1::*[(as Ontology::SoftwareItem).type == Ontology::SoftwareItemType::software_process]; // Apply the filter only on ComputingNode1's children
}

However, Syside 0.8.7 does not yet support this “inline” filtering. Support of this is scheduled for 0.9.0 release, which is currently going through internal QA and preparation for release stages (such as writing documentation similar to what I did above).

Post Scriptum

expose works similarly to import, therefore you can use the same syntax to specify what you want to expose to the view:

expose ComputingNodes::ComputingNode1; // Expose only 'ComputingNode1'
expose ComputingNodes::ComputingNode1::*; // Expose direct children of 'ComputingNode1' 
expose ComputingNodes::ComputingNode1::**; // Expose children of 'ComputingNode1' recursively
expose ComputingNodes::ComputingNode1::*::**; // This is essentially a combination of 'ComputingNode1' and 'ComputingNode1::**'

Multiple exposes in a view definition also “stack” as seen in the issue #3 solution.

You can also quickly play around and see what elements are exposed in your view if you use Automator with the following script. It will print out qualified names of all exposed (but not yet filtered) elements.

import syside

model, _ = syside.load_model([<paths_to_your_model_files>])

for view in model.nodes(syside.ViewUsage):
    if view.name == <your_view_usage_name>:
        for element in view.exposed_elements.collect():
            print(element)
Preview of 0.9.0's inline filters

Here I am also including an adapted version of your SysML model and a Python script that correctly filters out the elements to show only libraries or only processes with the ComputingNode1 preserved.

package Example {
    package Ontology {
        enum def SoftwareItemType {
            TBD;
            software_process;
            software_library;
        }
        metadata def SoftwareItem {
            attribute id : ScalarValues::String default "TBD";
            attribute type : SoftwareItemType default SoftwareItemType::TBD;
        }
    }

    package SoftwareItems {
        part def SoftwareItem1 {
            part softwareItem3 : SoftwareItem3 {
                @Ontology::SoftwareItem {
                    id = "3";
                    type = Ontology::SoftwareItemType::software_library;
                }
            }
            part softwareItem4 : SoftwareItem4 {
                @Ontology::SoftwareItem {
                    id = "4";
                    type = Ontology::SoftwareItemType::software_library;
                }
            }

            port port1 : SoftwareInterfaces::Port1;
            port port2 : SoftwareInterfaces::Port2;
        }

        part def SoftwareItem2 {
            part softwareItem3 : SoftwareItem3 {
                @Ontology::SoftwareItem {
                    id = "3";
                    type = Ontology::SoftwareItemType::software_library;
                }
            }

            port port2 : SoftwareInterfaces::Port2;
            port port3 : SoftwareInterfaces::Port3;
        }

        part def SoftwareItem3 {
            part softwareItem4 : SoftwareItem4 {
                @Ontology::SoftwareItem {
                    id = "4";
                    type = Ontology::SoftwareItemType::software_library;
                }
            }
        }

        part def SoftwareItem4 {
            part softwareItem3 : SoftwareItem3 {
                @Ontology::SoftwareItem {
                    id = "3";
                    type = Ontology::SoftwareItemType::software_library;
                }
            }
        }
    }

    package SoftwareInterfaces {
        port def Port1;
        port def Port2;
        port def Port3;
    }

    package ComputingNodes {
        part def ComputingNode1 {
            part softwareItem1 : SoftwareItems::SoftwareItem1 {
                @Ontology::SoftwareItem {
                    id = "1";
                    type = Ontology::SoftwareItemType::software_process;
                }
            }
            part softwareItem2 : SoftwareItems::SoftwareItem2 {
                @Ontology::SoftwareItem {
                    id = "2";
                    type = Ontology::SoftwareItemType::software_process;
                }
            }

            port port1 : SoftwareInterfaces::Port1;
            port port3 : SoftwareInterfaces::Port3;

            binding bind port1 = softwareItem1.port1;
            binding bind softwareItem2.port3 = port3;

            interface connect softwareItem1.port2 to softwareItem2.port2;
        }
    }

    package MyViews {
        view process_view {
            expose ComputingNodes::ComputingNode1;
            expose ComputingNodes::ComputingNode1::**[
                    (as Ontology::SoftwareItem).type == Ontology::SoftwareItemType::software_process
                ];
        }
        view library_view {
            expose ComputingNodes::ComputingNode1;
            expose ComputingNodes::ComputingNode1::**[
                    (as Ontology::SoftwareItem).type == Ontology::SoftwareItemType::software_library
                ];
        }
    }
}

Python script:

import syside


def filtered(
    element: syside.ViewUsage,
    compiler: syside.Compiler,
    lib: syside.Stdlib | None = None,
) -> list[syside.Element]:
    filter = syside.CompilerFilter(
        compiler=compiler, lib=lib or syside.Environment.get_default().lib
    )
    return element.exposed_elements.with_import_filter(filter).collect()


model, _ = syside.load_model(["model.sysml"])
lib = model.lib
compiler = syside.Compiler()

for view in model.nodes(syside.ViewUsage):
    if view.name == "process_view":
        print("Process View:")
        filtered_elements = filtered(view, compiler, lib)
        for element in filtered_elements:
            print(element)
    elif view.name == "library_view":
        print("Library View:")
        filtered_elements = filtered(view, compiler, lib)
        for element in filtered_elements:
            print(element)

Output:

Process View:
Example::ComputingNodes::ComputingNode1
Example::ComputingNodes::ComputingNode1::softwareItem1
Example::ComputingNodes::ComputingNode1::softwareItem2

Library View:
Example::ComputingNodes::ComputingNode1
Example::SoftwareItems::SoftwareItem1::softwareItem3
Example::SoftwareItems::SoftwareItem1::softwareItem4
Example::SoftwareItems::SoftwareItem2::softwareItem3