jointjs
Version:
JavaScript diagramming library
168 lines (130 loc) • 5.77 kB
JavaScript
var graph = new joint.dia.Graph();
var paper = new joint.dia.Paper({
el: document.getElementById('paper'),
model: graph,
width: 1000,
height: 600,
gridSize: 5,
snapLinks: true,
linkPinning: false,
defaultLink: new joint.shapes.logic.Wire,
validateConnection: function(vs, ms, vt, mt, e, vl) {
if (e === 'target') {
// target requires an input port to connect
if (!mt || !mt.getAttribute('class') || mt.getAttribute('class').indexOf('input') < 0) return false;
// check whether the port is being already used
var portUsed = this.model.getLinks().some(function(link) {
return (link.id !== vl.model.id &&
link.get('target').id === vt.model.id &&
link.get('target').port === mt.getAttribute('port'));
});
return !portUsed;
} else { // e === 'source'
// source requires an output port to connect
return ms && ms.getAttribute('class') && ms.getAttribute('class').indexOf('output') >= 0;
}
}
});
// zoom the viewport by 50%
paper.scale(1.5,1.5);
function toggleLive(model, signal) {
// add 'live' class to the element if there is a positive signal
model.findView(paper).vel.toggleClass('live', signal > 0);
}
function broadcastSignal(gate, signal) {
// broadcast signal to all output ports
setTimeout(function() {
joint.util.invoke(graph.getConnectedLinks(gate, { outbound: true }), 'set', 'signal', signal);
}, 0);
}
function initializeSignal() {
var signal = Math.random();
// > 0 wire with a positive signal is alive
// < 0 wire with a negative signal means, there is no signal
// 0 none of the above - reset value
// cancel all signals stores in wires
joint.util.invoke(graph.getLinks(), 'set', 'signal', 0);
// remove all 'live' classes
V(paper.viewport).find('.live').forEach(function(vel) {
vel.removeClass('live');
});
graph.getElements().forEach(function(element) {
// broadcast a new signal from every input in the graph
if (element instanceof joint.shapes.logic.Input) {
broadcastSignal(element, signal);
}
});
return signal;
}
// Every logic gate needs to know how to handle a situation, when a signal comes to their ports.
joint.shapes.logic.Gate.prototype.onSignal = function(signal, handler) {
handler(signal);
};
// The repeater delays a signal handling by 400ms
joint.shapes.logic.Repeater.prototype.onSignal = function(signal, handler) {
setTimeout(function() {
handler(signal);
}, 400);
};
// Output element just marks itself as alive.
joint.shapes.logic.Output.prototype.onSignal = function(signal) {
toggleLive(this, signal);
};
// diagramm setup
var gates = {
repeater: new joint.shapes.logic.Repeater({ position: { x: 410, y: 25 }}),
or: new joint.shapes.logic.Or({ position: { x: 550, y: 50 }}),
and: new joint.shapes.logic.And({ position: { x: 550, y: 150 }}),
not: new joint.shapes.logic.Not({ position: { x: 90, y: 140 }}),
nand: new joint.shapes.logic.Nand({ position: { x: 550, y: 250 }}),
nor: new joint.shapes.logic.Nor({ position: { x: 270, y: 190 }}),
xor: new joint.shapes.logic.Xor({ position: { x: 550, y: 200 }}),
xnor: new joint.shapes.logic.Xnor({ position: { x: 550, y: 100 }}),
input: new joint.shapes.logic.Input({ position: { x: 5, y: 45 }}),
output: new joint.shapes.logic.Output({ position: { x: 440, y: 290 }})
};
var wires = [
{ source: { id: gates.input.id, port: 'out' }, target: { id: gates.not.id, port: 'in' }},
{ source: { id: gates.not.id, port: 'out' }, target: { id: gates.nor.id, port: 'in1' }},
{ source: { id: gates.nor.id, port: 'out' }, target: { id: gates.repeater.id, port: 'in' }},
{ source: { id: gates.nor.id, port: 'out' }, target: { id: gates.output.id, port: 'in' }},
{ source: { id: gates.repeater.id, port: 'out' }, target: { id: gates.nor.id, port: 'in2' },
vertices: [{ x: 215, y: 100 }]
}
];
// add gates and wires to the graph
graph.addCells(joint.util.toArray(gates));
joint.util.forIn(wires, function(attributes) {
graph.addCell(paper.getDefaultLink().set(attributes));
});
graph.on('change:source change:target', function(model, end) {
var e = 'target' in model.changed ? 'target' : 'source';
if ((model.previous(e).id && !model.get(e).id) || (!model.previous(e).id && model.get(e).id)) {
// if source/target has been connected to a port or disconnected from a port reinitialize signals
current = initializeSignal();
}
});
graph.on('change:signal', function(wire, signal) {
toggleLive(wire, signal);
var magnitude = Math.abs(signal);
// if a new signal has been generated stop transmitting the old one
if (magnitude !== current) return;
var gate = wire.getTargetElement();
if (gate) {
gate.onSignal(signal, function() {
// get an array of signals on all input ports
var inboundLinks = graph.getConnectedLinks(gate, { inbound: true });
var linksByPorts = joint.util.groupBy(inboundLinks, function(wire) {
return wire.get('target').port;
});
var inputs = joint.util.toArray(linksByPorts).map(function(wires) {
return Math.max.apply(this, joint.util.invoke(wires, 'get', 'signal')) > 0;
});
// calculate the output signal
var output = magnitude * (gate.operation.apply(gate, inputs) ? 1 : -1);
broadcastSignal(gate, output);
});
}
});
// initialize signal and keep its value
var current = initializeSignal();