JS Plumb in React

Posted By : Akshay Singh | 30-Dec-2020

JS Plumb in React

Flowcharting & drawing lines on html using jsPlumb


 

Time for more & more AI & AR web apps has come, & with advance in technology, clients & customers require the web service or app to provide more information, by showing some animations, & dynamic drawings over the subject such as lines connecting the various parts of the AI subject, which add a lot more interactivity & comprehension of user with the service, also it boosts the AI appeal & quality of the service.

 

A similar scenario can be thought of a simple AI project, where a user uploads an invoice in format of image/pdf , it gets processed on backend, the response of the document contains the JSON for different characters recognized from the document uploaded, which then has to be shown on either side of the invoice document image on the screen in list format.

Also the recognized characters from the uploaded document have to be shown highlighted on the screen over the image of the document.

 

On clicking any of the listings, a connector line should appear connecting the list item to its corresponding highlighted characters. 

 

                                                                                



 

That appears to be a Rocket science. Isn’t it ? Let's check out.

 

How JsPlumb works ?

 

JSplumb is a flowcharting library which is used for showing real flowcharts on html page. It has draggable elements, moveable connectors, different types of connectors. More can be read on 

https://docs.jsplumbtoolkit.com/toolkit/current/index.html

 

To show connection between 2 points, which can be 2 html elements like <div> or <rect>, both the pairing elements need to be same. viz - <div> to <div> or <rect> to <rect>

Secondly, both elements should have unique ids & have jsPlumb endpoints added right after the component gets loaded onto html

 

Such as,

Source element  - <rect id=”name_source”></rect>

Target element - <rect id=”name_target”></rect>

 

Now endpoints should be made by this jsPlumb method for all source & target elements

 

jsPlumbInstance.addEndpoint(<ELEMENT ID>,<PROPERTIES SET JSON FOR SOURCE OR TARGET ENDPOINT TYPE>, {

anchor: "<POSITION OF THE ENDPOINT ON THE ELEMENT LEFT/RIGHT>",

uuid: “<ELEMENT ID>”,

});

 

  1. For Source --

jsPlumbInstance.addEndpoint(“name_source”, sourceEndpoint, {

anchor: "RightMiddle",

uuid: “name_source”,

});

 

  1. For Target --

jsPlumbInstance.addEndpoint(“name_target”, targetEndpoint, {

anchor: "LeftMiddle",

uuid: “name_target”,

});

 

The ‘sourceEndpoint’ & ‘targetEndpoint’ used in the snippets above are also a set of key value pairs provided by jsPlumb, that will be discussed later.

 

We are 90% done here, we have elements to be connected, we have their ids, we have setup their endpoints, now we can connect these elements using this method

 

jsPlumbInstance.connect({

uuids: [‘<SOURCE ELEMENT ID>’, ‘<TARGET ELEMENT ID>’]

});

 

We need to pass the respective ids for the elements we want to connect

 

jsPlumbInstance.connect({

uuids: [‘name_source’, ‘name_target’]

});

 

We’ll get connected elements like this

 

Setup 

 

JS Plumb is not a node library, hence it cannot be npm installed into the project.

 

  1. We need to acquire this using script tags.

 

Inside our React js project, go to /public/index.html

& use these script tags in the head section

 

<link rel="stylesheet" href="./css/jsplumbtoolkit-defaults.css" />

<link rel="stylesheet" href="./css/main.css" />

<link rel="stylesheet" href="./css/jsplumbtoolkit-demo.css" />

<link rel="stylesheet" href="./demo.css" />

 

Next, use these tags in body section

 

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

<script src="./js/demo-list.js"></script>

 

Now we’re ready to use the JSplumb instance inside our project

 

  1. Now , on the component where we receive the response for the uploaded content, & need to show the connectors for the response, we can use the JS Plumb instance

 

Before that, we need to know what kind of response we need for the working of jsPlumb

 

For each node , whether starting or ending, we need some coordinates to show the elements on, so the response needs to have atleast this set of data for each node




 

{

  "source_key": "name_source",

  "target_key": "name_target",

  "value": " <OCR value obtained from response>",

  "boundingCoordinates": [

    {

      "x1": 0.0856633,   

      "y1": 0.24884406,

      "x2": 0.21594289,

      "y2": 0.26523748,

      "x3": 0.21594289,

      "y3": 0.27742749,

      "x4": 0.0856633,

      "y4": 0.26229507

     }

    ]

 }

 

On the basis of which, multiple elements for source can be rendered on either side of document image with listing pairs such as

 

{

 sourceElementsSet.map(item => {

   return (

    <svg id='svgElement0'>                                                      

      <g id='boundingbox_group_0'>   

       <rect x= {item.boundingCoordinates[0].x1*100+'%'}

        y = {item.boundingCoordinates[0].y1*100+'%'}

        width={(item.boundingCoordinates[0].x2*100 -           item.boundingCoordinates[0].x1*100)+'%'}

        height={(item.boundingCoordinates[0].y4*100 -   item.boundingCoordinates[0].y1*100)+'%'}

        id={item.key}

        class="annotation-symbols-bounding-box"></rect>

      </g>

    </svg>

    )

  })

}

 

 

Where sourceElementsSet is our array of objects with prescribed set of data as shown above this code. We can render target elements in similar manner using say targetElementsSet , which will contain target elements coordinates to be shown on the document image.

 

  1. Now the endpoints for all rendered elements need to be assigned using addEndPoint function discussed above. , lifecycle hooks such as componentDidMound would be better place to do this

 

First lets import following items

 

import $ from "jquery";

import '../../css/font.css';

import '../../css/jsplumbtoolkit-defaults.css';

import '../../css/main.css';

import '../../css/jsplumbtoolkit-demo.css';

import '../../css/demo.css';

 

 

Then , using this function inside componentDidMount such as , also we need to pass instance of the class to this function as ‘self’ so that it can access the class instance ‘this’ inside function to use class methods.

 

For example, this.setState({myVar}); will be used as self.setState({myVar});

 

 

componentDidMount() {

  this.setJsPlumbCanvas(this); // passing ‘this’ to access this keyword 

 }

 

setJsPlumbCanvas = (self) =>

   window.jsPlumb.ready(function () {

     var offsetCalculators = {

       "RECT": function (el, parentOffset) {

         var x = el[0].getBoundingClientRect().x,

           y = el[0].getBoundingClientRect().y +                  (el[0].getBoundingClientRect().height / 2);

         return {

           left: x,

           top: y

         };

       }

     };

     var sizeCalculators = {

       "RECT": function (el) {

         var w = 0,

           h = 0;

         return [w, h];

       }

     };

     var originalOffset = window.jsPlumbInstance.prototype.getOffset;

     var originalSize = window.jsPlumbInstance.prototype.getSize;

 

     window.jsPlumbInstance.prototype.getOffset = function (el) {

       var tn = el.tagName.toUpperCase();

       if (offsetCalculators[tn]) {

         return offsetCalculators[tn]($(el), $(el).parent().offset());

       } else

         return $(el).offset();

     };

 

     window.jsPlumbInstance.prototype.getSize = function (el) {

       var tn = el.tagName.toUpperCase();

       if (sizeCalculators[tn]) {

         return sizeCalculators[tn]($(el));

       } else

         return [$(el).outerWidth(), $(el).outerHeight()];

     };

    

     instance = (window.jsp = window.jsPlumb.getInstance({

       DragOptions: {

         cursor: "pointer",

         zIndex: 2000

       },

       ConnectionOverlays: [

         [

           "Label",

           {

             location: 0.1,

             

             id: "label",

             cssClass: "aLabel",

           },

         ],

       ],

       Container: "canvas",

     }));

     instance.registerConnectionType("basic", basicType);

     //Checked

     for (let i = 0; i < self.state.sourceKeys.length; i++) {

       for (let j = 0; j < self.state.sourceKeys[i].length; j++) {

         //For Left Node end points  self.addEndPoint(self.state.sourceKeys[i][j].key,sourceEndpoint,"RightMiddle") ;

       }}

 

     for (let k = 0; k < self.state.targetKeys.length; k++) {

       for (let l = 0; l < self.state.targetKeys[k].length; l++) {

         //For Right Node end points      self.addEndPoint(self.state.targetKeys[k][l].key,targetEndPoint,"LeftMiddle") ;

       }

     }

   });

 }


 

  1. Now, on click of each list item in the list we rendered above, we can bind a function on click event, where we can get uuids of the 2 nodes & establish a connection between them.

 

Making an onClick event on the items we rendered above,

 

     <rect x= {item.boundingCoordinates[0].x1*100+'%'}

      onClick = {() => this.handleConnectionSetup(item)}

      y = {item.boundingCoordinates[0].y1*100+'%'}

 

handleLeftClick = (item) => {

 

   let {source_key,target_key} = item;

 

   if (connection) {

     instance.deleteConnection(connection);

   }

 

   if (rightKey) {

       connection = instance.connect({

         uuids: [source_key, target_key],

       });

   }

 }

 

      

Thats all about setting up flowcharting in react app using jsPlumb, Thanks for reading.




 

Request For Proposal

Sending message..

Ready to innovate ? Let's get in touch

Chat With Us