Angular 10: Using an SVG as a node shape

Hello,

I am trying to define a new shape for my force directed diagram using the “go.Shape.defineFigureGenerator” referenced in the following link.

Custom Figures

However, this example does not cover defining an SVG object using a path string rather it shows how to encode the path string using the following code block:

geo.add(new go.PathFigure(0, 0)
             .add(new go.PathSegment(go.PathSegment.Line, w, 0))
             .add(new go.PathSegment(go.PathSegment.Line, w, h - p1))
             .add(new go.PathSegment(go.PathSegment.Arc, 0, 90, w - p1, h - p1, p1, p1))
             .add(new go.PathSegment(go.PathSegment.Line, p1, h))
             .add(new go.PathSegment(go.PathSegment.Arc, 90, 90, p1, h - p1, p1, p1).close()));

Does GoJS support using just the path string to define the SVG object and if so how would I accomplish this?

Yes. For example: https://gojs.net/latest/samples/icons.html
Or: https://gojs.net/latest/samples/tiger.html

Also, if you have an SVG file you want to display, please read: https://gojs.net/latest/intro/pictures.html#UsingSVGAsPictureSource

Ok, so using the icons sample I got this far

image

Now what im stuck on is making the shape of the Node the raindrop SVG rather than just an icon on the node. How would I accomplish this?

What is the SVG for that figure?

I used the droplet SVG from the icons.js provided in the Icon demo.

"droplet":
                "M27.998 19.797c-0-0.022-0.001-0.044-0.001-0.066-0.001-0.045-0.002-0.089-0.004-0.134-0.313-9.864-11.993-19.597-11.993-19.597s-11.68 9.733-11.993 19.598c-0.001 0.045-0.003 0.089-0.004 0.134-0 0.022-0.001 0.044-0.001 0.066-0.001 0.067-0.002 0.135-0.002 0.203 0 0.074 0.001 0.148 0.002 0.221 0 0.006 0 0.012 0 0.018 0.127 6.517 5.45 11.76 11.997 11.76s11.87-5.244 11.997-11.761c0-0.006 0-0.012 0-0.018 0.001-0.074 0.002-0.147 0.002-0.221 0-0.068-0.001-0.136-0.002-0.203zM23.998 20.148l-0 0.013c-0.041 2.103-0.892 4.073-2.395 5.548-1.504 1.477-3.495 2.291-5.604 2.291-0.389 0-0.775-0.028-1.154-0.082 4.346-2.589 7.257-7.335 7.257-12.76 0-0.608-0.037-1.207-0.108-1.796 1.259 2.311 1.939 4.462 2 6.363l0 0.005c0.001 0.030 0.002 0.059 0.002 0.089l0.001 0.045c0.001 0.046 0.002 0.091 0.002 0.137 0 0.049-0.001 0.099-0.002 0.148z",

    myDiagram.nodeTemplate =
      $(go.Node, "Spot",
        $(go.Shape,
          { fill: "white", portId: "",
            geometryString: "F1 M27.998 19.797c-0-0.022-0.001-0.044-0.001-0.066-0.001-0.045-0.002-0.089-0.004-0.134-0.313-9.864-11.993-19.597-11.993-19.597s-11.68 9.733-11.993 19.598c-0.001 0.045-0.003 0.089-0.004 0.134-0 0.022-0.001 0.044-0.001 0.066-0.001 0.067-0.002 0.135-0.002 0.203 0 0.074 0.001 0.148 0.002 0.221 0 0.006 0 0.012 0 0.018 0.127 6.517 5.45 11.76 11.997 11.76s11.87-5.244 11.997-11.761c0-0.006 0-0.012 0-0.018 0.001-0.074 0.002-0.147 0.002-0.221 0-0.068-0.001-0.136-0.002-0.203zM23.998 20.148l-0 0.013c-0.041 2.103-0.892 4.073-2.395 5.548-1.504 1.477-3.495 2.291-5.604 2.291-0.389 0-0.775-0.028-1.154-0.082 4.346-2.589 7.257-7.335 7.257-12.76 0-0.608-0.037-1.207-0.108-1.796 1.259 2.311 1.939 4.462 2 6.363l0 0.005c0.001 0.030 0.002 0.059 0.002 0.089l0.001 0.045c0.001 0.046 0.002 0.091 0.002 0.137 0 0.049-0.001 0.099-0.002 0.148z",
            width: 50, height: 70
          },
          new go.Binding("fill", "color")),
        $(go.TextBlock,
          new go.Binding("text"))
      );

produces:
image

Ok, I think I understand, when I implemented it using your method I was able to replicate what you have in the image below.

However, when I moved the geometry string to the node attribute using a binding I did encounter some further issues.

 new go.Binding("location", "loc", go.Point.parse),
                $(go.Panel, 'Auto',
                    $(go.Shape, { stroke: null, fill: "white", width: 50, height: 70 },
                        new go.Binding("geometryString", "geo"),
                        new go.Binding('fill', 'color')
                    ),
                    $(go.TextBlock, { margin: 8 },
                        new go.Binding('text', 'key'))
                ),

image

https://gojs.net/latest/intro/geometry.html#GeometryParse
You need to declare that the geometry should be filled. Typically this can be done by prepending “F” to the string, but it’s better to call Geometry.fillPath.
https://gojs.net/latest/api/symbols/Geometry.html#static-fillPath

That did it!

Last question, in cases where I want to use a regular node shape for a specific node rather than custom geometry, would I need to specify the SVG path of that regular shape?

Example:

function geoFunc(geoname) {
            var geo = icons[geoname];
            
            if (geo === undefined) geo = icons["heart"];  // use this for an unknown icon name
            

            if (typeof geo === "string") {
                geo = icons[geoname] = go.Geometry.fillPath(geo);  // fill each geometry
            }
            return geo;
        }

Rather than having the undefined icons default to a heart, how would I default to a predefined shape like a circle or a RoundedRectangle?

You can bind the Shape.figure property too.

        $(go.Shape, . . .,
          new go.Binding("fill", "color"),
          new go.Binding("geometry", "geo", convertGeo)),

where

    function convertGeo(path, shape) {
      if (!path) return null;
      try {
        var geo = go.Geometry.parse(path, true);
        if (geo) return geo;
      } catch (ex) {}
      shape.figure = path;
      return null;
    }

and then either:

    myDiagram.model.commit(m => {
      m.set(node.data, "geo", "RoundedRectangle");
    });

or:

    myDiagram.model.commit(m => {
      m.set(node.data, "geo", "F M0 0 L50 25 25 50z");
    });

But, I think if you only have a fixed set of geometries, it would be better to cache and refer to them by name, just as the Icons sample does.

Im not sure if that quite works for what im trying to do, here is my data:

{ key: 'Alpha', color: 'lightblue', arr: [1, 2], loc: "0 0", type: "Type", geo: "droplet" },
        { key: 'Beta', color: 'orange', loc: "100 0", type: "Type1", geo: "droplet"},
        { key: 'Gamma', color: 'lightgreen', loc: "0 100", type: "Type1", geo: "droplet"},
        { key: 'Delta', color: 'pink', loc: "100 100", type: "Type1", geo: "none"}

The idea that I’m working off of is that either the node has a geometry that is defined or it is a roundedrectangle.

function geoFunc(geoname) {
            var geo = icons[geoname];
            if (geo === undefined) geo = icons["circle"];  // use this for an unknown icon name
            if (typeof geo === "string") {
                geo = icons[geoname] = go.Geometry.fillPath(geo);  // fill each geometry
            }
            if (geo == "none") {
                // Case where shape is set to Rounded Rectangle
            }
            return geo;
        }

Since my geometries are rather fixed and any case where im not using a custom geometry the shape needs to be a rounded rectangle. Is this possible without predefining the SVG for the rounded rectangle.

This seems to work in my test cases:

    myDiagram.nodeTemplate =
      $(go.Node, "Spot",
        $(go.Shape, "RoundedRectangle",
          new go.Binding("fill", "color"),
          new go.Binding("geometry", "geo", convertGeo)),
        $(go.TextBlock,
          new go.Binding("text"))
      );

    function convertGeo(path, shape) {
      if (!path) return null;
      if (path === "RoundedRectangle") {
        shape.figure = path;
        return null;
      } else {
        return go.Geometry.parse(path, true);
      }
    }

Looks like that worked for me! I had a error in my code that was unrelated causing this to not function properly. Everything is correct now. Thanks!