{"id":1754,"date":"2017-01-06T10:42:09","date_gmt":"2017-01-06T10:42:09","guid":{"rendered":"http:\/\/mindfusion.eu\/blog\/?p=1754"},"modified":"2021-01-21T14:07:04","modified_gmt":"2021-01-21T14:07:04","slug":"collaborative-drawing-with-mindfusion-diagramming-and-signalr","status":"publish","type":"post","link":"https:\/\/mindfusion.dev\/blog\/collaborative-drawing-with-mindfusion-diagramming-and-signalr\/","title":{"rendered":"Collaborative drawing with MindFusion.Diagramming and SignalR"},"content":{"rendered":"<p>In this post we&#8217;ll show how to use the ASP.NET MVC diagram library and <a href=\"https:\/\/www.asp.net\/signalr\" target=\"_blank\" rel=\"noopener noreferrer\">SignalR<\/a> to implement collaborative drawing of diagrams. This can be useful in visual planning tools where users work together on a task, such as project management or mind-mapping applications.<\/p>\n<p>The complete sample project is available here &#8211;<br \/>\n<a href=\"https:\/\/mindfusion.dev\/_samples\/CollabMindMap.zip\">CollabMindMap.zip<\/a><\/p>\n<p>Start by creating an ASP.NET MVC application in Visual Studio. Open Tools -&gt; Library Package Manager -&gt; Package Manager Console and install the MindFusion.Diagramming.Mvc package &#8211;<\/p>\n<pre>Install-Package MindFusion.Diagramming.Mvc \n<\/pre>\n<p>While we are there, also install the SignalR package &#8211;<\/p>\n<pre>install-package Microsoft.AspNet.SignalR\n<\/pre>\n<p>From the project&#8217;s context menu, Add submenu, select OWIN startup class and add SignalR to the <a href=\"http:\/\/owin.org\/\" target=\"_blank\" rel=\"noopener noreferrer\">OWIN<\/a> pipeline by calling &#8211;<\/p>\n<pre>app.MapSignalR();\n<\/pre>\n<p>Now lets add a diagram view to the home page at Views\/Home\/Index.cshtml, load the necessary script files and wire up diagram event handlers that will send change notifications to the hub &#8211;<\/p>\n<pre>@using MindFusion.Diagramming\n@using MindFusion.Diagramming.Mvc\n\n@{\n    var diagView = new DiagramView(\"diagramView\")\n        .NodeCreatedScript(\"onNodeCreated\")\n        .NodeModifiedScript(\"onNodeModified\")\n        .NodeTextEditedScript(\"onNodeTextEdited\")\n        .LinkCreatedScript(\"onLinkCreated\")\n        .LinkModifiedScript(\"onLinkModified\")\n        .LinkTextEditedScript(\"onLinkTextEdited\")\n        .ControlLoadedScript(\"onDiagramLoaded\")\n        .SetAllowInplaceEdit(true);\n\n    diagView.Diagram.DefaultShape = Shapes.Ellipse;\n}\n\n@Html.DiagramView(diagView, new { style = \"width:700px; height:600px;\" })\n\n@section scripts\n{\n    @Scripts.Render(\"~\/Scripts\/jquery.signalR-2.0.0.js\")\n    @Scripts.Render(\"~\/Scripts\/MindMap.js\")\n    @Scripts.Render(\"~\/signalr\/hubs\")\n}\n<\/pre>\n<p>The hub will synchronize operations done on the diagram by one client by sending a notification to all other connected clients. From the project context menu add a SignalR hub class, naming it DiagramHub. The model class we&#8217;ll use to describe node changes looks like this &#8211;<\/p>\n<pre>public class NodeModel\n{\n    [JsonProperty(\"x\")]\n    public double X { get; set; }\n\n    [JsonProperty(\"y\")]\n    public double Y { get; set; }\n\n    [JsonProperty(\"width\")]\n    public double Width { get; set; }\n\n    [JsonProperty(\"height\")]\n    public double Height { get; set; }\n\n    [JsonProperty(\"id\")]\n    public string Id { get; set; }\n\n    [JsonProperty(\"text\")]\n    public string Text { get; set; }\n}\n<\/pre>\n<p>Add these three methods to the hub class to synchronize node creation, move, resize and edit-text operations &#8211;<\/p>\n<pre>public void NodeCreated(NodeModel clientModel)\n{\n    Clients.AllExcept(Context.ConnectionId).nodeCreated(clientModel);\n}\npublic void NodeModified(NodeModel clientModel)\n{\n    Clients.AllExcept(Context.ConnectionId).nodeModified(clientModel);\n}\npublic void NodeTextEdited(NodeModel clientModel)\n{\n    Clients.AllExcept(Context.ConnectionId).nodeTextEdited(clientModel);\n}\n<\/pre>\n<p>The diagram event handlers in MindMap.js fill in the model objects and call respective hub methods &#8211;<\/p>\n<pre>function onNodeCreated(s, e)\n{\n    var hubId = $.connection.hub.id;\n    e.node.id = hubId + s.getItems().length;\n\n    var r = e.node.bounds;\n    var model =\n    {\n        id: e.node.id,\n        x: r.x,\n        y: r.y,\n        width: r.width,\n        height: r.height\n    };\n    \n    diagramHub.server.nodeCreated(model);\n}\n\nfunction onNodeModified(s, e)\n{\n    var r = e.node.bounds;\n    var model =\n    {\n        id: e.node.id,\n        x: r.x,\n        y: r.y,\n        width: r.width,\n        height: r.height\n    };\n    diagramHub.server.nodeModified(model);\n}\n\nfunction onNodeTextEdited(s, e)\n{\n    var model =\n    {\n        id: e.node.id,\n        text: e.getNewText()\n    };\n    diagramHub.server.nodeTextEdited(model);\n}\n<\/pre>\n<p>Handle notifications sent from server to clients by updating the diagram from received model objects &#8211;<\/p>\n<pre>$(function ()\n{\n    diagramHub = $.connection.diagramHub;\n    diagramHub.client.nodeCreated = function (model)\n    {\n        var node = diagram.factory.createShapeNode(\n            model.x, model.y, model.width, model.height);\n        node.id = model.id;\n    };\n    diagramHub.client.nodeModified = function (model)\n    {\n        var node = findNode(model.id);\n        node.setBounds(\n            new MindFusion.Drawing.Rect(\n                model.x, model.y, model.width, model.height),\n            true);\n    };\n    diagramHub.client.nodeTextEdited = function (model)\n    {\n        var node = findNode(model.id);\n        node.setText(model.text);\n    };\n    $.connection.hub.start();\n});\n<\/pre>\n<p>Finally add these helper functions for finding items and storing a global diagram reference &#8211;<\/p>\n<pre>function onDiagramLoaded(s, e)\n{\n    diagram = s;\n}\n\nfunction findNode(id)\n{\n    for (var i = 0; i &lt; diagram.nodes.length; i++)\n    {\n        var node = diagram.nodes[i];\n        if (id == node.id)\n            return node;\n    }\n    return null;\n}\n\nfunction findLink(id)\n{\n    for (var i = 0; i &lt; diagram.links.length; i++)\n    {\n        var link = diagram.links[i];\n        if (id == link.id)\n            return link;\n    }\n    return null;\n}\n<\/pre>\n<p>Start several copies of the application in separate browser instances on your system (or even on different machines if you publish it on IIS or Azure). Now start drawing nodes, moving them or editing their text &#8211; changes done on the diagram in one browser will be immediately reflected in all other browsers connected to the hub. However we aren&#8217;t yet synchronizing link operations; lets fix that &#8211;<\/p>\n<pre>public class LinkModel\n{\n    [JsonProperty(\"id\")]\n    public string Id { get; set; }\n\n    [JsonProperty(\"originId\")]\n    public string OriginId { get; set; }\n\n    [JsonProperty(\"destinationId\")]\n    public string DestinationId { get; set; }\n\n    [JsonProperty(\"text\")]\n    public string Text { get; set; }\n}\n<\/pre>\n<p>Add following hub methods in server class &#8211;<\/p>\n<pre>public void LinkCreated(LinkModel clientModel)\n{\n    Clients.AllExcept(Context.ConnectionId).linkCreated(clientModel);\n}\npublic void LinkModified(LinkModel clientModel)\n{\n    Clients.AllExcept(Context.ConnectionId).linkModified(clientModel);\n}\npublic void LinkTextEdited(LinkModel clientModel)\n{\n    Clients.AllExcept(Context.ConnectionId).linkTextEdited(clientModel);\n}\n<\/pre>\n<p>Call them from respective JavaScript handlers of diagram link events &#8211;<\/p>\n<pre>function onLinkCreated(s, e)\n{\n    var hubId = $.connection.hub.id;\n    e.link.id = hubId + s.getItems().length;\n\n    var model =\n    {\n        id: e.link.id,\n        originId: e.link.getOrigin().id,\n        destinationId: e.link.getDestination().id,\n    };\n    \n    diagramHub.server.linkCreated(model);\n}\n\nfunction onLinkModified(s, e)\n{\n    var hubId = $.connection.hub.id;\n    var model =\n    {\n        id: e.link.id,\n        originId: e.link.getOrigin().id,\n        destinationId: e.link.getDestination().id,\n    };\n    diagramHub.server.linkModified(model);\n}\n\nfunction onLinkTextEdited(s, e)\n{\n    var model =\n    {\n        id: e.link.id,\n        text: e.getNewText()\n    };\n    diagramHub.server.linkTextEdited(model);\n}\n<\/pre>\n<p>Handle link-related client notifications by creating or modifying links &#8211;<\/p>\n<pre>diagramHub.client.linkCreated = function (model)\n{\n    var link = diagram.factory.createDiagramLink(\n        findNode(model.originId), findNode(model.destinationId));\n    link.id = model.id;\n};\ndiagramHub.client.linkModified = function (model)\n{\n    var link = findLink(model.id);\n    link.setOrigin(findNode(model.originId));\n    link.setDestination(findNode(model.destinationId));\n};\ndiagramHub.client.linkTextEdited = function (model)\n{\n    var link = findLink(model.id);\n    link.setText(model.text);\n};\n<\/pre>\n<p>Now the application will also synchronize link operations across all connected clients. Here&#8217;s a small diagram synchronized between three different browsers &#8211;<br \/>\n<img decoding=\"async\" src=\"https:\/\/mindfusion.dev\/_samples\/collaborative_mindmap.png\" alt=\"collaborative mind map\" \/><\/p>\n<p>The sample above uses MindFusion&#8217;s ASP.NET MVC API. Code for other frameworks will look similar as MindFusion maintains same diagramming model for multiple platforms. You can download the trial version of any MindFusion.Diagramming component from <a href=\"http:\/\/mindfusion.dev\/download-diagramming-pack.html\" target=\"_blank\" rel=\"noopener noreferrer\">this page<\/a>.<\/p>\n<p>Enjoy!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In this post we&#8217;ll show how to use the ASP.NET MVC diagram library and SignalR to implement collaborative drawing of diagrams. This can be useful in visual planning tools where users work together on a task, such as project management &hellip; <a href=\"https:\/\/mindfusion.dev\/blog\/collaborative-drawing-with-mindfusion-diagramming-and-signalr\/\">Continue reading <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":true,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","enabled":false},"version":2}},"categories":[95,74],"tags":[7,451,3,80,450,449],"class_list":["post-1754","post","type-post","status-publish","format-standard","hentry","category-diagramming-2","category-sample-code","tag-asp-net","tag-collaboration","tag-diagram","tag-javascript","tag-mind-map","tag-mvc"],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_shortlink":"https:\/\/wp.me\/p3RlKs-si","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/mindfusion.dev\/blog\/wp-json\/wp\/v2\/posts\/1754","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/mindfusion.dev\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/mindfusion.dev\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/mindfusion.dev\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/mindfusion.dev\/blog\/wp-json\/wp\/v2\/comments?post=1754"}],"version-history":[{"count":6,"href":"https:\/\/mindfusion.dev\/blog\/wp-json\/wp\/v2\/posts\/1754\/revisions"}],"predecessor-version":[{"id":2613,"href":"https:\/\/mindfusion.dev\/blog\/wp-json\/wp\/v2\/posts\/1754\/revisions\/2613"}],"wp:attachment":[{"href":"https:\/\/mindfusion.dev\/blog\/wp-json\/wp\/v2\/media?parent=1754"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/mindfusion.dev\/blog\/wp-json\/wp\/v2\/categories?post=1754"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/mindfusion.dev\/blog\/wp-json\/wp\/v2\/tags?post=1754"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}