(function (global, factory) {
    typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('d3')) :
    typeof define === 'function' && define.amd ? define(['exports', 'd3'], factory) :
    (factory((global.d3 = global.d3 || {}),global.d3));
}(this, (function (exports,d3) { 'use strict';

    var PORT_MARKERS = {
    		"WEST": {
    			"INPUT": "#westInPortMarker",
    			"OUTPUT": "#westOutPortMarker"},
    		"EAST": {
    			"INPUT": "#eastInPortMarker",
    			"OUTPUT": "#eastOutPortMarker"},
    		"NORTH": {
    			"INPUT": "#northInPortMarker",
    			"OUTPUT": "#northOutPortMarker"},
    	    "SOUTH": {
    	    	"INPUT": "#southInPortMarker" ,
    	    	"OUTPUT": "#southOutPortMarker"},
    };

    function addMarkers(svg, PORT_PIN_SIZE) {
        var defs = svg.append("defs");
        // real size of marker
        var w = 7;
        var h = 10;
        
        function addMarker(id, arrowTranslate, arrowRotate=0) {
        	var rightArrow = "M 0 4  2 4  2 0  7 5  2 10  2 6  0 6 Z";
            var trans = "";
        
            if (arrowTranslate[0] != 0 || arrowTranslate[1] != 0)
            	trans += "translate(" + arrowTranslate[0] + ", " + arrowTranslate[1] + ")";
        
            if (arrowRotate != 0)
            	trans += "rotate(" + arrowRotate + ")";
        
            var cont = defs.append("g");
        
            cont
            .attr("id", id)
            .attr("class", "port")
            .append("path")
            .attr("d", rightArrow);
            
            if (trans)
            	cont
                .attr("transform", trans);
        }
        
        var horizYoffet = (PORT_PIN_SIZE[1] - h) * 0.5;
        var horizYoffet2 = (PORT_PIN_SIZE[1] + h) * 0.5;
        
        var vertXOffet = -(PORT_PIN_SIZE[1] - w) * 0.5;
        addMarker("westInPortMarker", [0, horizYoffet]);
        addMarker("westOutPortMarker",[w, horizYoffet2], 180);
        
        addMarker("eastInPortMarker", [w, horizYoffet2], 180);
        addMarker("eastOutPortMarker",[0, horizYoffet]);
        
        addMarker("northInPortMarker", [vertXOffet, -w], 90);
        addMarker("northOutPortMarker",[vertXOffet, 0], 270);
        
        addMarker("southInPortMarker", [vertXOffet, w], 270);
        addMarker("southOutPortMarker",[vertXOffet, 0], 90);
    }

    function getIOMarker(d) {
        var side = d.properties.portSide;
        var portType = d.direction;
        var marker = PORT_MARKERS[side][portType];
        if (marker === undefined) {
        	throw new Error("Wrong side, portType", side, portType)
        }
        return marker;
    }

    /*******************************************************************************
     * Copyright (c) 2017 Kiel University and others.
     * All rights reserved. This program and the accompanying materials
     * are made available under the terms of the Eclipse Public License v1.0
     * which accompanies this distribution, and is available at
     * http://www.eclipse.org/legal/epl-v10.html
     *******************************************************************************/
    class ELK {

      constructor({
        defaultLayoutOptions = {},
        algorithms = [ 'layered', 'stress', 'mrtree', 'radial', 'force', 'disco' ],
        workerFactory,
        workerUrl
      } = {}) {
        this.defaultLayoutOptions = defaultLayoutOptions;
        this.initialized = false;

        // check valid worker construction possible
        if (typeof workerUrl === 'undefined' && typeof workerFactory === 'undefined') {
          throw new Error("Cannot construct an ELK without both 'workerUrl' and 'workerFactory'.")
        }
        let factory = workerFactory;
        if (typeof workerUrl !== 'undefined' && typeof workerFactory === 'undefined') {
          // use default Web Worker
          factory = function(url) { return new Worker(url) };
        }

        // create the worker
        let worker = factory(workerUrl);
        if (typeof worker.postMessage !== 'function' ) {
          throw new TypeError("Created worker does not provide"
            + " the required 'postMessage' function.")
        }

        // wrap the worker to return promises
        this.worker = new PromisedWorker(worker);

        // initially register algorithms
        this.worker.postMessage({
          cmd: 'register',
          algorithms: algorithms
        })
          .then((r) => this.initialized = true)
          .catch(console.err);
      }

      layout(graph, { layoutOptions = this.defaultLayoutOptions } = {}) {
        if (!graph) {
          return Promise.reject(new Error("Missing mandatory parameter 'graph'."))
        }
        return this.worker.postMessage({
          cmd: 'layout',
          graph: graph,
          options: layoutOptions
        })
      }

      knownLayoutAlgorithms() {
        return this.worker.postMessage({ cmd: 'algorithms' })
      }

      knownLayoutOptions() {
        return this.worker.postMessage({ cmd: 'options' })
      }

      knownLayoutCategories() {
        return this.worker.postMessage({ cmd: 'categories' })
      }

      terminateWorker() {
        this.worker.terminate();
      }

    }

    class PromisedWorker {

      constructor(worker) {
        if (worker === undefined) {
          throw new Error("Missing mandatory parameter 'worker'.")
        }
        this.resolvers = {};
        this.worker = worker;
        this.worker.onmessage = (answer) => {
          // why is this necessary?
          setTimeout(() => {
            this.receive(this, answer);
          }, 0);
        };
      }

      postMessage(msg) {
        let id = this.id || 0;
        this.id = id + 1;
        msg.id = id;
        let self = this;
        return new Promise(function (resolve, reject) {
          // prepare the resolver
          self.resolvers[id] = function (err, res) {
            if (err) {
              reject(err);
            } else {
              resolve(res);
            }
          };
          // post the message
          self.worker.postMessage(msg);
        })
      }

      receive(self, answer) {
        let json = answer.data;
        let resolver = self.resolvers[json.id];
        if (resolver) {
          delete self.resolvers[json.id];
          if (json.error) {
            resolver(json.error);
          } else {
            resolver(null, json.data);
          }
        }
      }

      terminate() {
        if (this.worker.terminate) {
          this.worker.terminate();
        }
      }

    }

    const RUNNING_IN_NODE = (typeof require !== "undefined");
    const ELK_WORKER_NAME = "elk-worker.js";
    if (RUNNING_IN_NODE)
    	var IN_NODE_ELK_WORKER_NAME = __dirname + "/" + ELK_WORKER_NAME;
    const NO_LAYOUT = "org.eclipse.elk.noLayout";
    // kgraph properties that shall be copied
    const KGRAPH_KEYS = [
      'x', 'y',
      'width', 'height',
      "sections",
      'sourcePoint',
      'targetPoint',
      'junctionPoints',
      'properties'
    ].reduce(function(p, c) {p[c] = 1; return p;}, {});


    function findElkWorkerURL() {
    	if (RUNNING_IN_NODE)
    		return IN_NODE_ELK_WORKER_NAME;
    		
        // find name of elk worker script URL
        var elkWorkerScript;
        var scripts = document.getElementsByTagName('script');
        for(var i = 0; i < scripts.length; i++) {
        	if(scripts[i].src.endsWith(ELK_WORKER_NAME)) {
        		elkWorkerScript = scripts[i];
        		break;
        	}
        }
        if (typeof elkWorkerScript === "undefined")
        	throw new Error("Can not locate elk-worker.js");

        return elkWorkerScript.src;
        // var blob = new Blob(Array.prototype.map.call(
        // document.querySelectorAll('script[type=\'text\/js-worker\']'),
        // function (oScript) { return oScript.textContent; }),{type:
    	// 'text/javascript'});
    }

    class d3elk {
    	constructor() {
    		this.dispatch = d3.dispatch("finish");
    	    // containers
    	    this.nodes = [];
    	    this.edges = [];
    	    this.graph = {}; // internal (hierarchical graph)
    	    this._options = {};
    	    // {id(str): object from input graph}
    	    this._d3ObjMap = {};
    	    // dimensions
    	    this.width = 0;
    	    this.height = 0;
    	    this._transformGroup = undefined;
    	    // a function applied after each layout run
    	    if (RUNNING_IN_NODE) {
    	    	const { Worker } = require('webworker-threads');
    	    	var workerFactory = function (url) { 
    	    		return new Worker(url)
    	    	};
    	    } else {
    	    	var workerFactory = undefined;
    	    }
    	    // the layouter instance
    	    this.layouter = new ELK({
    	    	algorithms: [ 'layered'],
    	    	workerUrl:findElkWorkerURL(),
    	    	workerFactory: workerFactory,
    	    });
    	}

    	ports(n) {
    	  // by default the 'ports' field
    	  return n.ports || [];
    	}

    	labels(n) {
    	  return n.labels || [];
    	}
        /**
    	 * Setting the available area, the positions of the layouted graph are
    	 * currently scaled down.
    	 */
        size(size) {
          if (!arguments.length)
        	  return [this.width, this.height];
          this.width = size[0];
          this.height = size[1];
          
          if(this.graph != null) {
            this.graph.width = width;
            this.graph.height = height;
          }
          return this;
        };
    	
        /**
    	 * Convert section from ELK json to svg path string
    	 */
        static section2svgPath(section) {
          var pathBuff = ["M", section.startPoint.x, section.startPoint.y];
          if (section.bendPoints)
            section.bendPoints.forEach(function (bp, i) {
              pathBuff.push("L");
              pathBuff.push(bp.x);
              pathBuff.push(bp.y);
           });
           
           pathBuff.push("L");
           pathBuff.push(section.endPoint.x);
           pathBuff.push(section.endPoint.y);
           return pathBuff.join(" ")
         }

        /**
    	 * Sets the group used to perform 'zoomToFit'.
    	 */
        transformGroup(g) {
          if (!arguments.length)
        	  return this._transformGroup;
          this._transformGroup = g;
          return this;
        }

        options(opts) {
          if (!arguments.length)
        	  return this._options;
          this._options = opts;
          return this;
        }

        /**
    	 * Start the layout process.
    	 */
        start() {
          // alias applyLayout method
          var self = this;
          // start the layouter
          function onSuccess(kgraph)  {
            self.applyLayout(kgraph);
          }
          this.layouter.layout(
        		  this.graph,
        		  {layoutOptions: this._options}
          ).then(onSuccess,
        		 this.onError
          );
          return self;
        }

        getNodes() {
          if (this.__nodeCache != null)
            return this.__nodeCache;

          var queue = [this.graph],
              nodes = [],
              parent;
          // note that svg z-index is document order, literally
          while ((parent = queue.pop()) != null) {
            if (!parent.properties[NO_LAYOUT]) {
                nodes.push(parent);
                (parent.children || []).forEach(function(c) {
                  queue.push(c);
                });
            }
          }
          this.__nodeCache = nodes;
          return nodes;
        }

        getPorts() {
            if (this.__portsCache != null)
                return this.__portsCache;
            
            var ports = d3.merge(this.getNodes().map(function(n) {
                return n.ports || [];
            }));
            this.__portsCache = ports; 
        }

        getEdges() {
          if (this.__edgesCache != null)
            return this.__edgesCache;

          this.__edgesCache = this.graph.edges || [];

          var edgesOfChildren = d3.merge(
            this.getNodes()
            .filter(function (n) {
             return !n.hideChildren;
            })
            .map(function(n) {
                return n.edges || [];
            }));

          this.__edgesCache = this.__edgesCache.concat(edgesOfChildren);
          return this.__edgesCache;
        }

        invalidateCaches() {
           this._d3ObjMap = {};
           this.__nodeCache = null;
           this.__portsCache = null;
           this.__edgesCache = null;
        };

        kgraph(root) {
          if (!arguments.length)
        	  return this.graph;
            
          var g = this.graph = root;
          this.invalidateCaches();
          // alias applyLayout method
          if (!g.id)
              g.id = "root";
          if (!g.properties)
              g.properties = { 'algorithm': 'layered' };
          if (!g.properties.algorithm)
              g.properties.algorithm = 'layered';
          if (!g.width)
              g.width = width;
          if (!g.height)
              g.height = height;
          
          return this;
        };
        
        onError(e) {
          throw e;
        }
        
        /**
    	 * Clean all layout possitions from nodes, nets and ports
    	 */
        cleanLayout(n) {
            if (!arguments.length)
            	var n = this.graph;

            var cleanLayout = this.cleanLayout.bind(this);
            delete n.x;
            delete n.y;
            (n.ports || []).forEach(function (p) {
                delete p.x;
                delete p.y;
            });
            (n.edges || []).forEach(function (e) { 
                delete e.sections;
                delete e.junctionPoints;
            });
            (n.children || []).forEach(function(c) {
                cleanLayout(c);
            });
        }
        
        /**
    	 * Apply layout for the kgraph style. Converts relative positions to
    	 * absolute positions.
    	 */
        applyLayout(kgraph) {
          this.zoomToFit(kgraph);
          var nodeMap = {};
          // convert to absolute positions
          d3elk.toAbsolutePositions(kgraph, {x: 0, y:0}, nodeMap);
          d3elk.toAbsolutePositionsEdges(kgraph, nodeMap);
          this.copyElkProps(kgraph, this.graph);
          // invoke the 'finish' event
          this.dispatch.call('finish', {graph: kgraph});
        }
        
        /**
         * Webworker creates new graph object and layout props has to be
         * copied back to original graph
         * 
         * @param srcGraph: new graph from ELK worker
         * @param dstGraph: original graph provided by user
         **/
        copyElkProps(srcGraph, dstGraph) {
        	var d3Objs = this._d3ObjMap;
        	// init d3Objs
        	d3Objs[dstGraph.id] = d3Objs;
        	(dstGraph.edges || []).forEach(function(e) {
        		if (e.id in d3Objs)
        			throw new Error();
        		d3Objs[e.id] = e;
        	});
        	(dstGraph.children || []).forEach(function(n) {
        		d3Objs[n.id] = n;
        	});
        	(dstGraph.ports || []).forEach(function(p) {
        		d3Objs[p.id] = p;
        	});
        	
        	var copyProps = d3elk.copyProps.bind(this);
        	// copy props from this node
            copyProps(srcGraph, dstGraph);
            (srcGraph.ports || []).forEach(function(p) {
              copyProps(p, d3Objs[p.id]);
            });
            (srcGraph.labels || []).forEach(function(l, i) {
              copyProps(l, dstGraph.labels[i]);
            });
            // copy props from edges in this node
            (srcGraph.edges || []).forEach(function(e) {
                var l = d3Objs[e.id];
                copyProps(e, l);
                copyProps(e.source, l.source);
      	        copyProps(e.target, l.target);
                // make sure the bendpoint array is valid
                l.bendPoints = e.bendPoints || [];
        	});
            // copy props of children
            var copyElkProps = this.copyElkProps.bind(this);
            (srcGraph.children || []).forEach(function(n) {
        		copyElkProps(n, d3Objs[n.id]);
            });
        }
        static copyProps(src, dst) {
            var keys = KGRAPH_KEYS;
            for (var k in src) {
              if (keys[k]) {
            	  dst[k] = src[k];
              }
            }
        }

        static toAbsolutePositions(n, offset, nodeMap) {
          n.x = (n.x || 0) + offset.x;
          n.y = (n.y || 0) + offset.y;
          nodeMap[n.id] = n;
          // the offset for the children has to include padding
          var childOffset = {x: n.x, y: n.y};
          if (n.padding) {
            childOffset.x += n.padding.left || 0;
            childOffset.y += n.padding.top || 0;
          }
          // children
          (n.children || []).forEach(function(c) {
            c.parent = n;
            d3elk.toAbsolutePositions(c, childOffset, nodeMap);
          });
        }

        static isDescendant(node, child) {
          var parent = child.parent;
          while (parent) {
            if (parent == node) {
              return true;
            }
            parent = parent.parent;
          }
          return false;
        }

        static toAbsolutePositionsEdges(n, nodeMap) {
          // edges
          (n.edges || []).forEach(function (e) {
            // transform edge coordinates to absolute coordinates. Note that
            // node coordinates are already absolute and that
            // edge coordinates are relative to the source node's parent node
            // (unless the target node is a descendant of the source node)
            var srcNode = nodeMap[e.source];
            var tgtNode = nodeMap[e.target];
            var relative = d3elk.isDescendant(srcNode, tgtNode) ?
                            	srcNode : srcNode.parent;
            
            var offset = {x: 0, y: 0};
            if (relative) {
              offset.x = relative.x || 0;
              offset.y = relative.y || 0;
            }
            if (relative.padding) {
              offset.x += relative.padding.left || 0;
              offset.y += relative.padding.top || 0;
            }
            if (e.sections)
                e.sections.forEach(function (s) {
                    // ... and apply it to the edge
                    if (s.startPoint) {
                      s.startPoint.x += offset.x;
                      s.startPoint.y += offset.y;
                    }
                    if (s.endPoint) {
                      s.endPoint.x += offset.x;
                      s.endPoint.y += offset.y;
                    }
                    (s.bendPoints || []).forEach(function (bp) {
                      bp.x += offset.x;
                      bp.y += offset.y;
                    });
                });
            if (e.junctionPoints)
                e.junctionPoints.forEach(function (jp) {
                      jp.x += offset.x;
                      jp.y += offset.y;
                });
          });
          // children
          (n.children || []).forEach(function(c) {
        	  d3elk.toAbsolutePositionsEdges(c, nodeMap);
          });
        };
        
        
        /**
    	 * If a top level transform group is specified, we set the scale such that
    	 * the available space is used to its maximum.
    	 */
        zoomToFit(kgraph) {
          // scale everything so that it fits the specified size
          var scale = this.width / (kgraph.width || 1);
          var sh = this.height / (kgraph.height || 1);
          if (sh < scale) {
            scale = sh;
          }
          // if a transformation group was specified we
          // perform a 'zoomToFit'
          if (this._transformGroup) {
        	  this._transformGroup.attr("transform", "scale(" + scale + ")");
          }
        }
        
        // event listener register method
        on() {
            var value = this.dispatch.on.apply(this.dispatch, arguments);
            return value === this.dispatch ? this: value;
        };
        
        terminate() {
        	if (this.layouter)
        		this.layouter.terminateWorker();
        }

        // return the layouter object
    }

    /**
     * Returns whether the sides of ports are fixed.
     * 
     * @see PortSide
     * @return true if the port sides are fixed
     */

    function PortConstraints_isSideFixed(val) {
       return val == "FREE" || val != "UNDEFINED"
    }

    /**
     * apply hideChildren flag no node
     **/
    function applyHideChildren(n) {
        if (n.hideChildren) {
            if (n.children !== undefined) {
                n.__children = n.children;
                n.__edges = n.edges;
                delete n.children;
                delete n.edges;
            }
        } else {
            if (n.__children !== undefined) {
                n.children = n.__children;
                n.edges = n.__edges;
                delete n.__children;
                delete n.__edges;
            }
        }
        (n.children || []).forEach(applyHideChildren);
    }

    /**
     * HwScheme builds scheme diagrams after bindData(data) is called
     * 
     * @param svg:
     *            root svg element where scheme will be rendered
     * @note do specify size of svg to have optimal result
     */
    class HwSchematic {
        constructor(svg) {
            this.svg = svg;
            this.PORT_PIN_SIZE = [7, 13];
            this.PORT_HEIGHT = this.PORT_PIN_SIZE[1];
            this.CHAR_WIDTH = 7.55;
            this.CHAR_HEIGHT = 13;
            this.NODE_MIDDLE_PORT_SPACING = 20;
            this.MAX_NODE_BODY_TEXT_SIZE = [400, 400];
            // top, right, bottom, left
            this.BODY_TEXT_PADDING = [15, 10, 0, 10];

            addMarkers(svg, this.PORT_PIN_SIZE);
            this.root = svg.append("g");
            this.layouter = new d3elk();
        }
            
        widthOfText(text) {
            if (text)
                return text.length * this.CHAR_WIDTH;
            else
                return 0;
        }

        /**
         * Split bodyText of one to lines and resolve dimensions of body text
         * 
         * @param d
         *            component node
         */
        initBodyTextLines(d) {
            var max = Math.max;
            if (d.bodyText) {
                if (typeof d.bodyText === "string") {
                    d.bodyText = d.bodyText.split("\n");
                }
                var bodyTextW = 0;
                d.bodyText.forEach(function (line) {
                    bodyTextW = max(bodyTextW, line.length);
                });
                bodyTextW *= this.CHAR_WIDTH;
                var bodyTextH = d.bodyText.length * this.CHAR_HEIGHT;  
            } else {
                var bodyTextW = 0;
                var bodyTextH = 0;
            }
            var pad = this.BODY_TEXT_PADDING;
            if (bodyTextW  > 0)
                bodyTextW += pad[1] + pad[3];
            if (bodyTextH  > 0)
                bodyTextH += pad[0] + pad[2];
            return [bodyTextW, bodyTextH];
        }

        /**
         * Init bodyText and resolve size of node from body text and ports
         * 
         * @param d component node
         */
        initNodeSizes(d) {
            if (d.properties["org.eclipse.elk.noLayout"])
                return;
            var ignorePortLabel = d.children && !d.hideChildren;
            if (d.ports != null)
                d.ports.forEach(function(p) {
                    p.ignoreLabel = ignorePortLabel;
                });
            var widthOfText = this.widthOfText.bind(this);

            var labelW = widthOfText(d.name);
            var max = Math.max;
            var bodyTextSize = this.initBodyTextLines(d);
            const MBT = this.MAX_NODE_BODY_TEXT_SIZE;
            bodyTextSize[0] = Math.min(bodyTextSize[0], MBT[0]);
            bodyTextSize[1] = Math.min(bodyTextSize[1], MBT[1]);

            // {PortSide: (portCnt, portWidth)}
            var portDim = {
                    "WEST": [0, 0],
                    "EAST": [0, 0],
                    "SOUTH": [0, 0],
                    "NORTH": [0, 0]
            };
            var PORT_PIN_SIZE_x = this.PORT_PIN_SIZE[0],
                PORT_PIN_SIZE_y = this.PORT_PIN_SIZE[1];
            var CHAR_WIDTH = this.CHAR_WIDTH;
            if (d.ports != null)
              d.ports.forEach(function(p) {
                  var t = p.properties.portSide;
                  var indent = 0;
                  if (p.level > 0)
                      indent = (p.level + 1) * CHAR_WIDTH;
                  var portW = widthOfText(p.name) + indent;
                  var pDim = portDim[t];
                  if (pDim === undefined)
                      throw new Error(t);
                  pDim[0]++;
                  pDim[1] = max(pDim[1], portW);
                  
                  // dimension of connection pin
                  p.width = PORT_PIN_SIZE_x;
                  p.height = PORT_PIN_SIZE_y;
              });
             
            var west = portDim["WEST"],
                east = portDim["EAST"],
                south = portDim["SOUTH"],
                north = portDim["NORTH"];

            var portColums = 0;
            if (west[0])
                portColums += 1;
            if (east[0])
                portColums += 1;

            var middleSpacing = 0;
            if (portColums == 2)
                middleSpacing = this.NODE_MIDDLE_PORT_SPACING;
            var portW = max(west[1], east[1]);
            
            d.portLabelWidth = portW;
            d.width = max(portW * portColums + middleSpacing, labelW,
                          max(south[0], north[0]) * this.PORT_HEIGHT)
                        + bodyTextSize[0] + CHAR_WIDTH;
            d.height = max(max(west[0], east[0]) * this.PORT_HEIGHT,
                           bodyTextSize[1],
                           max(south[1], north[1]) * CHAR_WIDTH);
        }
        
        /**
         * @param bodyTexts
         *            list of strings
         */
        renderTextLines(bodyTexts) {
            const padTop = this.BODY_TEXT_PADDING[0];
            const padLeft = this.BODY_TEXT_PADDING[3];
            const MBT = this.MAX_NODE_BODY_TEXT_SIZE;
            const CHAR_WIDTH = this.CHAR_WIDTH;
            const CHAR_HEIGHT = this.CHAR_HEIGHT;

            bodyTexts.each(function() {
                var bodyText = d3.select(this);
                var d = bodyText.data()[0];
                var bodyTextLines = d.bodyText;
                var _MBT = [MBT[0] /CHAR_WIDTH, MBT[1] / this.CHAR_HEIGHT];
                
                if (bodyTextLines && (d.children == null 
                        || d.children.length == 0 
                        || d.hideChildren)) {
                    bodyTextLines.forEach(function (line, dy) {
                        if (line.length > _MBT[0])
                            line = line.slice(0, _MBT[0] - 3) + "...";
                        if (dy > _MBT[1])
                            return;
                        bodyText
                           .append("tspan")
                           .attr("x", d.portLabelWidth + padLeft)
                           .attr("y", padTop)
                           .attr("dy", dy + "em")
                           .text(line);
                    });
                }
            });
            
        }
        removeGraph() {
        	this.root.remove();
        	this.root = svg.append("g");
        }
        
        /**
         * Set bind graph data to graph rendering engine
         */
        bindData(graph) {
        	this.removeGraph();
            applyHideChildren(graph);
            var root = this.root;
            var layouter = this.layouter;
            var bindData = this.bindData.bind(this);
            var PORT_HEIGHT = this.PORT_HEIGHT;
            var CHAR_WIDTH = this.CHAR_WIDTH;


            // config of layouter
            layouter
                .options({
                    edgeRouting: "ORTHOGONAL",
                    // "org.eclipse.elk.layered.crossingMinimization.strategy":
                    // "LAYER_EVO"
                })
                .kgraph(graph)
                .size([width, height])
                .transformGroup(root);

            var nodes = layouter.getNodes().slice(1); // skip root node
            var edges = layouter.getEdges();
            nodes.forEach(this.initNodeSizes.bind(this));
                
            // by "g" we group nodes along with their ports
            var node = root.selectAll(".node")
                .data(nodes)
                .enter()
                .append("g");
            
            var nodeBody = node.append("rect");
            
            var port = node.selectAll(".port")
                .data(function(d) { return d.ports || []; })
                .enter()
                .append("g");
            
            var link = root.selectAll(".link")
                .data(edges)
                .enter()
                .append("path")
                .attr("class", "link");

            function toggleHideChildren(node) {
                var h = node.hideChildren = !node.hideChildren;
            }

            node.on("click", function (d) {
                var children;
                if (d.hideChildren)
                    children = d.__children;
                else
                    children = d.children;

                if (!children || children.length == 0)
                    return; // does not have anything to expand
                var graph = layouter.kgraph();
                layouter.cleanLayout();
                root.selectAll("*").remove();
                toggleHideChildren(d);    
                bindData(graph);
            });
            // Select net on click
            link.on("click", function(d) {
              var doSelect = !d.selected;
              var l = d3.select(this);
              var data = l.data()[0];
              // propagate click on all nets with same source
              var src = data.source;
              var srcP = data.sourcePort;
              link.classed("link-selected", function (d) {
                  if (d.source == src && d.sourcePort == srcP) {
                      d.selected = doSelect;
                  }
                  return d.selected;
              });
              d3.event.stopPropagation();
            });

            // apply layout
            layouter.on("finish", function applyLayout() {
              nodeBody
                .attr("width", function(d) { return d.width })
                .attr("height", function(d) { return d.height });

              var junctionPoints = [];
              // apply edge routes
              link.transition().attr("d", function(d) {
                if (!d.sections)
                    return "";
                if (d.bendpoints || d.sections.length > 1) {
                    throw new Error("NotImplemented");
                }
                if(d.junctionPoints)
                    d.junctionPoints.forEach(function (jp) {
                        junctionPoints.push(jp);
                    });
                return d3elk.section2svgPath(d.sections[0]);
              });

              var junctionPoints = root.selectAll(".junction-point")
                  .data(junctionPoints)
                  .enter()
                  .append("circle")
                  .attr("r", "3")
                  .attr("cx", function(d) {
                      return d.x;
                  })
                  .attr("cy", function(d) {
                      return d.y;
                  })
                  .attr("class", "junction-point");
              
              // apply node positions
              node.transition()
                .duration(0)
                .attr("transform", function(d) {
                    // if side of ports is not fixed resolve it from position
                    var c = d.properties['"org.eclipse.elk.portConstraints"'];
                    if (!PortConstraints_isSideFixed(c)) {
                        var w = d.width;
                        var h = d.height;
                        ports.forEach(function initPortSides(p) {
                            if (p.x < 0)
                                p.side = "WEST";
                            else if (p.y < 0)
                                p.side = "NORTH";
                            else if (p.x >= w)
                                p.side = "EAST";
                            else if (p.y >= h)
                                d.side = "SOUTH";
                            else
                                throw new Exception("wrong port position" + [p.x, p.y]);
                        });
                    }
                    if (typeof d.x === "undefined" || typeof d.x === "undefined") {
                        throw new Error("Node with undefined position", d);
                    }
                    return "translate(" + d.x + " " + d.y + ")"
                });
              
              // apply port positions
              port.transition()
                .duration(0)
                .attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"});

              // spot port name
              port.append("text")
                .attr("y", PORT_HEIGHT * 0.75)
                .text(function(d) {
                    if (d.ignoreLabel)
                        return "";
                    else if (d.level) {
                        var indent = '-'.repeat(d.level);
                        var side = d.properties.portSide;
                        if (side == "WEST") {
                           return indent + d.name;                    } else if (side == "EAST") {
                           return d.name + indent;
                        } else {
                            throw new Error(side);
                        }
                    } else
                        return d.name; 
                })
                .attr("x", function(d) {
                    var side = d.properties.portSide;
                    if (side == "WEST") {
                       return 7;
                    } else if (side == "EAST") {
                       return -this.getBBox().width - CHAR_WIDTH / 2;
                    } else if (side == "NORTH") {
                       return 0;
                    } else if (side == "SOUTH") {
                        return 0;
                    } else {
                        throw new Error(side);
                    }
                });
              
              // spot input/output marker
              port.append("use")
                  .attr("href", getIOMarker);
            });
            
            layouter.start();

            // set dimensions and style of node
            nodeBody
                .attr("class", function (d) { 
                    if (d.isExternalPort) {
                        return "node-external-port";
                    } else {
                        return "node";
                    }
                })
                .attr("rx", 5)
                .attr("ry", 5);
            
            // spot node label
            node.append("text")
                .text(function(d) { return d.name; });
            
            // spot node body text
            node.append("text")
                .call(this.renderTextLines.bind(this));
        }
        terminate() {
        	if (this.layouter)
        		this.layouter.terminate();
        }
    }

    exports.HwSchematic = HwSchematic;

    Object.defineProperty(exports, '__esModule', { value: true });

})));
