SVG image not showing in server side rendering

I am using Puppeteersharp on the server side in a .net core application to do server side rendering (https://gojs.net/latest/intro/serverSideImages.html) and generate images of the diagram.
The SVG images are not showing up in the generated diagram image. Below is my javascript code Im using in the server invoked from PuppeteerSharp (https://www.puppeteersharp.com/).

the same way of creating the blob url from svg image string and appending it to a simple html div element is working fine but it is not working in the GoJS diagram. What am I missing?


function getImageDataForReport() {
    var div = document.createElement("div");
    div.id = 'summary' + 'flowchartId';

    var modelJson = '{"class":"GraphLinksModel","linkKeyProperty":"key","linkFromPortIdProperty":"fromPort","linkToPortIdProperty":"toPort","nodeDataArray":[{"category":"Process","text":"Step 1","color":"","key":"1","loc":"-200 100","isComplete":false},{"category":"Process","text":"Step 2","color":"","key":"2","loc":"400 300","isComplete":false}],"linkDataArray":[]}';
    var diagram = go.GraphObject.make(go.Diagram, div, {
        "animationManager.isEnabled": false,
        "undoManager.isEnabled": false,
        "LayoutCompleted": (e) => { e.diagram.findTopLevelGroups().each((g) => { /*if (g.visible) dTemplates.locateGroup(g)*/ }) },
    });

    diagram.nodeTemplateMap.add("Process", processTemplate(false, [], false));

    var graphModel = new go.GraphLinksModel();
    graphModel = new go.GraphLinksModel(go.Model.fromJson(modelJson)["nodeDataArray"], go.Model.fromJson(modelJson)["linkDataArray"]);
    diagram.model.setDataProperty(graphModel, "linkKeyProperty", "key");
    diagram.model.setDataProperty(graphModel, "nodeKeyProperty", "key");
    graphModel.linkFromPortIdProperty = "fromPort";
    graphModel.linkToPortIdProperty = "toPort";
    diagram.model = graphModel;
    return diagram.makeImageData({
        scale: 1,
        background: "White"
    });
}

function processTemplate() {
    var imgElm = getProcessImage();
    var imgUrl = getProcessImage(true);
    return go.GraphObject.make(go.Node, "Spot",
        {
            avoidableMargin: new go.Margin(3, 3, 3, 3), locationSpot: go.Spot.Center, visible: true
        },
        new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
        go.GraphObject.make(go.Picture,
            {
                name: "ProcessStep",
                desiredSize: new go.Size(150, 100),
                element: imgElm,
                visible: true,
                source: imgUrl ? imgUrl : getProcessImage(true)
            }
        ),
        new go.Binding('source', '', (layer, trgtObj) => { return imgUrl ? imgUrl : getProcessImage(true); }).ofObject(),
        new go.Binding('desiredSize', 'size', go.Size.parse).makeTwoWay(go.Size.stringify),
        new go.Binding("element", "isComplete", (state) => { return imgElm; }),
        go.GraphObject.make(go.TextBlock,
            { font: "14px Helvetica Neue", stroke: "#000000" },
            {
                editable: false, maxLines: 4, wrap: go.TextBlock.WrapFit, overflow: go.TextBlock.OverflowEllipsis, margin: new go.Margin(0, 24, 0, 24), alignment: go.Spot.Center,
                verticalAlignment: go.Spot.Center, textAlign: 'center', stretch: go.GraphObject.Fill
            },
            new go.Binding("text").makeTwoWay()
        ),
        makePort("T", go.Spot.Top, go.Spot.TopSide, true, true, false),
        makePort("B", go.Spot.Bottom, go.Spot.BottomSide, true, true, false),
        makePort("L", go.Spot.Left, go.Spot.LeftSide, true, true, false),
        makePort("R", go.Spot.Right, go.Spot.RightSide, true, true, false),
    );
}

function getProcessImage(onlyUrl) {
    var svgStr = `<?xml version="1.0" encoding="UTF-8"?>
<svg width="173px" height="125px" viewBox="0 0 173 125" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
    <title>Process</title>
    <desc>Created with Sketch.</desc>
    <defs>
        <linearGradient x1="50%" y1="26.3513514%" x2="50%" y2="90.020587%" id="linearGradient-1">
            <stop stop-color="#EFF8FE" offset="0%"></stop>
            <stop stop-color="#E0F1FD" offset="100%"></stop>
        </linearGradient>
        <rect id="path-2" x="12" y="12" width="150" height="100"></rect>
    </defs>
    <g id="Flowchart-/-Shapes-/-Process-Step" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
        <g id="Process-Shape">
            <use fill="url(#linearGradient-1)" fill-rule="evenodd" xlink:href="#path-2"></use>
            <rect stroke="#B3DCF9" stroke-width="1" x="11.5" y="11.5" width="151" height="101"></rect>
            <rect stroke="#FFFFFF" stroke-width="1" stroke-linejoin="square" x="12.5" y="12.5" width="149" height="99"></rect>
        </g>
    </g>
</svg>`;
    var blb = new Blob([svgStr], { type: 'image/svg+xml' });
    var imgurl = URL.createObjectURL(blb);
    if (onlyUrl) return imgurl;
    var image = document.createElement('img');
    image.src = imgurl;
    return image;
}



function makePort(name, align, spot, output, input, editable) {
    const horizontal =
        align.equals(go.Spot.Top) || align.equals(go.Spot.Bottom);
    return go.GraphObject.make(go.Shape, {
        fill: "transparent",
        strokeWidth: 0,
        width: horizontal ? NaN : 8,
        height: !horizontal ? NaN : 8,
        alignment: align,
        stretch: horizontal ? go.GraphObject.Horizontal : go.GraphObject.Vertical,
        portId: name,
        fromSpot: spot,
        fromLinkable: editable ? output : editable,
        toSpot: spot,
        toLinkable: editable ? input : editable,
        cursor: "pointer",
        mouseEnter: editable ?
            function (e, port) {
                if (!e.diagram.isReadOnly && port instanceof go.Shape)
                    port.fill = "rgba(255,0,255,0.5)";
            }
            :
            function (e, port) {
                if (port instanceof go.Shape) port.fill = "transparent"
            }
        ,
        mouseLeave: function (e, port) {
            if (port instanceof go.Shape) port.fill = "transparent";
        }
    });
}

I just tried modifying the code in https://gojs.net/latest/intro/serverSideImages.html so that the node template included a Picture, with image sources including both PNG and SVG files.

Everything seems to work OK – the rendered diagram has the nodes including their respective images. I had those image files in the same directory – perhaps you have some CORS problem using your image files?

To eliminate that doubt of CORS or similar issue, If you look at my code, I am using the contents of the SVG image file assigned to a variable directly within my javascript function.

The same code is working fine when I use it in client side (regular chrome browser). The issue is only when I am rendering the diagram in headless browser (chromium).

It appears to me that the results are the same, both produce a .svg image that, when opened alone in Chrome, complains that it will not load the image resources, but when the .svg is embedded in an HTML page will display them just fine. This is a weird decision on Chrome’s part, but its not something we can ameliorate I think.

Here’s an example: https://codepen.io/simonsarris/pen/jOrppyx?editors=1010

This code adds the SVG to the page (and displays the images), but if you open the downloaded .svg file, Chrome will not display the images.

Thanks Simon. Based on your point, I remembered the callback option of makeImageData() function that I had to use earlier in the regular chrome browser. I just tried the same in headless SSR and it seems to be working except while reading the images located on a different server/domain (Possibly CORS issue?).

Yes, that sounds like CORS, though you’ll have to experiment to see what the issue is exactly. If you have not seen it, there is a property on Picture, sourceCrossOrigin, that can be set to help with some CORS issues.

yup I am using { sourceCrossOrigin: function (pict) { return "use-credentials"; } } in my template definition

You may need another value, like “Anonymous”

The value of “use-credentials” can sometimes cause SVG image drawing to fail, where “Anonymous” would succeed if there are no credentials needed. This is true when using images from Wikipedia at least.

There’s a live example of that here, outside of GoJS. An SVG image is drawn to a Canvas and then made into a base64 image URI. If you set the crossOrigin flag wrong, it won’t even get drawn to the Canvas. https://codepen.io/simonsarris/pen/gOMdxKd

1 Like