mermaid-js / mermaid

Generation of diagrams like flowcharts or sequence diagrams from text in a similar manner as markdown
https://mermaid.js.org
MIT License
71.91k stars 6.53k forks source link

How to add a new shape? #1500

Open emacsen opened 4 years ago

emacsen commented 4 years ago

I would like to add a custom shape to my diagram, specifically a flow chart, but possibly other types?

I have an SVG file for it. Is there a simple way to add it as a shape in my diagrams, either by using it directly or converting it to another representation and then using it from there?

Polirecyliente commented 3 years ago

Lets not allow this issue to die, a new one will be created with the same request in time, like it has already happened with #820 #548 #274

Adding a feature like this would increase the creative freedom of the users. For example, you could create an SVG with Tikz or with Matplotlib, and then use that as a shape of a flowchart in Mermaid. In this way Mermaid would not need to implement the drawing capabilities of Tikz or Matplotlib, because you would draw there, and then use the drawing in Mermaid.

Polirecyliente commented 3 years ago

One of the main powers of Mermaid, which is one of the main reasons why some of us even use Mermaid in the first place, is that Mermaid does the layout of shapes in space. Using this definition, "Mermaid does the layout of shapes in space", it would be useful to give custom shapes to Mermaid and have Mermaid then lay them out in space.

BrutalSimplicity commented 2 years ago

This would make Mermaid even more awesome. In general, I appreciate the styling, syntax, and documentation of Mermaid more than PlantUML, but beyond PlantUML's awkward syntax it has wide support for this already. It's not so pretty, but you can easily incorporate "sprites" into your diagrams. It would be really nice if Mermaid also had this feature.

jvieille commented 1 year ago

Any update? The basic shapes offered by Mermaid are extremely limited, specifically for flowcharts which can be used for many different applications. If "custom shapes" features is a painful feature to implement, may be enriching Mermaid shapes library based on users' requests could be an simple solution?

sdbbs commented 1 year ago

I came up with an example, kind of complicated, but it seems to work in the Online Editor/Mermaid Live Editor; since this is the open issue, I'll post it here. References:

Basically, now that I've found a hoster for SVG (svgur.com), I've uploaded an SVG "cloud" vector image, and I'm using it in an <img> html tag inside a node. This is a screenshot of what I'm seeing in the Online Editor:

Screenshot at 2023-02-06 02-13-20

Here it should be rendered via Github's Mermaid diagrams - and unfortunately, the https://svgshare.com/i/q2Q.svg here is not visible, and the same problem remains if that link is replaced with the cloud_scalable.svg version I uploaded here (and the same problem occurs if you click on Actions/(Download PNG) in Mermaid Live Editor):

%%{init: {'theme':'base'}}%%
flowchart LR
    %% DummyNode("") crashes the parser, must have at least one character
    %%DummyNode("A")

    subgraph Border
        direction LR

        M1["Machine 1"]:::wht;
        subgraph CloudContainer[ ]
            direction LR
            %%CloudImgNode("<img class='BgImgClass' src='https://user-images.githubusercontent.com/48317456/216860238-ada65abe-43e7-45ca-8901-60aa95035eb9.svg' /> <div class='TxtClass'>Subnet <br /> 10.1.1.0/24</div>")
            %% text with <br> does not render with position:absolute
            %% use img top and div padding to center text in image
            CloudImgNode("<img style='position:absolute;left:0;top:7px;color:#fff;' src='https://svgshare.com/i/q2Q.svg' /> <div style='position:relative;z-index:100;color:black;padding:18px;'>Subnet <br /> 10.1.1.0/24 </div>");
        end
        M2["Machine 2"]:::wht;

        M1 --> CloudImgNode;
        CloudImgNode --> M2;
    end

%% Defining Class Styles
%% background-image:"" passes, but not with any text inside quotes
%% node that classes here get defined with "#graph-div " prepended!
%% however, seemingly <img> and such are included via foreignObject, so cannot refer to these classes 
classDef BorderClass fill:#fff,stroke:#fff,stroke-width:4px,color:#fff,stroke-dasharray: 5 5,margin:0,padding:0;
classDef ContainerClass position:relative,padding:0,margin:0,fill:#fff,color:#000,stroke:none;
classDef ImgNodeClass fill:#fff,padding:0,margin:0,stroke:none,margin:0;
classDef wht fill:#fff,color:black,stroke:#000;

%%classDef BgImgClass display:block,position:absolute,left:0,top:0,width:100%;
%%classDef TxtClass display:block,position:absolute,z-index:100,left:0,top:0;
%%classDef InvisibleClass display:none;

%% Custom Styles
%% NOTE: this gets applied to <rect class="basic label-container", 
%% not to the sigbling g<g class="label" or the enclosing <g id="flowchart-CloudImgNode-9174"
%% classdef node class; does however apply the class to the enclosing <g id="flowchart-CloudImgNode-9174" element
%%style CloudImgNode fill:#fff,padding:0,margin:0,stroke:none;
%%style CloudImgNode padding:100px,margin:100px;

%%<div class='TxtClass'>Subnet <br /> 10.1.1.0/24</div>
%% Assigning Nodes to Classes
class Border BorderClass;

%% Cannot add multiple classes separated with , (end up as single word)
%% or space (parser crashes)
%% but thankfully, multiple commands work: they do append classes
%%class DummyNode BgImgClass;
%%class DummyNode TxtClass;
%%class DummyNode InvisibleClass;

class CloudContainer ContainerClass;
%%class CloudImgNode BgImgClass;
%%class CloudImgNode TxtClass;

%% gets applied to all childer, too!
class CloudImgNode ImgNodeClass;

This should be the rendered .png right from the Mermaid Live Editor - also here the svg is missing:

Note that:

Another thing that would have made things easier:

Here is the code:

%%{init: {'theme':'base'}}%%
flowchart LR
    %% DummyNode("") crashes the parser, must have at least one character
    %%DummyNode("A")

    subgraph Border
        direction LR

        M1["Machine 1"]:::wht;
        subgraph CloudContainer[ ]
            direction LR
            %%CloudImgNode("<img class='BgImgClass' src='https://svgshare.com/i/q24.svg' /> <div class='TxtClass'>Subnet <br /> 10.1.1.0/24</div>")
            %% text with <br> does not render with position:absolute
            %% use img top and div padding to center text in image
            CloudImgNode("<img style='position:absolute;left:0;top:7px;color:#fff;' src='https://svgshare.com/i/q2Q.svg' /> <div style='position:relative;z-index:100;color:black;padding:18px;'>Subnet <br /> 10.1.1.0/24 </div>");
        end
        M2["Machine 2"]:::wht;

        M1 --> CloudImgNode;
        CloudImgNode --> M2;
    end

%% Defining Class Styles
%% background-image:"" passes, but not with any text inside quotes
%% node that classes here get defined with "#graph-div " prepended!
%% however, seemingly <img> and such are included via foreignObject, so cannot refer to these classes 
classDef BorderClass fill:#fff,stroke:#fff,stroke-width:4px,color:#fff,stroke-dasharray: 5 5,margin:0,padding:0;
classDef ContainerClass position:relative,padding:0,margin:0,fill:#fff,color:#000,stroke:none;
classDef ImgNodeClass fill:#fff,padding:0,margin:0,stroke:none,margin:0;
classDef wht fill:#fff,color:black,stroke:#000;

%%classDef BgImgClass display:block,position:absolute,left:0,top:0,width:100%;
%%classDef TxtClass display:block,position:absolute,z-index:100,left:0,top:0;
%%classDef InvisibleClass display:none;

%% Custom Styles
%% NOTE: this gets applied to <rect class="basic label-container", 
%% not to the sigbling g<g class="label" or the enclosing <g id="flowchart-CloudImgNode-9174"
%% classdef node class; does however apply the class to the enclosing <g id="flowchart-CloudImgNode-9174" element
%%style CloudImgNode fill:#fff,padding:0,margin:0,stroke:none;
%%style CloudImgNode padding:100px,margin:100px;

%%<div class='TxtClass'>Subnet <br /> 10.1.1.0/24</div>
%% Assigning Nodes to Classes
class Border BorderClass;

%% Cannot add multiple classes separated with , (end up as single word)
%% or space (parser crashes)
%% but thankfully, multiple commands work: they do append classes
%%class DummyNode BgImgClass;
%%class DummyNode TxtClass;
%%class DummyNode InvisibleClass;

class CloudContainer ContainerClass;
%%class CloudImgNode BgImgClass;
%%class CloudImgNode TxtClass;

%% gets applied to all childer, too!
class CloudImgNode ImgNodeClass;
sdbbs commented 1 year ago

Well, in relation to previous post - tried image base64 inline embedding instead (https://www.base64-image.de/); and with that, now it is possible to download a correct PNG from Mermaid Live editor... However, Github will unfortunately not render even this kind of svg in the mermaid diagram code:

%%{init: {'theme':'base'}}%%
flowchart LR
    %% DummyNode("") crashes the parser, must have at least one character
    %%DummyNode("A")

    subgraph Border
        direction LR

        M1["Machine 1"]:::wht;
        subgraph CloudContainer[ ]
            direction LR
            %%CloudImgNode("<img class='BgImgClass' src='https://svgshare.com/i/q24.svg' /> <div class='TxtClass'>Subnet <br /> 10.1.1.0/24</div>")
            %% text with <br> does not render with position:absolute
            %% use img top and div padding to center text in ima
            CloudImgNode("<img style='position:absolute;left:0;top:7px;color:#fff;' src=' data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhLS0gQ3JlYXRlZCB3aXRoIElua3NjYXBlIChodHRwOi8vd3d3Lmlua3NjYXBlLm9yZy8pIC0tPgoKPHN2ZwogICBpZD0ic3ZnMiIKICAgc29kaXBvZGk6ZG9jbmFtZT0iY2xvdWQuc3ZnIgogICB2aWV3Qm94PSIwIDAgNTY1LjQwOTk1IDMxNi44NDM5OSIKICAgdmVyc2lvbj0iMS4xIgogICBpbmtzY2FwZTp2ZXJzaW9uPSIxLjEuMiAoMToxLjErMjAyMjAyMDUwODUyKzBhMDBjZjUzMzkpIgogICB3aWR0aD0iMTAwJSIKICAgaGVpZ2h0PSIxMDAlIgogICB4bWxuczppbmtzY2FwZT0iaHR0cDovL3d3dy5pbmtzY2FwZS5vcmcvbmFtZXNwYWNlcy9pbmtzY2FwZSIKICAgeG1sbnM6c29kaXBvZGk9Imh0dHA6Ly9zb2RpcG9kaS5zb3VyY2Vmb3JnZS5uZXQvRFREL3NvZGlwb2RpLTAuZHRkIgogICB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyIKICAgeG1sbnM6Y2M9Imh0dHA6Ly9jcmVhdGl2ZWNvbW1vbnMub3JnL25zIyIKICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIj4KICA8ZGVmcwogICAgIGlkPSJkZWZzNCI+CiAgICA8ZmlsdGVyCiAgICAgICBpZD0iZmlsdGVyMzMwMSIKICAgICAgIGlua3NjYXBlOm1lbnU9IkFCQ3MiCiAgICAgICBjb2xvci1pbnRlcnBvbGF0aW9uLWZpbHRlcnM9InNSR0IiCiAgICAgICB3aWR0aD0iMS4wODAxNTgxIgogICAgICAgeT0iLTAuMDcyOTc0ODY4IgogICAgICAgeD0iLTAuMDQwMDc5MDY4IgogICAgICAgaW5rc2NhcGU6bWVudS10b29sdGlwPSJCdWxnaW5nLCBtYXR0ZSBqZWxseSBjb3ZlcmluZyIKICAgICAgIGhlaWdodD0iMS4xNDU5NDk3IgogICAgICAgaW5rc2NhcGU6bGFiZWw9Ik1hdHRlIGplbGx5Ij4KICAgICAgPGZlR2F1c3NpYW5CbHVyCiAgICAgICAgIGlkPSJmZUdhdXNzaWFuQmx1cjMzMDMiCiAgICAgICAgIHJlc3VsdD0icmVzdWx0MCIKICAgICAgICAgc3RkRGV2aWF0aW9uPSIyIgogICAgICAgICBpbj0iU291cmNlQWxwaGEiIC8+CiAgICAgIDxmZUNvbXBvc2l0ZQogICAgICAgICBpZD0iZmVDb21wb3NpdGUzMzA1IgogICAgICAgICBrMz0iMTAiCiAgICAgICAgIGsyPSIxIgogICAgICAgICByZXN1bHQ9InJlc3VsdDMiCiAgICAgICAgIGluPSJTb3VyY2VHcmFwaGljIgogICAgICAgICBvcGVyYXRvcj0iYXJpdGhtZXRpYyIKICAgICAgICAgaW4yPSJyZXN1bHQwIgogICAgICAgICBrMT0iMCIKICAgICAgICAgazQ9IjAiIC8+CiAgICAgIDxmZUNvbXBvc2l0ZQogICAgICAgICBpZD0iZmVDb21wb3NpdGUzMzA3IgogICAgICAgICBpbjI9InJlc3VsdDMiCiAgICAgICAgIHJlc3VsdD0iZmJTb3VyY2VHcmFwaGljIgogICAgICAgICBpbj0icmVzdWx0MyIKICAgICAgICAgb3BlcmF0b3I9ImluIiAvPgogICAgICA8ZmVDb2xvck1hdHJpeAogICAgICAgICBpZD0iZmVDb2xvck1hdHJpeDM3MjIiCiAgICAgICAgIGluPSJmYlNvdXJjZUdyYXBoaWMiCiAgICAgICAgIHZhbHVlcz0iMSAwIDAgMCAwIDAgMSAwIDAgMCAwIDAgMSAwIDAgMCAwIDAgMC44NSAwIgogICAgICAgICByZXN1bHQ9InJlc3VsdDAiIC8+CiAgICAgIDxmZUdhdXNzaWFuQmx1cgogICAgICAgICBpZD0iZmVHYXVzc2lhbkJsdXIzNzI0IgogICAgICAgICBzdGREZXZpYXRpb249IjciCiAgICAgICAgIGluPSJmYlNvdXJjZUdyYXBoaWNBbHBoYSIgLz4KICAgICAgPGZlU3BlY3VsYXJMaWdodGluZwogICAgICAgICBpZD0iZmVTcGVjdWxhckxpZ2h0aW5nMzcyNiIKICAgICAgICAgcmVzdWx0PSJyZXN1bHQ0IgogICAgICAgICBzcGVjdWxhckNvbnN0YW50PSIwLjg5OTk5OTk4IgogICAgICAgICBzcGVjdWxhckV4cG9uZW50PSIyNSIKICAgICAgICAgc3VyZmFjZVNjYWxlPSI1Ij4KICAgICAgICA8ZmVEaXN0YW50TGlnaHQKICAgICAgICAgICBpZD0iZmVEaXN0YW50TGlnaHQzNzI4IgogICAgICAgICAgIGVsZXZhdGlvbj0iNjAiCiAgICAgICAgICAgYXppbXV0aD0iMjI1IiAvPgogICAgICA8L2ZlU3BlY3VsYXJMaWdodGluZz4KICAgICAgPGZlQ29tcG9zaXRlCiAgICAgICAgIGlkPSJmZUNvbXBvc2l0ZTM3MzAiCiAgICAgICAgIG9wZXJhdG9yPSJhdG9wIgogICAgICAgICBpbjI9InJlc3VsdDAiIC8+CiAgICA8L2ZpbHRlcj4KICA8L2RlZnM+CiAgPHNvZGlwb2RpOm5hbWVkdmlldwogICAgIGlkPSJiYXNlIgogICAgIGJvcmRlcmNvbG9yPSIjNjY2NjY2IgogICAgIGlua3NjYXBlOnBhZ2VzaGFkb3c9IjIiCiAgICAgaW5rc2NhcGU6d2luZG93LXk9IjI4IgogICAgIHBhZ2Vjb2xvcj0iI2ZmZmZmZiIKICAgICBpbmtzY2FwZTp3aW5kb3ctaGVpZ2h0PSI3MTIiCiAgICAgaW5rc2NhcGU6d2luZG93LW1heGltaXplZD0iMSIKICAgICBpbmtzY2FwZTp6b29tPSIwLjciCiAgICAgaW5rc2NhcGU6d2luZG93LXg9IjAiCiAgICAgc2hvd2dyaWQ9ImZhbHNlIgogICAgIGJvcmRlcm9wYWNpdHk9IjEuMCIKICAgICBpbmtzY2FwZTpjdXJyZW50LWxheWVyPSJsYXllcjEiCiAgICAgaW5rc2NhcGU6Y3g9IjIyNS43MTQyOSIKICAgICBpbmtzY2FwZTpjeT0iMjYwIgogICAgIGlua3NjYXBlOndpbmRvdy13aWR0aD0iMTM2NiIKICAgICBpbmtzY2FwZTpwYWdlb3BhY2l0eT0iMC4wIgogICAgIGlua3NjYXBlOmRvY3VtZW50LXVuaXRzPSJweCIKICAgICBpbmtzY2FwZTpwYWdlY2hlY2tlcmJvYXJkPSIwIgogICAgIGZpdC1tYXJnaW4tdG9wPSI0IgogICAgIGZpdC1tYXJnaW4tbGVmdD0iNCIKICAgICBmaXQtbWFyZ2luLXJpZ2h0PSI0IgogICAgIGZpdC1tYXJnaW4tYm90dG9tPSI0IiAvPgogIDxnCiAgICAgaWQ9ImxheWVyMSIKICAgICBpbmtzY2FwZTpsYWJlbD0iTGF5ZXIgMSIKICAgICBpbmtzY2FwZTpncm91cG1vZGU9ImxheWVyIgogICAgIHRyYW5zZm9ybT0idHJhbnNsYXRlKC03MC4xNTI3LC0zMTMuOTM5KSI+CiAgICA8ZwogICAgICAgaWQ9ImcyMzkzIj4KICAgICAgPHBhdGgKICAgICAgICAgaWQ9InBhdGgzMjAyIgogICAgICAgICBkPSJtIDQwMC4yMiwzMjAuOTQgYyAtNDkuMDE4LDAgLTkwLjYzOSwzMS4xMjYgLTEwNS42Niw3NC40MDYgLTEyLjc4MiwtNS42MzQ3IC0yNy4wMSwtOC42ODc1IC00MS43MTksLTguNjg3NSAtNTIuODYyLDAgLTk1LjY4OCwzOC45OTcgLTk1LjY4OCw4Ny4xMjUgMCwwLjU4NDkyIDAuMDE4NywxLjE2NzggMC4wMzEyLDEuNzUgLTIuMzY4MiwtMC4yMTg2MiAtNC43NTgyLC0wLjMxMjUgLTcuMTg3NSwtMC4zMTI1IC00MC4yMzgsMCAtNzIuODQ0LDMwLjY5MiAtNzIuODQ0LDY4LjU2MiAwLDM3Ljg3MSAzMi42MDYsNjguNTk0IDcyLjg0NCw2OC41OTQgMjAuMTAzLDAgMzguMzE4LC03LjY3MTIgNTEuNSwtMjAuMDYyIDIwLjE2NCwxMy45NTkgNTEuMDQsMjIuOTA2IDg1LjY1NiwyMi45MDYgMzcuMzg0LDAgNzAuMzczLC0xMC40MjcgOTAuMjUsLTI2LjM0NCAxNC4wNTQsMjAuOTM4IDM5LjM5NCwzNC45MDYgNjguMzEyLDM0LjkwNiAzMi40ODQsMCA2MC40MDcsLTE3LjY0IDcyLjkzOCwtNDIuOTY5IDYxLjIxMywtMi42MTA5IDEwOS45MSwtNDYuMzI3IDEwOS45MSwtOTkuODc1IDAsLTU1LjIyOCAtNTEuODEyLC0xMDAgLTExNS43MiwtMTAwIC00LjM0OSwwIC04LjYxOTMsMC4yMjExMyAtMTIuODQ0LDAuNjI1IC0xOC4zNjQsLTM1Ljk1NiAtNTYuMTM5LC02MC42MjUgLTk5Ljc4MSwtNjAuNjI1IHoiCiAgICAgICAgIHN0eWxlPSJmaWxsOiNmZmZmZmY7ZmlsbC1ydWxlOmV2ZW5vZGQ7c3Ryb2tlOiMwMDAwMDA7c3Ryb2tlLXdpZHRoOjYiCiAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiIC8+CiAgICAgIDxwYXRoCiAgICAgICAgIGlkPSJwYXRoMjk5OSIKICAgICAgICAgc29kaXBvZGk6cng9Ijk1LjcxNDI4NyIKICAgICAgICAgc29kaXBvZGk6cnk9Ijg3LjE0Mjg2IgogICAgICAgICBzdHlsZT0iZmlsbDojZmZmZmZmO2ZpbGwtcnVsZTpldmVub2RkO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoxcHgiCiAgICAgICAgIHNvZGlwb2RpOnR5cGU9ImFyYyIKICAgICAgICAgc29kaXBvZGk6b3Blbj0idHJ1ZSIKICAgICAgICAgc29kaXBvZGk6Y3k9IjQ3My43OTIxOCIKICAgICAgICAgc29kaXBvZGk6Y3g9IjI1Mi44NTc0MiIKICAgICAgICAgc29kaXBvZGk6ZW5kPSI1Ljc4ODg3NyIKICAgICAgICAgc29kaXBvZGk6c3RhcnQ9IjIuOTMyMTI1IgogICAgICAgICBzb2RpcG9kaTphcmMtdHlwZT0iYXJjIgogICAgICAgICBkPSJtIDE1OS4yMzUyOCw0OTEuOTEyNTkgYSA5NS43MTQyODcsODcuMTQyODYgMCAwIDEgNjAuNjMyMjMsLTk5LjkyMzQ4IDk1LjcxNDI4Nyw4Ny4xNDI4NiAwIDAgMSAxMTcuMjQ2OTIsNDAuNDYwNSIgLz4KICAgICAgPHBhdGgKICAgICAgICAgaWQ9InBhdGgzMDAzIgogICAgICAgICBzb2RpcG9kaTpyeD0iMTEwIgogICAgICAgICBzb2RpcG9kaTpyeT0iNjEuNDI4NTciCiAgICAgICAgIHN0eWxlPSJmaWxsOiNmZmZmZmY7ZmlsbC1ydWxlOmV2ZW5vZGQ7c3Ryb2tlOiMwMDAwMDA7c3Ryb2tlLXdpZHRoOjFweCIKICAgICAgICAgc29kaXBvZGk6dHlwZT0iYXJjIgogICAgICAgICBzb2RpcG9kaTpvcGVuPSJ0cnVlIgogICAgICAgICBzb2RpcG9kaTpjeT0iNTUzLjc5MDc3IgogICAgICAgICBzb2RpcG9kaTpjeD0iMjg3LjE0Mjg1IgogICAgICAgICBzb2RpcG9kaTplbmQ9IjMuMDk1MzIyNiIKICAgICAgICAgc29kaXBvZGk6c3RhcnQ9IjAiCiAgICAgICAgIHNvZGlwb2RpOmFyYy10eXBlPSJhcmMiCiAgICAgICAgIGQ9Ik0gMzk3LjE0Mjg1LDU1My43OTA3NyBBIDExMCw2MS40Mjg1NyAwIDAgMSAyODkuNjg3NDgsNjE1LjIwMjkgMTEwLDYxLjQyODU3IDAgMCAxIDE3Ny4yNjA1OCw1NTYuNjMyMDYiIC8+CiAgICAgIDxwYXRoCiAgICAgICAgIGlkPSJwYXRoMjk5NyIKICAgICAgICAgc29kaXBvZGk6cng9IjcyLjg1NzE0IgogICAgICAgICBzb2RpcG9kaTpyeT0iNjguNTcxNDI2IgogICAgICAgICBzdHlsZT0iZmlsbDojZmZmZmZmO2ZpbGwtcnVsZTpldmVub2RkO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoxcHgiCiAgICAgICAgIHNvZGlwb2RpOnR5cGU9ImFyYyIKICAgICAgICAgc29kaXBvZGk6b3Blbj0idHJ1ZSIKICAgICAgICAgc29kaXBvZGk6Y3k9IjU0My43OTA3NyIKICAgICAgICAgc29kaXBvZGk6Y3g9IjE1MCIKICAgICAgICAgc29kaXBvZGk6ZW5kPSI1LjI3NTcwNTciCiAgICAgICAgIHNvZGlwb2RpOnN0YXJ0PSI2LjA4ODY2MiIKICAgICAgICAgc29kaXBvZGk6YXJjLXR5cGU9ImFyYyIKICAgICAgICAgZD0ibSAyMjEuNDgzMDUsNTMwLjUzNTk5IGEgNzIuODU3MTQsNjguNTcxNDI2IDAgMCAxIC00My4yNjEzNyw3Ni40NzI4NCA3Mi44NTcxNCw2OC41NzE0MjYgMCAwIDEgLTg4LjMxMjA0NCwtMjQuNDQzMDYgNzIuODU3MTQsNjguNTcxNDI2IDAgMCAxIDcuNjExMDcyLC04Ni4zNDAxNSA3Mi44NTcxNCw2OC41NzE0MjYgMCAwIDEgOTEuMzg0NTIyLC0xMC40MTEyMSIgLz4KICAgICAgPHBhdGgKICAgICAgICAgaWQ9InBhdGgzMDA1IgogICAgICAgICBzb2RpcG9kaTpyeD0iODAiCiAgICAgICAgIHNvZGlwb2RpOnJ5PSI3Mi44NTcxNCIKICAgICAgICAgc3R5bGU9ImZpbGw6I2ZmZmZmZjtmaWxsLXJ1bGU6ZXZlbm9kZDtzdHJva2U6IzAwMDAwMDtzdHJva2Utd2lkdGg6MXB4IgogICAgICAgICBzb2RpcG9kaTp0eXBlPSJhcmMiCiAgICAgICAgIHNvZGlwb2RpOm9wZW49InRydWUiCiAgICAgICAgIHNvZGlwb2RpOmN5PSI1NTAuOTMzNTkiCiAgICAgICAgIHNvZGlwb2RpOmN4PSI0NDUuNzE0MjkiCiAgICAgICAgIHNvZGlwb2RpOmVuZD0iNC4xNjQ4NjQ3IgogICAgICAgICBzb2RpcG9kaTpzdGFydD0iNS44Mzk2NDQ3IgogICAgICAgICBzb2RpcG9kaTphcmMtdHlwZT0iYXJjIgogICAgICAgICBkPSJNIDUxNy45NzMzMyw1MTkuNjY3NjcgQSA4MCw3Mi44NTcxNCAwIDAgMSA0ODIuNTI4OTcsNjE1LjYxNzkgODAsNzIuODU3MTQgMCAwIDEgMzc2LjAwNjgzLDU4Ni42ODI2OSA4MCw3Mi44NTcxNCAwIDAgMSA0MDQuMDY4MjksNDg4LjcyNzAxIiAvPgogICAgICA8cGF0aAogICAgICAgICBpZD0icGF0aDMwMDciCiAgICAgICAgIHNvZGlwb2RpOnJ4PSIxMTEuNjUzOTIiCiAgICAgICAgIHNvZGlwb2RpOnJ5PSIxMTAiCiAgICAgICAgIHN0eWxlPSJmaWxsOiNmZmZmZmY7ZmlsbC1ydWxlOmV2ZW5vZGQ7c3Ryb2tlOiMwMDAwMDA7c3Ryb2tlLXdpZHRoOjFweCIKICAgICAgICAgc29kaXBvZGk6dHlwZT0iYXJjIgogICAgICAgICBzb2RpcG9kaTpvcGVuPSJ0cnVlIgogICAgICAgICBzb2RpcG9kaTpjeT0iNDMwLjkzMzYyIgogICAgICAgICBzb2RpcG9kaTpjeD0iNDAwLjIyNjM1IgogICAgICAgICBzb2RpcG9kaTplbmQ9IjYuMTE0MDA2OSIKICAgICAgICAgc29kaXBvZGk6c3RhcnQ9IjIuNzgxNjI2OSIKICAgICAgICAgc29kaXBvZGk6YXJjLXR5cGU9ImFyYyIKICAgICAgICAgZD0ibSAyOTUuNzI4NDUsNDY5LjY4MDI2IGEgMTExLjY1MzkyLDExMCAwIDAgMSAyMi44NjQ5OCwtMTEzLjc5MzQ4IDExMS42NTM5MiwxMTAgMCAwIDEgMTEzLjY0ODgyLC0zMC4zMzQwMSAxMTEuNjUzOTIsMTEwIDAgMCAxIDc4LjA0Mzk5LDg2Ljg1OTg3IiAvPgogICAgICA8cGF0aAogICAgICAgICBpZD0icGF0aDMwMDkiCiAgICAgICAgIHNvZGlwb2RpOnJ4PSIxMTUuNzE0MjkiCiAgICAgICAgIHNvZGlwb2RpOnJ5PSIxMDAiCiAgICAgICAgIHN0eWxlPSJmaWxsOiNmZmZmZmY7ZmlsbC1ydWxlOmV2ZW5vZGQ7c3Ryb2tlOiMwMDAwMDA7c3Ryb2tlLXdpZHRoOjFweCIKICAgICAgICAgc29kaXBvZGk6dHlwZT0iYXJjIgogICAgICAgICBzb2RpcG9kaTpvcGVuPSJ0cnVlIgogICAgICAgICBzb2RpcG9kaTpjeT0iNDgwLjkzMzYyIgogICAgICAgICBzb2RpcG9kaTpjeD0iNTEyLjg1NzEyIgogICAgICAgICBzb2RpcG9kaTplbmQ9IjIuMTU1NzExMiIKICAgICAgICAgc29kaXBvZGk6c3RhcnQ9IjMuODQ1MTc1IgogICAgICAgICBzb2RpcG9kaTphcmMtdHlwZT0iYXJjIgogICAgICAgICBkPSJNIDQyNC42MjE1Niw0MTYuMjM4MjggQSAxMTUuNzE0MjksMTAwIDAgMCAxIDU4NC4xNzEsNDAyLjE4MTkxIDExNS43MTQyOSwxMDAgMCAwIDEgNjA2LjczMjk2LDUzOS40MDA0MiAxMTUuNzE0MjksMTAwIDAgMCAxIDQ0OC45Njc5Nyw1NjQuMzA5NTMiIC8+CiAgICA8L2c+CiAgPC9nPgogIDxtZXRhZGF0YQogICAgIGlkPSJtZXRhZGF0YTIzIj4KICAgIDxyZGY6UkRGPgogICAgICA8Y2M6V29yaz4KICAgICAgICA8ZGM6Zm9ybWF0PmltYWdlL3N2Zyt4bWw8L2RjOmZvcm1hdD4KICAgICAgICA8ZGM6dHlwZQogICAgICAgICAgIHJkZjpyZXNvdXJjZT0iaHR0cDovL3B1cmwub3JnL2RjL2RjbWl0eXBlL1N0aWxsSW1hZ2UiIC8+CiAgICAgICAgPGNjOmxpY2Vuc2UKICAgICAgICAgICByZGY6cmVzb3VyY2U9Imh0dHA6Ly9jcmVhdGl2ZWNvbW1vbnMub3JnL3B1YmxpY2RvbWFpbi96ZXJvLzEuMC8iIC8+CiAgICAgICAgPGRjOnB1Ymxpc2hlcj4KICAgICAgICAgIDxjYzpBZ2VudAogICAgICAgICAgICAgcmRmOmFib3V0PSJodHRwOi8vb3BlbmNsaXBhcnQub3JnLyI+CiAgICAgICAgICAgIDxkYzp0aXRsZT5PcGVuY2xpcGFydDwvZGM6dGl0bGU+CiAgICAgICAgICA8L2NjOkFnZW50PgogICAgICAgIDwvZGM6cHVibGlzaGVyPgogICAgICA8L2NjOldvcms+CiAgICAgIDxjYzpMaWNlbnNlCiAgICAgICAgIHJkZjphYm91dD0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbGljZW5zZXMvcHVibGljZG9tYWluLyI+CiAgICAgICAgPGNjOnBlcm1pdHMKICAgICAgICAgICByZGY6cmVzb3VyY2U9Imh0dHA6Ly9jcmVhdGl2ZWNvbW1vbnMub3JnL25zI1JlcHJvZHVjdGlvbiIgLz4KICAgICAgICA8Y2M6cGVybWl0cwogICAgICAgICAgIHJkZjpyZXNvdXJjZT0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjRGlzdHJpYnV0aW9uIiAvPgogICAgICAgIDxjYzpwZXJtaXRzCiAgICAgICAgICAgcmRmOnJlc291cmNlPSJodHRwOi8vY3JlYXRpdmVjb21tb25zLm9yZy9ucyNEZXJpdmF0aXZlV29ya3MiIC8+CiAgICAgIDwvY2M6TGljZW5zZT4KICAgIDwvcmRmOlJERj4KICA8L21ldGFkYXRhPgo8L3N2Zz4K' /> <div style='position:relative;z-index:100;color:black;padding:18px;'>Subnet <br /> 10.1.1.0/24 </div>");
        end
        M2["Machine 2"]:::wht;

        M1 --> CloudImgNode;
        CloudImgNode --> M2;
    end

%% Defining Class Styles
%% background-image:"" passes, but not with any text inside quotes
%% node that classes here get defined with "#graph-div " prepended!
%% however, seemingly <img> and such are included via foreignObject, so cannot refer to these classes 
classDef BorderClass fill:#fff,stroke:#fff,stroke-width:4px,color:#fff,stroke-dasharray: 5 5,margin:0,padding:0;
classDef ContainerClass position:relative,padding:0,margin:0,fill:#fff,color:#000,stroke:none;
classDef ImgNodeClass fill:#fff,padding:0,margin:0,stroke:none,margin:0;
classDef wht fill:#fff,color:black,stroke:#000;

%%classDef BgImgClass display:block,position:absolute,left:0,top:0,width:100%;
%%classDef TxtClass display:block,position:absolute,z-index:100,left:0,top:0;
%%classDef InvisibleClass display:none;

%% Custom Styles
%% NOTE: this gets applied to <rect class="basic label-container", 
%% not to the sigbling g<g class="label" or the enclosing <g id="flowchart-CloudImgNode-9174"
%% classdef node class; does however apply the class to the enclosing <g id="flowchart-CloudImgNode-9174" element
%%style CloudImgNode fill:#fff,padding:0,margin:0,stroke:none;
%%style CloudImgNode padding:100px,margin:100px;

%%<div class='TxtClass'>Subnet <br /> 10.1.1.0/24</div>
%% Assigning Nodes to Classes
class Border BorderClass;

%% Cannot add multiple classes separated with , (end up as single word)
%% or space (parser crashes)
%% but thankfully, multiple commands work: they do append classes
%%class DummyNode BgImgClass;
%%class DummyNode TxtClass;
%%class DummyNode InvisibleClass;

class CloudContainer ContainerClass;
%%class CloudImgNode BgImgClass;
%%class CloudImgNode TxtClass;

%% gets applied to all childer, too!
class CloudImgNode ImgNodeClass;

However, the PNG export from the Mermaid Live editor works - unfortunately, Github refuses to render the Markdown link from Mermaid Live editor works (probably because it is too long?):

... and Github even refuses to parse the standalone PNG link as an URL.

In any case, I'll just post the changed line, just to point out how less readable everything becomes with each approach:

CloudImgNode("<img style='position:absolute;left:0;top:7px;color:#fff;' src=' data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhLS0gQ3JlYXRlZCB3aXRoIElua3NjYXBlIChodHRwOi8vd3d3Lmlua3NjYXBlLm9yZy8pIC0tPgoKPHN2ZwogICBpZD0ic3ZnMiIKICAgc29kaXBvZGk6ZG9jbmFtZT0iY2xvdWQuc3ZnIgogICB2aWV3Qm94PSIwIDAgNTY1LjQwOTk1IDMxNi44NDM5OSIKICAgdmVyc2lvbj0iMS4xIgogICBpbmtzY2FwZTp2ZXJzaW9uPSIxLjEuMiAoMToxLjErMjAyMjAyMDUwODUyKzBhMDBjZjUzMzkpIgogICB3aWR0aD0iMTAwJSIKICAgaGVpZ2h0PSIxMDAlIgogICB4bWxuczppbmtzY2FwZT0iaHR0cDovL3d3dy5pbmtzY2FwZS5vcmcvbmFtZXNwYWNlcy9pbmtzY2FwZSIKICAgeG1sbnM6c29kaXBvZGk9Imh0dHA6Ly9zb2RpcG9kaS5zb3VyY2Vmb3JnZS5uZXQvRFREL3NvZGlwb2RpLTAuZHRkIgogICB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyIKICAgeG1sbnM6Y2M9Imh0dHA6Ly9jcmVhdGl2ZWNvbW1vbnMub3JnL25zIyIKICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIj4KICA8ZGVmcwogICAgIGlkPSJkZWZzNCI+CiAgICA8ZmlsdGVyCiAgICAgICBpZD0iZmlsdGVyMzMwMSIKICAgICAgIGlua3NjYXBlOm1lbnU9IkFCQ3MiCiAgICAgICBjb2xvci1pbnRlcnBvbGF0aW9uLWZpbHRlcnM9InNSR0IiCiAgICAgICB3aWR0aD0iMS4wODAxNTgxIgogICAgICAgeT0iLTAuMDcyOTc0ODY4IgogICAgICAgeD0iLTAuMDQwMDc5MDY4IgogICAgICAgaW5rc2NhcGU6bWVudS10b29sdGlwPSJCdWxnaW5nLCBtYXR0ZSBqZWxseSBjb3ZlcmluZyIKICAgICAgIGhlaWdodD0iMS4xNDU5NDk3IgogICAgICAgaW5rc2NhcGU6bGFiZWw9Ik1hdHRlIGplbGx5Ij4KICAgICAgPGZlR2F1c3NpYW5CbHVyCiAgICAgICAgIGlkPSJmZUdhdXNzaWFuQmx1cjMzMDMiCiAgICAgICAgIHJlc3VsdD0icmVzdWx0MCIKICAgICAgICAgc3RkRGV2aWF0aW9uPSIyIgogICAgICAgICBpbj0iU291cmNlQWxwaGEiIC8+CiAgICAgIDxmZUNvbXBvc2l0ZQogICAgICAgICBpZD0iZmVDb21wb3NpdGUzMzA1IgogICAgICAgICBrMz0iMTAiCiAgICAgICAgIGsyPSIxIgogICAgICAgICByZXN1bHQ9InJlc3VsdDMiCiAgICAgICAgIGluPSJTb3VyY2VHcmFwaGljIgogICAgICAgICBvcGVyYXRvcj0iYXJpdGhtZXRpYyIKICAgICAgICAgaW4yPSJyZXN1bHQwIgogICAgICAgICBrMT0iMCIKICAgICAgICAgazQ9IjAiIC8+CiAgICAgIDxmZUNvbXBvc2l0ZQogICAgICAgICBpZD0iZmVDb21wb3NpdGUzMzA3IgogICAgICAgICBpbjI9InJlc3VsdDMiCiAgICAgICAgIHJlc3VsdD0iZmJTb3VyY2VHcmFwaGljIgogICAgICAgICBpbj0icmVzdWx0MyIKICAgICAgICAgb3BlcmF0b3I9ImluIiAvPgogICAgICA8ZmVDb2xvck1hdHJpeAogICAgICAgICBpZD0iZmVDb2xvck1hdHJpeDM3MjIiCiAgICAgICAgIGluPSJmYlNvdXJjZUdyYXBoaWMiCiAgICAgICAgIHZhbHVlcz0iMSAwIDAgMCAwIDAgMSAwIDAgMCAwIDAgMSAwIDAgMCAwIDAgMC44NSAwIgogICAgICAgICByZXN1bHQ9InJlc3VsdDAiIC8+CiAgICAgIDxmZUdhdXNzaWFuQmx1cgogICAgICAgICBpZD0iZmVHYXVzc2lhbkJsdXIzNzI0IgogICAgICAgICBzdGREZXZpYXRpb249IjciCiAgICAgICAgIGluPSJmYlNvdXJjZUdyYXBoaWNBbHBoYSIgLz4KICAgICAgPGZlU3BlY3VsYXJMaWdodGluZwogICAgICAgICBpZD0iZmVTcGVjdWxhckxpZ2h0aW5nMzcyNiIKICAgICAgICAgcmVzdWx0PSJyZXN1bHQ0IgogICAgICAgICBzcGVjdWxhckNvbnN0YW50PSIwLjg5OTk5OTk4IgogICAgICAgICBzcGVjdWxhckV4cG9uZW50PSIyNSIKICAgICAgICAgc3VyZmFjZVNjYWxlPSI1Ij4KICAgICAgICA8ZmVEaXN0YW50TGlnaHQKICAgICAgICAgICBpZD0iZmVEaXN0YW50TGlnaHQzNzI4IgogICAgICAgICAgIGVsZXZhdGlvbj0iNjAiCiAgICAgICAgICAgYXppbXV0aD0iMjI1IiAvPgogICAgICA8L2ZlU3BlY3VsYXJMaWdodGluZz4KICAgICAgPGZlQ29tcG9zaXRlCiAgICAgICAgIGlkPSJmZUNvbXBvc2l0ZTM3MzAiCiAgICAgICAgIG9wZXJhdG9yPSJhdG9wIgogICAgICAgICBpbjI9InJlc3VsdDAiIC8+CiAgICA8L2ZpbHRlcj4KICA8L2RlZnM+CiAgPHNvZGlwb2RpOm5hbWVkdmlldwogICAgIGlkPSJiYXNlIgogICAgIGJvcmRlcmNvbG9yPSIjNjY2NjY2IgogICAgIGlua3NjYXBlOnBhZ2VzaGFkb3c9IjIiCiAgICAgaW5rc2NhcGU6d2luZG93LXk9IjI4IgogICAgIHBhZ2Vjb2xvcj0iI2ZmZmZmZiIKICAgICBpbmtzY2FwZTp3aW5kb3ctaGVpZ2h0PSI3MTIiCiAgICAgaW5rc2NhcGU6d2luZG93LW1heGltaXplZD0iMSIKICAgICBpbmtzY2FwZTp6b29tPSIwLjciCiAgICAgaW5rc2NhcGU6d2luZG93LXg9IjAiCiAgICAgc2hvd2dyaWQ9ImZhbHNlIgogICAgIGJvcmRlcm9wYWNpdHk9IjEuMCIKICAgICBpbmtzY2FwZTpjdXJyZW50LWxheWVyPSJsYXllcjEiCiAgICAgaW5rc2NhcGU6Y3g9IjIyNS43MTQyOSIKICAgICBpbmtzY2FwZTpjeT0iMjYwIgogICAgIGlua3NjYXBlOndpbmRvdy13aWR0aD0iMTM2NiIKICAgICBpbmtzY2FwZTpwYWdlb3BhY2l0eT0iMC4wIgogICAgIGlua3NjYXBlOmRvY3VtZW50LXVuaXRzPSJweCIKICAgICBpbmtzY2FwZTpwYWdlY2hlY2tlcmJvYXJkPSIwIgogICAgIGZpdC1tYXJnaW4tdG9wPSI0IgogICAgIGZpdC1tYXJnaW4tbGVmdD0iNCIKICAgICBmaXQtbWFyZ2luLXJpZ2h0PSI0IgogICAgIGZpdC1tYXJnaW4tYm90dG9tPSI0IiAvPgogIDxnCiAgICAgaWQ9ImxheWVyMSIKICAgICBpbmtzY2FwZTpsYWJlbD0iTGF5ZXIgMSIKICAgICBpbmtzY2FwZTpncm91cG1vZGU9ImxheWVyIgogICAgIHRyYW5zZm9ybT0idHJhbnNsYXRlKC03MC4xNTI3LC0zMTMuOTM5KSI+CiAgICA8ZwogICAgICAgaWQ9ImcyMzkzIj4KICAgICAgPHBhdGgKICAgICAgICAgaWQ9InBhdGgzMjAyIgogICAgICAgICBkPSJtIDQwMC4yMiwzMjAuOTQgYyAtNDkuMDE4LDAgLTkwLjYzOSwzMS4xMjYgLTEwNS42Niw3NC40MDYgLTEyLjc4MiwtNS42MzQ3IC0yNy4wMSwtOC42ODc1IC00MS43MTksLTguNjg3NSAtNTIuODYyLDAgLTk1LjY4OCwzOC45OTcgLTk1LjY4OCw4Ny4xMjUgMCwwLjU4NDkyIDAuMDE4NywxLjE2NzggMC4wMzEyLDEuNzUgLTIuMzY4MiwtMC4yMTg2MiAtNC43NTgyLC0wLjMxMjUgLTcuMTg3NSwtMC4zMTI1IC00MC4yMzgsMCAtNzIuODQ0LDMwLjY5MiAtNzIuODQ0LDY4LjU2MiAwLDM3Ljg3MSAzMi42MDYsNjguNTk0IDcyLjg0NCw2OC41OTQgMjAuMTAzLDAgMzguMzE4LC03LjY3MTIgNTEuNSwtMjAuMDYyIDIwLjE2NCwxMy45NTkgNTEuMDQsMjIuOTA2IDg1LjY1NiwyMi45MDYgMzcuMzg0LDAgNzAuMzczLC0xMC40MjcgOTAuMjUsLTI2LjM0NCAxNC4wNTQsMjAuOTM4IDM5LjM5NCwzNC45MDYgNjguMzEyLDM0LjkwNiAzMi40ODQsMCA2MC40MDcsLTE3LjY0IDcyLjkzOCwtNDIuOTY5IDYxLjIxMywtMi42MTA5IDEwOS45MSwtNDYuMzI3IDEwOS45MSwtOTkuODc1IDAsLTU1LjIyOCAtNTEuODEyLC0xMDAgLTExNS43MiwtMTAwIC00LjM0OSwwIC04LjYxOTMsMC4yMjExMyAtMTIuODQ0LDAuNjI1IC0xOC4zNjQsLTM1Ljk1NiAtNTYuMTM5LC02MC42MjUgLTk5Ljc4MSwtNjAuNjI1IHoiCiAgICAgICAgIHN0eWxlPSJmaWxsOiNmZmZmZmY7ZmlsbC1ydWxlOmV2ZW5vZGQ7c3Ryb2tlOiMwMDAwMDA7c3Ryb2tlLXdpZHRoOjYiCiAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiIC8+CiAgICAgIDxwYXRoCiAgICAgICAgIGlkPSJwYXRoMjk5OSIKICAgICAgICAgc29kaXBvZGk6cng9Ijk1LjcxNDI4NyIKICAgICAgICAgc29kaXBvZGk6cnk9Ijg3LjE0Mjg2IgogICAgICAgICBzdHlsZT0iZmlsbDojZmZmZmZmO2ZpbGwtcnVsZTpldmVub2RkO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoxcHgiCiAgICAgICAgIHNvZGlwb2RpOnR5cGU9ImFyYyIKICAgICAgICAgc29kaXBvZGk6b3Blbj0idHJ1ZSIKICAgICAgICAgc29kaXBvZGk6Y3k9IjQ3My43OTIxOCIKICAgICAgICAgc29kaXBvZGk6Y3g9IjI1Mi44NTc0MiIKICAgICAgICAgc29kaXBvZGk6ZW5kPSI1Ljc4ODg3NyIKICAgICAgICAgc29kaXBvZGk6c3RhcnQ9IjIuOTMyMTI1IgogICAgICAgICBzb2RpcG9kaTphcmMtdHlwZT0iYXJjIgogICAgICAgICBkPSJtIDE1OS4yMzUyOCw0OTEuOTEyNTkgYSA5NS43MTQyODcsODcuMTQyODYgMCAwIDEgNjAuNjMyMjMsLTk5LjkyMzQ4IDk1LjcxNDI4Nyw4Ny4xNDI4NiAwIDAgMSAxMTcuMjQ2OTIsNDAuNDYwNSIgLz4KICAgICAgPHBhdGgKICAgICAgICAgaWQ9InBhdGgzMDAzIgogICAgICAgICBzb2RpcG9kaTpyeD0iMTEwIgogICAgICAgICBzb2RpcG9kaTpyeT0iNjEuNDI4NTciCiAgICAgICAgIHN0eWxlPSJmaWxsOiNmZmZmZmY7ZmlsbC1ydWxlOmV2ZW5vZGQ7c3Ryb2tlOiMwMDAwMDA7c3Ryb2tlLXdpZHRoOjFweCIKICAgICAgICAgc29kaXBvZGk6dHlwZT0iYXJjIgogICAgICAgICBzb2RpcG9kaTpvcGVuPSJ0cnVlIgogICAgICAgICBzb2RpcG9kaTpjeT0iNTUzLjc5MDc3IgogICAgICAgICBzb2RpcG9kaTpjeD0iMjg3LjE0Mjg1IgogICAgICAgICBzb2RpcG9kaTplbmQ9IjMuMDk1MzIyNiIKICAgICAgICAgc29kaXBvZGk6c3RhcnQ9IjAiCiAgICAgICAgIHNvZGlwb2RpOmFyYy10eXBlPSJhcmMiCiAgICAgICAgIGQ9Ik0gMzk3LjE0Mjg1LDU1My43OTA3NyBBIDExMCw2MS40Mjg1NyAwIDAgMSAyODkuNjg3NDgsNjE1LjIwMjkgMTEwLDYxLjQyODU3IDAgMCAxIDE3Ny4yNjA1OCw1NTYuNjMyMDYiIC8+CiAgICAgIDxwYXRoCiAgICAgICAgIGlkPSJwYXRoMjk5NyIKICAgICAgICAgc29kaXBvZGk6cng9IjcyLjg1NzE0IgogICAgICAgICBzb2RpcG9kaTpyeT0iNjguNTcxNDI2IgogICAgICAgICBzdHlsZT0iZmlsbDojZmZmZmZmO2ZpbGwtcnVsZTpldmVub2RkO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoxcHgiCiAgICAgICAgIHNvZGlwb2RpOnR5cGU9ImFyYyIKICAgICAgICAgc29kaXBvZGk6b3Blbj0idHJ1ZSIKICAgICAgICAgc29kaXBvZGk6Y3k9IjU0My43OTA3NyIKICAgICAgICAgc29kaXBvZGk6Y3g9IjE1MCIKICAgICAgICAgc29kaXBvZGk6ZW5kPSI1LjI3NTcwNTciCiAgICAgICAgIHNvZGlwb2RpOnN0YXJ0PSI2LjA4ODY2MiIKICAgICAgICAgc29kaXBvZGk6YXJjLXR5cGU9ImFyYyIKICAgICAgICAgZD0ibSAyMjEuNDgzMDUsNTMwLjUzNTk5IGEgNzIuODU3MTQsNjguNTcxNDI2IDAgMCAxIC00My4yNjEzNyw3Ni40NzI4NCA3Mi44NTcxNCw2OC41NzE0MjYgMCAwIDEgLTg4LjMxMjA0NCwtMjQuNDQzMDYgNzIuODU3MTQsNjguNTcxNDI2IDAgMCAxIDcuNjExMDcyLC04Ni4zNDAxNSA3Mi44NTcxNCw2OC41NzE0MjYgMCAwIDEgOTEuMzg0NTIyLC0xMC40MTEyMSIgLz4KICAgICAgPHBhdGgKICAgICAgICAgaWQ9InBhdGgzMDA1IgogICAgICAgICBzb2RpcG9kaTpyeD0iODAiCiAgICAgICAgIHNvZGlwb2RpOnJ5PSI3Mi44NTcxNCIKICAgICAgICAgc3R5bGU9ImZpbGw6I2ZmZmZmZjtmaWxsLXJ1bGU6ZXZlbm9kZDtzdHJva2U6IzAwMDAwMDtzdHJva2Utd2lkdGg6MXB4IgogICAgICAgICBzb2RpcG9kaTp0eXBlPSJhcmMiCiAgICAgICAgIHNvZGlwb2RpOm9wZW49InRydWUiCiAgICAgICAgIHNvZGlwb2RpOmN5PSI1NTAuOTMzNTkiCiAgICAgICAgIHNvZGlwb2RpOmN4PSI0NDUuNzE0MjkiCiAgICAgICAgIHNvZGlwb2RpOmVuZD0iNC4xNjQ4NjQ3IgogICAgICAgICBzb2RpcG9kaTpzdGFydD0iNS44Mzk2NDQ3IgogICAgICAgICBzb2RpcG9kaTphcmMtdHlwZT0iYXJjIgogICAgICAgICBkPSJNIDUxNy45NzMzMyw1MTkuNjY3NjcgQSA4MCw3Mi44NTcxNCAwIDAgMSA0ODIuNTI4OTcsNjE1LjYxNzkgODAsNzIuODU3MTQgMCAwIDEgMzc2LjAwNjgzLDU4Ni42ODI2OSA4MCw3Mi44NTcxNCAwIDAgMSA0MDQuMDY4MjksNDg4LjcyNzAxIiAvPgogICAgICA8cGF0aAogICAgICAgICBpZD0icGF0aDMwMDciCiAgICAgICAgIHNvZGlwb2RpOnJ4PSIxMTEuNjUzOTIiCiAgICAgICAgIHNvZGlwb2RpOnJ5PSIxMTAiCiAgICAgICAgIHN0eWxlPSJmaWxsOiNmZmZmZmY7ZmlsbC1ydWxlOmV2ZW5vZGQ7c3Ryb2tlOiMwMDAwMDA7c3Ryb2tlLXdpZHRoOjFweCIKICAgICAgICAgc29kaXBvZGk6dHlwZT0iYXJjIgogICAgICAgICBzb2RpcG9kaTpvcGVuPSJ0cnVlIgogICAgICAgICBzb2RpcG9kaTpjeT0iNDMwLjkzMzYyIgogICAgICAgICBzb2RpcG9kaTpjeD0iNDAwLjIyNjM1IgogICAgICAgICBzb2RpcG9kaTplbmQ9IjYuMTE0MDA2OSIKICAgICAgICAgc29kaXBvZGk6c3RhcnQ9IjIuNzgxNjI2OSIKICAgICAgICAgc29kaXBvZGk6YXJjLXR5cGU9ImFyYyIKICAgICAgICAgZD0ibSAyOTUuNzI4NDUsNDY5LjY4MDI2IGEgMTExLjY1MzkyLDExMCAwIDAgMSAyMi44NjQ5OCwtMTEzLjc5MzQ4IDExMS42NTM5MiwxMTAgMCAwIDEgMTEzLjY0ODgyLC0zMC4zMzQwMSAxMTEuNjUzOTIsMTEwIDAgMCAxIDc4LjA0Mzk5LDg2Ljg1OTg3IiAvPgogICAgICA8cGF0aAogICAgICAgICBpZD0icGF0aDMwMDkiCiAgICAgICAgIHNvZGlwb2RpOnJ4PSIxMTUuNzE0MjkiCiAgICAgICAgIHNvZGlwb2RpOnJ5PSIxMDAiCiAgICAgICAgIHN0eWxlPSJmaWxsOiNmZmZmZmY7ZmlsbC1ydWxlOmV2ZW5vZGQ7c3Ryb2tlOiMwMDAwMDA7c3Ryb2tlLXdpZHRoOjFweCIKICAgICAgICAgc29kaXBvZGk6dHlwZT0iYXJjIgogICAgICAgICBzb2RpcG9kaTpvcGVuPSJ0cnVlIgogICAgICAgICBzb2RpcG9kaTpjeT0iNDgwLjkzMzYyIgogICAgICAgICBzb2RpcG9kaTpjeD0iNTEyLjg1NzEyIgogICAgICAgICBzb2RpcG9kaTplbmQ9IjIuMTU1NzExMiIKICAgICAgICAgc29kaXBvZGk6c3RhcnQ9IjMuODQ1MTc1IgogICAgICAgICBzb2RpcG9kaTphcmMtdHlwZT0iYXJjIgogICAgICAgICBkPSJNIDQyNC42MjE1Niw0MTYuMjM4MjggQSAxMTUuNzE0MjksMTAwIDAgMCAxIDU4NC4xNzEsNDAyLjE4MTkxIDExNS43MTQyOSwxMDAgMCAwIDEgNjA2LjczMjk2LDUzOS40MDA0MiAxMTUuNzE0MjksMTAwIDAgMCAxIDQ0OC45Njc5Nyw1NjQuMzA5NTMiIC8+CiAgICA8L2c+CiAgPC9nPgogIDxtZXRhZGF0YQogICAgIGlkPSJtZXRhZGF0YTIzIj4KICAgIDxyZGY6UkRGPgogICAgICA8Y2M6V29yaz4KICAgICAgICA8ZGM6Zm9ybWF0PmltYWdlL3N2Zyt4bWw8L2RjOmZvcm1hdD4KICAgICAgICA8ZGM6dHlwZQogICAgICAgICAgIHJkZjpyZXNvdXJjZT0iaHR0cDovL3B1cmwub3JnL2RjL2RjbWl0eXBlL1N0aWxsSW1hZ2UiIC8+CiAgICAgICAgPGNjOmxpY2Vuc2UKICAgICAgICAgICByZGY6cmVzb3VyY2U9Imh0dHA6Ly9jcmVhdGl2ZWNvbW1vbnMub3JnL3B1YmxpY2RvbWFpbi96ZXJvLzEuMC8iIC8+CiAgICAgICAgPGRjOnB1Ymxpc2hlcj4KICAgICAgICAgIDxjYzpBZ2VudAogICAgICAgICAgICAgcmRmOmFib3V0PSJodHRwOi8vb3BlbmNsaXBhcnQub3JnLyI+CiAgICAgICAgICAgIDxkYzp0aXRsZT5PcGVuY2xpcGFydDwvZGM6dGl0bGU+CiAgICAgICAgICA8L2NjOkFnZW50PgogICAgICAgIDwvZGM6cHVibGlzaGVyPgogICAgICA8L2NjOldvcms+CiAgICAgIDxjYzpMaWNlbnNlCiAgICAgICAgIHJkZjphYm91dD0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbGljZW5zZXMvcHVibGljZG9tYWluLyI+CiAgICAgICAgPGNjOnBlcm1pdHMKICAgICAgICAgICByZGY6cmVzb3VyY2U9Imh0dHA6Ly9jcmVhdGl2ZWNvbW1vbnMub3JnL25zI1JlcHJvZHVjdGlvbiIgLz4KICAgICAgICA8Y2M6cGVybWl0cwogICAgICAgICAgIHJkZjpyZXNvdXJjZT0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjRGlzdHJpYnV0aW9uIiAvPgogICAgICAgIDxjYzpwZXJtaXRzCiAgICAgICAgICAgcmRmOnJlc291cmNlPSJodHRwOi8vY3JlYXRpdmVjb21tb25zLm9yZy9ucyNEZXJpdmF0aXZlV29ya3MiIC8+CiAgICAgIDwvY2M6TGljZW5zZT4KICAgIDwvcmRmOlJERj4KICA8L21ldGFkYXRhPgo8L3N2Zz4K' /> <div style='position:relative;z-index:100;color:black;padding:18px;'>Subnet <br /> 10.1.1.0/24 </div>");

sdbbs commented 1 year ago

Another rework of the previous example, this time using FontAwesome - not even this renders in Github mermaid diagram:

%%{init: {'theme':'base'}}%%
flowchart LR

    subgraph Border
        direction LR

        M1["Machine 1"]:::wht;
        subgraph CloudContainer[ ]
            direction LR
            %% text with <br> does not render with position:absolute
            %% use img top and div padding to center text in image; 
            %% color:#c8c8c8 seems not to work here - must escape the # as #35;
            CloudImgNode("<div style='position:absolute;left:8px;top:-20px;color:#35;bbb;font-size:80px;'>fa:fa-cloud</div> <div style='position:relative;z-index:100;color:black;padding:18px;'>Subnet <br /> 10.1.1.0/24 </div>");
        end
        M2["Machine 2"]:::wht;

        M1 --> CloudImgNode;
        CloudImgNode --> M2;
    end

%% Defining Class Styles
%% background-image:"" passes, but not with any text inside quotes
%% node that classes here get defined with "#graph-div " prepended!
%% however, seemingly <img> and such are included via foreignObject, so cannot refer to these classes 
classDef BorderClass fill:#fff,stroke:#fff,stroke-width:4px,color:#fff,stroke-dasharray: 5 5,margin:0,padding:0;
classDef ContainerClass position:relative,padding:0,margin:0,fill:#fff,color:#000,stroke:none;
classDef ImgNodeClass fill:#fff,padding:0,margin:0,stroke:none,margin:0;
classDef wht fill:#fff,color:black,stroke:#000;

%% Custom Styles

%% Assigning Nodes to Classes
%% class gets applied to all childer, too!
class Border BorderClass;
class CloudContainer ContainerClass;
class CloudImgNode ImgNodeClass;

At least PNG direct markdown link from Mermaid Live editor works - unfortunately, the alignment of the icon you see in the Live editor and the png below will not be the same (the below is tuned for PNG output)

... and at last I tried with the Unicode symbol for cloud - and this seems to work also in Github mermaid diagram:

%%{init: {'theme':'base'}}%%
flowchart LR

    subgraph Border
        direction LR

        M1["Machine 1"]:::wht;
        subgraph CloudContainer[ ]
            direction LR
            %% text with <br> does not render with position:absolute
            %% use img top and div padding to center text in image; color:#c8c8c8 seems not to work?
            CloudImgNode("<div style='position:absolute;left:12px;top:-10px;color:#35;bbb;font-size:80px;'>#9729;#65039;</div> <div style='position:relative;z-index:100;color:black;padding:18px;'>Subnet <br /> 10.1.1.0/24 </div>");
        end
        M2["Machine 2"]:::wht;

        M1 --> CloudImgNode;
        CloudImgNode --> M2;
    end

%% Defining Class Styles
%% background-image:"" passes, but not with any text inside quotes
%% node that classes here get defined with "#graph-div " prepended!
%% however, seemingly <img> and such are included via foreignObject, so cannot refer to these classes 
classDef BorderClass fill:#fff,stroke:#fff,stroke-width:4px,color:#fff,stroke-dasharray: 5 5,margin:0,padding:0;
classDef ContainerClass position:relative,padding:0,margin:0,fill:#fff,color:#000,stroke:none;
classDef ImgNodeClass fill:#fff,padding:0,margin:0,stroke:none,margin:0;
classDef wht fill:#fff,color:black,stroke:#000;

%% Custom Styles

%% Assigning Nodes to Classes
%% class gets applied to all childer, too!
class Border BorderClass;
class CloudContainer ContainerClass;
class CloudImgNode ImgNodeClass;
jgreywolf commented 1 year ago

@sdbbs Would you like to officially take this issue on? :)

jgreywolf commented 1 year ago

More information available in #1250

nair-sumesh commented 1 year ago

Shape for actors

rickdgray-dc commented 1 year ago

I think svg support is definitely the route to go. Simple ones are extremely compact compared to rendered images so they can easily be embedded into the markdown. I am wanting a simple cloud shape as well but for now will have to just use a box.

samjco commented 6 months ago

So it seems that the shapes are first created as points and added as functions.

For Example, (flowchart shapes) notice the function found at: https://github.com/mermaid-js/mermaid/blob/develop/packages/mermaid/src/diagrams/flowchart/flowChartShapes.js:

To Make a Trapezoid, notice the function on Line 120:

function trapezoid(parent, bbox, node) {
  const w = bbox.width;
  const h = bbox.height;
  const points = [
    { x: (-2 * h) / 6, y: 0 },
    { x: w + (2 * h) / 6, y: 0 },
    { x: w - h / 6, y: -h },
    { x: h / 6, y: -h },
  ];
  const shapeSvg = insertPolygonShape(parent, w, h, points);
  node.intersect = function (point) {
    return intersectPolygon(node, points, point);
  };
  return shapeSvg;
}

So make inorder to make new shape, there would need to be a need a way to convert a SVG code into js points.

Example SVG:

<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">
    <polygon points="10,10 40,10 30,40" fill="none" stroke="black" />
    <polyline points="50,10 70,10 70,30 50,30" fill="none" stroke="blue" />
</svg>

Python script (untested):

import xml.etree.ElementTree as ET

def parse_svg(svg_path):
    """Parse the SVG file and convert its shapes into JavaScript code."""
    # Parse the SVG file
    tree = ET.parse(svg_path)
    root = tree.getroot()

    for element in root.iter():
        if element.tag.endswith('polygon'):
            # Parse the points attribute into a list of coordinate tuples
            points = [(float(x), float(y)) for x, y in (p.split(',') for p in element.get('points').split())]

            # Get the bounding box width and height
            w = max(p[0] for p in points) - min(p[0] for p in points)
            h = max(p[1] for p in points) - min(p[1] for p in points)

            # Convert points into JavaScript object literals
            js_points = [
                f"{{ x: {p[0]}, y: {p[1]} }}"
                for p in points
            ]

            # Create a JavaScript code snippet
            js_code = f"const points = [{', '.join(js_points)}];"
            print(f"JavaScript Code:\n{js_code}\n")

def parse_svg_file(svg_path):
    """Parse an SVG file and print JavaScript code for each shape."""
    parse_svg(svg_path)

# Usage
parse_svg_file('example.svg')

Example output:

const points = [{ x: 10, y: 10 }, { x: 40, y: 10 }, { x: 30, y: 40 }];

So out NEW function may look like this:

function myNewShape(parent, bbox, node) {
  const w = bbox.width;
  const h = bbox.height;
  const points = [
{ x: 10, y: 10 }, 
{ x: 40, y: 10 }, 
{ x: 30, y: 40 }
  ];
  const shapeSvg = insertPolygonShape(parent, w, h, points);
  node.intersect = function (point) {
    return intersectPolygon(node, points, point);
  };
  return shapeSvg;
}

Please let me know if this works??

samjco commented 6 months ago

example.svg:

<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">
    <polygon points="10,10 40,10 30,40" fill="none" stroke="black" />
    <polyline points="50,10 70,10 70,30 50,30" fill="none" stroke="blue" />
</svg>

Other script versions:

Using PHP

<?php
function parse_svg($svg_path) {
    $xml = simplexml_load_file($svg_path);

    foreach ($xml->children() as $element) {
        $tag = strtolower($element->getName());

        if ($tag === "polygon") {
            $points = (string) $element['points'];
            $point_pairs = explode(" ", $points);

            $js_points = array_map(function ($pair) {
                list($x, $y) = explode(",", $pair);
                return "{ x: " . floatval($x) . ", y: " . floatval($y) . " }";
            }, $point_pairs);

            $js_code_content = implode(", ", $js_points);

            echo "JavaScript Array Content:\n" . $js_code_content . "\n\n";
        }
        // Additional logic for other shape types can be added here
    }
}

parse_svg('example.svg');
?>

Script Functionality:

SimpleXML: The script uses PHP's simplexml_load_file function to parse the SVG file. Polygon Points: Points are parsed from the points attribute, split into pairs, and converted into JavaScript object literals. JavaScript Snippet: These literals are concatenated into a string representing the contents of the points array. Output: The script prints this string to the console.

OR Using JS

const fs = require('fs');
const { DOMParser } = require('xmldom');

function parseSVG(svgPath) {
    // Read the SVG file as a string
    const svgContent = fs.readFileSync(svgPath, 'utf8');

    // Parse the SVG content into a DOM tree
    const doc = new DOMParser().parseFromString(svgContent, 'text/xml');

    const polygons = doc.getElementsByTagName('polygon');

    for (let i = 0; i < polygons.length; i++) {
        const points = polygons[i].getAttribute('points');
        const pointPairs = points.split(' ');

        const jsPoints = pointPairs.map(pair => {
            const [x, y] = pair.split(',');
            return `{ x: ${parseFloat(x)}, y: ${parseFloat(y)} }`;
        });

        const jsCodeContent = jsPoints.join(', ');

        console.log(`JavaScript Array Content:\n${jsCodeContent}\n`);
    }
}

// Usage
parseSVG('example.svg');

Script Functionality:

FS Module: The script uses Node.js's fs module to read the SVG file as a string. DOMParser: It uses the DOMParser class to convert the SVG content into a DOM tree. Polygon Points: Points are extracted from the points attribute, split into pairs, and converted into JavaScript object literals. JavaScript Snippet: The literals are concatenated into a string representing the array contents. Output: The script prints this string to the console.

Output: { x: 10, y: 10 }, { x: 40, y: 10 }, { x: 30, y: 40 }

Conclusion: Both scripts (PHP and JavaScript) parse the SVG file, extract its coordinates, and output them in a format suitable for JavaScript arrays. You can adapt and extend these scripts to handle various shapes or integrate them directly into your workflows.

samjco commented 6 months ago

Ah Fontawesome support is added. Now, how can we make custom FA icons https://fontawesome.com/search?o=r&f=brands