Using Flexbox Layout to Arrange Diagram Components in React

This tutorial demonstrates how to use the flexbox layout to arrange the various diagram controls that come with the diagram library for React. The controls we will arrange are DiagramView , NodeListView , Ruler , Overview, Zoomer and Ruler. This is a screenshot of the final application:

We build our project using the React framework and MindFusion diagram library for React available from https://www.npmjs.com/package/diagram-library-react.

I. Initial Project Setup

We open a terminal and navigate to the folder where we want to create our application. We use create-react-app to initialize an empty React application:

npx create-react-app flex-diagram

As you we have named our application flex-diagram. Once the initial React setup is complete, we install the diagram:

npm install diagram-library-react

Once we are done with that we can start coding our application. We add a new JavaScript file, which we name DiagramApp.js. In it we first import the default React namespace and declare a member import of the Component class. Then we declare default import of MindFuson’s diagram library under the name ‘mf’. Finally, we declare member imports of the control classes we intend to use: DiagramView, Overview, NodeListView, ZoomControl and Ruler:

import React, { Component } from 'react';
import mf from 'diagram-library';
import { DiagramView, Overview, NodeListView, ZoomControl, Ruler } from 'diagram-library-react';

Then we can create our class DagramApp, which extends component. We will need a constructor, so we declare one. In it we create an instance of the Diagram class, which represents the diagram. Note that this is not the DiagramView for React class, which renders the diagram. This is the interactive, diagram, which is actually rendered by DiagramView.

class DiagramApp extends Component {
  constructor(props) {
    super(props);

    // create the diagram
    var diagram = new mf.Diagramming.Diagram();
    ...........................

Then we create an array of ShapeNode instances, which will be used by the NodeListView control. Again, the NodeListView is a UI component that renders the nodes, but it does not create them. They are provided to it as a parameter.

var nodes = [];
    var shapes = ["Actor", "RoundRect", "Triangle", "Decision"];
    for (var i = 0; i < shapes.length; ++i) {
      var node = new mf.Diagramming.ShapeNode(diagram);
      node.setText(shapes[i]);
      node.setShape(shapes[i]);

      nodes.push(node);
 }

We create an array just with the id-s of the ShapeNode -s, which we will use as labels for them as well. You can check the id-s of the available predefined ShapeNode -s at https://www.mindfusion.eu/onlinehelp/jsdiagram/index.htm?CC_refTable_of_Predefined_Shapes_4.htm.

Finally, we need to store the diagram, together with the nodes and the shape id-s in the React state. We will need them when we create the HTML for our application. In React, the way to access class variables is to store them in the state.

    this.state = {
      diagram: diagram,
      nodes: nodes,
      captions: shapes
    };

II. Arranging the UI with CSS and the Flexbox Layout

Our flexbox layout has this structure:

We have colored the different areas of the page in different colors to show you the sections in the flexbox layout. The main container takes 100% of the space e.g. the whole page. We fix its height to be at least 500px. The layout of the flexbox is row without wrap, which means that the elements will not be rearranged in severa rows by resize. That is exactly what we need. Here is the code of the container style:

.container{
  display: flex;
  flex: 1 1 100%;
  min-height: 500px;
  flex-flow: row nowrap;
  background-color: azure;
  }

The first item in the container is the vertical column where we want to place the Overview and NodeListView controls under each other. This column needs to stay fixed size. We do not want it resized when the user resizes the page. It is the diagram in the middle that must be resized when the available space changes. We call the column “left-sidebar” and set its ‘flex-flow’ to column. We use again no wrap to stop the layout placing the two controls next to each other, if the available space gets too short. We also fix the width of this element to 200 pixels, which suits our controls fine.

.left-sidebar{
  flex: 0 0 200px;
  display: flex;
  flex-flow: column nowrap;
  background-color: beige;
  }

There is difference in how we want the Overview an NodeListView controls to take space. The overview requires fixed amount of space. We want the extra space to be given to the NodeListView. That is why we add one more style to be applied to it:

.item {
  align-self: stretch;
  background-color:lightpink;
  flex: 1 1 300px;  
  }

We give 1 as a value to flex-grow and flex-shrink, indicating that we expect this element to get resized rather than the other items in the right column.

We are ready to deal with the diagram now, which is in the middle. Here it is important to indicate that this element will stretch to take all the available place. Moreover, when the space gets smaller, it is this item that must shrink:

.center-area {
     align-self: stretch;
     background-color:chartreuse;
     flex: 10 10 500px;
     max-height: 800px;
     overflow: hidden;
}

We set big values to the flex-grow and ‘flex-shrink’ attributes and set the flex-basis to 500 px. We must restrict the height of the diagram to 800px, because it could get really long. Important attribute it ‘align-self’, which is applied to items arranged by flexbox containers, not to the container itself. Here we use this attribute to tell the diagram to stretch itself, when space is available. The overflow attribute is hidden to avoid rendering of needless scrollbars in large diagrams.

Finally, the right sidebar will host the Zoom control. It is similar to the left sidebar, but it does not have a ‘special’ item such as the NodeListView. The Zoom control is a UI control with fixed size and does not need special handling. We just need to make sure no extra space is diverted to it, if the window gets too big:

.right-sidebar{
  flex: 0 0 50px;
  background-color: lavender;  
  }

And that was the last style for the last element that we need to define. Now we are ready to apply those styles to the real React components and arrange them.

III. The ‘render’ Method

The first thing we declare in the ‘render’ method of our new DiagramApp React component are some properties:

 var props = {
      "id": "diagram1",
      "linkHeadShapeSize": 2,
      "routeLinks": true,
      "roundedLinks": true,
      "backBrush": "#e0e9e9"
    };

These are properties for the Diagram instance and we will provide them through the JavaScrpt spread operator. Then in the return statement we first declare the container div element. Its style is “container”. In it we declare the left sidebar, which will hold the overview and the NodeListView. We declare them as well:

return (      

        <div className="container">
         
            <div className="left-sidebar">
           
              <Overview diagram={this.state.diagram}></Overview>  
  		<NodeListView className="item" 
                nodes={this.state.nodes}
                captions={this.state.captions}
              ></NodeListView>        
             
             
            </div>            

        </div>      

    );

The Overview is bound to the diagram, which we keep in the state object. The NodeListView requires list with the nodes and captions, if we want to have captions over nodes. We want and we have stored the nodes and the captions in state as well.

After that is turn for the diagram. It will take the central area and its class is ‘center-area’. In addition to Diagram, we will use the Ruler control as well. We place it first and nest the diagram inside it. We use the spread operator to assign to the diagram the properties, which we have initialize at the beginning of the method.

<div className="center-area">              
            <Ruler style={{ width: "100%", height:"800px" }}>
                <DiagramView diagram={this.state.diagram} 
                  {...props}
                  />
            </Ruler>
            </div>

Finally, we declare the right sidebar, which contains the Zoom control. The Zoom control also needs to be bound to a diagram instance and we use state to get reference to our diagram:

         
          <div className="right-sidebar">
          <ZoomControl diagram={this.state.diagram}></ZoomControl>        
        </div>

With that our component is ready. We need only to place it in index.js instead of the default App Component:

import DiagramApp from './DiagramApp';

ReactDOM.render(
 
    ,
  document.getElementById('root')
);

We can run the sample with

npm start

Then you can see your application on port 3000 on localhost.

You can download the full code structured as npm project from this link:

Download Full Source Code

For technical question, please use the JS Diagram forum: https://mindfusion.eu/Forum/YaBB.pl?board=jsdiag_disc

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.

The CanvasLayer in JavaScript Maps

In this blog post we are going to use the CanvasLayer of the JavaScript map library to build a web page that renders a city map with overlay polygons of the districts, covered by various couriers of a company. Each area is a separate in different color. Here is the final result:

Canvas Layer in MindFusion Map Control

Continue reading

Custom Painting of Resources in Java Scheduler

In this blog post we will explain how to color cells and resources in the Resource view of the calendar based on a certain criteria. In our case we take the “Resource Table” sample from the Samples for the Java Swing Scheduler and we will edit its code to color the header and background of cells that correspond to given resources, in our sample it is an employee:

We will also add tooltips that show when the mouse is over cells that correspond to this employee:

Continue reading

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.