Last active
November 8, 2019 06:51
-
-
Save nitaku/2f4e1d27ce589847a003 to your computer and use it in GitHub Desktop.
Example graph DSL (PEG.js)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
window.AppView = Backbone.D3View.extend | |
initialize: () -> | |
# retrieve the grammar from an external file | |
d3.text 'minidot.peg.js', (grammar) => | |
left = @d3el.append 'div' | |
.attr | |
class: 'left' | |
editor = new Editor | |
model: @model | |
grammar: grammar | |
left.node().appendChild(editor.el) | |
editor.render() | |
matrix = new Matrix | |
model: @model | |
left.node().appendChild(matrix.el) | |
matrix.render() | |
node_link = new NodeLink | |
model: @model | |
@el.appendChild(node_link.el) | |
node_link.render() | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Generated by CoffeeScript 1.10.0 | |
(function() { | |
window.AppView = Backbone.D3View.extend({ | |
initialize: function() { | |
return d3.text('minidot.peg.js', (function(_this) { | |
return function(grammar) { | |
var editor, left, matrix, node_link; | |
left = _this.d3el.append('div').attr({ | |
"class": 'left' | |
}); | |
editor = new Editor({ | |
model: _this.model, | |
grammar: grammar | |
}); | |
left.node().appendChild(editor.el); | |
editor.render(); | |
matrix = new Matrix({ | |
model: _this.model | |
}); | |
left.node().appendChild(matrix.el); | |
matrix.render(); | |
node_link = new NodeLink({ | |
model: _this.model | |
}); | |
_this.el.appendChild(node_link.el); | |
return node_link.render(); | |
}; | |
})(this)); | |
} | |
}); | |
}).call(this); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Backbone.D3View.js 0.3.1 | |
// --------------- | |
// (c) 2015 Adam Krebs | |
// Backbone.D3View may be freely distributed under the MIT license. | |
// For all details and documentation: | |
// https://github.com/akre54/Backbone.D3View | |
(function (factory) { | |
if (typeof define === 'function' && define.amd) { define(['backbone', 'd3'], factory); | |
} else if (typeof exports === 'object') { module.exports = factory(require('backbone'), require('d3')); | |
} else { factory(Backbone, d3); } | |
}(function (Backbone, d3) { | |
// Cached regex to match an opening '<' of an HTML tag, possibly left-padded | |
// with whitespace. | |
var paddedLt = /^\s*</; | |
var ElementProto = (typeof Element !== 'undefined' && Element.prototype) || {}; | |
var matchesSelector = ElementProto.matches || | |
ElementProto.webkitMatchesSelector || | |
ElementProto.mozMatchesSelector || | |
ElementProto.msMatchesSelector || | |
ElementProto.oMatchesSelector; | |
Backbone.D3ViewMixin = { | |
// A reference to the d3 selection backing the view. | |
d3el: null, | |
namespace: d3.ns.prefix.svg, | |
$: function(selector) { | |
return this.el.querySelectorAll(selector); | |
}, | |
$$: function(selector) { | |
return this.d3el.selectAll(selector); | |
}, | |
_removeElement: function() { | |
this.undelegateEvents(); | |
this.d3el.remove(); | |
}, | |
_createElement: function(tagName) { | |
var ns = typeof this.namespace === 'function' ? this.namespace() : this.namespace; | |
return ns ? | |
document.createElementNS(ns, tagName) : | |
document.createElement(tagName); | |
}, | |
_setElement: function(element) { | |
if (typeof element == 'string') { | |
if (paddedLt.test(element)) { | |
var el = document.createElement('div'); | |
el.innerHTML = element; | |
this.el = el.firstChild; | |
} else { | |
this.el = document.querySelector(element); | |
} | |
} else { | |
this.el = element; | |
} | |
this.d3el = d3.select(this.el); | |
}, | |
_setAttributes: function(attributes) { | |
this.d3el.attr(attributes); | |
}, | |
// `delegate` supports two- and three-arg forms. The `selector` is optional. | |
delegate: function(eventName, selector, listener) { | |
if (listener === undefined) { | |
listener = selector; | |
selector = null; | |
} | |
var view = this; | |
var wrapped = function(event) { | |
var node = event.target, | |
idx = 0, | |
o = d3.event; | |
d3.event = event; | |
// The `event` object is stored in `d3.event` but Backbone expects it as | |
// the first argument to the listener. | |
if (!selector) { | |
listener.call(view, d3.event, node.__data__, idx++); | |
d3.event = o; | |
return; | |
} | |
while (node && node !== view.el) { | |
if (matchesSelector.call(node, selector)) { | |
listener.call(view, d3.event, node.__data__, idx++); | |
} | |
node = node.parentNode; | |
} | |
d3.event = o; | |
}; | |
var map = this._domEvents || (this._domEvents = {}); | |
var handlers = map[eventName] || (map[eventName] = []); | |
handlers.push({selector: selector, listener: listener, wrapped: wrapped}); | |
this.el.addEventListener(eventName, wrapped, false); | |
return this; | |
}, | |
undelegate: function(eventName, selector, listener) { | |
if (!this._domEvents || !this._domEvents[eventName]) return; | |
if (typeof selector !== 'string') { | |
listener = selector; | |
selector = null; | |
} | |
var handlers = this._domEvents[eventName].slice(); | |
var i = handlers.length; | |
while (i--) { | |
var handler = handlers[i]; | |
var match = (listener ? handler.listener === listener : true) && | |
(selector ? handler.selector === selector : true); | |
if (!match) continue; | |
this.el.removeEventListener(eventName, handler.wrapped, false); | |
this._domEvents[eventName].splice(i, 1); | |
} | |
}, | |
undelegateEvents: function() { | |
var map = this._domEvents, el = this.el; | |
if (!el || !map) return; | |
Object.keys(map).forEach(function(eventName) { | |
map[eventName].forEach(function(handler) { | |
el.removeEventListener(eventName, handler.wrapped, false); | |
}); | |
}); | |
this._domEvents = {}; | |
return this; | |
} | |
}; | |
Backbone.D3View = Backbone.View.extend(Backbone.D3ViewMixin); | |
return Backbone.D3View; | |
})); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
window.Editor = Backbone.D3View.extend | |
namespace: null | |
tagName: 'div' | |
events: | |
input: 'compile' | |
initialize: (conf) -> | |
@d3el.classed 'editor', true | |
@textarea = @d3el.append 'textarea' | |
@status_bar = @d3el.append 'div' | |
.attr | |
class: 'status_bar' | |
@parser = PEG.buildParser conf.grammar | |
# example code | |
@textarea.node().value = ''' | |
0--1 | |
2--3--4--5--2 | |
a->b->c->a | |
Z | |
foo<-bar,baz,bob | |
One,Two,Three,Four,Five* | |
Four--foo | |
''' | |
@compile() | |
compile: () -> | |
@status_bar.text 'All ok.' | |
@status_bar.classed 'error', false | |
try | |
graph = @parser.parse @textarea.node().value | |
@model.update graph | |
catch e | |
@status_bar.text "Line #{e.location.start.line}: #{e.message}" | |
@status_bar.classed 'error', true | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
.editor { | |
display: flex; | |
flex-direction: column; | |
} | |
.editor textarea { | |
flex-grow: 1; | |
height: 0; | |
resize: none; | |
border: 0; | |
outline: 0; | |
} | |
.editor .status_bar { | |
height: 22px; | |
background: #DDD; | |
border-top: 1px solid gray; | |
font-family: sans-serif; | |
font-size: 12px; | |
padding: 4px; | |
box-sizing: border-box; | |
} | |
.editor .error { | |
background: #F77; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Generated by CoffeeScript 1.10.0 | |
(function() { | |
window.Editor = Backbone.D3View.extend({ | |
namespace: null, | |
tagName: 'div', | |
events: { | |
input: 'compile' | |
}, | |
initialize: function(conf) { | |
this.d3el.classed('editor', true); | |
this.textarea = this.d3el.append('textarea'); | |
this.status_bar = this.d3el.append('div').attr({ | |
"class": 'status_bar' | |
}); | |
this.parser = PEG.buildParser(conf.grammar); | |
this.textarea.node().value = '0--1\n2--3--4--5--2\na->b->c->a\nZ\nfoo<-bar,baz,bob\nOne,Two,Three,Four,Five*\nFour--foo'; | |
return this.compile(); | |
}, | |
compile: function() { | |
var e, error, graph; | |
this.status_bar.text('All ok.'); | |
this.status_bar.classed('error', false); | |
try { | |
graph = this.parser.parse(this.textarea.node().value); | |
return this.model.update(graph); | |
} catch (error) { | |
e = error; | |
this.status_bar.text("Line " + e.location.start.line + ": " + e.message); | |
return this.status_bar.classed('error', true); | |
} | |
} | |
}); | |
}).call(this); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
window.Graph = Backbone.Model.extend | |
defaults: | |
graph: { | |
nodes: [] | |
links: [] | |
} | |
selected: null | |
update: (graph) -> | |
# objectify the graph | |
index = {} | |
graph.nodes.forEach (n) -> index[n.id] = n | |
graph.links.forEach (l) -> | |
l.id = l.source + (if l.directed then '->' else '--') + l.target | |
l.source = index[l.source] | |
l.target = index[l.target] | |
# FIXME handle direction (e.g. link id should be in lexicographic order for undirected links) | |
@set 'graph', graph | |
select: (id) -> | |
if id is @get 'selected' | |
@set 'selected', null | |
else | |
@set 'selected', id | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Generated by CoffeeScript 1.10.0 | |
(function() { | |
window.Graph = Backbone.Model.extend({ | |
defaults: { | |
graph: { | |
nodes: [], | |
links: [] | |
}, | |
selected: null | |
}, | |
update: function(graph) { | |
var index; | |
index = {}; | |
graph.nodes.forEach(function(n) { | |
return index[n.id] = n; | |
}); | |
graph.links.forEach(function(l) { | |
l.id = l.source + (l.directed ? '->' : '--') + l.target; | |
l.source = index[l.source]; | |
return l.target = index[l.target]; | |
}); | |
return this.set('graph', graph); | |
}, | |
select: function(id) { | |
if (id === this.get('selected')) { | |
return this.set('selected', null); | |
} else { | |
return this.set('selected', id); | |
} | |
} | |
}); | |
}).call(this); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
app = new AppView | |
el: 'body' | |
model: new Graph |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
html, body { | |
margin: 0; | |
padding: 0; | |
width: 100%; | |
height: 100%; | |
overflow: hidden; | |
} | |
body { | |
display: flex; | |
flex-direction: row; | |
} | |
.left { | |
width: 300px; | |
border-right: 2px solid gray; | |
display: flex; | |
flex-direction: column; | |
} | |
.editor { | |
height: 0; | |
flex-grow: 1; | |
} | |
.node_link { | |
width: 0; | |
flex-grow: 2; | |
} | |
.matrix { | |
height: 300px; | |
border-top: 2px solid gray; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="utf-8"> | |
<title>Example graph DSL in PEG.js</title> | |
<link rel="stylesheet" href="index.css"> | |
<script src="http://d3js.org/d3.v3.min.js"></script> | |
<script src="/webvis/tmp/peg-0.9.0.min.js"></script> | |
<script src="http://underscorejs.org/underscore-min.js"></script> | |
<script src="http://backbonejs.org/backbone-min.js"></script> | |
<script src="backbone.d3view.js"></script> | |
<script src="http://marvl.infotech.monash.edu/webcola/cola.v3.min.js"></script> | |
<!-- your views go here --> | |
<script src="AppView.js"></script> | |
<script src="Editor.js"></script> | |
<link rel="stylesheet" href="Editor.css"> | |
<script src="NodeLink.js"></script> | |
<link rel="stylesheet" href="NodeLink.css"> | |
<script src="Matrix.js"></script> | |
<link rel="stylesheet" href="Matrix.css"> | |
<!-- your models go here --> | |
<script src="Graph.js"></script> | |
</head> | |
<body> | |
<script src="index.js"></script> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Generated by CoffeeScript 1.10.0 | |
(function() { | |
var app; | |
app = new AppView({ | |
el: 'body', | |
model: new Graph | |
}); | |
}).call(this); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
MARGIN = 4 | |
window.Matrix = Backbone.D3View.extend | |
tagName: 'svg' | |
events: | |
'click .cell': (evt, d) -> @model.select d.id | |
initialize: () -> | |
@d3el.classed 'matrix', true | |
@vis = @d3el.append 'g' | |
.attr | |
transform: "translate(#{MARGIN},#{MARGIN})" | |
@listenTo @model, 'change:graph', @render | |
@listenTo @model, 'change:selected', @focus | |
render: () -> | |
width = @el.getBoundingClientRect().width | |
height = @el.getBoundingClientRect().height | |
size = Math.min(width, height) - 2*MARGIN | |
graph = @model.get 'graph' | |
# store the node index within its data structure | |
graph.nodes.forEach (d, i) -> d.i = i | |
cell_size = size / graph.nodes.length | |
cells = @vis.selectAll '.cell' | |
.data graph.links, (d) -> d.id | |
enter_cells = cells.enter().append 'rect' | |
.attr | |
class: 'cell' | |
enter_cells.append 'title' | |
.text (d) -> d.id | |
cells | |
.attr | |
x: (d) -> d.target.i*cell_size | |
y: (d) -> d.source.i*cell_size | |
width: cell_size | |
height: cell_size | |
fill: (d) -> if d.directed then 'orange' else 'teal' | |
cells.exit() | |
.remove() | |
# mirror cells for undirected links | |
mirror_cells = @vis.selectAll '.mirror' | |
.data graph.links.filter((d) -> not d.directed), (d) -> 'mirror_' + d.id | |
enter_mirror_cells = mirror_cells.enter().append 'rect' | |
.attr | |
class: 'mirror cell' | |
enter_mirror_cells.append 'title' | |
.text (d) -> d.id | |
mirror_cells | |
.attr | |
x: (d) -> d.source.i*cell_size | |
y: (d) -> d.target.i*cell_size | |
width: cell_size | |
height: cell_size | |
fill: (d) -> 'teal' | |
mirror_cells.exit() | |
.remove() | |
focus: () -> | |
id = @model.get 'selected' | |
@vis.selectAll '.cell' | |
.classed 'selected', (d) -> d.id is id | |
.classed 'unselected', (d) -> if id is null then null else d.id isnt id |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
.matrix .cell { | |
shape-rendering: crispEdges; | |
opacity: 0.6; | |
} | |
.matrix .selected.cell, .matrix .cell:hover { | |
opacity: 1; | |
} | |
.matrix .unselected.cell { | |
opacity: 0.2; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Generated by CoffeeScript 1.10.0 | |
(function() { | |
var MARGIN; | |
MARGIN = 4; | |
window.Matrix = Backbone.D3View.extend({ | |
tagName: 'svg', | |
events: { | |
'click .cell': function(evt, d) { | |
return this.model.select(d.id); | |
} | |
}, | |
initialize: function() { | |
this.d3el.classed('matrix', true); | |
this.vis = this.d3el.append('g').attr({ | |
transform: "translate(" + MARGIN + "," + MARGIN + ")" | |
}); | |
this.listenTo(this.model, 'change:graph', this.render); | |
return this.listenTo(this.model, 'change:selected', this.focus); | |
}, | |
render: function() { | |
var cell_size, cells, enter_cells, enter_mirror_cells, graph, height, mirror_cells, size, width; | |
width = this.el.getBoundingClientRect().width; | |
height = this.el.getBoundingClientRect().height; | |
size = Math.min(width, height) - 2 * MARGIN; | |
graph = this.model.get('graph'); | |
graph.nodes.forEach(function(d, i) { | |
return d.i = i; | |
}); | |
cell_size = size / graph.nodes.length; | |
cells = this.vis.selectAll('.cell').data(graph.links, function(d) { | |
return d.id; | |
}); | |
enter_cells = cells.enter().append('rect').attr({ | |
"class": 'cell' | |
}); | |
enter_cells.append('title').text(function(d) { | |
return d.id; | |
}); | |
cells.attr({ | |
x: function(d) { | |
return d.target.i * cell_size; | |
}, | |
y: function(d) { | |
return d.source.i * cell_size; | |
}, | |
width: cell_size, | |
height: cell_size, | |
fill: function(d) { | |
if (d.directed) { | |
return 'orange'; | |
} else { | |
return 'teal'; | |
} | |
} | |
}); | |
cells.exit().remove(); | |
mirror_cells = this.vis.selectAll('.mirror').data(graph.links.filter(function(d) { | |
return !d.directed; | |
}), function(d) { | |
return 'mirror_' + d.id; | |
}); | |
enter_mirror_cells = mirror_cells.enter().append('rect').attr({ | |
"class": 'mirror cell' | |
}); | |
enter_mirror_cells.append('title').text(function(d) { | |
return d.id; | |
}); | |
mirror_cells.attr({ | |
x: function(d) { | |
return d.source.i * cell_size; | |
}, | |
y: function(d) { | |
return d.target.i * cell_size; | |
}, | |
width: cell_size, | |
height: cell_size, | |
fill: function(d) { | |
return 'teal'; | |
} | |
}); | |
return mirror_cells.exit().remove(); | |
}, | |
focus: function() { | |
var id; | |
id = this.model.get('selected'); | |
return this.vis.selectAll('.cell').classed('selected', function(d) { | |
return d.id === id; | |
}).classed('unselected', function(d) { | |
if (id === null) { | |
return null; | |
} else { | |
return d.id !== id; | |
} | |
}); | |
} | |
}); | |
}).call(this); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// DOT-like syntax translated to d3.js data structure | |
/* | |
1--2--3--4--1 | |
1--3 | |
2--4 | |
*/ | |
{ | |
var node_index = {}; | |
var graph = { | |
nodes: [], | |
links: [] | |
}; | |
} | |
start | |
= Def ('\n' Def)* { return graph; } | |
Def 'definition' | |
= CliqueDef | |
/ NodesLinksDef | |
CliqueDef 'clique definition' | |
= list:NodeList '*' { | |
list.forEach(function(a){ | |
list.forEach(function(b){ | |
if(a !== b) { | |
graph.links.push({ | |
source: d3.min([a,b]), | |
target: d3.max([a,b]) | |
}); | |
} | |
}); | |
}); | |
} | |
NodesLinksDef 'nodes-links definition' | |
= first:NodeList rest:(_ Link _ NodeList)* { | |
var node_lists = [first].concat(rest.map(function(d){ return d[3]; })); | |
node_lists.forEach(function(list, i){ | |
// add links between contiguous node lists | |
if(i < node_lists.length-1) { | |
var list_a = list; | |
var list_b = node_lists[i+1]; | |
var l = rest[i][1]; | |
list_a.forEach(function(a){ | |
list_b.forEach(function(b){ | |
var link = {}; | |
if(l == 'undirected') { | |
link.source = d3.min([a,b]); | |
link.target = d3.max([a,b]); | |
} | |
else if (l == 'ltr') { | |
link.source = a; | |
link.target = b; | |
link.directed = true; | |
} | |
else if (l == 'rtl') { | |
link.source = b; | |
link.target = a; | |
link.directed = true; | |
}; | |
graph.links.push(link); | |
}); | |
}); | |
} | |
}); | |
} | |
Link 'link specifier' | |
= '--' { return 'undirected'; } | |
/ '->' { return 'ltr'; } | |
/ '<-' { return 'rtl'; } | |
NodeList 'node list' | |
= first:Node rest:(',' Node)* { | |
var nodes = [first].concat(rest.map(function(d){ return d[1]; })); | |
nodes.forEach(function(id) { | |
// add a node if its ID was not previously found | |
if(!(id in node_index)) { | |
var node = {id: id}; | |
node_index[id] = node; | |
graph.nodes.push(node); | |
} | |
}); | |
return nodes; | |
} | |
Node 'identifier' | |
= [_a-zA-Z0-9]+ { return text(); } | |
_ 'whitespace' | |
= [ \t]* |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
R = 18 | |
window.NodeLink = Backbone.D3View.extend | |
tagName: 'svg' | |
events: | |
'click .link': (evt, d) -> @model.select d.id | |
initialize: () -> | |
@d3el.classed 'node_link', true | |
defs = @d3el.append 'defs' | |
# define arrow markers for graph links | |
defs.append 'marker' | |
.attr | |
id: 'end-arrow' | |
viewBox: '0 0 10 10' | |
refX: 4+R | |
refY: 5 | |
orient: 'auto' | |
.append 'path' | |
.attr | |
d: 'M0,0 L0,10 L10,5 z' | |
# append a group for zoomable content | |
zoomable_layer = @d3el.append('g') | |
zoom = d3.behavior.zoom() | |
.scaleExtent([-Infinity,Infinity]) | |
.on 'zoom', () -> | |
zoomable_layer | |
.attr | |
transform: "translate(#{zoom.translate()})scale(#{zoom.scale()})" | |
@d3el.call(zoom) | |
# create two layers for nodes and links | |
@links_layer = zoomable_layer.append 'g' | |
@nodes_layer = zoomable_layer.append 'g' | |
@listenTo @model, 'change:graph', @render | |
@listenTo @model, 'change:selected', @focus | |
render: () -> | |
width = @el.getBoundingClientRect().width | |
height = @el.getBoundingClientRect().height | |
graph = @model.get 'graph' | |
# draw nodes | |
nodes = @nodes_layer.selectAll '.node' | |
.data graph.nodes, (d) -> d.id | |
enter_nodes = nodes.enter().append 'g' | |
.attr | |
class: 'node' | |
enter_nodes.append 'circle' | |
.attr | |
r: R | |
# draw the label | |
enter_nodes.append 'text' | |
.text (d) -> d.id | |
.attr | |
dy: '0.35em' | |
nodes.exit() | |
.remove() | |
# draw links | |
links = @links_layer.selectAll '.link' | |
.data graph.links, (d) -> d.id | |
links | |
.enter().append 'line' | |
.attr | |
class: 'link' | |
links | |
.classed 'directed', (d) -> d.directed | |
links.exit() | |
.remove() | |
### cola layout ### | |
graph.nodes.forEach (v) -> | |
v.width = 3*R | |
v.height = 3*R | |
d3cola = cola.d3adaptor() | |
.size([width, height]) | |
.linkDistance(100) | |
.avoidOverlaps(true) | |
.nodes(graph.nodes) | |
.links(graph.links) | |
.on 'tick', () -> | |
# update nodes and links | |
nodes | |
.attr('transform', (d) -> "translate(#{d.x},#{d.y})") | |
links | |
.attr('x1', (d) -> d.source.x) | |
.attr('y1', (d) -> d.source.y) | |
.attr('x2', (d) -> d.target.x) | |
.attr('y2', (d) -> d.target.y) | |
drag = d3cola.drag() | |
drag.on 'dragstart', () -> | |
# silence other listener | |
d3.event.sourceEvent.stopPropagation() | |
nodes | |
.call(drag) | |
d3cola.start(30,30,30) | |
focus: () -> | |
id = @model.get 'selected' | |
@links_layer.selectAll '.link' | |
.classed 'selected', (d) -> d.id is id | |
.classed 'unselected', (d) -> if id is null then null else d.id isnt id | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
.node_link .node > circle { | |
fill: #dddddd; | |
stroke: #777777; | |
stroke-width: 2px; | |
} | |
.node_link .node > text { | |
font-family: sans-serif; | |
text-anchor: middle; | |
pointer-events: none; | |
-webkit-touch-callout: none; /* iOS Safari */ | |
-webkit-user-select: none; /* Chrome/Safari/Opera */ | |
-khtml-user-select: none; /* Konqueror */ | |
-moz-user-select: none; /* Firefox */ | |
-ms-user-select: none; /* IE/Edge */ | |
user-select: none; /* non-prefixed version, currently | |
not supported by any browser */ | |
} | |
.node_link .link { | |
stroke: teal; | |
stroke-width: 4px; | |
opacity: 0.6; | |
} | |
.node_link .directed.link { | |
stroke: orange; | |
marker-end: url(#end-arrow); | |
} | |
.node_link #end-arrow { | |
fill: orange; | |
} | |
.node_link .selected.link { | |
opacity: 1; | |
} | |
.node_link .unselected.link { | |
opacity: 0.2; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Generated by CoffeeScript 1.10.0 | |
(function() { | |
var R; | |
R = 18; | |
window.NodeLink = Backbone.D3View.extend({ | |
tagName: 'svg', | |
events: { | |
'click .link': function(evt, d) { | |
return this.model.select(d.id); | |
} | |
}, | |
initialize: function() { | |
var defs, zoom, zoomable_layer; | |
this.d3el.classed('node_link', true); | |
defs = this.d3el.append('defs'); | |
defs.append('marker').attr({ | |
id: 'end-arrow', | |
viewBox: '0 0 10 10', | |
refX: 4 + R, | |
refY: 5, | |
orient: 'auto' | |
}).append('path').attr({ | |
d: 'M0,0 L0,10 L10,5 z' | |
}); | |
zoomable_layer = this.d3el.append('g'); | |
zoom = d3.behavior.zoom().scaleExtent([-Infinity, Infinity]).on('zoom', function() { | |
return zoomable_layer.attr({ | |
transform: "translate(" + (zoom.translate()) + ")scale(" + (zoom.scale()) + ")" | |
}); | |
}); | |
this.d3el.call(zoom); | |
this.links_layer = zoomable_layer.append('g'); | |
this.nodes_layer = zoomable_layer.append('g'); | |
this.listenTo(this.model, 'change:graph', this.render); | |
return this.listenTo(this.model, 'change:selected', this.focus); | |
}, | |
render: function() { | |
var d3cola, drag, enter_nodes, graph, height, links, nodes, width; | |
width = this.el.getBoundingClientRect().width; | |
height = this.el.getBoundingClientRect().height; | |
graph = this.model.get('graph'); | |
nodes = this.nodes_layer.selectAll('.node').data(graph.nodes, function(d) { | |
return d.id; | |
}); | |
enter_nodes = nodes.enter().append('g').attr({ | |
"class": 'node' | |
}); | |
enter_nodes.append('circle').attr({ | |
r: R | |
}); | |
enter_nodes.append('text').text(function(d) { | |
return d.id; | |
}).attr({ | |
dy: '0.35em' | |
}); | |
nodes.exit().remove(); | |
links = this.links_layer.selectAll('.link').data(graph.links, function(d) { | |
return d.id; | |
}); | |
links.enter().append('line').attr({ | |
"class": 'link' | |
}); | |
links.classed('directed', function(d) { | |
return d.directed; | |
}); | |
links.exit().remove(); | |
/* cola layout */ | |
graph.nodes.forEach(function(v) { | |
v.width = 3 * R; | |
return v.height = 3 * R; | |
}); | |
d3cola = cola.d3adaptor().size([width, height]).linkDistance(100).avoidOverlaps(true).nodes(graph.nodes).links(graph.links).on('tick', function() { | |
nodes.attr('transform', function(d) { | |
return "translate(" + d.x + "," + d.y + ")"; | |
}); | |
return links.attr('x1', function(d) { | |
return d.source.x; | |
}).attr('y1', function(d) { | |
return d.source.y; | |
}).attr('x2', function(d) { | |
return d.target.x; | |
}).attr('y2', function(d) { | |
return d.target.y; | |
}); | |
}); | |
drag = d3cola.drag(); | |
drag.on('dragstart', function() { | |
return d3.event.sourceEvent.stopPropagation(); | |
}); | |
nodes.call(drag); | |
return d3cola.start(30, 30, 30); | |
}, | |
focus: function() { | |
var id; | |
id = this.model.get('selected'); | |
return this.links_layer.selectAll('.link').classed('selected', function(d) { | |
return d.id === id; | |
}).classed('unselected', function(d) { | |
if (id === null) { | |
return null; | |
} else { | |
return d.id !== id; | |
} | |
}); | |
} | |
}); | |
}).call(this); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment