Is it possible to make maxWidth / maxHeight / imageStretch behave like max-width and max-height for html img tags?

Hi there!

Most of the question is already in the topic.

Basically my nodeTemplate looks like the one below, an image and a textbox below, with an arbitrary amount of contactpoints via binding + itemTemplate:

diagram.nodeTemplate =
    $(go.Node, "Vertical",
        new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
        $(go.Panel, "Spot",
            $(go.Picture,
                new go.Binding("source", "imageUrl"),
                {imageStretch: go.GraphObject.Uniform, maxSize: new go.Size(100, 100)},
                new go.Binding("width"),
                new go.Binding("height"),
            ),
            new go.Binding("itemArray", "contactpoints"),
            {
                itemTemplate:
                    $(go.Panel,
                        new go.Binding("alignment", "", obj => new go.Spot(obj.x, obj.y)),
                        new go.Binding("portId", "id", id => "" + id),
                        {
                            margin: 0,
                            alignment: new go.Spot(0, 0, 0, 0),
                            toSpot: go.Spot.Top, fromSpot: go.Spot.Top
                        },
                        $(go.Shape, "Rectangle",
                            {
                                width: 4, height: 4, fill: "blue",
                                stroke: null, strokeWidth: 0,
                                fromLinkable: true, toLinkable: true, cursor: "pointer"
                            },
                        )
                    ),
                background: "lightgreen"
            }
        ),
        $(go.TextBlock, {
                text: "Test",
                textAlign: "center",
                font: "10pt helvetica, arial, sans-serif",
                stroke: "black",
                background: "lightblue",
                margin: new go.Margin(0, 0, 0, 0),
                height: 18,
                editable: false,
                isMultiline: false
            },
        )
    );

Test node:

{
key: "1", type: "", name: "", loc: "0 0", width: 445, height: 371, templateId: "", imageUrl: "/images/modules/unavailable.png",
contactpoints: [{type: "CanPort", in: Direction.Left, out: Direction.Right, x: 0.5, y: 0.0, name: "", id: "MYPORT"}]
}

The issue I have is that the node is bigger than the scaled image. I hoped it would behave like an html <img> with max-width and max-height where the whole image is scaled to respect both of the max-values while retaining its aspect ratio. In GoJs the image is scaled down but both maxWidth and maxHeight are preserved (or taken as the node’s size!?) and empty spaces remain right and left or above and below.

This image illustrates that:

Is there a way to achieve what I want to do?

Thank you in advance for your time and effort!
Florian

Most likely all you need to do is set Picture.imageStretch to go.GraphObject.UniformToFill

Example: https://codepen.io/simonsarris/pen/BavVeqW

Unfortunately UniformToFill does not solve my issue, either…
The node is still set to 100x100, the only difference is that the image is now clipped right and left.

The same picture as an html <img> is scaled to e.g. w:100 and h:83.

As nothing else in my node setup has an exact size set I hoped it would behave accordingly and the whole node would be scaled (to 100x83) while maintaining the aspect ratio of the image.

I also tried not using the url directly, instead binding it to the Picture.element like this:

picture.bind(new go.Binding("element", "imageUrl", (imageUrl: string) => {
	const img = document.createElement("img");
	img.src = imageUrl;
	img.style.maxWidth = "100px";
	img.style.maxHeight = "100px";
	return img;
}));

…but to no avail. :(

ok… I found a solution:

To achieve what I was looking for I added the following binding to the picture:

new go.Binding("maxSize", "imageUrl", (imageUrl: string) => {
	const img = document.createElement("img");
	img.src = imageUrl;
	img.style.maxWidth = "100px";
	img.style.maxHeight = "100px";
	img.style.position = "absolute";
	img.style.opacity = "0";
	document.getElementById("root")!.appendChild(img);
	console.log(img.clientWidth +"x"+ img.clientHeight);
	const maxSize = new go.Size(img.clientWidth, img.clientHeight);
	document.getElementById("root")!.removeChild(img);
	return maxSize;
})

It’s not ideal or the most elegant… e.g. I could get rid of appendChild/removeChild and setting style properties by only creating the img element, setting its src and calculating maxSize based on img.width and img.height.

I’m confused about what all possible cases you need to cover are, exactly, where you would be using maxSize in this way. I don’t recommend that you keep that binding.

If you simply removed width and height - if you do not set these at all, then the Picture will get its width and height from the loaded image. And then you could remove maxSize and this new binding. Is that all you want? What doesn’t that satisfy?

In my case all nodes are generated at runtime from images given by users, so all images are of unknown size, some might be small while others could be huge.
I looked for a way to keep the node sizes in check without too much pre- or post-processing, hence maxSize.

But you’re right to discourage making use of my “solution”, as it doesn’t work reliably… the img created by document.createElement("img") now returns 0 for width, height, clientWidth, clientHeight, I suppose because the image isn’t loaded yet when I want to get its dimensions.

I think I’ll need to get the images’ widths and heights through some other means and set the nodes’ desiredSizes accordingly.

You have a lot of options here, but yes, ideally if you’re creating images dynamically, you should use an image loading library (or write something small yourself) to load them before

You can also set a Picture.element instead of Picture.source, depending on how you want to control your assets. So one can write:

const img = new Image(); // don't bother with DOM
img.src = 'someSource';
// later, eg img.onload or something:
myGoJSPicture.element = img
// or a binding

Yet another idea: wrap the Picture with a “Viewbox” Panel that has whatever fixed desiredSize or maxSize that you like. GoJS Panels -- Northwoods Software