UNPKG

cytoscape-fcose

Version:

The fCoSE layout for Cytoscape.js by Bilkent with fast compound node placement

1,482 lines 52.8 kB
// define default stylesheet
let defaultStylesheet =  [
  {
    selector: 'node',
    style: {
      'background-color': '#bdd3d4',
      'label': 'data(id)',
      'text-valign': 'center',
      'background-opacity': 0.7
    }
  },         

  {
  selector: ':parent',
    style: {
//      'background-opacity': 0.333,
      'background-color': '#e8e8e8',
      'border-color': '#DADADA',
//      'border-width': 3,
      'text-valign': 'bottom'
    }
  },

  {
    selector: 'edge',
    style: {
      'curve-style': 'straight',
      'line-color': '#bdd3d4'
    }
  },

  {
    selector: 'node:selected',
    style: {
      'background-color': '#33ff00',
      'border-color': '#22ee00'
    }
  },
  
  {
    selector: 'node.fixed',
    style: {
      'shape': 'diamond',
      'background-color': '#9D9696',
    }
  }, 
  
  {
    selector: 'node.fixed:selected',
    style: {
      'background-color': '#33ff00',
    }
  },
  
  {
    selector: 'node.alignment',
    style: {
      'shape': 'round-heptagon',
      'background-color': '#fef2d1',
    }
  }, 
  
  {
    selector: 'node.alignment:selected',
    style: {
      'background-color': '#33ff00',
    }
  },  

  {
    selector: 'node.relative',
    style: {
      'shape': 'rectangle',
      'background-color': '#fed3d1',
    }
  }, 
  
  {
    selector: 'node.relative:selected',
    style: {
      'background-color': '#33ff00',
    }
  },

  {
    selector: 'edge:selected',
    style: {
      'line-color': '#33ff00'
    }
  }                 
];

let cy = window.cy = cytoscape({
  container: document.getElementById('cy'),
  ready: function(){              
    let layoutUtilities = this.layoutUtilities({
      desiredAspectRatio: this.width()/this.height()
    });

    this.nodes().forEach(function(node){
      let size = Math.random()*40+30;
      node.css("width", size);
      node.css("height", size);
    });

    let initialLayout = this.layout({name: 'fcose', step: 'all', animationEasing: 'ease-out'});
    initialLayout.pon('layoutstart').then(function( event ){
      constraints.fixedNodeConstraint = JSON.parse(JSON.stringify(sample1_constraints.fixedNodeConstraint));
      clearConstraintListTable();
      fillConstraintListTableFromConstraints(); 
    });
    initialLayout.run();     
  },
  layout: {name: 'preset'},
  style: defaultStylesheet,
  elements: {
        nodes: [
      {data: {id: 'n1'}},
      {data: {id: 'n2'}},
      {data: {id: 'n3', parent: 'n8'}},
      {data: {id: 'n5'}},
      {data: {id: 'n6', parent: 'n8'}},
      {data: {id: 'n7', parent: 'n8'}},
      {data: {id: 'n8'}},
      {data: {id: 'f1'}, classes: ['fixed']},
      {data: {id: 'f2'}, classes: ['fixed']},
      {data: {id: 'f3', parent: 'n8'}, classes: ['fixed']}, 
    ],
    edges: [
      {data: {source: 'n1', target: 'f1'}},
      {data: {source: 'n1', target: 'n3'}},
      {data: {source: 'f1', target: 'n2'}},
      {data: {source: 'f1', target: 'n3'}},
      {data: {source: 'n3', target: 'f2'}},
      {data: {source: 'f2', target: 'n5'}},
      {data: {source: 'n5', target: 'n8'}},
      {data: {source: 'n6', target: 'n3'}},
      {data: {source: 'n6', target: 'n7'}},
      {data: {source: 'n6', target: 'f3'}}
    ]
  },
  wheelSensitivity: 0.3
});

let constraints = {
  fixedNodeConstraint: undefined,
  alignmentConstraint: undefined,
  relativePlacementConstraint: undefined
};

// Handle Menu ------------------------------------------

// Graph file input
document.getElementById("openFile").addEventListener("click", function () {
  document.getElementById("inputFile").click();
});

$("body").on("change", "#inputFile", function (e, fileObject) {
  var inputFile = this.files[0] || fileObject;

  if (inputFile) {
    var fileExtension = inputFile.name.split('.').pop();
    var r = new FileReader();
    r.onload = function (e) {
      cy.remove(cy.elements());
      var content = e.target.result;
      if (fileExtension == "graphml" || fileExtension == "xml") {
        cy.graphml({layoutBy: 'null'});
        cy.graphml(content);
//        updateGraphStyle();
      } else if (fileExtension == "json") {
        cy.json({elements: JSON.parse(content)});
//        updateGraphStyle();
      } else {
        var tsv = cy.tsv();
        tsv.importTo(content);
      }
    };
    r.addEventListener('loadend', function () {      
      onLoad();
      clearConstraintListTable();      
      constraints.fixedNodeConstraint = undefined;
      constraints.alignmentConstraint = undefined;
      constraints.relativePlacementConstraint = undefined;
      
      document.getElementById("nodeList").addEventListener("change", function(){
        document.getElementById("fixedNodeX").value = Math.round(cy.getElementById(document.getElementById("nodeList").value).position("x"));
        document.getElementById("fixedNodeY").value = Math.round(cy.getElementById(document.getElementById("nodeList").value).position("y"));
      });
      
//      if (inputFile.name == "samples/sample1.graphml") {
//        cy.nodes().forEach(function (node, i) {
//          let width = [30, 70, 110];
//          let size = width[i % 3];
//          node.css("width", size);
//          node.css("height", size);
//        });
//      }
       
    });
    r.readAsText(inputFile);
  } else {
    alert("Failed to load file");
  }
  $("#inputFile").val(null);
});

let updateGraphStyle = function () {
  cy.nodes().forEach(function (node) {
    node.style({
      'background-image': node.data('background-image'),
      'width': node.data('bbox').w,
      'height': node.data('bbox').h,
      "border-width": node.data('border-width'),
      "border-color": node.data('border-color'),
      "background-color": node.data('background-color'),
      "background-opacity": node.data('background-opacity'),
      "background-fit": "cover",
      "background-position-x": "50%",
      "background-position-y": "50%",
      "text-wrap": "wrap",
      "font-size": node.data('font-size'),
      "color": node.data('color')
    });

    if (node.data('label')) {
      node.style({
        'label': node.data('label')
      });
    }
  });

  cy.edges().forEach(function (edge) {
    edge.style({
      'width': edge.data('width'),
      "line-color": edge.data('line-color')
    });
  });
};

$("body").on("change", "#inputConstraint", function (e, fileObject) {
  var inputFile = this.files[0] || fileObject;

  if (inputFile) {
    var fileExtension = inputFile.name.split('.').pop();
    var r = new FileReader();
    r.onload = function (e) {
      var content = e.target.result;
      if (fileExtension == "json") {
        constraints.fixedNodeConstraint = undefined;
        constraints.alignmentConstraint = undefined;
        constraints.relativePlacementConstraint = undefined;        
        let constraintObject = JSON.parse( content );
        if(constraintObject.fixedNodeConstraint)
          constraints.fixedNodeConstraint = constraintObject.fixedNodeConstraint;
        if(constraintObject.alignmentConstraint)
          constraints.alignmentConstraint = constraintObject.alignmentConstraint;
        if(constraintObject.relativePlacementConstraint)
          constraints.relativePlacementConstraint = constraintObject.relativePlacementConstraint;
        clearConstraintListTable();
        fillConstraintListTableFromConstraints();
      }
    };
    r.addEventListener('loadend', function () {          
    });
    r.readAsText(inputFile);
  } else {
    alert("Failed to load file");
  }
  $("#inputFile").val(null);
});

document.getElementById("exportConstraint").addEventListener("click", function(){
  let constraintString = JSON.stringify(constraints, null, 2);
  download('constraint.json', constraintString);
});

let download = function(filename, text) {
    var pom = document.createElement('a');
    pom.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
    pom.setAttribute('download', filename);

    if (document.createEvent) {
        var event = document.createEvent('MouseEvents');
        event.initEvent('click', true, true);
        pom.dispatchEvent(event);
    }
    else {
        pom.click();
    }
};

/*
// SVG
document.getElementById("saveAsSVG").addEventListener("click", function(){
    let svgContent = cy.svg({scale: 1, full: true});
    let blob = new Blob([svgContent], {type:"image/svg+xml;charset=utf-8"});
    saveAs(blob, "graph.svg");
});

// see http://stackoverflow.com/questions/16245767/creating-a-blob-from-a-base64-string-in-javascript
let b64toBlob = function(b64Data, contentType, sliceSize) {
  contentType = contentType || '';
  sliceSize = sliceSize || 512;

  var byteCharacters = atob(b64Data);
  var byteArrays = [];

  for (var offset = 0; offset < byteCharacters.length; offset += sliceSize) {
    var slice = byteCharacters.slice(offset, offset + sliceSize);

    var byteNumbers = new Array(slice.length);
    for (var i = 0; i < slice.length; i++) {
      byteNumbers[i] = slice.charCodeAt(i);
    }

    var byteArray = new Uint8Array(byteNumbers);

    byteArrays.push(byteArray);
  }

  var blob = new Blob(byteArrays, {type: contentType});
  return blob;
}
*/

// Sample File Changer
let sampleFileNames = {
    "sample5" : unix,
    "sample5_constraints" : unix_constraints,
    "sample6" : chalk,
    "sample6_constraints" : chalk_constraints,    
    "sample7" : uwsn,
    "sample7_constraints" : uwsn_constraints,
    "sample8" : call_graph,
    "sample8_constraints" : callGraph_constraints,
    "sample9" : wsn,
    "sample9_constraints" : wsn_constraints,
};

document.getElementById("sample").addEventListener("change", function(){
  cy.startBatch();
  cy.elements().remove();
  cy.style().clear();
  
  var selectionObject = document.getElementById("sample");
  
  var selected = selectionObject.options[selectionObject.selectedIndex].index;
  if(selected == 0){
    cy.add(elements1);
    applyPostLoadOperations(selected);
  }
  else if(selected == 1){
    cy.add(elements2);
    applyPostLoadOperations(selected);
  }
  else if(selected == 2){
    cy.add(elements3);
    applyPostLoadOperations(selected);
  }
  else if(selected == 3){
    cy.add(elements4);
    applyPostLoadOperations(selected);
  }
  else if(selected == 4){
    cy.add(elements5);
    applyPostLoadOperations(selected);
  }    

  function applyPostLoadOperations(selected){
    cy.nodes().forEach(function(node){
      let size = Math.random()*40+30;
      node.css("width", size);
      node.css("height", size);   
    });
    cy.style(defaultStylesheet);
    
    clearConstraintListTable();
    constraints.fixedNodeConstraint = undefined;
    constraints.alignmentConstraint = undefined;
    constraints.relativePlacementConstraint = undefined;
    
    if(selected == 0){
      if(sample1_constraints.fixedNodeConstraint)
        constraints.fixedNodeConstraint = JSON.parse(JSON.stringify(sample1_constraints.fixedNodeConstraint));
      clearConstraintListTable();
      fillConstraintListTableFromConstraints(); 
    }    
    if(selected == 1){
      if(sample2_constraints.alignmentConstraint)
        constraints.alignmentConstraint = JSON.parse(JSON.stringify(sample2_constraints.alignmentConstraint));
      clearConstraintListTable();
      fillConstraintListTableFromConstraints(); 
    }
    if(selected == 2){
      if(sample3_constraints.relativePlacementConstraint)
        constraints.relativePlacementConstraint = JSON.parse(JSON.stringify(sample3_constraints.relativePlacementConstraint));
      clearConstraintListTable();
      fillConstraintListTableFromConstraints(); 
    }    
    if(selected == 3){
      if(sample4_constraints.fixedNodeConstraint)
        constraints.fixedNodeConstraint = JSON.parse(JSON.stringify(sample4_constraints.fixedNodeConstraint));
      if(sample4_constraints.alignmentConstraint)
        constraints.alignmentConstraint = JSON.parse(JSON.stringify(sample4_constraints.alignmentConstraint));
      if(sample4_constraints.relativePlacementConstraint)
        constraints.relativePlacementConstraint = JSON.parse(JSON.stringify(sample4_constraints.relativePlacementConstraint));
      clearConstraintListTable();
      fillConstraintListTableFromConstraints(); 
    }     
  }
  
  if (sampleFileNames[selectionObject.value]) {
    //$.getJSON("samples/" + sampleFileNames[selectionObject.value] + ".json", function(json) {
        let json = sampleFileNames[selectionObject.value];
        cy.json(json);
        cy.nodes().forEach(function (node) {
          node.style({
            'width': node.data('bbox').w,
            'height': node.data('bbox').h,
            "border-width": node.data('border-width'),
//            "border-color": node.data('border-color'),
          });

          if (node.data('class') === 'process' || node.data('class') === 'association' || node.data('class') === "dissociation") {
            node.style({
              'background-color': node.data('background-color'),
              'background-opacity': 0.3
            });
          } else {
            node.style({
              'background-image': node.data('background-image'),
//              'background-color': node.data('background-color'),
              'background-opacity': node.data('background-opacity'),
              'background-fit': 'contain',
              'background-position-x': '50%',
              'background-position-y': '50%',
            });
          }

          if (node.data('label')) {
            node.style({
              'label': node.data('label'),
              'text-wrap': 'wrap',
              'font-size': node.data('font-size'),
              'color': node.data('color')
            });
          }
        });
        
        constraints.fixedNodeConstraint = undefined;
        constraints.alignmentConstraint = undefined;
        constraints.relativePlacementConstraint = undefined; 
        
        let constraintObject = sampleFileNames[selectionObject.value + "_constraints"];
        if(constraintObject.fixedNodeConstraint)
          constraints.fixedNodeConstraint = JSON.parse(JSON.stringify(constraintObject.fixedNodeConstraint));
        if(constraintObject.alignmentConstraint)
          constraints.alignmentConstraint = JSON.parse(JSON.stringify(constraintObject.alignmentConstraint));
        if(constraintObject.relativePlacementConstraint)
          constraints.relativePlacementConstraint = JSON.parse(JSON.stringify(constraintObject.relativePlacementConstraint));
        clearConstraintListTable();
        fillConstraintListTableFromConstraints(); 
     // });

  }
  cy.endBatch();

  if(selected == 4){  
    cy.nodes().not(":parent").positions(function(node, i){
      return {
        x: elements5_positions[i].x,
        y: elements5_positions[i].y
      };    
    });
    cy.layout({name: "preset", fit:true, animate:true, animationDuration: 1000, animationEasing: undefined}).run();
  }
  else {
    let finalOptions = Object.assign({}, options);
    finalOptions.step = "all";
    cy.layout(finalOptions).run();
  }
//  cy.layout({name: 'random', padding: 100}).run();
  onLoad();  

  document.getElementById("nodeList").addEventListener("change", function(){
    document.getElementById("fixedNodeX").value = Math.round(cy.getElementById(document.getElementById("nodeList").value).position("x"));
    document.getElementById("fixedNodeY").value = Math.round(cy.getElementById(document.getElementById("nodeList").value).position("y"));
  });
});

// Layout buttons

let options = {
  name: 'fcose',
  quality: "default",
  randomize: true,
  animate: true,
  animationDuration: 1000,
  animationEasing: undefined,
  fit: true,
  padding: 30,
  nestingFactor: 0.1,
  gravityRangeCompound: 1.5,
  gravityCompound: 1.0
};

// Randomize
document.getElementById("randomizeButton").addEventListener("click", function () {
  var layout = cy.layout({
    name: 'random',
    animate: true
  });

  layout.run();
});

// Fcose
document.getElementById("fcoseButton").addEventListener("click", function(){
  let finalOptions = Object.assign({}, options);
  finalOptions.step = "all";
  finalOptions.randomize = !(document.getElementById("incremental").checked);
  
  if(document.getElementById("sample").value == "sample6"){
    finalOptions.nestingFactor = 0.4;
    finalOptions.gravityRangeCompound = 0;
    finalOptions.gravityCompound = 3.0;
  }
  
  if(document.getElementById("sample").value == "sample8"){
    finalOptions.idealEdgeLength = 70; 
  }  
  
  finalOptions.fixedNodeConstraint = constraints.fixedNodeConstraint ? constraints.fixedNodeConstraint : undefined;
  finalOptions.alignmentConstraint = constraints.alignmentConstraint ? constraints.alignmentConstraint : undefined;
  finalOptions.relativePlacementConstraint = constraints.relativePlacementConstraint ? constraints.relativePlacementConstraint : undefined;
  let layout = cy.layout(finalOptions);
  layout.one("layoutstop", function( event ){
    document.getElementById("fixedNodeX").value = Math.round(cy.getElementById(document.getElementById("nodeList").value).position("x"));
    document.getElementById("fixedNodeY").value = Math.round(cy.getElementById(document.getElementById("nodeList").value).position("y"));
  });
  cy.layoutUtilities("get").setOption("randomize", finalOptions.randomize);
//  let start = performance.now();
  layout.run();
//  console.log((performance.now() - start) + " ms" );
});

/*
// CoLa
document.getElementById("colaButton").addEventListener("click", function(){
  
  if(constraints.fixedNodeConstraint) {
    constraints.fixedNodeConstraint.forEach(function(constraint){
      cy.getElementById(constraint.nodeId).position({x: constraint.position.x, y: constraint.position.y});
      cy.getElementById(constraint.nodeId).lock();
    });
  }
  
  let gapInequalities = [];
  if(constraints.relativePlacementConstraint) {
    constraints.relativePlacementConstraint.forEach(function(constraint){
      if(constraint.left) {
        gapInequalities.push({"axis": "x", "left": cy.getElementById(constraint.left), "right": cy.getElementById(constraint.right), "gap": constraint.gap});
      }
      else {
        gapInequalities.push({"axis": "y", "left": cy.getElementById(constraint.top), "right": cy.getElementById(constraint.bottom), "gap": constraint.gap});
      }
    });
  }
  
  let options = {
    name: 'cola',
    animate: false,
    maxSimulationTime: 20000,
    randomize: true,
    unconstrIter: 10, 
    userConstIter: 15, 
    allConstIter: 20,
    handleDisconnected: false,
    edgeLength: 50,
    alignment: constraints.alignmentConstraint ? constraints.alignmentConstraint : undefined,
    gapInequalities: gapInequalities
  };
  
  let layout = cy.layout(options);
  layout.one("layoutstop", function( event ){
    document.getElementById("fixedNodeX").value = Math.round(cy.getElementById(document.getElementById("nodeList").value).position("x"));
    document.getElementById("fixedNodeY").value = Math.round(cy.getElementById(document.getElementById("nodeList").value).position("y"));
  });

  let start = performance.now();
  layout.run();
  console.log((performance.now() - start) + " ms" );
  
  cy.nodes().unlock();
});
*/

// Draft
document.getElementById("draftButton").addEventListener("click", function(){
  let finalOptions = Object.assign({}, options);
  finalOptions.quality = "draft";
  finalOptions.fixedNodeConstraint = constraints.fixedNodeConstraint ? constraints.fixedNodeConstraint : undefined;
  finalOptions.alignmentConstraint = constraints.alignmentConstraint ? constraints.alignmentConstraint : undefined;
  finalOptions.relativePlacementConstraint = constraints.relativePlacementConstraint ? constraints.relativePlacementConstraint : undefined;  
  let layout = cy.layout(finalOptions);
  layout.one("layoutstop", function( event ){
    document.getElementById("fixedNodeX").value = Math.round(cy.getElementById(document.getElementById("nodeList").value).position("x"));
    document.getElementById("fixedNodeY").value = Math.round(cy.getElementById(document.getElementById("nodeList").value).position("y"));
  });
  cy.layoutUtilities("get").setOption("randomize", true);
  layout.run();
});

// Transform
document.getElementById("transformButton").addEventListener("click", function(){
  let finalOptions = Object.assign({}, options);
  finalOptions.step = "transformed";
  finalOptions.fixedNodeConstraint = constraints.fixedNodeConstraint ? constraints.fixedNodeConstraint : undefined;
  finalOptions.alignmentConstraint = constraints.alignmentConstraint ? constraints.alignmentConstraint : undefined;
  finalOptions.relativePlacementConstraint = constraints.relativePlacementConstraint ? constraints.relativePlacementConstraint : undefined;
  
  let layout = cy.layout(finalOptions);
  layout.one("layoutstop", function( event ){
    document.getElementById("fixedNodeX").value = Math.round(cy.getElementById(document.getElementById("nodeList").value).position("x"));
    document.getElementById("fixedNodeY").value = Math.round(cy.getElementById(document.getElementById("nodeList").value).position("y"));
  });            
  layout.run();
});

// Enforce
document.getElementById("enforceButton").addEventListener("click", function(){
  let finalOptions = Object.assign({}, options);
  finalOptions.step = "enforced";
  
  finalOptions.fixedNodeConstraint = constraints.fixedNodeConstraint ? constraints.fixedNodeConstraint : undefined;
  finalOptions.alignmentConstraint = constraints.alignmentConstraint ? constraints.alignmentConstraint : undefined;
  finalOptions.relativePlacementConstraint = constraints.relativePlacementConstraint ? constraints.relativePlacementConstraint : undefined;

  let layout = cy.layout(finalOptions);
  layout.one("layoutstop", function( event ){
    document.getElementById("fixedNodeX").value = Math.round(cy.getElementById(document.getElementById("nodeList").value).position("x"));
    document.getElementById("fixedNodeY").value = Math.round(cy.getElementById(document.getElementById("nodeList").value).position("y"));
  });            
  layout.run();
});

document.getElementById("coseButton").addEventListener("click", function(){
  let finalOptions = Object.assign({}, options);
  
  if(document.getElementById("sample").value == "sample6"){
    finalOptions.nestingFactor = 0.4;
    finalOptions.gravityRangeCompound = 0;
    finalOptions.gravityCompound = 3.0;
  }
  
  if(document.getElementById("sample").value == "sample8"){
    finalOptions.idealEdgeLength = 70; 
  }   

  finalOptions.fixedNodeConstraint = constraints.fixedNodeConstraint ? constraints.fixedNodeConstraint : undefined;
  finalOptions.alignmentConstraint = constraints.alignmentConstraint ? constraints.alignmentConstraint : undefined;
  finalOptions.relativePlacementConstraint = constraints.relativePlacementConstraint ? constraints.relativePlacementConstraint : undefined;
  
  finalOptions.randomize = false;
  finalOptions.step = "cose";  
  let layout = cy.layout(finalOptions);
  layout.one("layoutstop", function( event ){
    document.getElementById("fixedNodeX").value = Math.round(cy.getElementById(document.getElementById("nodeList").value).position("x"));
    document.getElementById("fixedNodeY").value = Math.round(cy.getElementById(document.getElementById("nodeList").value).position("y"));
  });
  cy.layoutUtilities("get").setOption("randomize", finalOptions.randomize);
  layout.run();
});

// Handle Constraints ----------------------------

let onLoad = function(){
  let nodeList = "<select id='nodeList' class='custom-select custom-select-sm' style='width:auto;' onchange='onSelect()'>";
  let simpleNodes = cy.nodes().not(":parent");
  for(let i = 0; i < simpleNodes.length; i++){
    let node = simpleNodes[i];
    let label = (node.data('label'))?(node.data('label')):(node.id());
    if(label.length > 15)
      label = label.substring(0, 12).concat("...");
    nodeList += "<option value='" + cy.nodes().not(":parent")[i].id() + "'>" + label + "</option>";
  }
  let listComponentForFixed = document.getElementById("nodeListColumn");
  listComponentForFixed.innerHTML = nodeList;
  document.getElementById("fixedNodeX").value = Math.round(cy.nodes().not(":parent")[0].position("x"));
  document.getElementById("fixedNodeY").value = Math.round(cy.nodes().not(":parent")[0].position("y"));

  let nodeListRP1 = "<select id='nodeListRP1' class='custom-select custom-select-sm' style='width:auto;' onchange='onSelectRP1()'>";
  for(let i = 0; i < simpleNodes.length; i++){
    let node = simpleNodes[i];
    let label = (node.data('label'))?(node.data('label')):(node.id()); 
    if(label.length > 15)
      label = label.substring(0, 12).concat("...");    
    nodeListRP1 += "<option value=" + cy.nodes().not(":parent")[i].id() + ">" + label + "</option>";
  }

  let nodeListRP2 = "<select id='nodeListRP2' class='custom-select custom-select-sm' style='width:auto;' onchange='onSelectRP2()'>";
  for(let i = 0; i < simpleNodes.length; i++){
    let node = simpleNodes[i];
    let label = (node.data('label'))?(node.data('label')):(node.id());
    if(label.length > 15)
      label = label.substring(0, 12).concat("...");    
    nodeListRP2 += "<option value=" + cy.nodes().not(":parent")[i].id() + ">" + label + "</option>";
  }            

  let listComponentForRP1 = document.getElementById("nodeListColumnRP1");
  listComponentForRP1.innerHTML = nodeListRP1;

  let listComponentForRP2 = document.getElementById("nodeListColumnRP2");
  listComponentForRP2.innerHTML = nodeListRP2;            
};

let onSelect = function(){
  let id = document.getElementById("nodeList").value;
  cy.elements().unselect();
  cy.getElementById(id).select();
};

let onSelectRP1 = function(){
  let id = document.getElementById("nodeListRP1").value;
  cy.elements().unselect();
  cy.getElementById(id).select();
};

let onSelectRP2 = function(){
  let id = document.getElementById("nodeListRP2").value;
  cy.elements().unselect();
  cy.getElementById(id).select();
};

document.addEventListener('DOMContentLoaded', function(){
  document.getElementById("nodeList").addEventListener("change", function(){
    document.getElementById("fixedNodeX").value = Math.round(cy.getElementById(document.getElementById("nodeList").value).position("x"));
    document.getElementById("fixedNodeY").value = Math.round(cy.getElementById(document.getElementById("nodeList").value).position("y"));
  });
});

cy.ready( function(event){
  onLoad();
});

document.getElementById("nodeList").addEventListener("change", function(){
  document.getElementById("fixedNodeX").value = Math.round(cy.getElementById(document.getElementById("nodeList").value).position("x"));
  document.getElementById("fixedNodeY").value = Math.round(cy.getElementById(document.getElementById("nodeList").value).position("y"));
});

cy.on("position", "node", function(event) {
  if(event.target.id() == document.getElementById("nodeList").value){
    document.getElementById("fixedNodeX").value = Math.round(cy.getElementById(document.getElementById("nodeList").value).position("x"));
    document.getElementById("fixedNodeY").value = Math.round(cy.getElementById(document.getElementById("nodeList").value).position("y"));
  }
});

// Adding Constraints
let constraintListTable = document.getElementById("constraintListTable");

document.getElementById("fixedNode").addEventListener("click",function(){
  let nodeId = document.getElementById("nodeList").value;
  let exist = false;
  if(constraints.fixedNodeConstraint){
    constraints.fixedNodeConstraint.forEach(function(constraintObject){
      if(constraintObject.nodeId == nodeId){
        exist = true;
      }
    }); 
  }
  if(!exist){
    let fixedNode = {nodeId: nodeId, position: Object.assign({}, {x: parseInt(document.getElementById("fixedNodeX").value), y: parseInt(document.getElementById("fixedNodeY").value)})};
    if(constraints.fixedNodeConstraint){
      constraints.fixedNodeConstraint.push(fixedNode);
    }
    else{
      constraints.fixedNodeConstraint = [fixedNode];
    }
      
    addToHistory("Fixed", [nodeId], fixedNode.position);
  }
});

document.getElementById("verticalAlignment").addEventListener("click", function(){
  if(cy.nodes(":selected").not(":parent").length > 0){
    let valignArray = [];
    cy.nodes(":selected").not(":parent").forEach(function(node){
      valignArray.push(node.id());
    });
    if(constraints.alignmentConstraint){
      if(constraints.alignmentConstraint.vertical){
        constraints.alignmentConstraint.vertical.push(valignArray);
      }
      else{
        constraints.alignmentConstraint.vertical = [valignArray];
      }
    }
    else{
      constraints.alignmentConstraint = {};
      constraints.alignmentConstraint.vertical = [valignArray];
    }
    addToHistory("Alignment", valignArray, 'vertical'); 
  }
});

document.getElementById("horizontalAlignment").addEventListener("click", function(){
  if(cy.nodes(":selected").not(":parent").length > 0){
    let halignArray = [];
    cy.nodes(":selected").not(":parent").forEach(function(node){
      halignArray.push(node.id());
    });
    if(constraints.alignmentConstraint){
      if(constraints.alignmentConstraint.horizontal){
        constraints.alignmentConstraint.horizontal.push(halignArray);
      }
      else{
        constraints.alignmentConstraint.horizontal = [halignArray];
      }
    }
    else{
      constraints.alignmentConstraint = {};
      constraints.alignmentConstraint.horizontal = [halignArray];
    }
    addToHistory("Alignment", halignArray, 'horizontal'); 
  }
});

document.getElementById("relativePlacement").addEventListener("click", function(){
  let nodeId1 = document.getElementById("nodeListRP1").value;
  let nodeId2 = document.getElementById("nodeListRP2").value;
  let isExist = false;
  if(constraints.relativePlacementConstraint){
    constraints.relativePlacementConstraint.forEach(function(constraint){
      if(constraint["left"]){
        if((constraint["left"] == nodeId1 && constraint["right"] == nodeId2 || constraint["left"] == nodeId2 && constraint["right"] == nodeId1) && document.getElementById("directionList").value == "left-right"){
          isExist = true;
        }
      }
      else {
        if((constraint["top"] == nodeId1 && constraint["bottom"] == nodeId2 || constraint["top"] == nodeId2 && constraint["bottom"] == nodeId1) && document.getElementById("directionList").value == "top-bottom"){
          isExist = true;
        }
      }
    });
  }
  if((nodeId1 != nodeId2) && !isExist){
    let relativePlacementConstraint;
    if(document.getElementById("directionList").value == "left-right"){
      relativePlacementConstraint = {left: nodeId1, right: nodeId2, gap: (document.getElementById("gap").value) ? (parseInt(document.getElementById("gap").value)) : undefined};
    }
    else{
      relativePlacementConstraint = {top: nodeId1, bottom: nodeId2, gap: (document.getElementById("gap").value) ? (parseInt(document.getElementById("gap").value)) : undefined};
    }            
    if(constraints.relativePlacementConstraint){
      constraints.relativePlacementConstraint.push(relativePlacementConstraint);
    }
    else{
      constraints.relativePlacementConstraint = [];
      constraints.relativePlacementConstraint.push(relativePlacementConstraint);
    }
    
    if(document.getElementById("directionList").value == "left-right")
      addToHistory("Relative", [nodeId1, nodeId2], 'l-r - ' + ((document.getElementById("gap").value) ? (parseInt(document.getElementById("gap").value)) : parseInt(cy.getElementById(nodeId1).width()/2 + cy.getElementById(nodeId2).width()/2 + 50)));
    else{
      addToHistory("Relative", [nodeId1, nodeId2], 't-b - ' + ((document.getElementById("gap").value) ? (parseInt(document.getElementById("gap").value)) : parseInt(cy.getElementById(nodeId1).height()/2 + cy.getElementById(nodeId2).height()/2 + 50)));
    }
  }
});

let addToHistory = function( constraintType, nodeIds, constraintInfo) {
  
  let row = constraintListTable.insertRow();
  let cell4 = row.insertCell(0);
  let cell3 = row.insertCell(0);
  let cell2 = row.insertCell(0);
  let cell1 = row.insertCell(0);
  cell1.innerHTML = constraintType;
  
  if(constraintType == 'Fixed'){
    let label = (cy.getElementById(nodeIds[0]).css('label') ? cy.getElementById(nodeIds[0]).css('label') : nodeIds);
    if(label.length > 15)
      label = label.substring(0, 12).concat("...");
    cell2.innerHTML = label;
    cell3.innerHTML = "x: "+constraintInfo.x+" y: "+constraintInfo.y;
  }
  else if(constraintType == 'Alignment'){
    let nodeList = "";
    nodeIds.forEach(function(nodeId, index){
      let label = (cy.getElementById(nodeId).css('label') ? cy.getElementById(nodeId).css('label') : nodeId);
      if(label.length > 15)
        label = label.substring(0, 12).concat("...");      
      if(index == 0)
        nodeList += label;
      else
        nodeList += ' - ' + label;
    });
    cell2.innerHTML = nodeList;
    cell3.innerHTML = constraintInfo;
  }
  else{
    let nodeList = "";
    nodeIds.forEach(function(nodeId, index){
      let label = (cy.getElementById(nodeId).css('label') ? cy.getElementById(nodeId).css('label') : nodeId);
      if(label.length > 15)
        label = label.substring(0, 12).concat("...");       
      if(index == 0)
        nodeList += label;
      else
        nodeList += ' - ' + label;
    });
    cell2.innerHTML = nodeList;
    cell3.innerHTML = constraintInfo;
  }
  
  // needed for highlighting constrained nodes
  let instance = cy.viewUtilities({
    highlightStyles: [
      { node: { 'background-color': '#da14ff', 'border-color': '#980eb2'}, edge: {} }
    ]
  });

  let rowToHighlight = $('#constraintListTable').find('tr').eq(row.rowIndex);

  let collectionToHighlight = cy.collection();
  nodeIds.forEach(function(id){
    collectionToHighlight = collectionToHighlight.union(cy.getElementById(id));
  });

  // 'Delete' symbol
  let button = document.createElement('button');
  button.setAttribute('class','close');
  button.setAttribute('aria-label', 'Close');
  if(constraintType == 'Fixed')
    button.onclick = function(event){
      deleteRowElements(row, nodeIds);
      instance.removeHighlights(collectionToHighlight.nodes());
    };
  else if(constraintType == 'Alignment')
    button.onclick = function(event){
      deleteRowElements(row, nodeIds, constraintInfo);
      instance.removeHighlights(collectionToHighlight.nodes());
    };
  else
    button.onclick = function(event){
      deleteRowElements(row, nodeIds, constraintInfo);
      instance.removeHighlights(collectionToHighlight.nodes());
    };
  let xSymbol = document.createElement('span');
  xSymbol.setAttribute('aria-hidden', 'true');
  xSymbol.style.color = "red";
  xSymbol.innerHTML = '&times';
  button.appendChild(xSymbol);
  cell4.appendChild(button);
  
  rowToHighlight.hover(function() {
    instance.highlight(collectionToHighlight.nodes(), 0);
  }, function() {
    instance.removeHighlights(collectionToHighlight.nodes());
  });
};

// Delete Row Elements
let deleteRowElements = function (row, nodeIds, info) {
  let constraintType = row.cells[0].innerHTML;
  if (constraintType == 'Fixed') {
    constraints.fixedNodeConstraint.forEach(function (item, index) {
      if (item.nodeId == nodeIds[0]) {
        constraints.fixedNodeConstraint.splice(index, 1);
      }
    });
    if (constraints.fixedNodeConstraint.length == 0) {
      constraints.fixedNodeConstraint = undefined;
    }
  } else if (constraintType == 'Alignment') {
    if (info == 'vertical') {
      constraints.alignmentConstraint.vertical.forEach(function (item, index) {
        if (item.length == nodeIds.length) {
          let equal = true;
          item.forEach(function (nodeId, i) {
            if (nodeId != nodeIds[i]) {
              equal = false;
            }
          });
          if (equal) {
            constraints.alignmentConstraint.vertical.splice(index, 1);
            if (constraints.alignmentConstraint.vertical.length == 0) {
              delete constraints.alignmentConstraint.vertical;
              if (!constraints.alignmentConstraint.horizontal) {
                constraints.alignmentConstraint = undefined;
              }
            }
          }
        }
      });
    } else {
      constraints.alignmentConstraint.horizontal.forEach(function (item, index) {
        if (item.length == nodeIds.length) {
          let equal = true;
          item.forEach(function (nodeId, i) {
            if (nodeId != nodeIds[i]) {
              equal = false;
            }
          });
          if (equal) {
            constraints.alignmentConstraint.horizontal.splice(index, 1);
            if (constraints.alignmentConstraint.horizontal.length == 0) {
              delete constraints.alignmentConstraint.horizontal;
              if (!constraints.alignmentConstraint.vertical) {
                constraints.alignmentConstraint = undefined;
              }
            }
          }
        }
      });
    }
  }
  else{
    constraints.relativePlacementConstraint.forEach(function(item, index){
      if(info.substring(0,1) == 'l'){
        if(item.left && item.left == nodeIds[0] && item.right == nodeIds[1]){
          constraints.relativePlacementConstraint.splice(index, 1);
          if(constraints.relativePlacementConstraint.length == 0){
            constraints.relativePlacementConstraint = undefined;
          }
        }
      }
      else{
        if(item.top && item.top == nodeIds[0] && item.bottom == nodeIds[1]){
          constraints.relativePlacementConstraint.splice(index, 1);
          if(constraints.relativePlacementConstraint.length == 0){
            constraints.relativePlacementConstraint = undefined;
          }          
        }        
      }
    });
  }
  constraintListTable.deleteRow(row.rowIndex);
};

// Clear logs table
let clearConstraintListTable = function() {
  if (constraintListTable.rows.length > 1) {
    let length = constraintListTable.rows.length;
    for (let i = 0; i < length - 1; i++) {
      constraintListTable.deleteRow(1);
    }
  }
};

let fillConstraintListTableFromConstraints = function () {
  if(constraints.fixedNodeConstraint){
    constraints.fixedNodeConstraint.forEach(function(constraint){
      addToHistory("Fixed", [constraint.nodeId], constraint.position);
    });    
  }
  if(constraints.alignmentConstraint){
    if(constraints.alignmentConstraint.vertical){
      constraints.alignmentConstraint.vertical.forEach(function(item){
        addToHistory("Alignment", item, 'vertical');
      });
    }
    if(constraints.alignmentConstraint.horizontal){
      constraints.alignmentConstraint.horizontal.forEach(function(item){
        addToHistory("Alignment", item, 'horizontal');
      });
    }    
  }
  if(constraints.relativePlacementConstraint){
    constraints.relativePlacementConstraint.forEach(function(constraint){
      if(constraint.left)
        addToHistory("Relative", [constraint.left, constraint.right], 'l-r - ' + (constraint.gap ? parseInt(constraint.gap) : parseInt(cy.getElementById(constraint.left).width()/2 + cy.getElementById(constraint.right).width()/2 + 50)));
      else
        addToHistory("Relative", [constraint.top, constraint.bottom], 't-b - ' + (constraint.gap ? parseInt(constraint.gap) : parseInt(cy.getElementById(constraint.top).height()/2 + cy.getElementById(constraint.bottom).height()/2 + 50)));
    });
    
  }
};

//// Samples
let elements1 = {
  nodes: [
    {data: {id: 'n1'}},    
    {data: {id: 'n2'}},
    {data: {id: 'n3', parent: 'n8'}},
    {data: {id: 'n5'}},
    {data: {id: 'n6', parent: 'n8'}},
    {data: {id: 'n7', parent: 'n8'}},
    {data: {id: 'n8'}},
    {data: {id: 'f1'}, classes: ['fixed']},
    {data: {id: 'f2'}, classes: ['fixed']},
    {data: {id: 'f3', parent: 'n8'}, classes: ['fixed']}, 
  ],
  edges: [
    {data: {source: 'n1', target: 'f1'}},
    {data: {source: 'n1', target: 'n3'}},
    {data: {source: 'f1', target: 'n2'}},
    {data: {source: 'f1', target: 'n3'}},
    {data: {source: 'n3', target: 'f2'}},
    {data: {source: 'f2', target: 'n5'}},
    {data: {source: 'n5', target: 'n8'}},
    {data: {source: 'n6', target: 'n3'}},
    {data: {source: 'n6', target: 'n7'}},
    {data: {source: 'n6', target: 'f3'}}
  ]
};

let elements2 = {
  nodes: [
    {data: {id: 'n1', parent: 'n6'}},    
    {data: {id: 'n2', parent: 'n6'}},    
    {data: {id: 'n3'}},    
    {data: {id: 'n4'}},
    {data: {id: 'n5', parent: 'n8'}},     
    {data: {id: 'n6'}},
    {data: {id: 'n7'}},
    {data: {id: 'n8'}},    
    {data: {id: 'v1', parent: 'n6'}, classes: ['alignment']},
    {data: {id: 'v2'}, classes: ['alignment']},    
    {data: {id: 'v3', parent: 'n8'}, classes: ['alignment']},
    {data: {id: 'v4', parent: 'n8'}, classes: ['alignment']},
    {data: {id: 'v5', parent: 'n5'}, classes: ['alignment']},
    {data: {id: 'h1'}, classes: ['alignment']},
    {data: {id: 'h2'}, classes: ['alignment']},
    {data: {id: 'h3'}, classes: ['alignment']},     
    {data: {id: 'h4', parent: 'n7'}, classes: ['alignment']},
    {data: {id: 'h5', parent: 'n7'}, classes: ['alignment']},     
    {data: {id: 'vh1', parent: 'n6'}, classes: ['alignment']},
    {data: {id: 'vh2', parent: 'n5'}, classes: ['alignment']}
  ],
  edges: [
    {data: {source: 'v1', target: 'vh1'}},
    {data: {source: 'v1', target: 'n1'}}, 
    {data: {source: 'n1', target: 'n2'}},    
    {data: {source: 'n2', target: 'vh1'}},
    {data: {source: 'n6', target: 'h1'}},
    {data: {source: 'h1', target: 'h2'}},
    {data: {source: 'n6', target: 'n3'}},
    {data: {source: 'n3', target: 'v2'}},
    {data: {source: 'n3', target: 'h4'}},    
    {data: {source: 'h4', target: 'h5'}},
    {data: {source: 'h4', target: 'v3'}},
    {data: {source: 'v3', target: 'v4'}},
    {data: {source: 'v3', target: 'n5'}},
    {data: {source: 'v5', target: 'vh2'}},    
    {data: {source: 'v3', target: 'n4'}},
    {data: {source: 'h3', target: 'n8'}},    
    {data: {source: 'n4', target: 'h3'}}
  ]
};

let elements3 = {
  nodes: [
    {data: {id: 'r1', parent: 'n8'}, classes: ['relative']},
    {data: {id: 'r2', parent: 'n8'}, classes: ['relative']},
    {data: {id: 'r3', parent: 'n8'}, classes: ['relative']},
    {data: {id: 'r4'}, classes: ['relative']},
    {data: {id: 'r5'}, classes: ['relative']},
    {data: {id: 'r6', parent: 'n7'}, classes: ['relative']},
    {data: {id: 'r7', parent: 'n10'}, classes: ['relative']},    
    {data: {id: 'r8', parent: 'n10'}, classes: ['relative']},    
    {data: {id: 'n1', parent: 'n7'}},
    {data: {id: 'n2'}},
    {data: {id: 'n3'}},
    {data: {id: 'n4'}},
    {data: {id: 'n5'}},
    {data: {id: 'n6'}},
    {data: {id: 'n7', parent: 'n10'}},
    {data: {id: 'n8'}},
    {data: {id: 'n9'}},
    {data: {id: 'n10'}}
  ],
  edges: [
    {data: {source: 'r6', target: 'n1'}},
    {data: {source: 'r6', target: 'n3'}},
    {data: {source: 'n1', target: 'r8'}},
    {data: {source: 'n1', target: 'r5'}},
    {data: {source: 'n1', target: 'r7'}},
    {data: {source: 'n2', target: 'n3'}},
    {data: {source: 'n2', target: 'r2'}},
    {data: {source: 'n2', target: 'n4'}},
    {data: {source: 'r5', target: 'n5'}},
    {data: {source: 'r5', target: 'n6'}},
    {data: {source: 'r2', target: 'r1'}},
    {data: {source: 'r2', target: 'r3'}},
    {data: {source: 'n7', target: 'n9'}},
    {data: {source: 'n5', target: 'n6'}},
    {data: {source: 'n5', target: 'r4'}}
  ]
};

let elements4 = [
  {group: 'nodes', data: {id: 'n1'}},
  {group: 'nodes', data: {id: 'n2'}},
  {group: 'nodes', data: {id: 'n3'}},
  {group: 'nodes', data: {id: 'n4'}},
  {group: 'nodes', data: {id: 'n5'}},  
  {group: 'nodes', data: {id: 'n6', parent: 'n24'}},
  {group: 'nodes', data: {id: 'n7', parent: 'n24'}},
  {group: 'nodes', data: {id: 'n8', parent: 'n24'}},
  {group: 'nodes', data: {id: 'n9'}},
  {group: 'nodes', data: {id: 'n10', parent: 'n28'}},
  {group: 'nodes', data: {id: 'n11', parent: 'n28'}},
  {group: 'nodes', data: {id: 'n12', parent: 'n28'}},
  {group: 'nodes', data: {id: 'n13', parent: 'n28'}},
  {group: 'nodes', data: {id: 'n14', parent: 'n28'}},
  {group: 'nodes', data: {id: 'n15'}},
  {group: 'nodes', data: {id: 'n16'}},
  {group: 'nodes', data: {id: 'n17'}},
  {group: 'nodes', data: {id: 'n18'}},
  {group: 'nodes', data: {id: 'n19'}},
  {group: 'nodes', data: {id: 'n20', parent: 'n26'}},
  {group: 'nodes', data: {id: 'n21', parent: 'n26'}},
  {group: 'nodes', data: {id: 'n22', parent: 'n27'}},    
  {group: 'nodes', data: {id: 'n23', parent: 'n25'}},
  {group: 'nodes', data: {id: 'n24'}},
  {group: 'nodes', data: {id: 'n25'}},
  {group: 'nodes', data: {id: 'n26', parent: 'n30'}},
  {group: 'nodes', data: {id: 'n27', parent: 'n29'}},
  {group: 'nodes', data: {id: 'n28', parent: 'n29'}},
  {group: 'nodes', data: {id: 'n29', parent: 'n30'}},
  {group: 'nodes', data: {id: 'n30'}},
  {group: 'nodes', data: {id: 'f1', parent: 'n29'}, classes: ['fixed']},
  {group: 'nodes', data: {id: 'f2', parent: 'n28'}, classes: ['fixed']},  
  {group: 'nodes', data: {id: 'h1'}, classes: ['alignment']},
  {group: 'nodes', data: {id: 'h2'}, classes: ['alignment']},
  {group: 'nodes', data: {id: 'h3'}, classes: ['alignment']},
  {group: 'nodes', data: {id: 'v1', parent: 'n24'}, classes: ['alignment']},  
  {group: 'nodes', data: {id: 'v2', parent: 'n25'}, classes: ['alignment']},  
  {group: 'nodes', data: {id: 'v3'}, classes: ['alignment']},
  {group: 'nodes', data: {id: 'v4'}, classes: ['alignment']},
  {group: 'nodes', data: {id: 'h4r'}, classes: ['alignment']},
  {group: 'nodes', data: {id: 'r1', parent: 'n29'}, classes: ['relative']},  
  {group: 'nodes', data: {id: 'r2', parent: 'n29'}, classes: ['relative']},
  {group: 'nodes', data: {id: 'r3'}, classes: ['relative']},
  {group: 'edges', data: {id: 'e2', source: 'n2', target: 'n3'}},
  {group: 'edges', data: {id: 'e3', source: 'n1', target: 'n2'}},
  {group: 'edges', data: {id: 'e4', source: 'n3', target: 'v1'}},
  {group: 'edges', data: {id: 'e5', source: 'n2', target: 'v1'}},
  {group: 'edges', data: {id: 'e6', source: 'v1', target: 'n4'}},
//  {group: 'edges', data: {id: 'e7', source: 'n5', target: 'n6'}},
  {group: 'edges', data: {id: 'e8', source: 'v1', target: 'n5'}},
  {group: 'edges', data: {id: 'e9', source: 'v1', target: 'n6'}},
  {group: 'edges', data: {id: 'e10', source: 'v1', target: 'n7'}},
//  {group: 'edges', data: {id: 'e11', source: 'n3', target: 'n9'}},
  {group: 'edges', data: {id: 'e12', source: 'n6', target: 'n8'}},
  {group: 'edges', data: {id: 'e13', source: 'h1', target: 'n16'}},
  { group:'edges', data:{ id: 'e14', source: 'v2', target: 'n23'} },
  {group: 'edges', data: {id: 'e15', source: 'n16', target: 'n15'}},
  {group: 'edges', data: {id: 'e16', source: 'n16', target: 'h2'}},
  {group: 'edges', data: {id: 'e17', source: 'n15', target: 'n17'}},
  {group: 'edges', data: {id: 'e18', source: 'h3', target: 'v3'}},
  {group: 'edges', data: {id: 'e19', source: 'v3', target: 'n18'}},
  {group: 'edges', data: {id: 'e20', source: 'n17', target: 'n19'}},
  {group: 'edges', data: {id: 'e21', source: 'n18', target: 'n19'}},
  {group: 'edges', data: {id: 'e22', source: 'h2', target: 'n19'}},
  {group: 'edges', data: {id: 'e23', source: 'n19', target: 'h3'}},
  {group: 'edges', data: {id: 'e25', source: 'v4', target: 'n20'}},
  {group: 'edges', data: {id: 'e26', source: 'n20', target: 'n21'}},
  {group: 'edges', data: {id: 'e27', source: 'n25', target: 'f1'}},
  {group: 'edges', data: {id: 'e29', source: 'f1', target: 'n26'}},
  {group: 'edges', data: {id: 'e30', source: 'f1', target: 'r2'}},
  {group: 'edges', data: {id: 'e31', source: 'f1', target: 'r1'}},
  {group: 'edges', data: {id: 'e33', source: 'h3', target: 'v2'}},
  {group: 'edges', data: {id: 'e35', source: 'f2', target: 'n11'}},
  {group: 'edges', data: {id: 'e36', source: 'f2', target: 'n10'}},
  {group: 'edges', data: {id: 'e37', source: 'n11', target: 'n10'}},
  {group: 'edges', data: {id: 'e38', source: 'n12', target: 'n13'}},
  {group: 'edges', data: {id: 'e39', source: 'n12', target: 'n14'}},
  {group: 'edges', data: {id: 'e40', source: 'h2', target: 'n27'}},
  {group: 'edges', data: {id: 'e41', source: 'n9', target: 'r3'}},
  {group: 'edges', data: {id: 'e42', source: 'n9', target: 'h4r'}},
  {group: 'edges', data: {id: 'e43', source: 'r3', target: 'h4r'}},
  {group: 'edges', data: {id: 'e44', source: 'n11', target: 'r3'}},
  {group: 'edges', data: {id: 'e45', source: 'n14', target: 'r3'}}
];

let elements5 = {
  nodes: [
    {data: {id: 'r1'}, classes: ['relative'], position:{x:318, y:150}},
    {data: {id: 'r2'}, classes: ['relative'], position:{x:413, y:148}},
    {data: {id: 'r3'}, classes: ['relative'], position:{x:128, y:-151}},
    {data: {id: 'r4'}, classes: ['relative'], position:{x:132, y:-38}},   
    {data: {id: 'f1v', parent: 'n8'}, classes: ['fixed'], position:{x:-79, y:276}},    
    {data: {id: 'f2h', parent: 'n6'}, classes: ['fixed'], position:{x:96, y:82}},
    {data: {id: 'v1', parent: 'n5'}, classes: ['alignment'], position:{x:-290, y:153}},
    {data: {id: 'v2', parent: 'n5'}, classes: ['alignment'], position:{x:-197, y:152}},
    {data: {id: 'v3', parent: 'n8'}, classes: ['alignment'], position:{x:-85, y:177}},
    {data: {id: 'h1', parent: 'n7'}, classes: ['alignment'], position:{x:1, y:-9}},
    {data: {id: 'h2', parent: 'n7'}, classes: ['alignment'], position:{x:0, y:-104}},
    {data: {id: 'n0', parent: 'n6'}, position:{x:186, y:219}},
    {data: {id: 'n1', parent: 'n6'}, position:{x:197, y:122}},
    {data: {id: 'n2', parent: 'n6'}, position:{x:93, y:186}},
    {data: {id: 'n3'}, position:{x:-267, y:20}},
    {data: {id: 'n4'}, position:{x:-167, y:-10}},
    {data: {id: 'n5', parent: 'n8'}},
    {data: {id: 'n6'}},
    {data: {id: 'n7'}},
    {data: {id: 'n8'}}    
  ],
  edges: [
    {data: {source: 'n0', target: 'n1'}},
    {data: {source: 'n0', target: 'n2'}},
    {data: {source: 'n6', target: 'r1'}},
    {data: {source: 'n1', target: 'f2h'}},
    {data: {source: 'r1', target: 'r2'}},
    {data: {source: 'n2', target: 'f2h'}},
    {data: {source: 'n6', target: 'r4'}},
    {data: {source: 'n3', target: 'n4'}},
    {data: {source: 'n3', target: 'n8'}},
    {data: {source: 'n4', target: 'v3'}},
    {data: {source: 'r4', target: 'h1'}},
    {data: {source: 'r4', target: 'r3'}},
    {data: {source: 'h1', target: 'h2'}},
    {data: {source: 'v1', target: 'v2'}},
    {data: {source: 'v3', target: 'n5'}},
    {data: {source: 'h1', target: 'v3'}},
    {data: {source: 'v3', target: 'f1v'}}
  ]
};

let elements5_positions = [
  {x:318, y:150}, 
  {x:413, y:148},
  {x:128, y:-151},
  {x:132, y:-38},
  {x:-79, y:276},
  {x:96, y:82},
  {x:-290, y:153},
  {x:-197, y:152},
  {x:-85, y:177},
  {x:1, y:-9},
  {x:0, y:-104},
  {x:186, y:219},
  {x:197, y:122},
  {x:93, y:186},
  {x:-267, y:20},
  {x:-167, y:-10},
  {x:318, y:150},
  {x:318, y:150}
];

let sample1_constraints = {
  "fixedNodeConstraint": [
    {
      "nodeId": "f1",
      "position": {
        "x": -150,
        "y": -100
      }
    },
    {
      "nodeId": "f2",
      "position": {
        "x": -50,
        "y": -150
      }
    },
    {
      "nodeId": "f3",
      "position": {
        "x": 100,
        "y": 150
      }
    }
  ]
};

let sample2_constraints = {
  "alignmentConstraint": {
    "vertical": [
      [
        "v1",
        "v2",
        "vh1"
      ],
      [
        "v3",
        "v4",
        "v5",
        "vh2"
      ]
    ],
    "horizontal": [
      [
        "h1",
        "h2",
        "h3"
      ],
      [
        "h4",
        "h5",
        "vh1",
        "vh2"
      ]
    ]
  }
};

let sample3_constraints = {
  "relativePlacementConstraint": [
    {
      "left": "r1",
      "right": "r2",
      "gap": 100
    },
    {
      "top": "r1",
      "bottom": "r2",
      "gap": 100
    },
    {
      "left": "r2",
      "right": "r3",
      "gap": 200
    },
    {
      "top": "r4",
      "bottom": "r5",
      "gap": 150
    },
    {
      "left": "r6",
      "right": "r7",
      "gap": 150
    },
    {
      "top": "r8",
      "bottom": "r7",
      "gap": 100
    }
  ]
};

let sample4_constraints = {
  "fixedNodeConstraint": [
    {
      "nodeId": "f1",
      "position": {
        "x": -100,
        "y": 0
      }
    },
    {
      "nodeId": "f2",
      "position": {
        "x": 300,
        "y": 0
      }
    }
  ],
  "alignmentConstraint": {
    "horizontal": [
      [
        "h1",
        "h2",
        "h3",
        "h4r"
      ]
    ],
    "vertical": [
      [
        "v1",
        "v2",
        "v3",
        "v4"        
      ]
    ]
  },
  "relativePlacementConstraint": [
    {
      "top": "r1",
      "bottom": "r2",
      "gap": 150
    },    
    {
      "left": "r3",
      "right": "h4r",
      "gap": 150
    }
  ]
};