Powered by AppSignal & Oban Pro

verilog

stage2/koi.livemd

verilog

Mix.install([
  {:kino, "~> 0.10.0"}
])

Section

defmodule KinoOhi.Simcir do
  use Kino.JS

  def new(html) do
    Kino.JS.new(__MODULE__, html)
  end

  asset "main.js" do
    """
    'use strict';    
    var c0;
    function run(str) {
    //var src = document.getElementById("src");
    //var str = src.value;
    var codes = str.split("\\n");  
    var len = codes.length;
    var i;
    for (i = 0; i < len; i++)
    {
    var code = codes[i].split(" ");
    switch (code[0])
    {
    case "":
    break;
    case "out":
    var a = parseInt(code[1]);
      var b = parseInt(code[2]);
      c0.out(a, b);
    break;
    case "wire":
    break;
    case "make":
      var a = parseInt(code[1]);
      c0 = new J(a);
    break;
    case "in":
      var a = parseInt(code[1]);
      var b = parseInt(code[2]);
      c0.in(a, b);
    break;
    case "nand":  
      var a = parseInt(code[1]);
      var b = parseInt(code[2]);
      var c = parseInt(code[3]);
      c0.nand(a, b, c);
    break;
    case "xor":  
      var a = parseInt(code[1]);
      var b = parseInt(code[2]);
      var c = parseInt(code[3]);
      c0.xor(a, b, c);
    break;
    case "and":  
      var a = parseInt(code[1]);
      var b = parseInt(code[2]);
      var c = parseInt(code[3]);
      c0.and(a, b, c);
    break;
    case "or":  
      var a = parseInt(code[1]);
      var b = parseInt(code[2]);
      var c = parseInt(code[3]);
      c0.or(a, b, c);
    break;
    case "nor":  
      var a = parseInt(code[1]);
      var b = parseInt(code[2]);
      var c = parseInt(code[3]);
      c0.nor(a, b, c);
    break;
    case "not":  
      var a = parseInt(code[1]);
      var b = parseInt(code[2]);
      c0.not(a, b);
    break;
    default:
      alert(code[0]);
    break;
    }
    }
    c0.disp('koko');
    }
    class J {
    constructor(n) {
    var str = '{"width": 600,	"height": 600, "showToolbox": false, "toolbox": [], "devices": [], "connectors": []}';
    this.json = JSON.parse(str);
    this.dev = [];
    this.con = [];
    this.id = 0;
    this.y0 = 40;
    this.y1 = 40;
    this.y2 = 40;
    this.n = n;
    this.s = [];
    return this;
    }
    in(a, b) {
    b += 1;
    for(var i = a; i < b; i++)
    {
     var d = {
       "type": "In",
       "id": "dev" + this.id,
       "x": 40,
       "y": this.y0,
       "label": "In"
    };
    this.json.devices.push(d);
    this.s[i] = this.id;
    this.y0 += 50;
    this.id += 1;
    }
    return this;
    }
    wire(a, b) {
    b += 1;
    for(var i = a; i < b; i++)
    {
     this.s[i] = i;
    }
    return this;
    }
    out(a, b) {
    b += 1;
    for(var i = a; i < b; i++)
    {
     var d = {
       "type": "Out",
       "id": "dev" + this.id,
       "x": 400,
       "y": this.y2,
       "label": "Out"
     };
     this.json.devices.push(d);
     var c0 = {
       "from": "dev" + this.id + ".in0",
       "to": "dev" + this.s[i] + ".out0"
     };
     this.json.connectors.push(c0);
     this.y2 += 50;
     this.id += 1;
    }
    return this;
    }
    not(a, c) {
    var d = {
     "type": "NOT",
     "id": "dev" + this.id,
     "x": 120,
     "y": this.y1,
     "label": "NOT"
    };
    this.json.devices.push(d);
    var c0 = {
     "from": "dev" + this.id + ".in0",
     "to": "dev" + this.s[a] + ".out0"
    };
    this.s[c] = this.id;
    this.json.connectors.push(c0);
    this.y1 += 40;
    this.id += 1;
    return this;
    }
    xor(a, b, c) {
    var d = {
     "type": "XOR",
     "id": "dev" + this.id,
     "x": 150,
     "y": this.y1,
     "label": "XOR"
    };
    this.json.devices.push(d);
    var c0 = {
     "from": "dev" + this.id + ".in0",
     "to": "dev" + this.s[a] + ".out0"
    };
    var c1 = {
     "from": "dev" + this.id + ".in1",
     "to": "dev" + this.s[b] + ".out0"
    }
    this.s[c] = this.id;
    this.json.connectors.push(c0);
    this.json.connectors.push(c1);
    this.y1 += 40;
    this.id += 1;
    return this;
    }
    nand(a, b, c) {
    var d = {
     "type": "NAND",
     "id": "dev" + this.id,
     "x": 200,
     "y": this.y1,
     "label": "NAND"
    };
    this.json.devices.push(d);
    var c0 = {
     "from": "dev" + this.id + ".in0",
     "to": "dev" + this.s[a] + ".out0"
    };
    var c1 = {
     "from": "dev" + this.id + ".in1",
     "to": "dev" + this.s[b] + ".out0"
    }
    this.s[c] = this.id;
    this.json.connectors.push(c0);
    this.json.connectors.push(c1);
    this.y1 += 50;
    this.id += 1;
    return this;
    }
    and(a, b, c) {
    var d = {
     "type": "AND",
     "id": "dev" + this.id,
     "x": 210,
     "y": this.y1,
     "label": "AND"
    };
    this.json.devices.push(d);
    var c0 = {
     "from": "dev" + this.id + ".in0",
     "to": "dev" + this.s[a] + ".out0"
    };
    var c1 = {
     "from": "dev" + this.id + ".in1",
     "to": "dev" + this.s[b] + ".out0"
    }
    this.s[c] = this.id;
    this.json.connectors.push(c0);
    this.json.connectors.push(c1);
    this.y1 += 50;
    this.id += 1;
    return this;
    }
    nor(a, b, c) {
    var d = {
     "type": "NOR",
     "id": "dev" + this.id,
     "x": 200,
     "y": this.y1,
     "label": "NOR"
    };
    this.json.devices.push(d);
    var c0 = {
     "from": "dev" + this.id + ".in0",
     "to": "dev" + this.s[a] + ".out0"
    };
    var c1 = {
     "from": "dev" + this.id + ".in1",
     "to": "dev" + this.s[b] + ".out0"
    }
    this.s[c] = this.id;
    this.json.connectors.push(c0);
    this.json.connectors.push(c1);
    this.y1 += 50;
    this.id += 1;
    return this;
    }
    or(a, b, c) {
    var d = {
     "type": "OR",
     "id": "dev" + this.id,
     "x": 300,
     "y": this.y1,
     "label": "OR"
    };
    this.json.devices.push(d);
    var c0 = {
     "from": "dev" + this.id + ".in0",
     "to": "dev" + this.s[a] + ".out0"
    };
    var c1 = {
     "from": "dev" + this.id + ".in1",
     "to": "dev" + this.s[b] + ".out0"
    }
    this.s[c] = this.id;
    this.json.connectors.push(c0);
    this.json.connectors.push(c1);
    this.y1 += 40;
    this.id += 1;
    return this;
    }
    disp(s) {
    var koko = document.getElementById(s);
    var str = JSON.stringify(this.json);  
    koko.value = str;
    }
    }
    var simcir = {};
    simcir.$ = function() {
    var debug = location.hash == '#debug';
    var cacheIdKey = '.lessqCacheId';
    var cacheIdSeq = 0;
    var cache = {};
    var getCache = function(elm) {
    var cacheId = elm[cacheIdKey];
    if (typeof cacheId == 'undefined')
    {
    elm[cacheIdKey] = cacheId = cacheIdSeq++;
    cache[cacheId] = debug? { e: elm }: {};
    }
    return cache[cacheId];
    };
    var hasCache = function(elm) {
    return typeof elm[cacheIdKey] != 'undefined';
    };
    if (debug)
    {
    var lastKeys = {};
    var showCacheCount = function() {
    var cnt = 0;
    var keys = {};
    for (var k in cache)
    {
    cnt += 1;
    if (!lastKeys[k])
    {
    	console.log(cache[k]);
    }
    keys[k] = true;
    }
    lastKeys = keys;
    console.log('cacheCount:' + cnt);
    window.setTimeout(showCacheCount, 5000);
    };
    showCacheCount();
    }
    var removeCache = function(elm) {
    if (typeof elm[cacheIdKey] != 'undefined')
    {
    var cacheId = elm[cacheIdKey];
    var listenerMap = cache[cacheId].listenerMap;
    for (var type in listenerMap)
    {
    var listeners = listenerMap[type];
    for (var i = 0; i < listeners.length; i += 1)
    {
    	elm.removeEventListener(type, listeners[i]);
    }
    }
    delete elm[cacheIdKey];
    delete cache[cacheId];
    }
    while (elm.firstChild)
    {
    removeCache(elm.firstChild);
    elm.removeChild(elm.firstChild);
    }
    };
    var getData = function(elm) {
    if (!getCache(elm).data)
    {
    getCache(elm).data = {};
    }
    return getCache(elm).data;
    };
    var getListeners = function(elm, type) {
    if (!getCache(elm).listenerMap)
    {
    getCache(elm).listenerMap = {};
    }
    if (!getCache(elm).listenerMap[type])
    {
    getCache(elm).listenerMap[type] = [];
    }
    return getCache(elm).listenerMap[type];
    };
    var addEventListener = function(elm, type, listener, add) {
    var listeners = getListeners(elm, type);
    var newListeners = [];
    for (var i = 0; i < listeners.length; i += 1)
    {
    if (listeners[i] != listener)
    {
    newListeners.push(listeners[i]);
    }
    }
    if (add)
    {
    newListeners.push(listener);
    }
    getCache(elm).listenerMap[type] = newListeners;
    return true;
    };
    var CustomEvent = {
    preventDefault: function() {
    this._pD = true;
    },
    stopPropagation: function() {
    this._sP = true;
    },
    stopImmediatePropagation: function() {
    this._sIp = true;
    }
    };
    var trigger = function(elm, type, data) {
    var event = {
    type: type,
    target: elm,
    currentTarget: null,
    _pD: false,
    _sP: false,
    _sIp: false,
    __proto__: CustomEvent
    };
    for (var e = elm; e != null; e = e.parentNode)
    {
    if (!hasCache(e))
    {
    continue;
    }
    if (!getCache(e).listenerMap)
    {
    continue;
    }
    if (!getCache(e).listenerMap[type])
    {
    continue;
    }
    event.currentTarget = e;
    var listeners = getCache(e).listenerMap[type];
    for (var i = 0; i < listeners.length; i += 1)
    {
    listeners[i].call(e, event, data);
    if (event._sIp)
    {
    	return;
    }
    }
    if (event._sP)
    {
    return;
    }
    }
    };
    var data = function(elm, kv) {
    if (arguments.length == 2)
    {
    if (typeof kv == 'string') return getData(elm)[kv];
    for (var k in kv)
    {
    getData(elm)[k] = kv[k];
    }
    }
    else if (arguments.length == 3)
    {
    getData(elm)[kv] = arguments[2];
    }
    return elm;
    };
    var extend = function(o1, o2) {
    for (var k in o2)
    {
    o1[k] = o2[k];
    }
    return o1;
    };
    var each = function(it, callback) {
    if (typeof it.splice == 'function')
    {
    for (var i = 0; i < it.length; i += 1)
    {
    callback(i, it[i]);
    }
    }
    else
    {
    for (var k in it)
    {
    callback(k, it[k]);
    }
    }
    };
    var grep = function(list, accept) {
    var newList = [];
    for (var i = 0; i < list.length; i += 1)
    {
    var item = list[i];
    if (accept(item))
    {
    newList.push(item);
    }
    }
    return newList;
    };
    var addClass = function(elm, className, add) {
    var classes = (elm.getAttribute('class') || '').split(/\\s+/g);
    var newClasses = '';
    for (var i = 0; i < classes.length; i+= 1)
    {
    if (classes[i] == className)
    {
    continue;
    }
    newClasses += ' ' + classes[i];
    }
    if (add)
    {
    newClasses += ' ' + className;
    }
    elm.setAttribute('class', newClasses);
    };
    var hasClass = function(elm, className) {
    var classes = (elm.getAttribute('class') || '').split(/\\s+/g);
    for (var i = 0; i < classes.length; i+= 1)
    {
    if (classes[i] == className)
    {
    return true;
    }
    }
    return false;
    };
    var matches = function(elm, selector) {
    if (elm.nodeType != 1)
    {
    return false;
    }
    else if (!selector)
    {
    return true;
    }
    var sels = selector.split(/[,\\s]+/g);
    for (var i = 0; i < sels.length; i += 1)
    {
    var sel = sels[i];
    if (sel.substring(0, 1) == '#')
    {
    throw 'not supported:' + sel;
    }
    else if (sel.substring(0, 1) == '.')
    {
    if (hasClass(elm, sel.substring(1)))
    {
    	return true;
    }
    }
    else
    {
    if (elm.tagName.toUpperCase() == sel.toUpperCase())
    {
    	return true;
    }
    }
    }
    return false;
    };
    var parser = new window.DOMParser();
    var html = function(html) {
    var doc = parser.parseFromString('<div xmlns="http://www.w3.org/1999/xhtml">' + html + '</div>', 'text/xml').firstChild;
    var elms = [];
    while (doc.firstChild)
    {
    elms.push(doc.firstChild);
    doc.removeChild(doc.firstChild);
    }
    elms.__proto__ = fn;
    return elms;
    };
    var pxToNum = function(px) {
    if (typeof px != 'string' || px.length <= 2 || px.charAt(px.length - 2) != 'p' || px.charAt(px.length - 1) != 'x')
    {
    throw 'illegal px:' + px;
    }
    return +px.substring(0, px.length - 2);
    };
    var buildQuery = function(data) {
    var query = '';
    for (var k in data)
    {
    if (query.length > 0)
    {
    query += '&';
    }
    query += window.encodeURIComponent(k);
    query += '=';
    query += window.encodeURIComponent(data[k]);
    }
    return query;
    };
    var parseResponse = function() {
    var contentType = this.getResponseHeader('content-type');
    if (contentType != null)
    {
    contentType = contentType.replace(/\\s*;.+$/, '').toLowerCase();
    }
    if (contentType == 'text/xml' || contentType == 'application/xml')
    {
    return parser.parseFromString(this.responseText, 'text/xml');
    }
    else if (contentType == 'text/json' || contentType == 'application/json')
    {
    return JSON.parse(this.responseText);
    }
    else
    {
    return this.response;
    }
    };
    var ajax = function(params) {
    params = extend({
    url: '',
    method: 'GET',
    contentType: 'application/x-www-form-urlencoded;charset=UTF-8',
    cache: true,
    processData: true,
    async: true
    }, params);
    if (!params.async)
    {
    throw 'not supported.';
    }
    var method = params.method.toUpperCase();
    var data = null;
    var contentType = params.contentType;
    if (method == 'POST' || method == 'PUT')
    {
    data = (typeof params.data == 'object' && params.processData) ? buildQuery(params.data) : params.data;
    }
    else
    {
    contentType = false;
    }
    var xhr = params.xhr? params.xhr(): new window.XMLHttpRequest();
    xhr.open(method, params.url, params.async);
    if (contentType !== false)
    {
    xhr.setRequestHeader('Content-Type', contentType);
    }
    xhr.onreadystatechange = function() {
    if(xhr.readyState == window.XMLHttpRequest.DONE)
    {
    try
    {
    	if (xhr.status == 200)
    	{
    		done.call(xhr, parseResponse.call(this));
    	}
    	else
    	{
    		fail.call(xhr);
    	}
    }
    finally
    {
    	always.call(xhr);
    }
    }
    };
    window.setTimeout(function() {
    xhr.send(data);
    }, 0);
    var done = function(data) {};
    var fail = function() {};
    var always = function() {};
    var $ = {
    done: function(callback) {
    done = callback;
    return $;
    },
    fail: function(callback) {
    fail = callback;
    return $;
    },
    always: function(callback) {
    always = callback;
    return $;
    },
    abort: function() {
    xhr.abort();
    return $;
    }
    };
    return $;
    };
    var fn = {
    attr: function(kv) {
    if (arguments.length == 1)
    {
    if (typeof kv == 'string') return this.getAttribute(kv);
    for (var k in kv)
    {
    	this.setAttribute(k, kv[k]);
    }
    }
    else if (arguments.length == 2)
    {
    this.setAttribute(kv, arguments[1]);
    }
    return this;
    },
    prop: function(kv) {
    if (arguments.length == 1)
    {
    if (typeof kv == 'string') return this[kv];
    for (var k in kv)
    {
    	this[k] = kv[k];
    }
    }
    else if (arguments.length == 2)
    {
    this[kv] = arguments[1];
    }
    return this;
    },
    css: function(kv) {
    if (arguments.length == 1)
    {
    if (typeof kv == 'string') return this.style[kv];
    for (var k in kv)
    {
    	this.style[k] = kv[k];
    }
    }
    else if (arguments.length == 2)
    {
    this.style[kv] = arguments[1];
    }
    return this;
    },
    data: function(kv) {
    var args = [ this ];
    for (var i = 0; i < arguments.length; i += 1)
    {
    args.push(arguments[i]);
    };
    return data.apply(null, args);
    },
    val: function() {
    if (arguments.length == 0)
    {
    return this.value || '';
    }
    else if (arguments.length == 1)
    {
    this.value = arguments[0];
    }
    return this;
    },
    on: function(type, listener) {
    var types = type.split(/\\s+/g);
    for (var i = 0; i < types.length; i += 1)
    {
    this.addEventListener(types[i], listener);
    addEventListener(this, types[i], listener, true);
    }
    return this;
    },
    off: function(type, listener) {
    var types = type.split(/\\s+/g);
    for (var i = 0; i < types.length; i += 1)
    {
    this.removeEventListener(types[i], listener);
    addEventListener(this, types[i], listener, false);
    }
    return this;
    },
    trigger: function(type, data) {
    trigger(this, type, data);
    return this;
    },
    offset: function() {
    var off = {
    left: 0,
    top: 0
    };
    var base = null;
    for (var e = this; e.parentNode != null; e = e.parentNode)
    {
    if (e.offsetParent != null)
    {
    	base = e;
    	break;
    }
    }
    if (base != null)
    {
    for (var e = base; e.offsetParent != null; e = e.offsetParent)
    {
    	off.left += e.offsetLeft;
    	off.top += e.offsetTop;
    }
    }
    for (var e = this; e.parentNode != null && e != document.body; e = e.parentNode)
    {
    off.left -= e.scrollLeft;
    off.top -= e.scrollTop;
    }
    return off;
    },
    append: function(elms) {
    if (typeof elms == 'string')
    {
    elms = html(elms);
    }
    for (var i = 0; i < elms.length; i += 1)
    {
    this.appendChild(elms[i]);
    }
    return this;
    },
    prepend: function(elms) {
    if (typeof elms == 'string')
    {
    elms = html(elms);
    }
    for (var i = 0; i < elms.length; i += 1)
    {
    if (this.firstChild)
    {
    	this.insertBefore(elms[i], this.firstChild);
    }
    else
    {
    	this.appendChild(elms[i]);
    }
    }
    return this;
    },
    insertBefore: function(elms) {
    var elm = elms[0];
    elm.parentNode.insertBefore(this, elm);
    return this;
    },
    insertAfter: function(elms) {
    var elm = elms[0];
    if (elm.nextSibling)
    {
    elm.parentNode.insertBefore(this, elm.nextSibling);
    }
    else
    {
    elm.parentNode.appendChild(this);
    }
    return this;
    },
    remove: function() {
    if (this.parentNode)
    {
    this.parentNode.removeChild(this);
    }
    removeCache(this);
    return this;
    },
    detach: function() {
    if (this.parentNode)
    {
    this.parentNode.removeChild(this);
    }
    return this;
    },
    parent: function() {
    return $(this.parentNode);
    },
    closest: function(selector) {
    for (var e = this; e != null; e = e.parentNode)
    {
    if (matches(e, selector))
    {
    	return $(e);
    }
    }
    return $();
    },
    find: function(selector) {
    var elms = [];
    var childNodes = this.querySelectorAll(selector);
    for (var i = 0; i < childNodes.length; i += 1)
    {
    elms.push(childNodes.item(i));
    }
    elms.__proto__ = fn;
    return elms;
    },
    children: function(selector) {
    var elms = [];
    var childNodes = this.childNodes;
    for (var i = 0; i < childNodes.length; i += 1)
    {
    if (matches(childNodes.item(i), selector))
    {
    	elms.push(childNodes.item(i));
    }
    }
    elms.__proto__ = fn;
    return elms;
    },
    index: function(selector) {
    return Array.prototype.indexOf.call($(this).parent().children(selector), this);
    },
    clone: function() {
    return $(this.cloneNode(true));
    },
    focus: function() {
    this.focus(); return this;
    },
    select: function() {
    this.select(); return this;
    },
    submit: function() {
    this.submit(); return this;
    },
    scrollLeft: function() {
    if (arguments.length == 0) return this.scrollLeft;
    this.scrollLeft = arguments[0];
    return this;
    },
    scrollTop: function() {
    if (arguments.length == 0) return this.scrollTop;
    this.scrollTop = arguments[0];
    return this;
    },
    html: function() {
    if (arguments.length == 0) return this.innerHTML;
    this.innerHTML = arguments[0];
    return this;
    },
    text: function() {
    if (typeof this.textContent != 'undefined')
    {
    if (arguments.length == 0) return this.textContent;
    this.textContent = arguments[0];
    return this;
    }
    else
    {
    if (arguments.length == 0) return this.innerText;
    this.innerText = arguments[0];
    return this;
    }
    },
    outerWidth: function(margin) {
    var w = this.offsetWidth;
    if (margin)
    {
    var cs = window.getComputedStyle(this, null);
    return w + pxToNum(cs.marginLeft) + pxToNum(cs.marginRight);
    }
    return w;
    },
    innerWidth: function() {
    var cs = window.getComputedStyle(this, null);
    return this.offsetWidth - pxToNum(cs.borderLeftWidth) - pxToNum(cs.borderRightWidth);
    },
    width: function() {
    if (this == window) return this.innerWidth;
    var cs = window.getComputedStyle(this, null);
    return this.offsetWidth - pxToNum(cs.borderLeftWidth) - pxToNum(cs.borderRightWidth) - pxToNum(cs.paddingLeft) - pxToNum(cs.paddingRight);
    },
    outerHeight: function(margin) {
    var h = this.offsetHeight;
    if (margin)
    {
    var cs = window.getComputedStyle(this, null);
    return h + pxToNum(cs.marginTop) + pxToNum(cs.marginBottom);
    }
    return h;
    },
    innerHeight: function() {
    var cs = window.getComputedStyle(this, null);
    return this.offsetHeight - pxToNum(cs.borderTopWidth) - pxToNum(cs.borderBottomWidth);
    },
    height: function() {
    if (this == window) return this.innerHeight;
    var cs = window.getComputedStyle(this, null);
    return this.offsetHeight - pxToNum(cs.borderTopWidth) - pxToNum(cs.borderBottomWidth) - pxToNum(cs.paddingTop) - pxToNum(cs.paddingBottom);
    },
    addClass: function(className) {
    addClass(this, className, true);
    return this;
    },
    removeClass: function(className) {
    addClass(this, className, false);
    return this;
    },
    hasClass: function(className) {
    return hasClass(this, className);
    }
    };
    each(fn, function(name, func) {
    fn[name] = function() {
    var newRet = null;
    for (var i = 0; i < this.length; i += 1)
    {
    var elm = this[i];
    var ret = func.apply(elm, arguments);
    if (elm !== ret)
    {
    	if (ret != null && ret.__proto__ == fn)
    	{
    		if (newRet == null)
    		{
    			newRet = [];
    		}
    		newRet = newRet.concat(ret);
    	}
    	else
    	{
    		return ret;
    	}
    }
    }
    if (newRet != null)
    {
    newRet.__proto__ = fn;
    return newRet;
    }
    return this;
    };
    });
    fn = extend(fn, {
    each: function(callback) {
    for (var i = 0; i < this.length; i += 1)
    {
    callback.call(this[i], i);
    }
    return this;
    },
    first: function() {
    return $(this.length > 0 ? this[0] : null);
    },
    last: function() {
    return $(this.length > 0 ? this[this.length - 1] : null);
    }
    });
    var $ = function(target) {
    if (typeof target == 'function')
    {
    return $(document).on('DOMContentLoaded', target);
    }
    else if (typeof target == 'string')
    {
    if (target.charAt(0) == '<')
    {
    return html(target);
    }
    else
    {
    var childNodes = document.querySelectorAll(target);
    var elms = [];
    for (var i = 0; i < childNodes.length; i += 1)
    {
    	elms.push(childNodes.item(i));
    }
    elms.__proto__ = fn;
    return elms;
    }
    }
    else if (typeof target == 'object' && target != null)
    {
    if (target.__proto__ == fn)
    {
    return target;
    }
    else
    {
    var elms = [];
    elms.push(target);
    elms.__proto__ = fn;
    return elms;
    }
    }
    else
    {
    var elms = [];
    elms.__proto__ = fn;
    return elms;
    }
    };
    return extend($, {
    fn: fn,
    extend: extend,
    each: each,
    grep: grep,
    data: data,
    ajax: ajax
    });
    }();
    !function($s) {
    var $ = $s.$;
    var createSVGElement = function(tagName) {
    return $(document.createElementNS('http://www.w3.org/2000/svg', tagName));
    };
    var createSVG = function(w, h) {
    return createSVGElement('svg').attr({
    version: '1.1',
    width: w,
    height: h,
    viewBox: '0 0 ' + w + ' ' + h
    });
    };
    var graphics = function($target) {
    var attr = {};
    var buf = '';
    var moveTo = function(x, y) {
    buf += ' M ' + x + ' ' + y;
    };
    var lineTo = function(x, y) {
    buf += ' L ' + x + ' ' + y;
    };
    var curveTo = function(x1, y1, x, y) {
    buf += ' Q ' + x1 + ' ' + y1 + ' ' + x + ' ' + y;
    };
    var closePath = function(close) {
    if (close)
    {
    buf += ' Z';
    }
    $target.append(createSVGElement('path').attr('d', buf).attr(attr));
    buf = '';
    };
    var drawRect = function(x, y, width, height) {
    $target.append(createSVGElement('rect').attr({
    x: x,
    y: y,
    width: width,
    height: height
    }).attr(attr));
    };
    var drawCircle = function(x, y, r) {
    $target.append(createSVGElement('circle').attr({
    cx: x,
    cy: y,
    r: r
    }).attr(attr));
    };
    return {
    attr: attr,
    moveTo: moveTo,
    lineTo: lineTo,
    curveTo: curveTo,
    closePath: closePath,
    drawRect: drawRect,
    drawCircle: drawCircle
    };
    };
    var transform = function() {
    var attrX = 'simcir-transform-x';
    var attrY = 'simcir-transform-y';
    var attrRotate = 'simcir-transform-rotate';
    var num = function($o, k) {
    var v = $o.attr(k);
    return v ? +v : 0;
    };
    return function($o, x, y, rotate) {
    if (arguments.length >= 3)
    {
    var transform = 'translate(' + x + ' ' + y + ')';
    if (rotate)
    {
    	transform += ' rotate(' + rotate + ')';
    }
    $o.attr('transform', transform);
    $o.attr(attrX, x);
    $o.attr(attrY, y);
    $o.attr(attrRotate, rotate);
    }
    else if (arguments.length == 1)
    {
    return {
    	x: num($o, attrX),
    	y: num($o, attrY),
    	rotate: num($o, attrRotate)
    };
    }
    };
    }();
    var offset = function($o) {
    var x = 0;
    var y = 0;
    while ($o[0].nodeName != 'svg')
    {
    var pos = transform($o);
    x += pos.x;
    y += pos.y;
    $o = $o.parent();
    }
    return {
    x: x,
    y: y
    };
    };
    var enableEvents = function($o, enable) {
    $o.css('pointer-events', enable ? 'visiblePainted' : 'none');
    };
    var disableSelection = function($o) {
    $o.each(function() {
    this.onselectstart = function() {
    return false;
    };
    }).css('-webkit-user-select', 'none');
    };
    var controller = function() {
    var id = 'controller';
    return function($ui, controller) {
    if (arguments.length == 1)
    {
    return $.data($ui[0], id);
    }
    else if (arguments.length == 2)
    {
    $.data($ui[0], id, controller);
    }
    };
    }();
    var eventQueue = function() {
    var delay = 50;
    var limit = 40;
    var _queue = null;
    var postEvent = function(event) {
    if (_queue == null)
    {
    _queue = [];
    }
    _queue.push(event);
    };
    var dispatchEvent = function() {
    var queue = _queue;
    _queue = null;
    while (queue.length > 0)
    {
    var e = queue.shift();
    e.target.trigger(e.type);
    }
    };
    var getTime = function() {
    return new Date().getTime();
    };
    var timerHandler = function() {
    var start = getTime();
    while (_queue != null && getTime() - start < limit)
    {
    dispatchEvent();
    }
    window.setTimeout(timerHandler, Math.max(delay - limit, delay - (getTime() - start)));
    };
    timerHandler();
    return {
    postEvent: postEvent
    };
    }();
    var unit = 16;
    var fontSize = 12;
    var createLabel = function(text) {
    return createSVGElement('text').text(text).css('font-size', fontSize + 'px');
    };
    var createNode = function(type, label, description, headless) {
    var $node = createSVGElement('g').attr('simcir-node-type', type);
    if (!headless)
    {
    $node.attr('class', 'simcir-node');
    }
    var node = createNodeController({
    $ui: $node,
    type: type,
    label: label,
    description: description,
    headless: headless
    });
    if (type == 'in')
    {
    controller($node, createInputNodeController(node));
    }
    else if (type == 'out')
    {
    controller($node, createOutputNodeController(node));
    }
    else
    {
    throw 'unknown type:' + type;
    }
    return $node;
    };
    var isActiveNode = function($o) {
    return $o.closest('.simcir-node').length == 1 && $o.closest('.simcir-toolbox').length == 0;
    };
    var createNodeController = function(node) {
    var _value = null;
    var setValue = function(value, force) {
    if (_value === value && !force)
    {
    return;
    }
    _value = value;
    eventQueue.postEvent({target: node.$ui, type: 'nodeValueChange'});
    };
    var getValue = function() {
    return _value;
    };
    if (!node.headless)
    {
    node.$ui.attr('class', 'simcir-node simcir-node-type-' + node.type);
    var $circle = createSVGElement('circle').attr({
    cx: 0,
    cy: 0,
    r: 4
    });
    node.$ui.on('mouseover', function(event) {
    if (isActiveNode(node.$ui))
    {
    	node.$ui.addClass('simcir-node-hover');
    }
    });
    node.$ui.on('mouseout', function(event) {
    if (isActiveNode(node.$ui))
    {
    	node.$ui.removeClass('simcir-node-hover');
    }
    });
    node.$ui.append($circle);
    var appendLabel = function(text, align) {
    var $label = createLabel(text).attr('class', 'simcir-node-label');
    enableEvents($label, false);
    if (align == 'right')
    {
    	$label.attr('text-anchor', 'start').attr('x', 6).attr('y', fontSize / 2);
    } else if (align == 'left')
    {
    	$label.attr('text-anchor', 'end').attr('x', -6).attr('y', fontSize / 2);
    }
    node.$ui.append($label);
    };
    if (node.label)
    {
    if (node.type == 'in')
    {
    	appendLabel(node.label, 'right');
    }
    else if (node.type == 'out')
    {
    	appendLabel(node.label, 'left');
    }
    }
    if (node.description)
    {
    if (node.type == 'in')
    {
    	appendLabel(node.description, 'left');
    }
    else if (node.type == 'out')
    {
    	appendLabel(node.description, 'right');
    }
    }
    node.$ui.on('nodeValueChange', function(event) {
    if (_value != null)
    {
    	node.$ui.addClass('simcir-node-hot');
    }
    else
    {
    	node.$ui.removeClass('simcir-node-hot');
    }
    });
    }
    return $.extend(node, {
    setValue: setValue,
    getValue: getValue
    });
    };
    var createInputNodeController = function(node) {
    var output = null;
    var setOutput = function(outNode) {
    output = outNode;
    };
    var getOutput = function() {
    return output;
    };
    return $.extend(node, {
    setOutput: setOutput,
    getOutput: getOutput
    });
    };
    var createOutputNodeController = function(node) {
    var inputs = [];
    var super_setValue = node.setValue;
    var setValue = function(value) {
    super_setValue(value);
    for (var i = 0; i < inputs.length; i += 1)
    {
    inputs[i].setValue(value);
    }
    };
    var connectTo = function(inNode) {
    if (inNode.getOutput() != null)
    {
    inNode.getOutput().disconnectFrom(inNode);
    }
    inNode.setOutput(node);
    inputs.push(inNode);
    inNode.setValue(node.getValue(), true);
    };
    var disconnectFrom = function(inNode) {
    if (inNode.getOutput() != node)
    {
    throw 'not connected.';
    }
    inNode.setOutput(null);
    inNode.setValue(null, true);
    inputs = $.grep(inputs, function(v) {
    return v != inNode;
    });
    };
    var getInputs = function() {
    return inputs;
    };
    return $.extend(node, {
    setValue: setValue,
    getInputs: getInputs,
    connectTo: connectTo,
    disconnectFrom: disconnectFrom
    });
    };
    var createDevice = function(deviceDef, headless, scope) {
    headless = headless || false;
    scope = scope || null;
    var $dev = createSVGElement('g');
    if (!headless)
    {
    $dev.attr('class', 'simcir-device');
    }
    controller($dev, createDeviceController({
    $ui: $dev,
    deviceDef: deviceDef,
    headless: headless,
    scope: scope,
    doc: null
    }));
    var factory = factories[deviceDef.type];
    if (factory)
    {
    factory(controller($dev));
    }
    if (!headless)
    {
    controller($dev).createUI();
    }
    return $dev;
    };
    var createDeviceController = function(device) {
    var inputs = [];
    var outputs = [];
    var addInput = function(label, description) {
    var $node = createNode('in', label, description, device.headless);
    $node.on('nodeValueChange', function(event) {
    device.$ui.trigger('inputValueChange');
    });
    if (!device.headless)
    {
    device.$ui.append($node);
    }
    var node = controller($node);
    inputs.push(node);
    return node;
    };
    var addOutput = function(label, description) {
    var $node = createNode('out', label, description, device.headless);
    if (!device.headless)
    {
    device.$ui.append($node);
    }
    var node = controller($node);
    outputs.push(node);
    return node;
    };
    var getInputs = function() {
    return inputs;
    };
    var getOutputs = function() {
    return outputs;
    };
    var disconnectAll = function() {
    $.each(getInputs(), function(i, inNode) {
    var outNode = inNode.getOutput();
    if (outNode != null)
    {
    	outNode.disconnectFrom(inNode);
    }
    });
    $.each(getOutputs(), function(i, outNode) {
    $.each(outNode.getInputs(), function(i, inNode) {
    	outNode.disconnectFrom(inNode);
    });
    });
    };
    device.$ui.on('dispose', function() {
    $.each(getInputs(), function(i, inNode) {
    inNode.$ui.remove();
    });
    $.each(getOutputs(), function(i, outNode) {
    outNode.$ui.remove();
    });
    device.$ui.remove();
    });
    var selected = false;
    var setSelected = function(value) {
    selected = value;
    device.$ui.trigger('deviceSelect');
    };
    var isSelected = function() {
    return selected;
    };
    var label = device.deviceDef.label;
    var defaultLabel = device.deviceDef.type;
    if (typeof label == 'undefined')
    {
    label = defaultLabel;
    }
    var setLabel = function(value) {
    value = value.replace(/^\\s+|\\s+$/g, '');
    label = value || defaultLabel;
    device.$ui.trigger('deviceLabelChange');
    };
    var getLabel = function() {
    return label;
    };
    var getSize = function() {
    var nodes = Math.max(device.getInputs().length, device.getOutputs().length);
    return {
    width: unit * 2,
    height: unit * Math.max(2, device.halfPitch ? (nodes + 1) / 2 : nodes)
    };
    };
    var layoutUI = function() {
    var size = device.getSize();
    var w = size.width;
    var h = size.height;
    device.$ui.children('.simcir-device-body').attr({x: 0, y: 0, width: w, height: h});
    var pitch = device.halfPitch? unit / 2: unit;
    var layoutNodes = function(nodes, x) {
    var offset = (h - pitch * (nodes.length - 1)) / 2;
    $.each(nodes, function(i, node) {
    	transform(node.$ui, x, pitch * i + offset);
    });
    };
    layoutNodes(getInputs(), 0);
    layoutNodes(getOutputs(), w);
    device.$ui.children('.simcir-device-label').attr({
    x: w / 2,
    y: h + fontSize
    });
    };
    var createUI = function() {
    device.$ui.attr('class', 'simcir-device');
    device.$ui.on('deviceSelect', function() {
    if (selected)
    {
    	$(this).addClass('simcir-device-selected');
    }
    else
    {
    	$(this).removeClass('simcir-device-selected');
    }
    });
    var $body = createSVGElement('rect').
    attr('class', 'simcir-device-body').
    attr('rx', 2).attr('ry', 2);
    device.$ui.prepend($body);
    var $label = createLabel(label).
    attr('class', 'simcir-device-label').
    attr('text-anchor', 'middle');
    device.$ui.on('deviceLabelChange', function() {
    $label.text(getLabel());
    });
    var label_dblClickHandler = function(event) {
    event.preventDefault();
    event.stopPropagation();
    var $workspace = $(event.target).closest('.simcir-workspace');
    if (!controller($workspace).data().editable) 
    {
    	return;
    }
    var title = 'Enter device name ';
    var $labelEditor = $('<input type="text"/>').
    	addClass('simcir-label-editor').
    	val($label.text()).
    	on('keydown', function(event) {
    		if (event.keyCode == 13) 
    		{
    			// ENTER
    			setLabel($(this).val());
    			$dlg.remove();
    		} 
    		else if (event.keyCode == 27) 
    		{
    			// ESC
    			$dlg.remove();
    		}
    	});
    var $placeHolder = $('<div></div>').
    	append($labelEditor);
    var $dlg = showDialog(title, $placeHolder);
    $labelEditor.focus();
    };
    device.$ui.on('deviceAdd', function() {
    $label.on('dblclick', label_dblClickHandler);
    });
    device.$ui.on('deviceRemove', function() {
    $label.off('dblclick', label_dblClickHandler);
    });
    device.$ui.append($label);
    layoutUI();
    };
    var getState = function() {
    return null;
    };
    return $.extend(device, {
    addInput: addInput,
    addOutput: addOutput,
    getInputs: getInputs,
    getOutputs: getOutputs,
    disconnectAll: disconnectAll,
    setSelected: setSelected,
    isSelected: isSelected,
    getLabel: getLabel,
    halfPitch: false,
    getSize: getSize,
    createUI: createUI,
    layoutUI: layoutUI,
    getState: getState
    });
    };
    var createConnector = function(x1, y1, x2, y2) {
    return createSVGElement('path').attr('d', 'M ' + x1 + ' ' + y1 + ' L ' + x2 + ' ' + y2).attr('class', 'simcir-connector');
    };
    var connect = function($node1, $node2) {
    var type1 = $node1.attr('simcir-node-type');
    var type2 = $node2.attr('simcir-node-type');
    if (type1 == 'in' && type2 == 'out')
    {
    controller($node2).connectTo(controller($node1));
    }
    else if (type1 == 'out' && type2 == 'in')
    {
    controller($node1).connectTo(controller($node2));
    }
    };
    var buildCircuit = function(data, headless, scope) {
    var $devices = [];
    var $devMap = {};
    var getNode = function(path) {
    if (!path.match(/^(\\w+)\\.(in|out)([0-9]+)$/g))
    {
    throw 'unknown path:' + path;
    }
    var devId = RegExp.$1;
    var type = RegExp.$2;
    var index = +RegExp.$3;
    return (type == 'in') ? controller($devMap[devId]).getInputs()[index] : controller($devMap[devId]).getOutputs()[index];
    };
    $.each(data.devices, function(i, deviceDef) {
    var $dev = createDevice(deviceDef, headless, scope);
    transform($dev, deviceDef.x, deviceDef.y);
    $devices.push($dev);
    $devMap[deviceDef.id] = $dev;
    });
    $.each(data.connectors, function(i, conn) {
    var nodeFrom = getNode(conn.from);
    var nodeTo = getNode(conn.to);
    if (nodeFrom && nodeTo)
    {
    connect(nodeFrom.$ui, nodeTo.$ui);
    }
    });
    return $devices;
    };
    var dialogManager = function() {
    var dialogs = [];
    var updateDialogs = function($dlg, remove) {
    var newDialogs = [];
    $.each(dialogs, function(i) {
    if (dialogs[i] != $dlg)
    {
    	newDialogs.push(dialogs[i]);
    }
    });
    if (!remove)
    {
    newDialogs.push($dlg);
    }
    $.each(newDialogs, function(i) {
    newDialogs[i].css('z-index', '' + (i + 1));
    });
    dialogs = newDialogs;
    };
    return {
    add: function($dlg) {
    updateDialogs($dlg);
    },
    remove: function($dlg) {
    updateDialogs($dlg, true);
    },
    toFront: function($dlg) {
    updateDialogs($dlg);
    }
    };
    }();
    var showDialog = function(title, $content) {
    var $closeButton = function() {
    var r = 16;
    var pad = 4;
    var $btn = createSVG(r, r).attr('class', 'simcir-dialog-close-button');
    var g = graphics($btn);
    g.drawRect(0, 0, r, r);
    g.attr['class'] = 'simcir-dialog-close-button-symbol';
    g.moveTo(pad, pad);
    g.lineTo(r - pad, r - pad);
    g.closePath();
    g.moveTo(r - pad, pad);
    g.lineTo(pad, r - pad);
    g.closePath();
    return $btn;
    }();
    var $title = $('<div></div>').addClass('simcir-dialog-title').text(title).css('cursor', 'default').on('mousedown', function(event) {
    event.preventDefault();
    });
    var $dlg = $('<div></div>').addClass('simcir-dialog').css({
    position:'absolute'
    }).append($title.css('float', 'left')).append($closeButton.css('float', 'right')).append($('<br/>').css('clear', 'both')).append($content);
    $('BODY').append($dlg);
    dialogManager.add($dlg);
    var dragPoint = null;
    var dlg_mouseDownHandler = function(event) {
    if (!$(event.target).hasClass('simcir-dialog') && !$(event.target).hasClass('simcir-dialog-title'))
    {
    return;
    }
    event.preventDefault();
    dialogManager.toFront($dlg);
    var off = $dlg.offset();
    dragPoint = {
    x: event.pageX - off.left,
    y: event.pageY - off.top
    };
    $(document).on('mousemove', dlg_mouseMoveHandler);
    $(document).on('mouseup', dlg_mouseUpHandler);
    };
    var dlg_mouseMoveHandler = function(event) {
    moveTo(event.pageX - dragPoint.x, event.pageY - dragPoint.y);
    };
    var dlg_mouseUpHandler = function(event) {
    $(document).off('mousemove', dlg_mouseMoveHandler);
    $(document).off('mouseup', dlg_mouseUpHandler);
    };
    $dlg.on('mousedown', dlg_mouseDownHandler);
    $closeButton.on('mousedown', function() {
    $dlg.trigger('close');
    $dlg.remove();
    dialogManager.remove($dlg);
    });
    var w = $dlg.width();
    var h = $dlg.height();
    var cw = $(window).width();
    var ch = $(window).height();
    var getProp = function(id) {
    return $('HTML')[id]() || $('BODY')[id]();
    };
    var x = (cw - w) / 2 + getProp('scrollLeft');
    var y = (ch - h) / 2 + getProp('scrollTop');
    var moveTo = function(x, y) {
    $dlg.css({
    left: x + 'px',
    top: y + 'px'
    });
    };
    moveTo(x, y);
    return $dlg;
    };
    var createDeviceRefFactory = function(data) {
    return function(device) {
    var $devs = buildCircuit(data, true, {});
    var $ports = [];
    $.each($devs, function(i, $dev) {
    var deviceDef = controller($dev).deviceDef;
    if (deviceDef.type == 'In' || deviceDef.type == 'Out')
    {
    	$ports.push($dev);
    }
    });
    $ports.sort(function($p1, $p2) {
    var x1 = controller($p1).deviceDef.x;
    var y1 = controller($p1).deviceDef.y;
    var x2 = controller($p2).deviceDef.x;
    var y2 = controller($p2).deviceDef.y;
    if (x1 == x2)
    {
    	return (y1 < y2) ? -1 : 1;
    }
    return (x1 < x2) ? -1 : 1;
    });
    var getDesc = function(port) {
    return port? port.description: '';
    };
    $.each($ports, function(i, $port) {
    var port = controller($port);
    var portDef = port.deviceDef;
    var inPort;
    var outPort;
    if (portDef.type == 'In')
    {
    	outPort = port.getOutputs()[0];
    	inPort = device.addInput(portDef.label, getDesc(outPort.getInputs()[0]));
    	var inNode = port.getInputs()[0];
    	if (inNode.getOutput() != null)
    	{
    		inNode.getOutput().disconnectFrom(inNode);
    	}
    }
    else if (portDef.type == 'Out')
    {
    	inPort = port.getInputs()[0];
    	outPort = device.addOutput(portDef.label, getDesc(inPort.getOutput()));
    	var outNode = port.getOutputs()[0];
    	$.each(outNode.getInputs(), function(i, inNode) {
    		if (inNode.getOutput() != null)
    		{
    			inNode.getOutput().disconnectFrom(inNode);
    		}
    	});
    }
    inPort.$ui.on('nodeValueChange', function() {
    	outPort.setValue(inPort.getValue());
    });
    });
    var super_getSize = device.getSize;
    device.getSize = function() {
    var size = super_getSize();
    return {
    	width: unit * 4,
    	height: size.height
    };
    };
    device.$ui.on('dispose', function() {
    $.each($devs, function(i, $dev) {
    	$dev.trigger('dispose');
    });
    });
    device.$ui.on('dblclick', function(event) {
    event.preventDefault();
    event.stopPropagation();
    showDialog(device.deviceDef.label || device.deviceDef.type, setupSimcir($('<div></div>'), data)).on('close', function() {
    	$(this).find('.simcir-workspace').trigger('dispose');
    });
    });
    };
    };
    var createCustomLayoutDeviceRefFactory = function(data) {
    return function(device) {
    var $devs = buildCircuit(data, true, {});
    var $ports = [];
    var intfs = [];
    $.each($devs, function(i, $dev) {
    var deviceDef = controller($dev).deviceDef;
    if (deviceDef.type == 'In' || deviceDef.type == 'Out')
    {
    	$ports.push($dev);
    }
    });
    var getDesc = function(port) {
    return port? port.description: '';
    };
    $.each($ports, function(i, $port) {
    var port = controller($port);
    var portDef = port.deviceDef;
    var inPort;
    var outPort;
    if (portDef.type == 'In')
    {
    	outPort = port.getOutputs()[0];
    	inPort = device.addInput();
    	intfs.push({
    		node: inPort,
    		label: portDef.label,
    		desc: getDesc(outPort.getInputs()[0])
    	});
    	var inNode = port.getInputs()[0];
    	if (inNode.getOutput() != null)
    	{
    		inNode.getOutput().disconnectFrom(inNode);
    	}
    }
    else if (portDef.type == 'Out')
    {
    	inPort = port.getInputs()[0];
    	outPort = device.addOutput();
    	intfs.push({
    		node: outPort,
    		label: portDef.label,
    		desc: getDesc(inPort.getOutput())
    	});
    	var outNode = port.getOutputs()[0];
    	$.each(outNode.getInputs(), function(i, inNode) {
    		if (inNode.getOutput() != null)
    		{
    			inNode.getOutput().disconnectFrom(inNode);
    		}
    	});
    }
    inPort.$ui.on('nodeValueChange', function() {
    	outPort.setValue(inPort.getValue());
    });
    });
    var layout = data.layout;
    var cols = layout.cols;
    var rows = layout.rows;
    rows = ~~((Math.max(1, rows) + 1) / 2) * 2;
    cols = ~~((Math.max(1, cols) + 1) / 2) * 2;
    var updateIntf = function(intf, x, y, align) {
    transform(intf.node.$ui, x, y);
    if (!intf.$label)
    {
    	intf.$label = createLabel(intf.label).attr('class', 'simcir-node-label');
    	enableEvents(intf.$label, false);
    	intf.node.$ui.append(intf.$label);
    }
    if (align == 'right')
    {
    	intf.$label.attr('text-anchor', 'start').attr('x', 6).attr('y', fontSize / 2);
    }
    else if (align == 'left')
    {
    	intf.$label.attr('text-anchor', 'end').attr('x', -6).attr('y', fontSize / 2);
    }
    else if (align == 'top')
    {
    	intf.$label.attr('text-anchor', 'middle').attr('x', 0).attr('y', -6);
    }
    else if (align == 'bottom')
    {
    	intf.$label.attr('text-anchor', 'middle').attr('x', 0).attr('y', fontSize + 6);
    }
    };
    var doLayout = function() {
    var x = 0;
    var y = 0;
    var w = unit * cols / 2;
    var h = unit * rows / 2;
    device.$ui.children('.simcir-device-label').attr({y: y + h + fontSize});
    device.$ui.children('.simcir-device-body').attr({x: x, y: y, width: w, height: h});
    $.each(intfs, function(i, intf) {
    	if (layout.nodes[intf.label] && layout.nodes[intf.label].match(/^([TBLR])([0-9]+)$/))
    	{
    		var off = +RegExp.$2 * unit / 2;
    		switch(RegExp.$1)
    		{
    		case 'T':
    			updateIntf(intf, x + off, y, 'bottom');
    		break;
    		case 'B':
    			updateIntf(intf, x + off, y + h, 'top');
    		break;
    		case 'L':
    			updateIntf(intf, x, y + off, 'right');
    		break;
    		case 'R':
    			updateIntf(intf, x + w, y + off, 'left');
    		break;
    		}
    	}
    	else
    	{
    		transform(intf.node.$ui, 0, 0);
    	}
    });
    };
    device.getSize = function() {
    return {width: unit * cols / 2, height: unit * rows / 2};
    };
    device.$ui.on('dispose', function() {
    $.each($devs, function(i, $dev) {
    	$dev.trigger('dispose');
    });
    });
    if (data.layout.hideLabelOnWorkspace) {
    device.$ui.on('deviceAdd', function() {
    	device.$ui.children('.simcir-device-label').css('display', 'none');
    }).on('deviceRemove', function() {
    	device.$ui.children('.simcir-device-label').css('display', '');
    });
    }
    device.$ui.on('dblclick', function(event) {
    event.preventDefault();
    event.stopPropagation();
    showDialog(device.deviceDef.label || device.deviceDef.type, setupSimcir($('<div></div>'), data)).on('close', function() {
    	$(this).find('.simcir-workspace').trigger('dispose');
    });
    });
    var super_createUI = device.createUI;
    device.createUI = function() {
    super_createUI();
    doLayout();
    };
    };
    };
    var factories = {};
    var defaultToolbox = [];
    var registerDevice = function(type, factory, deprecated) {
    if (typeof factory == 'object')
    {
    if (typeof factory.layout == 'object')
    {
    factory = createCustomLayoutDeviceRefFactory(factory);
    }
    else
    {
    factory = createDeviceRefFactory(factory);
    }
    }
    factories[type] = factory;
    if (!deprecated)
    {
    defaultToolbox.push({type: type});
    }
    };
    var createScrollbar = function() {
    var _value = 0;
    var _min = 0;
    var _max = 0;
    var _barSize = 0;
    var _width = 0;
    var _height = 0;
    var $body = createSVGElement('rect');
    var $bar = createSVGElement('g').append(createSVGElement('rect')).attr('class', 'simcir-scrollbar-bar');
    var $scrollbar = createSVGElement('g').attr('class', 'simcir-scrollbar').append($body).append($bar).on('unitup', function(event) {
    setValue(_value - unit * 2);
    }).on('unitdown', function(event) {
    setValue(_value + unit * 2);
    }).on('rollup', function(event) {
    setValue(_value - _barSize);
    }).on('rolldown', function(event) {
    setValue(_value + _barSize);
    });
    var dragPoint = null;
    var bar_mouseDownHandler = function(event) {
    event.preventDefault();
    event.stopPropagation();
    var pos = transform($bar);
    dragPoint = {
    x: event.pageX - pos.x,
    y: event.pageY - pos.y
    };
    $(document).on('mousemove', bar_mouseMoveHandler);
    $(document).on('mouseup', bar_mouseUpHandler);
    };
    var bar_mouseMoveHandler = function(event) {
    calc(function(unitSize) {
    setValue((event.pageY - dragPoint.y) / unitSize);
    });
    };
    var bar_mouseUpHandler = function(event) {
    $(document).off('mousemove', bar_mouseMoveHandler);
    $(document).off('mouseup', bar_mouseUpHandler);
    };
    $bar.on('mousedown', bar_mouseDownHandler);
    var body_mouseDownHandler = function(event) {
    event.preventDefault();
    event.stopPropagation();
    var off = $scrollbar.parent('svg').offset();
    var pos = transform($scrollbar);
    var y = event.pageY - off.top - pos.y;
    var barPos = transform($bar);
    if (y < barPos.y)
    {
    $scrollbar.trigger('rollup');
    }
    else
    {
    $scrollbar.trigger('rolldown');
    }
    };
    $body.on('mousedown', body_mouseDownHandler);
    var setSize = function(width, height) {
    _width = width;
    _height = height;
    layout();
    };
    var layout = function() {
    $body.attr({
    x: 0,
    y: 0,
    width: _width,
    height: _height
    });
    var visible = _max - _min > _barSize;
    $bar.css('display', visible? 'inline': 'none');
    if (!visible)
    {
    return;
    }
    calc(function(unitSize) {
    $bar.children('rect').attr({
    	x: 0,
    	y: 0,
    	width: _width,
    	height: _barSize * unitSize
    });
    transform($bar, 0, _value * unitSize);
    });
    };
    var calc = function(f) {
    f(_height / (_max - _min));
    };
    var setValue = function(value) {
    setValues(value, _min, _max, _barSize);
    };
    var setValues = function(value, min, max, barSize) {
    value = Math.max(min, Math.min(value, max - barSize));
    var changed = (value != _value);
    _value = value;
    _min = min;
    _max = max;
    _barSize = barSize;
    layout();
    if (changed)
    {
    $scrollbar.trigger('scrollValueChange');
    }
    };
    var getValue = function() {
    return _value;
    };
    controller($scrollbar, {
    setSize: setSize,
    setValues: setValues,
    getValue: getValue
    });
    return $scrollbar;
    };
    var getUniqueId = function() {
    var uniqueIdCount = 0;
    return function() {
    return 'simcir-id' + uniqueIdCount++;
    };
    }();
    var createWorkspace = function(data) {
    data = $.extend({
    width: 400,
    height: 200,
    showToolbox: true,
    editable: true,
    toolbox: defaultToolbox,
    devices: [],
    connectors: [],
    }, data);
    var scope = {};
    var workspaceWidth = data.width;
    var workspaceHeight = data.height;
    var barWidth = unit;
    var toolboxWidth = data.showToolbox? unit * 6 + barWidth: 0;
    var connectorsValid = true;
    var connectorsValidator = function() {
    if (!connectorsValid)
    {
    updateConnectors();
    connectorsValid = true;
    }
    };
    var $workspace = createSVG(workspaceWidth, workspaceHeight).attr('class', 'simcir-workspace').on('nodeValueChange', function(event) {
    connectorsValid = false;
    window.setTimeout(connectorsValidator, 0);
    }).on('dispose', function() {
    $(this).find('.simcir-device').trigger('dispose');
    $toolboxPane.remove();
    $workspace.remove();
    });
    disableSelection($workspace);
    var $defs = createSVGElement('defs');
    $workspace.append($defs);
    !function() {
    var patId = getUniqueId();
    var pitch = unit / 2;
    var w = workspaceWidth - toolboxWidth;
    var h = workspaceHeight;
    $defs.append(createSVGElement('pattern').attr({
    id: patId,
    x: 0,
    y: 0,
    width: pitch / w,
    height: pitch / h
    }).append(createSVGElement('rect').attr('class', 'simcir-pin-hole').attr({
    x: 0,
    y: 0,
    width: 1,
    height: 1
    })));
    $workspace.append(createSVGElement('rect').attr({
    x: toolboxWidth,
    y: 0,
    width: w,
    height: h
    }).css({
    fill: 'url(#' + patId + ')'
    }));
    }();
    var $toolboxDevicePane = createSVGElement('g');
    var $scrollbar = createScrollbar();
    $scrollbar.on('scrollValueChange', function(event) {
    transform($toolboxDevicePane, 0, -controller($scrollbar).getValue());
    });
    controller($scrollbar).setSize(barWidth, workspaceHeight);
    transform($scrollbar, toolboxWidth - barWidth, 0);
    var $toolboxPane = createSVGElement('g').attr('class', 'simcir-toolbox').append(createSVGElement('rect').attr({
    x: 0,
    y: 0,
    width: toolboxWidth,
    height: workspaceHeight
    })).append($toolboxDevicePane).append($scrollbar).on('wheel', function(event) {
    event.preventDefault();
    var oe = event.originalEvent || event;
    if (oe.deltaY < 0)
    {
    $scrollbar.trigger('unitup');
    }
    else if (oe.deltaY > 0)
    {
    $scrollbar.trigger('unitdown');
    }
    });
    var $devicePane = createSVGElement('g');
    transform($devicePane, toolboxWidth, 0);
    var $connectorPane = createSVGElement('g');
    var $temporaryPane = createSVGElement('g');
    enableEvents($connectorPane, false);
    enableEvents($temporaryPane, false);
    if (data.showToolbox)
    {
    $workspace.append($toolboxPane);
    }
    $workspace.append($devicePane);
    $workspace.append($connectorPane);
    $workspace.append($temporaryPane);
    var addDevice = function($dev) {
    $devicePane.append($dev);
    $dev.trigger('deviceAdd');
    };
    var removeDevice = function($dev) {
    $dev.trigger('deviceRemove');
    controller($dev).disconnectAll();
    $dev.trigger('dispose');
    updateConnectors();
    };
    var disconnect = function($inNode) {
    var inNode = controller($inNode);
    if (inNode.getOutput() != null)
    {
    inNode.getOutput().disconnectFrom(inNode);
    }
    updateConnectors();
    };
    var updateConnectors = function() {
    $connectorPane.children().remove();
    $devicePane.children('.simcir-device').each(function() {
    var device = controller($(this));
    $.each(device.getInputs(), function(i, inNode) {
    	if (inNode.getOutput() != null)
    	{
    		var p1 = offset(inNode.$ui);
    		var p2 = offset(inNode.getOutput().$ui);
    		var $conn = createConnector(p1.x, p1.y, p2.x, p2.y);
    		if (inNode.getOutput().getValue() != null)
    		{
    			$conn.addClass('simcir-connector-hot');
    		}
    		$connectorPane.append($conn);
    	}
    });
    });
    };
    var loadToolbox = function(data) {
    var vgap = 8;
    var y = vgap;
    $.each(data.toolbox, function(i, deviceDef) {
    var $dev = createDevice(deviceDef);
    $toolboxDevicePane.append($dev);
    var size = controller($dev).getSize();
    transform($dev, (toolboxWidth - barWidth - size.width) / 2, y);
    y += (size.height + fontSize + vgap);
    });
    controller($scrollbar).setValues(0, 0, y, workspaceHeight);
    };
    var getData = function() {
    var devIdCount = 0;
    $devicePane.children('.simcir-device').each(function() {
    var $dev = $(this);
    var device = controller($dev);
    var devId = 'dev' + devIdCount++;
    device.id = devId;
    $.each(device.getInputs(), function(i, node) {
    	node.id = devId + '.in' + i;
    });
    $.each(device.getOutputs(), function(i, node) {
    	node.id = devId + '.out' + i;
    });
    });
    var toolbox = [];
    var devices = [];
    var connectors = [];
    var clone = function(obj) {
    return JSON.parse(JSON.stringify(obj));
    };
    $toolboxDevicePane.children('.simcir-device').each(function() {
    var $dev = $(this);
    var device = controller($dev);
    toolbox.push(device.deviceDef);
    });
    $devicePane.children('.simcir-device').each(function() {
    var $dev = $(this);
    var device = controller($dev);
    $.each(device.getInputs(), function(i, inNode) {
    	if (inNode.getOutput() != null)
    	{
    		connectors.push({from:inNode.id, to:inNode.getOutput().id});
    	}
    });
    var pos = transform($dev);
    var deviceDef = clone(device.deviceDef);
    deviceDef.id = device.id;
    deviceDef.x = pos.x;
    deviceDef.y = pos.y;
    deviceDef.label = device.getLabel();
    var state = device.getState();
    if (state != null)
    {
    	deviceDef.state = state;
    }
    devices.push(deviceDef);
    });
    return {
    width: data.width,
    height: data.height,
    showToolbox: data.showToolbox,
    editable: data.editable,
    toolbox: toolbox,
    devices: devices,
    connectors: connectors
    };
    };
    var getText = function() {
    var data = getData();
    var buf = '';
    var print = function(s) {
    buf += s;
    };
    var println = function(s) {
    print(s);
    buf += '\\r\\n';
    };
    var printArray = function(array) {
    $.each(array, function(i, item) {
    	println('		' + JSON.stringify(item).replace(/</g, '\\u003c').replace(/>/g, '\\u003e') + (i + 1 < array.length? ',': ''));
    });
    };
    println('{');
    println('	"width":' + data.width + ',');
    println('	"height":' + data.height + ',');
    println('	"showToolbox":' + data.showToolbox + ',');
    println('	"toolbox":[');
    printArray(data.toolbox);
    println('	],');
    println('	"devices":[');
    printArray(data.devices);
    println('	],');
    println('	"connectors":[');
    printArray(data.connectors);
    println('	]');
    print('}');
    return buf;
    };
    var dragMoveHandler = null;
    var dragCompleteHandler = null;
    var adjustDevice = function($dev) {
    var pitch = unit / 2;
    var adjust = function(v) {
    return Math.round(v / pitch) * pitch;
    };
    var pos = transform($dev);
    var size = controller($dev).getSize();
    var x = Math.max(0, Math.min(pos.x, workspaceWidth - toolboxWidth - size.width));
    var y = Math.max(0, Math.min(pos.y, workspaceHeight - size.height));
    transform($dev, adjust(x), adjust(y));
    };
    var beginConnect = function(event, $target) {
    var $srcNode = $target.closest('.simcir-node');
    var off = $workspace.offset();
    var pos = offset($srcNode);
    if ($srcNode.attr('simcir-node-type') == 'in')
    {
    disconnect($srcNode);
    }
    dragMoveHandler = function(event) {
    var x = event.pageX - off.left;
    var y = event.pageY - off.top;
    $temporaryPane.children().remove();
    $temporaryPane.append(createConnector(pos.x, pos.y, x, y));
    };
    dragCompleteHandler = function(event) {
    $temporaryPane.children().remove();
    var $dst = $(event.target);
    if (isActiveNode($dst))
    {
    	var $dstNode = $dst.closest('.simcir-node');
    	connect($srcNode, $dstNode);
    	updateConnectors();
    }
    };
    };
    var beginNewDevice = function(event, $target) {
    var $dev = $target.closest('.simcir-device');
    var pos = offset($dev);
    $dev = createDevice(controller($dev).deviceDef, false, scope);
    transform($dev, pos.x, pos.y);
    $temporaryPane.append($dev);
    var dragPoint = {
    x: event.pageX - pos.x,
    y: event.pageY - pos.y
    };
    dragMoveHandler = function(event) {
    transform($dev, event.pageX - dragPoint.x, event.pageY - dragPoint.y);
    };
    dragCompleteHandler = function(event) {
    var $target = $(event.target);
    if ($target.closest('.simcir-toolbox').length == 0)
    {
    	$dev.detach();
    	var pos = transform($dev);
    	transform($dev, pos.x - toolboxWidth, pos.y);
    	adjustDevice($dev);
    	addDevice($dev);
    }
    else
    {
    	$dev.trigger('dispose');
    }
    };
    };
    var $selectedDevices = [];
    var addSelected = function($dev) {
    controller($dev).setSelected(true);
    $selectedDevices.push($dev);
    };
    var deselectAll = function() {
    $devicePane.children('.simcir-device').each(function() {
    controller($(this)).setSelected(false);
    });
    $selectedDevices = [];
    };
    var beginMoveDevice = function(event, $target) {
    var $dev = $target.closest('.simcir-device');
    var pos = transform($dev);
    if (!controller($dev).isSelected())
    {
    deselectAll();
    addSelected($dev);
    $dev.parent().append($dev.detach());
    }
    var dragPoint = {
    x: event.pageX - pos.x,
    y: event.pageY - pos.y
    };
    dragMoveHandler = function(event) {
    enableEvents($dev, false);
    var curPos = transform($dev);
    var deltaPos = {
    	x: event.pageX - dragPoint.x - curPos.x,
    	y: event.pageY - dragPoint.y - curPos.y
    };
    $.each($selectedDevices, function(i, $dev) {
    	var curPos = transform($dev);
    	transform($dev, curPos.x + deltaPos.x, curPos.y + deltaPos.y);
    });
    updateConnectors();
    };
    dragCompleteHandler = function(event) {
    var $target = $(event.target);
    enableEvents($dev, true);
    $.each($selectedDevices, function(i, $dev) {
    	if ($target.closest('.simcir-toolbox').length == 0)
    	{
    		adjustDevice($dev);
    		updateConnectors();
    	}
    	else
    	{
    		removeDevice($dev);
    	}
    });
    };
    };
    var beginSelectDevice = function(event, $target) {
    var intersect = function(rect1, rect2) {
    return !(rect1.x > rect2.x + rect2.width || rect1.y > rect2.y + rect2.height || rect1.x + rect1.width < rect2.x || rect1.y + rect1.height < rect2.y);
    };
    var pointToRect = function(p1, p2) {
    return {
    	x: Math.min(p1.x, p2.x),
    	y: Math.min(p1.y, p2.y),
    	width: Math.abs(p1.x - p2.x),
    	height: Math.abs(p1.y - p2.y)
    };
    };
    deselectAll();
    var off = $workspace.offset();
    var pos = offset($devicePane);
    var p1 = {
    x: event.pageX - off.left,
    y: event.pageY - off.top
    };
    dragMoveHandler = function(event) {
    deselectAll();
    var p2 = {
    	x: event.pageX - off.left,
    	y: event.pageY - off.top
    };
    var selRect = pointToRect(p1, p2);
    $devicePane.children('.simcir-device').each(function() {
    	var $dev = $(this);
    	var devPos = transform($dev);
    	var devSize = controller($dev).getSize();
    	var devRect = {
    		x: devPos.x + pos.x,
    		y: devPos.y + pos.y,
    		width: devSize.width,
    		height: devSize.height
    	};
    	if (intersect(selRect, devRect))
    	{
    		addSelected($dev);
    	}
    });
    $temporaryPane.children().remove();
    $temporaryPane.append(createSVGElement('rect').attr(selRect).attr('class', 'simcir-selection-rect'));
    };
    };
    var mouseDownHandler = function(event) {
    event.preventDefault();
    event.stopPropagation();
    var $target = $(event.target);
    if (!data.editable)
    {
    return;
    }
    if (isActiveNode($target))
    {
    beginConnect(event, $target);
    }
    else if ($target.closest('.simcir-device').length == 1)
    {
    if ($target.closest('.simcir-toolbox').length == 1)
    {
    	beginNewDevice(event, $target);
    }
    else
    {
    	beginMoveDevice(event, $target);
    }
    }
    else
    {
    beginSelectDevice(event, $target);
    }
    $(document).on('mousemove', mouseMoveHandler);
    $(document).on('mouseup', mouseUpHandler);
    };
    var mouseMoveHandler = function(event) {
    if (dragMoveHandler != null)
    {
    dragMoveHandler(event);
    }
    };
    var mouseUpHandler = function(event) {
    if (dragCompleteHandler != null)
    {
    dragCompleteHandler(event);
    }
    dragMoveHandler = null;
    dragCompleteHandler = null;
    $devicePane.children('.simcir-device').each(function() {
    enableEvents($(this), true);
    });
    $temporaryPane.children().remove();
    $(document).off('mousemove', mouseMoveHandler);
    $(document).off('mouseup', mouseUpHandler);
    };
    $workspace.on('mousedown', mouseDownHandler);
    loadToolbox(data);
    $.each(buildCircuit(data, false, scope), function(i, $dev) {
    addDevice($dev);
    });
    updateConnectors();
    controller($workspace, {
    data: getData,
    text: getText
    });
    return $workspace;
    };
    var clearSimcir = function($placeHolder) {
    $placeHolder = $($placeHolder[0]);
    $placeHolder.find('.simcir-workspace').trigger('dispose');
    $placeHolder.children().remove();
    return $placeHolder;
    };
    var setupSimcir = function($placeHolder, data) {
    $placeHolder = clearSimcir($placeHolder);
    var $workspace = simcir.createWorkspace(data);
    var $dataArea = $('<textarea></textarea>').addClass('simcir-json-data-area').attr('readonly', 'readonly').css('width', $workspace.attr('width') + 'px').css('height', $workspace.attr('height') + 'px');
    var showData = false;
    var toggle = function() {
    $workspace.css('display', !showData? 'inline': 'none');
    $dataArea.css('display', showData? 'inline': 'none');
    if (showData)
    {
    $dataArea.val(controller($workspace).text()).focus();
    }
    showData = !showData;
    };
    $placeHolder.text('');
    $placeHolder.append($('<div></div>').addClass('simcir-body').append($workspace).append($dataArea).on('click', function(event) {
    if (event.ctrlKey || event.metaKey)
    {
    toggle();
    }
    }));
    toggle();
    return $placeHolder;
    };
    var setupSimcirDoc = function($placeHolder) {
    var $table = $('<table><tbody></tbody></table>').addClass('simcir-doc-table');
    $.each(defaultToolbox, function(i, deviceDef) {
    var $dev = createDevice(deviceDef);
    var device = controller($dev);
    if (!device.doc)
    {
    return;
    }
    var doc = $.extend({description: '', params: []},device.doc);
    var size = device.getSize();
    var $tr = $('<tr></tr>');
    var hgap = 32;
    var vgap = 8;
    var $view = createSVG(size.width + hgap * 2, size.height + vgap * 2 + fontSize);
    var $dev = createDevice(deviceDef);
    transform($dev, hgap, vgap);
    $view.append($dev);
    $tr.append($('<td></td>').css('text-align', 'center').append($view));
    var $desc = $('<td></td>');
    $tr.append($desc);
    if (doc.description)
    {
    $desc.append($('<span></span>').text(doc.description));
    }
    $desc.append($('<div>Params</div>').addClass('simcir-doc-title'));
    var $paramsTable = $('<table><tbody></tbody></table>').addClass('simcir-doc-params-table');
    $paramsTable.children('tbody').append($('<tr></tr>').append($('<th>Name</th>')).append($('<th>Type</th>')).append($('<th>Default</th>')).append($('<th>Description</th>')));
    $paramsTable.children('tbody').append($('<tr></tr>').append($('<td>type</td>')).append($('<td>string</td>')).append($('<td>-</td>').css('text-align', 'center')).append($('<td>"' + deviceDef.type + '"</td>')));
    if (!doc.labelless)
    {
    $paramsTable.children('tbody').append($('<tr></tr>').append($('<td>label</td>')).append($('<td>string</td>')).append($('<td>same with type</td>').css('text-align', 'center')).append($('<td>label for a device.</td>')));
    }
    if (doc.params)
    {
    $.each(doc.params, function(i, param) {
    	$paramsTable.children('tbody').append($('<tr></tr>').append($('<td></td>').text(param.name)).append($('<td></td>').text(param.type)).append($('<td></td>').css('text-align', 'center').text(param.defaultValue)).append($('<td></td>').text(param.description)));
    });
    }
    $desc.append($paramsTable);
    if (doc.code)
    {
    $desc.append($('<div>Code</div>').addClass('simcir-doc-title'));
    $desc.append($('<div></div>').addClass('simcir-doc-code').text(doc.code));
    }
    $table.children('tbody').append($tr);
    });
    $placeHolder.append($table);
    };
    $(function() {
    document.getElementById('make').onclick = function() {
    	$('.simcir').each(function() {
    	//var koko0 = document.getElementById('koko0');
    	var koko = document.getElementById('koko');
    	//alert(koko0.value);
    run();
    	var text = koko.value.replace(/^\\s+|\\s+$/g, '');
    	//alert(text);
    	var $placeHolder = $(this);
    	setupSimcir($placeHolder, JSON.parse(text || '{}'));
    });
    };
    });
    $(function() {
    $('.simcir-doc').each(function() {
    setupSimcirDoc($(this));
    });
    });
    $.extend($s, {
    registerDevice: registerDevice,
    clearSimcir: clearSimcir,
    setupSimcir: setupSimcir,
    createWorkspace: createWorkspace,
    createSVGElement: createSVGElement,
    offset: offset,
    transform: transform,
    enableEvents: enableEvents,
    graphics: graphics,
    controller: controller,
    unit: unit
    });
    }(simcir);
    !function($s) {
    'use strict';
    var $ = $s.$;
    var unit = $s.unit;
    var connectNode = function(in1, out1) {
    var in1_super_setValue = in1.setValue;
    in1.setValue = function(value, force) {
    var changed = in1.getValue() !== value;
    in1_super_setValue(value, force);
    if (changed || force)
    {
    out1.setValue(in1.getValue());
    }
    };
    };
    var createPortFactory = function(type) {
    return function(device) {
    var in1 = device.addInput();
    var out1 = device.addOutput();
    connectNode(in1, out1);
    var super_createUI = device.createUI;
    device.createUI = function() {
    super_createUI();
    var size = device.getSize();
    var cx = size.width / 2;
    var cy = size.height / 2;
    device.$ui.append($s.createSVGElement('circle').attr({
    	cx: cx,
    	cy: cy,
    	r: unit / 2
    }).attr('class', 'simcir-port simcir-node-type-' + type));
    device.$ui.append($s.createSVGElement('circle').attr({
    	cx: cx,
    	cy: cy,
    	r: unit / 4
    }).attr('class', 'simcir-port-hole'));
    };
    };
    };
    var createJointFactory = function() {
    var maxFadeCount = 16;
    var fadeTimeout = 100;
    var Direction = {
    WE: 0,
    NS: 1,
    EW: 2,
    SN: 3
    };
    return function(device) {
    var in1 = device.addInput();
    var out1 = device.addOutput();
    connectNode(in1, out1);
    var state = device.deviceDef.state || {
    direction: Direction.WE
    };
    device.getState = function() {
    return state;
    };
    device.getSize = function() {
    return {
    	width: unit,
    	height: unit
    };
    };
    var super_createUI = device.createUI;
    device.createUI = function() {
    super_createUI();
    var $label = device.$ui.children('.simcir-device-label');
    $label.attr('y', $label.attr('y') - unit / 4);
    var $point = $s.createSVGElement('circle').css('pointer-events', 'none').css('opacity', 0).attr('r', 2).addClass('simcir-connector').addClass('simcir-joint-point');
    device.$ui.append($point);
    var $path = $s.createSVGElement('path').css('pointer-events', 'none').css('opacity', 0).addClass('simcir-connector');
    device.$ui.append($path);
    var $title = $s.createSVGElement('title').text('Double-Click to change a direction.');
    var updatePoint = function() {
    	$point.css('display', out1.getInputs().length > 1 ? '' : 'none');
    };
    updatePoint();
    var super_connectTo = out1.connectTo;
    out1.connectTo = function(inNode) {
    	super_connectTo(inNode);
    	updatePoint();
    };
    var super_disconnectFrom = out1.disconnectFrom;
    out1.disconnectFrom = function(inNode) {
    	super_disconnectFrom(inNode);
    	updatePoint();
    };
    var updateUI = function() {
    	var x0,
    		y0,
    		x1,
    		y1;
    	x0 = y0 = x1 = y1 = unit / 2;
    	var d = unit / 2;
    	var direction = state.direction;
    	if (direction == Direction.WE)
    	{
    		x0 -= d;
    		x1 += d;
    	}
    	else if (direction == Direction.NS)
    	{
    		y0 -= d;
    		y1 += d;
    	}
    	else if (direction == Direction.EW)
    	{
    		x0 += d;
    		x1 -= d;
    	}
    	else if (direction == Direction.SN)
    	{
    		y0 += d;
    		y1 -= d;
    	}
    	$path.attr('d', 'M' + x0 + ' ' + y0 + 'L' + x1 + ' ' + y1);
    	$s.transform(in1.$ui, x0, y0);
    	$s.transform(out1.$ui, x1, y1);
    	$point.attr({
    		cx: x1,
    		cy: y1
    	});
    	if (direction == Direction.EW || direction == Direction.WE)
    	{
    		device.$ui.children('.simcir-device-body').attr({
    			x: 0,
    			y: unit / 4,
    			width: unit,
    			height: unit / 2
    		});
    	}
    	else
    	{
    		device.$ui.children('.simcir-device-body').attr({
    			x: unit / 4,
    			y: 0,
    			width: unit / 2,
    			height: unit
    		});
    	}
    };
    updateUI();
    var fadeCount = 0;
    var setOpacity = function(opacity) {
    	device.$ui.children('.simcir-device-body,.simcir-node').css('opacity', opacity);
    	$path.css('opacity', 1 - opacity);
    	$point.css('opacity', 1 - opacity);
    };
    var fadeout = function() {
    	window.setTimeout(function() {
    		if (fadeCount > 0)
    		{
    			fadeCount -= 1;
    			setOpacity(fadeCount / maxFadeCount);
    			fadeout();
    		}
    	}, fadeTimeout);
    };
    var isEditable = function($dev) {
    	var $workspace = $dev.closest('.simcir-workspace');
    	return !!$s.controller($workspace).data().editable;
    };
    var device_mouseoutHandler = function(event) {
    	if (!isEditable($(event.target)))
    	{
    		return;
    	}
    	if (!device.isSelected())
    	{
    		fadeCount = maxFadeCount;
    		fadeout();
    	}
    };
    var device_dblclickHandler = function(event) {
    	if (!isEditable($(event.target)))
    	{
    		return;
    	}
    	state.direction = (state.direction + 1) % 4;
    	updateUI();
    	$(this).trigger('mousedown').trigger('mouseup');
    };
    device.$ui.on('mouseover', function(event) {
    		if (!isEditable($(event.target)))
    		{
    			$title.text('');
    			return;
    		}
    		setOpacity(1);
    		fadeCount = 0;
    	}).on('deviceAdd', function() {
    		if ($(this).closest('BODY').length == 0)
    		{
    			setOpacity(0);
    		}
    		$(this).append($title).on('mouseout', device_mouseoutHandler).on('dblclick', device_dblclickHandler);
    		$label.css('display', 'none');
    	}).on('deviceRemove', function() {
    		$(this).off('mouseout', device_mouseoutHandler).off('dblclick', device_dblclickHandler);
    		$title.remove();
    		$label.css('display', '');
    	}).on('deviceSelect', function() {
    		if (device.isSelected())
    		{
    			setOpacity(1);
    			fadeCount = 0;
    		}
    		else
    		{
    			if (fadeCount == 0)
    			{
    				setOpacity(0);
    			}
    		}
    	});
    };
    };
    };
    $s.registerDevice('In', createPortFactory('in'));
    $s.registerDevice('Out', createPortFactory('out'));
    $s.registerDevice('Joint', createJointFactory());
    }(simcir);


    import ("https://kazuhikoarase.github.io/simcirjs/simcir-basicset.js")
    import ("https://kazuhikoarase.github.io/simcirjs/simcir-library.js")



    export function init(ctx, html) {
      ctx.importCSS("https://kazuhikoarase.github.io/simcirjs/simcir.css")
      ctx.importCSS("https://kazuhikoarase.github.io/simcirjs/simcir-basicset.css")
    	const out0 = document.createElement("div");
    out0.className = "simcir";
    out0.style.width = "100%";
    out0.style.height = "500px";
    ctx.root.appendChild(out0);
      const koko = document.createElement("div");
    koko.id = "koko";
    ctx.root.appendChild(koko);

      run(html);
    }
    """
  end
end

KinoOhi.Simcir.new("""
make 10
in 0 2
wire 3 8
not 2 3
and 0 1 4
and 1 2 5
not 0 6
and 3 4 7
and 5 6 8
or 7 8 9
out 9 9
""")