Example using Picture.reloadSource?

The addition of Picture.reloadSource seems to be what I’ve been looking for in gojs for quite some time, but I can’t find any examples of how it should be used.

Specifically, I have a diagram with a custom picture node that can have its source attribute change on the fly (not necessarily the server-side source image, but the URL in the source attribute on the Picture element which ideally would cause the picture shown in the diagram to change). I can update the model, but it seems like it is necessary to also call reloadSource at some point. Problem is, I can’t determine how to get a reference to the picture element within the node whose data have been updated. Is there an example anywhere of how a node with a dynamically changing picture element should be updated?

I suspect that that new method might not of interest to you.

Has the value of Picture.source actually changed for the particular Picture in that one Node? Setting that property to a new value will automatically cause the image to be reloaded and eventually the picture to be redrawn.

But if the value of the Picture.source property has not changed, then maybe you do want to call Picture.reloadSource.

Well, i have a binding to the source attribute:
new go.Binding ("source"),...
and occasionally the server pushes a list of updated nodes and associated data to the client, which goes through a script that iterates over all the diagram nodes and updates the relevant attributes in the node’s data, and this often can include the source attribute receiving a new URL:

 dataObj = myDiagram.model.findNodeDataForKey (theKey);
...
 myDiagram.model.setDataProperty(dataObj, "source", "http://..../image.png");

Other changes made inside this same function for other attributes (fillColor, font, etc.) all have their updates reflected in the diagram. Just not the source attribute. That’s why I thought it needed to be forced.

In any case, an example of how to invoke these new functions would be helpful. It’s not clear what object is supposed to be used to call the method from.

You’re doing the right thing – calling Model.set (a.k.a. Model.setDataProperty). Hopefully you are calling it within a single transaction for all of the changes.

I just tried a test where I modified the Picture.source property for a Picture in a Node, and it displayed the correct, updated image that one would expect. If you could produce a reproducible case for us, we can look into it.

Well wouldn’t you know it? That was the ONE place in the code that wasn’t wrapped in a transaction. All fixed now, so thanks for making me look a second time.

Would still be interesting to see the proper method for calling reloadSource, since we WILL have issues where images on servers are updated and reloads need to be forced for image nodes. Thanks!

There’s no data binding way to do that – you’ll have to find the Pictures and call the method on them.

Hmmm, well I suppose you could temporarily set the source value to an empty string or something, and then set it back again to the original value.

Yes, this trick works. I’m still having an issue with image objects, SVGs, and canvas-based images being redrawn properly in Picture objects however. It’s a fairly complicated application, so let me describe the issue.

We have Go.JS nodes that have a picture element in them that is (ultimately) aimed at an image object attached to the DOM. We use the Picture to display a D3 graph in the node. The data attribute on the node containing the data to be graphed/drawn is bound to the “element” attribute on the Picture. So when the data attribute changes, the binding function for element is called. We pass the data off to our D3 graphing function which creates a temporary SVG graph, which we then render to a newly created image object in the DOM (or a canvas in some cases), then we return that image object to GoJS as the result of the binding function.

This generally works, but there are several instances where the updated data isn’t drawn in the picture element until some other update happens to the diagram. The update can be as simple as moving the mouse across a node that has a mouse enter/leave event handler which redraws the node. When that happens, the Picture backed by the canvas/image with the graph is suddenly drawn.

The problem is that this happens only intermittently and it’s been a multi-year effort spanning 1.x and 2.x version of GoJS to try and figure out why the graphs draw perfectly sometimes and need to be “prodded” for others.

Here are some code particulars:

node definition
    myDiagram.nodeTemplateMap.add('data',
        $go(go.Node, "Auto", {
                selectionObjectName: "BODY",
                resizable: true,
                resizeObjectName: "BODY",
                deletable: true
            },
            new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
            $go(go.Panel, "Auto", {
                    name: "BODY"
                 },
                new go.Binding("desiredSize", "rsize", go.Size.parse).makeTwoWay(go.Size.stringify),
					$go(go.Picture,
						{name: "picture"},
						new go.Binding ("imageStretch", "scaling", function (v) { if (v=="true") {return go.GraphObject.Uniform; } else { return go.GraphObject.None; }}),
						new go.Binding("width", "rsize", function (v) { return go.Size.parse(v).width;}),
						new go.Binding("height", "rsize", function (v) { return go.Size.parse(v).height;}),
          				new go.Binding("element", "rendered_args", makeDataImage)
                 	)
            )
        )
    );

When the “rendered_args” attribute is updated in response to a message from the server, we’re doing it inside a transaction. This logic works for all of the other nodes with text items, colors, fonts, etc. that get updated by changes from the server. But when the canvas/image for one of these “data” nodes changes, it’s hit or miss as to whether or not the node refreshes the picture.

The makeDataImage function in a nutshell:

makeDataImage
	function makeDataImage (vargs, picture) {
            //info is extracted from the server data passed in vargs
           //this canvas stuff is mostly a red herring as it is for a dual purpose mode we no longer use
		var canvases = document.getElementById("tempCanvas");
		var canv = document.createElement("canvas");
		canv.width = info.minWidth;
		canv.style.width = canv.width + "px";
		canv.height = info.minHeight;
		canv.style.height = canv.height + "px";
		var div = document.createElement("div");
		div.style.position = "absolute";
		div.appendChild(canv);
		canvases.appendChild(div);

		var returnObject = renderer.render (canv, vargs, info);
		canvases.removeChild (div);
        if (returnObject){
            return returnObject;
        }
        // return the canvas object as the default.
		return canv;
	};

The “render” function above calls a helper function elsewhere to render the D3-created SVG image into an image object that it creates:

_svgToImage

function _svgToImage(svgObj) {
if (!svgObj){
return null;
}

// get svg data
var xml = new XMLSerializer().serializeToString(svgObj);

// make it base64
var svg64 = btoa(xml);
var b64Start = 'data:image/svg+xml;base64,';

// prepend a "header"
var image64 = b64Start + svg64;
var w = parseInt(svgObj.getAttribute("width"), 10);
var h = parseInt(svgObj.getAttribute("height"), 10);
var imgObj = new Image(w, h);
imgObj.src = image64;

return imgObj;

}

Given how twisted all this looks, there are a couple of questions. First, should (or can) we be returning the SVG data directly to GoJS to use as the source for the Picture, rather than aiming the element at an image object (or canvas – we have 2 modes for doing this)?

If so, I guess it begs the question of how we should do this if we are generating images or canvases on the fly rather than SVG.

But I suppose the ultimate question is what are we doing wrong, because (if you can make sense out of the mess I posted above), it does work sometimes, which makes it seem like a minor issue or a timing problem more than something fundamentally wrong.

I should also add, FWIW, if the render function creates and returns a canvas instead of a SVG jammed into an image object, the code works as expected 100% of the time. (That is, when a canvas is returned by the element binding function, it always works as expected, but when it’s an image object, it only updates the display when something else (another node) in the diagram forces a redraw.

If you are setting/binding Picture.element instead of Picture.source, you should be calling Picture.redraw instead of Picture.reloadSource.

Yes, that is what we’ve tried, but the question is where/when? In the client code that updates the data bound to the element attribute, it seems to be too soon (looking at debugging output) to call redraw. It doesn’t seem like we can call it from inside the binding function because we don’t really have a reference to the Picture (that I know of). I’ve tried using a setTimeout function to call redraw on the picture some variable number of milliseconds after the bound element data is updated. Redraw really never seems to have any effect.
This is why my original question was where and how do you call redraw()?

Whenever it is finished drawing whatever you will be showing using a Picture.

Yes, drawing is often asynchronous, so if you have your own HTMLImageElement you might need to implement a load event listener on it before you set its source.

That is probably enough of a clue for me to go chase this rabbit some more. I think at the time we’re drawing the SVG, we do have knowledge of the node involved (and hence, the Picture), so we can pass that down into the code that instantiates the image object and trigger a redraw inside the load event handler.

Thanks! I’ll let you know when/if that works and post a bit of follow-up for others who may run into this.

Well, close but still confused.
In the code that injects the SVG data into the new image object:

Summary
function _svgToImage(svgObj, picture) {
...
var imgObj = new Image(w, h);
var fn = function () {
		var pic = picture;
		console.log ("#$# Image has loaded now");
		if (pic) {
			try {
				console.log ("redrawing it " + typeof pic + " : " + pic);
				pic.redraw ();
			}
			catch (err) {
				console.log ("error loading picture: " + err);
			}
		}
		else {
			console.log ("no picture provided to redraw");
		}
	};
imgObj.addEventListener("load", fn);
imgObj.src = image64;

return imgObj;

It throws an exception that pic.redraw is not a function with the following console output:
redrawing it object : Picture()#1126
svg-to-img.js:31 error loading picture: TypeError: pic.redraw is not a function

This is go.js v.2.1.15, btw. I don’t seem to be able to find any context where I can call the redraw method on a Picture object and have it succeed. In every case, it reports that redraw() is not a function.

I don’t know how you are using _svgToImage, but apparently it is not being called with an instance of Picture. You don’t create a closure referring to the variable that is the second argument of the binding conversion function?

I think it IS a Picture, at least as the object reports itself. Doesn’t the “Picture()#1126” come from GoJS reporting the object as a Picture when it is printed to the console?

If I call redraw at the same point that the console message reports a valid Picture object, it still fails with the same error. Elsewhere in the code, I can find the node by its key, then ask for the named object within the node called “picture” (see the earlier node definition) and call the resulting object’s redraw method and it also fails.
e.g.:
var theNode = myDiagram.findNodeForKey (theKey);
var pic = theNode.findObject(“picture”);
console.log (pic); // prints Picture()#1126
pic.redraw(); //throws an exception

This is why I’m asking for an example of redraw being called properly, in the correct context. I can’t find any situation, anywhere in the model definition (binding function), image load event handler, or anywhere else that pic.redraw() is valid.

In the case of the SVG function in my previous note, the argument being passed down the call chain to it is the second argument to the binding function (i.e. the makeDataImage binding function from earlier notes) in the node definition, which is supposed to be the Picture object, right?

Anyway, as you can imagine, this is pretty frustrating. Obviously the code mostly works because the graphs DO eventually show up in the picture nodes, but only when something else in the diagram causes it to render. picture.redraw seems to be the thing needed, but either I have no earthly clue what it takes to invoke it, or it actually doesn’t exist in the version of GoJS I have. I can’t tell what the methods are on the object since the code is obfuscated so I have no way of knowing.

Maybe you’re using an ancient version of GoJS. What’s the value of go.version?

It’s current (minus one point release, I see), from https://cdnjs.cloudflare.com/ajax/libs/gojs/2.1.15/go.js

I really don’t know what else to do. I totally get the mechanics at play here. I just don’t understand why that method isn’t callable.

From my previous note, is the fact that the object is showing up in the console log as “Picture()#1126” indicative of a proper GoJS picture object?

Picture.redraw was silently introduced in 2.0 (not sure which update) and documented in 2.1.

Yes, if you .toString() a Picture, you should get something like what you showed. The fact that there’s nothing between the parentheses I think indicates that there’s no value for Picture.source, which is most commonly used instead of Picture.element. A better way of testing is by evaluating x instanceof go.Picture.

But maybe you want to examine it more carefully to make sure it is the Picture that you care about, not another one in a different Node, or one that isn’t in any Node at all.

Please look at the following code and the associated console output. As far as I can tell, this seems to indicate that a proper Picture object is in hand and it does not have the redraw (or reloadSource) method available. (I’ve tried both, just to make sure.) I do not believe that redraw or reloadSource are present in the GoJS code for 2.1.16.

function makeDataImage (vargs, picture) {
...
try {
    console.log ("calling redraw in makeDataImage");
    picture.redraw();
 }
 catch (err) {
     console.log ("makeDataImage err " + err);
     console.log ("GoJS version " + go.version);
     console.log ("Is Picture? " + (picture instanceof go.Picture));
     console.log ("makeDataImage w " + picture.width);
     console.log ("makeDataImage h " + picture.height);
     console.log ("makeDataImage is " + picture.imageStretch);
  }
...
}

Outputs:

calling redraw in makeDataImage
process_diagram_impl.js:708 makeDataImage err TypeError: picture.redraw is not a function
process_diagram_impl.js:709 GoJS version 2.1.16
process_diagram_impl.js:710 Is Picture? true    
process_diagram_impl.js:710 makeDataImage w 417
process_diagram_impl.js:711 makeDataImage h 212
process_diagram_impl.js:712 makeDataImage is EnumValue.Uniform

The values of the properties for width, height, and imageStretch all match the expected values of the Picture in the node being drawn, so it would seem 99.9% certain that “picture” contains a valid GoJS picture object. Yet, calling redraw() throws an exception.

Is there any way to interpret this other than the definition of Picture does not include “redraw()”?

For reference, the node definition that uses the makeDataImage function for binding to the element attribute:

    myDiagram.nodeTemplateMap.add('data',
        $go(go.Node, "Auto", {
                selectionObjectName: "BODY",
                resizable: true,
                resizeObjectName: "BODY",
                deletable: true
            },
            new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
            $go(go.Panel, "Auto", {
                    name: "BODY"
                 },
                new go.Binding("desiredSize", "rsize", go.Size.parse).makeTwoWay(go.Size.stringify),
					$go(go.Picture,
						{name: "picture"},
						new go.Binding ("imageStretch", "scaling", function (v) { if (v=="true") {return go.GraphObject.Uniform; } else { return go.GraphObject.None; }}),
						new go.Binding("width", "rsize", function (v) { return go.Size.parse(v).width;}),
						new go.Binding("height", "rsize", function (v) { return go.Size.parse(v).height;}),
          				new go.Binding("element", "rendered_args", makeDataImage)
                 	)
            )
        )
    );

I’m so sorry – you are completely correct. The methods Picture.redraw and Picture.reloadSource are not exported from the Picture prototype. Which is weird, since they clearly show up in the generated go.d.ts file, which is the “real” list of exported symbols. (Some symbols are not officially documented in the API, but they are still in the go.d.ts file so that code can use them.)

We’ll fix this for the 2.1.17 release, which we’ll do Monday or Tuesday. I’ll start building a beta copy right now.