Combine diagram and freehand draw

Hi,

Apologies for a newbie question. I’d like to use the diagram element alongside the freehand draw tool. I took the example from the website and addedd the freehand drawing tool, but after I draw something the event appears to be captured by the diagram element and creates a new diagram mode instead.

Is this possible?

I’ve got the following so far
var $ = go.GraphObject.make;

var myDiagram =
$(go.Diagram, “myDiagramDiv”,
{
initialContentAlignment: go.Spot.Center, // center Diagram contents
“undoManager.isEnabled”: true, // enable Ctrl-Z to undo and Ctrl-Y to redo
layout: $(go.TreeLayout, // specify a Diagram.layout that arranges trees
{ angle: 90, layerSpacing: 35 })
});
myDiagram.toolManager.mouseDownTools.insertAt(3, new GeometryReshapingTool());
// the template we defined earlier
myDiagram.nodeTemplate =
$(go.Node, “Horizontal”,
{ background: “#44CCFF” },
$(go.Picture,
{ margin: 10, width: 50, height: 50, background: “red” },
new go.Binding(“source”)),
$(go.TextBlock, “Default Text”,
{ margin: 12, stroke: “white”, font: “bold 16px sans-serif” },
new go.Binding(“text”, “name”))
);

var toolX = new FreehandDrawingTool();
// provide the default JavaScript object for a new polygon in the model
toolX.archetypePartData =
{ stroke: “green”, strokeWidth: 3 };
// install as first mouse-down-tool
myDiagram.toolManager.mouseDownTools.insertAt(0, toolX);

// define a Link template that routes orthogonally, with no arrowhead
myDiagram.linkTemplate =
$(go.Link,
{ routing: go.Link.Orthogonal, corner: 5 },
$(go.Shape, { strokeWidth: 3, stroke: “#555” })); // the link shape

var model = $(go.TreeModel);
model.nodeDataArray =
[
{ key: “1”, name: “Animal”, source: “animal.jpg” },
{ key: “2”, parent: “1”, name: “Beaker”, source: “beaker.jpg” },
{ key: “3”, parent: “1”, name: “Miss Piggy”, source: “piggy.jpg” },
{ key: “4”, parent: “3”, name: “Fozzie”, source: “fozzie.jpg” },
{ key: “5”, parent: “3”, name: “Kermit”, source: “kermit.jpg” },
{ key: “6”, parent: “2”, name: “Bunsen”, source: “bunsen.jpg” }
];
myDiagram.model = model;

Thanks

I’ll assume you were starting from the FlowChart sample and added in the initialization of the FreehandDrawingTool and the node template from extensions/FreehandDrawing.html. Basically, you would add this code to FlowChart.html:

    // create drawing tool for myDiagram, defined in FreehandDrawingTool.js
    var tool = new FreehandDrawingTool();
    // provide the default JavaScript object for a new polygon in the model
    tool.archetypePartData =
      { category: "Freehand", stroke: "green", strokeWidth: 3 };
    // install as first mouse-down-tool
    myDiagram.toolManager.mouseDownTools.insertAt(0, tool);

    myDiagram.nodeTemplateMap.add("Freehand",
      $(go.Part,
        { locationSpot: go.Spot.Center },
        new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
        {
          selectionAdorned: true, selectionObjectName: "SHAPE",
          selectionAdornmentTemplate:  // custom selection adornment: a blue rectangle
            $(go.Adornment, "Auto",
              $(go.Shape, { stroke: "dodgerblue", fill: null }),
              $(go.Placeholder, { margin: -1 }))
        },
        { resizable: true, resizeObjectName: "SHAPE" },
        { rotatable: true, rotateObjectName: "SHAPE" },
        { reshapable: true },  // GeometryReshapingTool assumes nonexistent Part.reshapeObjectName would be "SHAPE"
        $(go.Shape,
          { name: "SHAPE", fill: null, strokeWidth: 1.5 },
          new go.Binding("desiredSize", "size", go.Size.parse).makeTwoWay(go.Size.stringify),
          new go.Binding("angle").makeTwoWay(),
          new go.Binding("geometryString", "geo").makeTwoWay(),
          new go.Binding("fill"),
          new go.Binding("stroke"),
          new go.Binding("strokeWidth"))));

Note how I gave the freehand-drawn node template a name and set the FreehandDrawingTool.archetypePartData’s category: "Freehand". This is to prevent a conflict with the templates already used by the FlowChart sample.

This works well, except for what I am guessing that you are complaining about: whenever the user uses the mouse/finger, it’s drawing a new “Freehand” part. That’s because the FreehandDrawingTool was designed to be used in a modal fashion. Note how in the extensions/FreehandDrawing.html sample, there are buttons to enable/disable that tool.

If you don’t want to have two modes in your app, one mode for doing normal operations such as for selecting, dragging, linking, and one mode for freehand drawing, then the FreehandDrawingTool needs a Tool.canStart override to control when it can run.

It isn’t clear to me what you want the policy to be, but maybe you want to put the FreehandDrawingTool as the last mouse-move tool:

    // install as last mouse-move-tool
    myDiagram.toolManager.mouseMoveTools.add(tool);

and you want to override Tool.canStart to only work if it’s started not on top of an existing Node or Link:

FreehandDrawingTool.prototype.canStart = function() {
  if (!this.isEnabled) return false;
  var diagram = this.diagram;
  if (diagram === null || diagram.isReadOnly || diagram.isModelReadOnly) return false;
  if (!diagram.allowInsert) return false;
  // don't include the following check when this tool is running modally
  if (diagram.currentTool !== this) {
    // only operates in the background, not on some Part
    var part = diagram.findPartAt(diagram.lastInput.documentPoint, true);
    if (part !== null) return false;
  }
  return true;
};

These two changes allow the user to select nodes or links, move or copy nodes and draw new links, while automatically freehand-drawing in the diagram background.

We should update the extensions/FreehandDrawingTool.js file in a similar manner, in case the tool is used mode-lessly.

You can implement different policies by choosing where to insert the FreehandDrawingTool in the ToolManager.mouseMoveTools or ToolManager.mouseDownTools lists and by changing the FreehandDrawingTool.canStart predicate.

Hi,

Thanks for the reply, that’s really helpful. I’m actually fine with having a different mode for selecting and drawing. I’ve implemented that and it works fine except that if I move a part of the diagram and then draw something with the freehand tool, the diagram part resets back to the original json specified.
Can you tell me where I’m going wrong?
var $ = go.GraphObject.make;

var myDiagram =
	$(go.Diagram, "myDiagramDiv", {
		initialContentAlignment : go.Spot.Center, // center Diagram contents
		"undoManager.isEnabled" : true, // enable Ctrl-Z to undo and Ctrl-Y to redo
		layout : $(go.TreeLayout, // specify a Diagram.layout that arranges trees
		{
			angle : 90,
			layerSpacing : 35
		})
	});
myDiagram.toolManager.mouseDownTools.insertAt(3, new GeometryReshapingTool());
// the template we defined earlier
myDiagram.nodeTemplate =
	$(go.Node, "Horizontal", {
		background : "#44CCFF"
	},
		$(go.Picture, {
			margin : 10,
			width : 50,
			height : 50,
			background : "red"
		},
			new go.Binding("source")),
		$(go.TextBlock, "Default Text", {
			margin : 12,
			stroke : "white",
			font : "bold 16px sans-serif"
		},
			new go.Binding("text", "name")));

// create drawing tool for myDiagram, defined in FreehandDrawingTool.js
var tool = new FreehandDrawingTool();
// provide the default JavaScript object for a new polygon in the model
tool.archetypePartData = {
	category : "Freehand",
	stroke : "green",
	strokeWidth : 3
};
// install as first mouse-down-tool
myDiagram.toolManager.mouseDownTools.insertAt(0, tool);

myDiagram.nodeTemplateMap.add("Freehand",
	$(go.Part, {
		locationSpot : go.Spot.Center
	},
		new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify), {
		selectionAdorned : true,
		selectionObjectName : "SHAPE",
		selectionAdornmentTemplate : // custom selection adornment: a blue rectangle
		$(go.Adornment, "Auto",
			$(go.Shape, {
				stroke : "dodgerblue",
				fill : null
			}),
			$(go.Placeholder, {
				margin : -1
			}))
	}, {
		resizable : true,
		resizeObjectName : "SHAPE"
	}, {
		rotatable : true,
		rotateObjectName : "SHAPE"
	}, {
		reshapable : true
	}, // GeometryReshapingTool assumes nonexistent Part.reshapeObjectName would be "SHAPE"
		$(go.Shape, {
			name : "SHAPE",
			fill : null,
			strokeWidth : 1.5
		},
			new go.Binding("desiredSize", "size", go.Size.parse).makeTwoWay(go.Size.stringify),
			new go.Binding("angle").makeTwoWay(),
			new go.Binding("geometryString", "geo").makeTwoWay(),
			new go.Binding("fill"),
			new go.Binding("stroke"),
			new go.Binding("strokeWidth"))));

// define a Link template that routes orthogonally, with no arrowhead
myDiagram.linkTemplate =
	$(go.Link, {
		routing : go.Link.Orthogonal,
		corner : 5
	},
		$(go.Shape, {
			strokeWidth : 3,
			stroke : "#555"
		})); // the link shape

var model = $(go.TreeModel);
model.nodeDataArray =
	[{
		key : "1",
		name : "Animal",
		source : "animal.jpg"
	}, {
		key : "2",
		parent : "1",
		name : "Beaker",
		source : "beaker.jpg"
	}, {
		key : "3",
		parent : "1",
		name : "Miss Piggy",
		source : "piggy.jpg"
	}, {
		key : "4",
		parent : "3",
		name : "Fozzie",
		source : "fozzie.jpg"
	}, {
		key : "5",
		parent : "3",
		name : "Kermit",
		source : "kermit.jpg"
	}, {
		key : "6",
		parent : "2",
		name : "Bunsen",
		source : "bunsen.jpg"
	}
];
myDiagram.model = model;
function mode(draw) {
	var tool = myDiagram.toolManager.mouseDownTools.elt(0);
    tool.isEnabled = draw;
}

Thanks

I have no idea. Are you using the debug library and watching for any warnings or errors?

It sounds as if maybe there’s an error that’s causing an UndoManager.rollbackTransaction. And/or that the drawing wasn’t completed in its own transaction.