Using ControlNode-s in the JavaScript Diagram

Featured

In this blog post we will look on how to build ControlNode -s with Mindusion Diagramming library for JavaScript. We will create a web page that creates a template for cooking recipes. Each recipe is a DiagramNode . Rows in the table with ingredients can be added or removed dynamically with buttons. When you click on the recipe image, a browse dialog appears and you can point to another one.

You can test the page yourself:

I. General Settings

We need a blank HTML page and a blank JavaScript file, which will hold the code for the application.

We add links to three JavaScript files. We add them at the end of the web page, right before the closing BODY tag:

<script src="MindFusion.Common.js" type="text/javascript"></script>
<script src="MindFusion.Diagramming.js" type="text/javascript"></script>
<script src="RecipeNodes.js" type="text/javascript"></script>

In the web page we need two Canvas elements: one for the diagram and one for an overview control. The Overview control shows a mini-version of the diagram. It is convinient but not necessary in order to render a flowchart.

<div id="content" style="top: 60px; bottom: 24px;">
        <div style="position: absolute; left: 0px; top: 0px; bottom: 0px; width: 200px; border-right: 1px solid #e2e4e7;
			overflow: hidden; vertical-align: top;">
            <!-- The Overview component is bound to the canvas element below -->
            <div style="position: absolute; top: 0px; bottom: 0px; right: 0px; width: 200px;
				height: 200px; border-bottom: 1px solid #e2e4e7; background-color: #c0c0c0;">
                <canvas id="overview" width="200" height="200">
                </canvas>
            </div>            
        </div>
        <!-- The Diagram component is bound to the canvas element below -->
        <div style="position: absolute; left: 200px; top: 0px; right: 0px; bottom: 0px; overflow: auto;">
            <canvas id="diagram" width="2100" height="2100">
                This page requires a browser that supports HTML 5 Canvas element.
            </canvas>
        </div>
        <!-- The ZoomControl component is bound to the canvas element below -->
        <div style="width: 50px; height: 300px; position: absolute; top: 20px; right: 35px;
			width: 50px; height: 300px;z-index:3;">
            <canvas id="zoomer" width="50" height="300">
            </canvas>
        </div>
    </div>

We also add a ZoomControl control and we provide all HTML controls with an id. This is important because we need this id to refer to the Canvas elements from JavaScript code.

We’ve also added some CSS styling options to the web page. They are meant to make the HTML controls that we will embed in our
control node prettier.

II. The Controls

Now that we have set up all code in the web page, we start coding the JavaScript code-behind file. We add some namespace mappings, to make the code shorter. If you are using Visual Studio Code, you can add the TypeScript definitions of the Diagram library. This will enable IntelliSense support while you code.

We handle the DOMCOntentLoaded event of the initial HTML document to initialize the MindFusion controls.

document.addEventListener("DOMContentLoaded", function ()
{
	 // create a Diagram component that wraps the "diagram" canvas
    var diagram = Diagram.create(document.getElementById("diagram"));
    diagram.setVirtualScroll(true);
    diagram.setBehavior(Behavior.LinkControls);    

    // create an Overview component that wraps the "overview" canvas
    var overview = MindFusion.Diagramming.Overview.create(document.getElementById("overview"));
    overview.setDiagram(diagram);

    // create an ZoomControl component that wraps the "zoomer" canvas
    var zoomer = MindFusion.Controls.ZoomControl.create(document.getElementById("zoomer"));
    zoomer.setTarget(diagram);
}});

We get the HTML element that corresponds to each of the three Canvas elements that we have created in the web page. We use the document.getElementById of the Web API. You see now that we query the document using the id-s of the Canvas elements, which we have previously specified.

The Overview and ZoomControl instances use the setDiagram and setTarget methods prespectively, to specify the Diagram instance, to which they should be bound.

III. The Node Template

The ControlNode class allows us to use plain HTML in order to initialize the look of a ControlNode This is done with the setTemplate method. You can specify different templates for each instance of ControlNode that you create.

var node1 = new MindFusion.Diagramming.ControlNode(diagram);
        node1.setTemplate(`<input value="Cupcakes" data-interactive="true"></input>
	<div style="padding:10px"><img width="70" height="70" title="Click to choose picture" style="float:left" src="data:image/png;base64,${placeholderBase64}" data-interactive="true" data-event-click="placeholderClick" /><table width="50%"><tr><td contenteditable='true' data-interactive="true">softened butter</td><td contenteditable='true' data-interactive="true">110g</td></tr><tr><td contenteditable='true' data-interactive="true">golden caster sugar</td><td contenteditable='true' data-interactive="true">110g</td></tr><tr><td contenteditable='true' data-interactive="true">large eggs</td><td contenteditable='true' data-interactive="true">2</td></tr><tr><td contenteditable='true' data-interactive="true">vanilla extract</td><td contenteditable='true' data-interactive="true">½ tsp</td></tr>
<tr><td contenteditable='true' data-interactive="true">self-raising flour</td><td contenteditable='true' data-interactive="true">110g</td></tr></table></div>
	<button data-interactive="true" data-event-click="addRow">Add Ingredient</button>
	<button data-interactive="true" data-event-click="removeRow">Remove Ingredient</button>
	<div style="width: 100%;"><textarea id="w3review" name="w3review"  rows="4" columns="50" data-interactive="true">
  Using an electric whisk beat 110g softened butter and 110g golden caster sugar together until pale and fluffy then whisk in 2 large eggs, one at a time, scraping down the sides of the bowl after each addition.
Add ½ tsp vanilla extract, 110g self-raising flour and a pinch of salt, whisk until just combined then spoon the mixture into the cupcake cases.
  </textarea></div>`);
        node1.setBounds(new Rect(40, 20, 100, 100))
        diagram.addItem(node1);

We also use setBounds to specify the location of the new DiagramNode on the diagram’s work area. It is also important to add the new node to the collection of DiagramItem -s with addItem if we open the web page in browser, we will see the recipe node for cupcakes. If we create new nodes, however, we will see that they render as default white rectangles:

In order to change that we need to replace the default templates for nodes of the Diagram with a custom one: our recipe node. Here is the new template:

 var defaultTemplate = `
<input placeholder="Recipe Title" data-interactive="true"></input>
	<div style="padding:10px"><img width="70" height="70" title="Click to choose picture" style="float:left" src="data:image/png;base64,${placeholderBase64}" /><table width="50%"><tr><td contenteditable='true' data-interactive="true">Milk</td><td contenteditable='true' data-interactive="true">1 cup</td></tr></table></div>

	<div><button>Add Ingredient</button>
	<button>Remove Ingredient</button>
	<div><textarea id="w3review" name="w3review"  rows="4" columns="50" data-interactive="true">
  Preparation
  </textarea></div>`;

Now we can call the setDefaultControlTemplate method to tell the Diagram that from now on, when new DiagramNode -s are created, they will have this template:

diagram.setDefaultControlTemplate(defaultTemplate);

IV. Interactivity

When we create a new node, the table renders with a sample row, which you cannot edit. The same is with the title. If we want to make the controls editable we need to set the data-interractive atribute:

<div><button data-interactive="true" data-event-click="addRow">Add Ingredient</button>

As a rule each ControlNode exposes various attributes, which are accessible through the following syntax:

 data-attributeName="value"

As you see from the code above, there is data-event-click attribute. This attribute specifies the event handler of the click event for the button that is rendered in the node. Here it is:

function addRow(e, sender)
{
	var table = sender.getContent().getElementsByTagName("table")[0];
	
	
	  var rows = table.getElementsByTagName('tr');
	  if(rows.length > 0)
      {
		   var clone = rows[rows.length - 1].cloneNode(true); 
           table.appendChild(clone);
	  }
	  else
	  {
		  var tr = document.createElement('tr');
           
          var td = document.createElement('td');
         td.appendChild(document.createTextNode('Milk'))
         td.setAttribute('contenteditable', 'true');
		 td.setAttribute('data-interactive', 'true');
         tr.appendChild(td);
		 
		 td = document.createElement('td');
         td.appendChild(document.createTextNode('1 cup'))
         td.setAttribute('contenteditable', 'true');
		 td.setAttribute('data-interactive', 'true');
         tr.appendChild(td);
		 
		 table.appendChild(tr);
      }
}

In the event handler we check if there are rows in the table, and if yes – we copy the first one. If there are no rows, we add a new row with default data. The data-event-eventName is the syntax to handle any event that is available on the HTML control that you have included in the ControlNode is how we specify that the image of a cupcake is clickable:

<img width="70" height="70" title="Click to choose picture" style="float:left" src="data:image/png;base64,${placeholderBase64}" data-interactive="true" data-event-click="placeholderClick" />

As you see, it is interactive and we handle the click event on it. Once we click on the image, a dialog renders and we can choose a new image:

function placeholderClick(e, sender)
{
    var input = document.createElement("input");
    input.type = "file";
    var img = e.target;
    input.addEventListener('change', e =>
    {
        Utils.toDataUrl(URL.createObjectURL(e.target.files[0]), function (base64str)
        {
            img.src = base64str;
            sender.setTag(base64str);
            sender.createImage();
        });
    })
    input.click();
}

When a new image is chosen, we assign its Base64 represenation as source to the image element that is in our ControlNode template, which represents cooking recipes.

And these were all the steps you need to make in order to get interactive, recipe nodes with customizable count of ingredients, title and image.

You can download the source code from:

Download Recipe Nodes JavaScript Diagram Sample

Technical support is available through MindFusion forum here.

About Diagramming for JavaScript: This native JavaScript library provides developers with the ability to create and customize any type of diagram, decision tree, flowchart, class hierarchy, graph, genealogy tree, BPMN diagrams and much more. The control offers rich event set, numerous customization options, animations, graph operations, styling and themes. You have more than 100 predefined nodes, table nodes and more than 15 automatic layout algorithms. Learn more about Diagramming for JavaScript at https://mindfusion.eu/javascript-diagram.html.

Pan and Zoom Programmatically in a JavaScript Diagram

We will build a diagram with 50 random nodes and we will zoom and pan this diagram programmatically. Here is a screenshot of the final diagram, which is a link to the sample:

We will use the MindFusion Diagramming library for JavaScript.

I. Project Setup

We add a reference to the MindFusion.Diagramming.js and MindFusion.Common.js files. We also add a reference to another file called MouseEvents.js. This is our code-behind file.

<script src="MindFusion.Common.js" type="text/javascript"></script>
<script src="MindFusion.Diagramming.js" type="text/javascript"></script>
<script src="MouseEvents.js" type="text/javascript"></script>

In the BODY of the web page we create a Canvas element, to which we assign an id. This is important, because we will refer to the Canvas in code:

<div style="width: 100%; height: 100%; overflow: auto;">
    <canvas id="diagram_canvas" width="2100" height="2100">
        This page requires a browser that supports HTML 5 Canvas element.
    </canvas>
</div>

II. Diagram Settings

In the code-behind file that we called MouseEvents.js we use the DOMContentLoaded event to initialize the diagram.

document.addEventListener("DOMContentLoaded", function ()
{
    // create a Diagram component that wraps the "diagram_canvas" canvas
    diagram = MindFusion.AbstractionLayer.createControl(Diagram, null, null, null, document.getElementById("diagram_canvas"));
    diagram.setBounds(new Rect(5, 5, 2000, 1000));

We use the createControl method of the AbstractionLayer class to create an instance of the Diagram class. The setBounds method determines the size of the diagram’s drawing area. If this size is bigger than the size of the Canvas, the diagram automatically shows scrollbars. Note that only if the diagram’s area is larger than the canvas we can use panning.

We use some settings of the Diagram class to customize the application:

diagram.setDefaultShape("Rectangle");
diagram.setRouteLinks(true);
diagram.setRoundedLinks(true);
diagram.setShowGrid(false);

The links will be routed and rounded and no grid will be rendered.

III. Diagram Items

We create the diagram nodes with the createShapeNode method of the Factory class. The Factory class as an instance is available through the getFactory() method:
for(var i = 0; i < 50; i++)

    {
        var colorIndex = Math.floor(Math.random() * 3);  
        var shape = diagram.getFactory().createShapeNode(new Rect(136, 36, 20, 10));
        shape.setBrush({ type: 'SolidBrush', color: colors[colorIndex] });
        if(i % 3   == 0)
            shape.setShape('Ellipse');
        else 
            shape.setShape('Rectangle');
        if( i % 7 == 0)
        {
            shape.setBounds(new Rect(136, 36, 16, 8));	
        }
		
        shape.setText("Node " + (i + 1).toString());
        shape.setTextColor("white");
    }

We make each third shape Ellipse and we choose the brush on a random principle out of three brushes, that we initialized in an array. Each seventh shape is slightly smaller – that is set with the setBounds method, which takes as an argument a Rect, that is slightly smaller than the Rect instance that we use when we create the shape nodes.

The connectors among the nodes are created with the createDiagramLink method of Factory . We cycle through all 50 nodes and connect each one of them with a randomly taken node from the diagram nodes collection. This collection is available through the nodes proeprty of the Diagram class:

diagram.nodes.forEach(function(node)
{
    var nodeIndex = Math.floor(Math.random() * 50);  

    var node2 = diagram.nodes[nodeIndex];
    var link = diagram.getFactory().createDiagramLink(node, node2);
    link.setHeadShape("Circle");
})

We customize the appearance of the link through the setHeadShape method. We choose the ‘Circle’ shape as a head to each link.

We have created the diagram items with the same bounds, which means they are on top of each other. The best way to arrange them is with one of the automatic layout algorithms, available with the JsDiagram. They are members of the MindFusion.Graphs namespace – you can check the rest. In our sample we’ve chosen the LayeredLayout ,which provides quite nice result. We set its direction to LayoutDirection .There a few other properties that we’ve set that regulate the node distance, the layer distance and more:

var layout = new MindFusion.Graphs.LayeredLayout();
layout.direction = MindFusion.Graphs.LayoutDirection.LeftToRight;
layout.siftingRounds = 0;
layout.nodeDistance = 8;
layout.layerDistance = 8;
diagram.arrange(layout);

All layouts are applies through the arrange method of the Diagram that takes an instance of the layout as an argument.

IV. Pan and Zoom

We will implement pan and zoom by handling standard DOM events. The first one is the “wheel” event, which we attach to the diagram canvas element:

var dgrm = document.getElementById('diagram_canvas');

dgrm.addEventListener('wheel', function(e)
{
    var zoom = diagram.getZoomFactor();
    zoom -= e.deltaY / 10;
    if (zoom > 10)
        diagram.setZoomFactor(zoom);

    e.preventDefault(); // do not scroll
});

We use the getZoomFactor and setZoomFactor methods of the Diagram , to manipulate the zoom ratio. The zoom step is calculated based on the deltaY value of the event args. You can command the amount of zoom by dividing by a smaller or a larger number. It is important that we call preventDefault() on the event arguments, to surpass the default response of the canvas to the wheel event.

The panning is implemented by handling the mousedown and mouseup DOM events of the Canvas.

/* events fired on the draggable target */
dgrm.addEventListener('mousedown', function(e)
{
 if( e.ctrlKey)
	diagram.setBehavior(MindFusion.Diagramming.Behavior.Pan);
 
}, false);

dgrm.addEventListener('mouseup', function(e)
{
 if( e.ctrlKey)
	diagram.setBehavior(MindFusion.Diagramming.Behavior.LinkShapes);
 
}, false);

If we want to make the Diagram pan we need simply to change the diagram’s behavior with the setBehavior method. The options are members of the Behavior enumeration. When the user clicks on the Diagram and the Ctrl key is pressed, we change the diagram’s behavior to “Pan”. When the mouse is up, but the Ctrl key is pressed, we rest the behavior back to LinkShapes. This is the default behavior, where dragging with the mouse creates new shapes, while dragging between existing DiagramShape -s, creates DiagramLink -s.

With that our sample is ready. You can download the source code from this link:

Download the Mouse Events Sample with JavaScript Diagram

Technical support is available through MindFusion forum here.

About Diagramming for JavaScript: This native JavaScript library provides developers with the ability to create and customize any type of diagram, decision tree, flowchart, class hierarchy, graph, genealogy tree, BPMN diagrams and much more. The control offers rich event set, numerous customization options, animations, graph operations, styling and themes. You have more than 100 predefined nodes, table nodes and more than 15 automatic layout algorithms. Learn more about Diagramming for JavaScript at https://mindfusion.eu/javascript-diagram.html.

Tree with Nodes that Have Multiple Parents

Most tree structures are built as hierarchies: the number of nodes increases at each level. In our sample we will look at a tree, where not all branches have a higher number of nodes than their predecessors. In our tree some nodes will have multiple parents e.g. there are nodes that have several ancestors.

You can try the sample online:

In order to build this application we use MindFusion Diagramming for JavaScript library.

I. General Settings

In a web page we add the code that initializes a Canvas. We give the Canvas an id:

<div style="overflow: visible; height: 100%; margin: 1px; padding: 0px;">
    <canvas id="diagram" width="2100" height="2500">
        This page requires a browser that supports HTML 5 Canvas element.
    </canvas>
</div>

We add references to the two JavaScipt files that provide the diagramming functionality: MindFusion.Diagramming and MindFusion.Common. We also add a reference to a code-behind file that contains the JavaScript code for our application:

<script src="Scripts/MindFusion.Common.js" type="text/javascript"></script>
<script src="Scripts/MindFusion.Diagramming.js" type="text/javascript"></script>
<script src="MultipleParentNodes.js" type="text/javascript"></script>

We have placed those files in a subfolder called Scripts, which is located in our main application folder.

II. Diagram Settings

We create the diagram in the window.onload event handler. We want to be sure that all scripts are loaded:

window.onload = function(e)
{
    var diagramEl = document.getElementById('diagram');
    // create a Diagram component that wraps the "diagram" canvas
    diagram = AbstractionLayer.createControl(Diagram, null, null, null, diagramEl);
    diagram.setAllowInplaceEdit(true);
    diagram.setRouteLinks(true);
    diagram.setShowGrid(true);
    diagram.setRoundedLinks(true);
    diagram.setBounds(new Rect(0, 0, 2000,2500));
}

We create the Diagram using a reference to the DOM element of the Canvas from the web page. We set its allowInplaceEdit property to true, which lets users edit interactively nodes and links. We use showGrid to render a background grid that helps to align nodes and links. We setRoundedLinks and give the diagram a big work are with the setBounds method.

II. Creating the Diagram Nodes

We create the DiagramNode -s in a separate method, which we call after the control is created and all settings are made:

function onLoaded()
{
    var nodes = {};

    for(var i = 0; i < 5; i++)
    {
        nodes[i] = diagram.getFactory().createShapeNode(new Rect(20, 20, 20, 12));
        nodes[i].setShape('Rectangle');
        nodes[i].setBrush({ type: 'SolidBrush', color: '#567939' });
    };

We initialize a list, where we will store dynamically those nodes that we want to have reference to. At first we create 5 ShapeNode -s that are the first level of the tree. We use the createShapeNode method of Factory to create the ShapeNode -s and DiagramLink -s. Note that we will create all nodes with equal bounds. We don’t have to worry about their location because we will apply an automatic layout at the end.

Factory is available through the getFactory method of Diagram You do not usually create the class but get an instance of it through the Diagram instance.

We use setShape to provide the id af the diagram shape that we want the node to take. A list with the diagram shapes available, together with their id-s can be found in the online help.

We also use setBrush to specify the fill of the ShapeNode . In our case we use a SolidBrush ,but there are other options to choose from.

We create then a single ShapeNode that will be the next level:

var node5 = diagram.getFactory().createShapeNode(new Rect(20, 20, 20, 12 ));
node5.setShape('Rectangle');
node5.setBrush({ type: 'SolidBrush', color: '#6f9c49' });

We color it in a slightly lighter shade of green than the nodes at the first level. We then use the Factory class once again to create the DiagramLink -s between the 5 nodes at the first level and this one single node at the second level:

    for(var i = 0; i < 5; i++)
    { 
        var link = diagram.getFactory().createDiagramLink(nodes[i], node5);	
        link.setHeadShape("Triangle");
        link.setText("20%");
        link.setHeadShapeSize(3.0);
        link.setHeadBrush({ type: 'SolidBrush', color: '#7F7F7F' });
    };

The setText and setHeadShape methods of the DiagramLink class allow us to specify the label of the link and its shape at the end. There is also setBaseShape that allows us the specify the shape at the start of the DiagramLink.

The lovely thing about the Factory class is that it adds automatically the newly created DiagramItem -s, such as nodes and links, to the items collection of the diagram. You can find the newly created DiagramNode -s and DiagramLink -s also as members of the nodes and links collections respectively.

Now we have 5 links coming from all 5 nodes from the first level that point to the second level:

Node with Multiple Parents

We go on doing the rest of the diagram in the same way. We create ShapeNode -s with Factory and then bind the nodes with Factory .

III. Layout

We use the LayeredLayout to arrange all nodes of the diagram. Since the diagram is not a typical tree, we prefer the LayeredLayout to the TreeLayout

var lLayout = new MindFusion.Graphs.LayeredLayout();
diagram.arrange(lLayout);

It is really easy to apply any other algorithm on the diagram – you just need to create an instance of it and call the arrange method of your diagram to apply this instance. You can quickly change layouts and experiment to see which one provides the best result.

In our case the LayeredLayout looks fine and with this we are done building the tree.

You can download the sample together with all libraries used from the following link:

A JavaScript Graph with Nodes that Have Multiple Parents

Technical support is available through MindFusion forum here.

About Diagramming for JavaScript: This native JavaScript library provides developers with the ability to create and customize any type of diagram, decision tree, flowchart, class hierarchy, graph, genealogy tree, BPMN diagrams and much more. The control offers rich event set, numerous customization options, animations, graph operations, styling and themes. You have more than 100 predefined nodes, table nodes and more than 15 automatic layout algorithms. Learn more about Diagramming for JavaScript at https://mindfusion.eu/javascript-diagram.html.

Custom Diagram Nodes With Clipped Images

In this blog post we will create an org chart diagram that uses custom nodes for each employee. The diagram will be created with the Diagramming for JavaScript library. For the nodes we use the CompositeNode class, which enables us to create diagram nodes whose appearance can be defined via composition of components and layout containers.

Click on the image below to run the sample:

Custom Composite Nodes with Clipped Images

I. References and HTML Settings

The first thing that we’ll do is create a web page for the sample and add the references to the necessary JavaScript files. In the section of the page we provide a reference to the following jQuery files:

<script src="common/jquery.min.js"></script>
<script src="common/jquery-ui.min.js"></script>

At the end of the HTML page, just before the closing tag we place references to the two JavaScript files used by the Diagramming library:

<script src="MindFusion.Common.js"></script>
<script src="MindFusion.Diagramming.js"></script>

Our sample has its JS code in a separate file called Script.js. We place a reference to it as well:

<script src="Script.js"></script>

The diagram library needs an HTML Canvas to draw itself onto. We add one in the middle of the web page:

<div style="width: 100%; height: 100%; overflow: auto;">
<canvas id="diagram" width="2100" height="2100">
This page requires a browser that supports HTML 5 Canvas element.
</canvas>
</div>

II. The OrgChartNode

In the Script.js file we first add mappings to some enums and classes that we’ll use from the diagram library:

var Diagram = MindFusion.Diagramming.Diagram;
var CompositeNode = MindFusion.Diagramming.CompositeNode;
var Behavior = MindFusion.Diagramming.Behavior;

var Alignment = MindFusion.Drawing.Alignment;
var Rect = MindFusion.Drawing.Rect;

Now we call the classFromTemplate method of CompositeNode that generates a node class using a JSON template that we’ll provide:

var OrgChartNode = CompositeNode.classFromTemplate("OrgChartNode",
{
component: "GridPanel",
rowDefinitions: ["*"],
columnDefinitions: ["22", "*"],
...............

In this code we indicate the panel that will be used by the CompositeNode is a GridPanel. Then we declare two lists that set the width and height of the grid rows and columns. The number of members in each array indicate how many rows/columns the grid has. In our case we have one row that takes all place and two columns: one is with fixed with of 22 pixels, the other takes the rest of the available space.

The JSON definition of the CompositeNode continues with an array with the children:

children:
[
{
component: "Rect",
name: "Background",
pen: "black",
brush: "white",
columnSpan: 2
},
{
component: "Image",
name: "Image",
autoProperty: true,
location: "ceo.png",
margin: "1",
imageAlign: "Fit"
},

The first child uses a Rect component that we call “Background”. It is rendered with a white brush, has a black outline and spans on two columns e.g. it fills all available space or each node.

The second child is an image. Note the row:

autoProperty: true

That means that we want to be able to access this component as a property. In such cases the library generates automatic set/get methods using the name of the component. In our sample they will be setImage / getImage.

The third child is a StackPanel component. This is the container for the text labels next to the node. This child has its own collection of children nodes:

component: "StackPanel",
orientation: "Vertical",
gridColumn: 1,
margin: "1",
verticalAlignment: "Near",
children:
[
{
component: "Text",
name: "Title",
autoProperty: true,
text: "title",
font: "Arial bold"
},
{
component: "Text",
name: "FullName",
autoProperty: true,
text: "full name",
pen: "blue",
padding: "1,0,1,0"
},
{
component: "Text",
name: "Details",
autoProperty: true,
text: "details",
font: "Arial 3"
}

The children of this new StackPanel are text components, which are called Title, FullName and Details. They have their autoProperty set to true, which means we can access their value through automatic setter and getter methods.

III. The Diagram and the Nodes

in the read() function of the document we create an instance of the Diagram class using a reference to the canvas we’ve created in section I.

// create a Diagram component that wraps the "diagram" canvas
diagram = Diagram.create($("#diagram")[0]);

Then we enable interactive drawing of custom nodes by calling setCustomNodeType and Then we enable interactive drawing of custom nodes by calling setCustomNodeType and setBehavior:

// enable drawing of custom nodes interactively
diagram.setCustomNodeType(OrgChartNode);
diagram.setBehavior(Behavior.Custom);

The behavior o the diagram is set to Custom, which means that when the user starts drawing nodes the library shall draw nodes specified by CustomNodeType. The setCustomNodeType method tells the diagram that these custom nodes are of type OrgChartNode.

Now it is really easy and intuitive to create nodes:

var node1 = new OrgChartNode(diagram);
node1.setBounds(new Rect(25, 15, 60, 25));
node1.setTitle("CEO");
node1.setFullName("John Smith");
node1.setDetails(
"Our beloved leader. \r\n" +
"The CEO of this great corporation.");
node1.setImage("ceo.png");
diagram.addItem(node1);

We create a few more nodes using the same code and we bind them in a hierarchy. The links among the nodes are created by calling the Diagram Factory createDiagramLink method of the diagram Factory class:

diagram.getFactory().createDiagramLink(node1, node2);
diagram.getFactory().createDiagramLink(node1, node3);
diagram.getFactory().createDiagramLink(node1, node4);
diagram.getFactory().createDiagramLink(node4, node5);

IV. Rounded Images

We want to add now a custom feature to the node – instead of drawing the image as a rectangle we want to clip it and show it as an ellipse. We’ll do this by using a method that replaces the standard setImage method.

The new method is called createImageClip and takes as parameters two objects: one is the image URL and the other is the node that uses this image.

function createImageClip(path, node)
{
var canvas = document.createElement('canvas'),
ctx = canvas.getContext('2d'),
img = document.createElement('img');
..............

We create two HTMLElements – canvas and image, and we get the 2D context of the Canvas. Then, in an event handler of the onload event of the image we clip the canvas to an area defined by a Path. The path reads the size of the image and creates a full arc e.g. a circle inside that rectangle. Then the context draws the image and the new canvas is set as an image to the node using the setImage method:

img.src = path;
img.onload = function ()
{
canvas.width = img.width;
canvas.height = img.height;
var halfSize = img.width / 2;
ctx.save();
ctx.beginPath();
ctx.arc(halfSize, halfSize, halfSize, 0, Math.PI * 2, true);
ctx.closePath();
ctx.clip();

ctx.drawImage(img, 0, 0, img.width, img.height);

node.setImage(canvas.toDataURL());
};

You can use this approach to create clippings of images with variable shape.

Now instead of calling:

node1.setImage("ceo.png");

we call our custom method this way:

createImageClip("ceo.png", node1);

We do this for all nodes in the org chart.

That’s the end of this tutorial. You can download the sample together with all JavaScript libraries used from this link:

Custom Nodes With Image Clipping in JavaScript: Sample Download

Find out more about Diagramming for JavaScript at https://mindfusion.eu/javascript-diagram.html

JavaScript Database Designer with SQL Generator

We are going to use the JS flowchart library as a database design tool. We will create DB tables, add rows, connect the tables and generate SQL statements that would create the tables.

Here is a screenshot of the application:

Database Designer Application with SQL Generator

Database Designer Application with SQL Generator

I. Project Setup

We need two JavaScript libraries for the flowchart:

  • MindFusion.Common.js
  • MindFusion.Diagramming.js

We copy them in the work folder of the project, where we will put the HTML and the JavaScript code behind. Then we create an HTML file and name it DBDesign.html. There we will reference the two JavaScript libraries:

http://MindFusion.Common.js
http://MindFusion.Diagramming.js

We reference those two libraries at the end of the HTML file, just before the closing tag. This way we are sure that the majority of the browsers will load the scripts correct.

We need an HTML 5 Canvas element for the diagram to draw itself onto and we create one inside a <div> tag:

This page requires a browser that supports HTML 5 Canvas element.

It’s important to set and id for the Canvas element, that’s how we will get it in the JavaScript code behind file.

We create the JS file to be used by this project as DBDesign.js and we place it in the same directory as the two other JS files. We add a reference to it in the HTML page:

http://DBDesign.js

II. UI Controls

The DBDesigner has a line of controls at the bottoms that provide menus – add/edit/delete row, create/delete/rename table and a button for connection info. We create them as buttons:

…..

We add a textarea for the generated SQL and we close the div:

 

When the user presses one of those buttons we show a dialog. The dialogs are forms. Here is the form that renames a table:

III. General Diagram Settings

Let’s start coding the JavaScript methods for the DBDesign application. We use the document.ready method to initialize the Diagram:

var Diagram = MindFusion.Diagramming.Diagram;

var diagram;

$(document).ready(function () {
   // create a Diagram component that wraps the "diagram" canvas
   diagram = MindFusion.AbstractionLayer.createControl(Diagram, null, null, null, $("#diagram")[0]);
……….

});

We use the id of the diagram Canvas that we set in the web page and now create the diagram control. Once we have it we set some properties to it:

// set some Diagram properties.
diagram.setBehavior(Behavior.LinkTables);
diagram.setAllowSelfLoops(false);
diagram.setBackBrush('#F0F0F0');
diagram.setLinkHeadShape('Triangle');
diagram.setLinkHeadShapeSize(4);
diagram.getSelection().allowMultipleSelection = false;

We change the default Behavior of the diagram control to “LinkTables”, which means users would be able to connect table rows. We stop users from creating self loops on tables and add some styling: the back brush is set to light gray, the head shape of links is ‘Triangle’ and we forbid the users to select multiple objects.

The styling of the diagram is done through themes. We create a theme and add to it a style for the table nodes:

// set the Diagram style.
var theme = new Theme();

var tableNodeStyle = new Style();
tableNodeStyle.setBrush({ type: 'LinearGradientBrush', color1: 'rgb(224, 233, 233)', color2: 'rgb(102, 154, 204)', angle: 30 });
tableNodeStyle.setTextColor({ type: 'SolidBrush', color: 'rgb(45, 57, 86)' });
tableNodeStyle.setStroke('rgb(192, 192, 192)');

The tableNodeStyle sets the brush, text color and stroke for the tables. Let’s tell the theme object that this is the style for table nodes:

theme.styles['std:TableNode'] = tableNodeStyle;

And let’s tell the diagram control that it has a theme:

diagram.setTheme(theme);

Link styling is done in the same way and you can find the code in the *.zip file that is available for download.

IV. Events

Handling events is the most important part of this application. We have events raised by the diagram elements and we have events that are raised by the JavaScript buttons. Let’s start with the js buttons. When the web page is loaded there is a single button active from the row of buttons available at the bottom of the page – “Create table”. In the document.ready() method we wire the button with an event:

$('#btnCreateTable').button().click(function (event) { createTable(); });

This event calls the createTable method that generates a TableNode instance:

function createTable() {
	// create a new table with the specified extent
	var table = diagram.getFactory().createTableNode(
				15 + tableCount * 3, 15 + tableCount * 4, 50, 60);
	table.setText("Table" + tableCount++);
	table.redimTable(2, 0);
	table.setScrollable(true);
	table.setConnectionStyle(ConnectionStyle.Rows);

	// set the first column to resize with the table
	table.getColumn(0).columnStyle = ColumnStyle.AutoWidth;

	generateSQL();
}

The createTableNode method accepts as arguments the x and y coordinates of the new TableNode and its width and height. We create initially the table with two columns and no rows. By default the tables can be scrolled and the links connect table rows.

The generateSQL method is a simple one – it just creates an SQL table. You can expand the sample with more complicated SQL statements but in our case we just create a table with the columns that were set to the TableNode:

function generateSQL() {
   var text = '';

   // enumerate all tables in the current diagram
   ArrayList.forEach(diagram.nodes, function (table) {
   text += "CREATE TABLE " + table.getText() + "\r\n(";

   // enumerate all rows of a table
   for (var r = 0; r < table.cells.rows; ++r) {
   // get text of cells in current row
   text += "\t" + table.getCell(0, r).getText() + " " + table.getCell(1, r).getText();
   if (r < table.cells.rows - 1)
	 text += ",\r\n";
   }
	text += "\r\n);\r\n\r\n";
   });

  $('#generatedSql')[0].innerHTML = text;
}

When the SQL text is generated we assign it to the textarea instance that we created.

V. Diagram Events

Here we will talk about the events fired by the diagram control. Once a table is created the users can double click on it to create new rows, edit or delete existing rows. This happens when we handle the nodeDoubleClicked event:

diagram.addEventListener(Events.nodeDoubleClicked, function (sender, args) {
	if (tblClicked != args.getNode()) {
		tblClicked = args.getNode();
	}
….

});

Here we identify the table that is clicked and then we have to decide which dialogue to show:

if (tblClicked) {
		var cellClicked = tblClicked.cellFromPoint(args.getMousePosition());
		if (cellClicked) {
			rowClicked = cellClicked.row;
			editRowOpen();
		}
		else if (tblClicked.hitTestManipulators(args.getMousePosition()) == null) {
		if (args.getMousePosition().y <= tblClicked.getBounds().y + tblClicked.getCaptionHeight())
			renameTableOpen();
			else
			addRowOpen();
		    }
		}

If an existing cell is clicked we open the editRow form. If the caption of the table was clicked we open the form for rename of a table. If none of those, we open the form that adds a new row.

Let’s look how the addRow dialogue opens:

function addRowOpen() {
  var table = tblClicked || diagram.getActiveItem();

  if (!table || !AbstractionLayer.isInstanceOfType(TableNode, table))
	return;

   addRowDialog.dialog("open");
}

the method calls the dialog method of addRowDialog. At the beginning of the js file we have declared a variable:

var addRowDialog = null

Then we create the addRowDialog object:

addRowDialog = $("#addRow-dialog").dialog({
		autoOpen: false,
		resizable: false,
		height: 'auto',
		width: 250,
		modal: false,
		buttons: {
			"OK": addRow,
			Cancel: function () {
				addRowDialog.dialog("close");
			}
		},
		close: function () {
			addRowType.val("NUMBER");
			addRowType.selectmenu("refresh");
			addRowForm[0].reset();
		}
	});
	addRowForm = addRowDialog.find("form").on("submit", function (event) {
		event.preventDefault();
		addRow();
	});

Here we create the dialog that has auto height, width of 250 and two buttons: OK and Cancel. The Cancel button closes the dialog. When the user has pressed OK the form is submitted and the addRow method is called.

The form that shows is defined in the HTML page and looks like that:

NUMBER CHAR(32) DATE VARCHAR BLOB

The addRow method gets the clicked table and gets the two cells at the last row. It gets the text that was chosen in the dialog and assigns it to the cells. Then the dialog is closed and the SQL is generated once again.

function addRow() {
	var table = tblClicked || diagram.getActiveItem();

	if (!table || !AbstractionLayer.isInstanceOfType(TableNode, table))
		return;

	table.addRow();

	var lastRow = table.cells.rows - 1;

	// use the cell indexer to access cells by their column and row
	table.getCell(0, lastRow).setText(addRowName[0].value);
	table.getCell(1, lastRow).setText(addRowType[0].value);

	// close the dialog
	addRowDialog.dialog("close");

	// refresh SQL definition
	generateSQL();
}

And that’s the end for this tutorial. You can download the sample together with the necessary JavaScript libraries from this link:

Download the JavaScript Database Designer Application

Find out more about MindFusion JavaScript Diagram Library at https://mindfusion.eu/javascript-diagram.html