Build an Adjustable Network Diagram with Code

Here is a simple diagram in the main drawing UI (also known as the "structure editor"), in which it can be adjusted and extended, and in the code editor, where you can experiment with the code. (There is no need to sign in to experiment - but signing in is required to save your work.)

It is implemented by the following code:


pj.require('/diagram/graph.js','/shape/circle.js','/shape/arcArrow.js',
function (graphP,circlePP,arrowPP) {
  var geom = pj.geom,svg = pj.svg,ui = pj.ui;
  var item = svg.Element.mk('<g>');// the root of the diagram we are assembling
  var graph = item.set('graph',graphP.instantiate());
  var circleP = graph.installAsVertexPrototype(circlePP);
  var arrowP = graph.installAsEdgePrototype(arrowPP);
  circleP.r = 12;
  circleP.fill = 'blue';
  var circle1 = graph.addVertex();
  var circle2 = graph.addVertex();
  circle1.__moveto(geom.Point.mk(-50,0));
  circle2.__moveto(geom.Point.mk(50,0));
  // set the parameters of the edge prototype
  arrowP.stroke = 'orange';
  arrowP.radius = 1; // radius of the arc as a multiple of arrow length
  arrowP.tailGap = 7; // gap between tail of arrow and its designated start point
  arrowP.headGap = 7; // gap between head of arrow and its designated end
  arrowP.solidHead = false;
  graph.connectVertices(circle1,circle2);
  graph.connectVertices(circle2,circle1);
  return item;
});

This example illustrates building a diagram using operations defined by its diagram type. Diagram types are external components, rather than anything built into the PrototypeJungle platform itself. The platform allows adding as many diagram types as you like, each with its own API. In this case, the diagram type is a network - a collection of nodes connected by arrows or lines, defined in /diagram/graph.js. Such a structure is referred to as a graph in mathematical terminology. In this terminology the nodes are called "vertices" and the connectors "edges" - a terminology adopted in the API.

Now, lets walk through the code.


    pj.require('/diagram/graph.js','/shape/circle.js','/shape/arcArrow.js',
    function (graphP,circlePP,arrowPP) {

pj The PrototypeJungle implementation is installed under the one global variable, prototypeJungle, with pj as a synonym (like $ for jQuery). Furthermore, the PrototypeJungle implementation is itself a prototype tree, with functionality allocated under children such as pj.geom, pj.svg, and pj.ui.

Aside:Doing things this way may seem old-school in that it forgoes use of flexible module systems such as commonJS, AMD, or ES6 modules. However, the hierarchical nature of the implementation is needed for the serialization scheme to work properly, so this flexibility is sacrificed for a reason. Of course, whatever module system is preferred may be used in projects which include the prototypeJungle libraries. Ironically, PrototypeJungle has its own module-like capability in pj.require, but this is used in external contexts, not the construction of the prototypeJungle tree itself.

pj.require(... binds the variables graphP, circlePP and arrowPP to the components defined in '/diagram/graph.js', '/shape/circle.js', and '/shape/arcArrow.js', respectively. Moving on:


    var item = svg.Element.mk('<g/>');// the root of the diagram we are assembling

pj.svg.Element.mk creates a prototype tree from SVG markup.


    var graph = item.set('graph',graphP.instantiate());

installs an instantiation of the graph component under the root. Instantiation, a fundamental operation in PrototypeJungle, is explained here.

item.set('name',ch) has the effect of item.name = ch, but also assigns item as the parent of ch (PrototypeJungle items are trees, as explained here).


    var circleP = graph.installAsVertexPrototype(circlePP);

This defines the prototype that will be used for vertices in this graph as a circle. Note that installAsVertexPrototype is a method of the graph component, defined externally to the PrototypeJungle platform.


    var arrowP = graph.installAsEdgePrototype(arrowPP);

has similar effect.


    var circle1 = graph.addVertex();

instantiates the vertex prototype assigned by installAsVertexPrototype.


  circle1.__moveto(geom.Point.mk(-50,0));

moves the resulting circle to the given position. __moveto and geom.Point.mk are operations defined by the platform.

circle2 is created and moved analogously.


 graph.connectVertices(circle1,circle2);

instantiates the edge prototype (in this case an arrow), and connects its ends to circle1 and circle2 respectively. graph.connectVertices(circle2,circle1); runs an arrow in the other direction.

The diagram can also be constructed without use of the graph component, but then the arrows won't be automatically updated with dragging of the circles - the graph component handles that. This more direct approach is presented here to give a better feeling for how to build things outside of the context of particular diagrams. The approach involves these alternative snippets of code for introducing the circles and arrows:


  var circleP = item.set('circleP',circlePP.instantiate());
  item.set('circle1',circleP.instantiate()).__show();
  item.set('circle2',circleP.instantiate()).__show();
  
and

  var arrowP = item.set('arrowP',arrowPP.instantiate());
  item.set('arrow1',arrowP.instantiate()).__show();
  item.set('arrow2',arrowP.instantiate()).__show();
  

The __show()s are needed because items in the catalog are initially hidden by convention, as they normally serve as prototypes, which should be invisible.

Since there is no autopositioning of the arrows, we also need:


   var p1 = geom.Point.mk(-50,0);
   var p2 = geom.Point.mk(50,0)
   item.arrow1.setEnds(p1,p2); //set start and end points of the arrow
   item.arrow2.setEnds(p2,p1);
   

This variant of the code can be found here.