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.