Browse Source

Fixes and improvements in DOT parser regarding subgraphs

css_transitions
josdejong 11 years ago
parent
commit
e79d816b0e
5 changed files with 173 additions and 123 deletions
  1. +6
    -0
      src/graph/Graph.js
  2. +64
    -51
      src/graph/dotparser.js
  3. +28
    -16
      test/dotparser.js
  4. +71
    -52
      vis.js
  5. +4
    -4
      vis.min.js

+ 6
- 0
src/graph/Graph.js View File

@ -1083,6 +1083,9 @@ Graph.prototype._setNodes = function(nodes) {
this.nodesData = new DataSet(); this.nodesData = new DataSet();
this.nodesData.add(nodes); this.nodesData.add(nodes);
} }
else if (!nodes) {
this.nodesData = new DataSet();
}
else { else {
throw new TypeError('Array or DataSet expected'); throw new TypeError('Array or DataSet expected');
} }
@ -1206,6 +1209,9 @@ Graph.prototype._setEdges = function(edges) {
this.edgesData = new DataSet(); this.edgesData = new DataSet();
this.edgesData.add(edges); this.edgesData.add(edges);
} }
else if (!edges) {
this.edgesData = new DataSet();
}
else { else {
throw new TypeError('Array or DataSet expected'); throw new TypeError('Array or DataSet expected');
} }

+ 64
- 51
src/graph/dotparser.js View File

@ -140,35 +140,54 @@
* @param {Object} node * @param {Object} node
*/ */
function addNode(graph, node) { function addNode(graph, node) {
var nodes = graph.nodes;
if (!nodes) {
nodes = [];
graph.nodes = nodes;
var i, len;
var current = null;
// find root graph (in case of subgraph)
var graphs = [graph]; // list with all graphs from current graph to root graph
var root = graph;
while (root.parent) {
graphs.push(root.parent);
root = root.parent;
} }
// find existing node
var current = null;
for (var i = 0, len = nodes.length; i < len; i++) {
if (node.id === nodes[i].id) {
current = nodes[i];
break;
// find existing node (at root level) by its id
if (root.nodes) {
for (i = 0, len = root.nodes.length; i < len; i++) {
if (node.id === root.nodes[i].id) {
current = root.nodes[i];
break;
}
} }
} }
if (current) {
// merge attributes
if (node.attr) {
current.attr = merge(current.attr, node.attr);
if (!current) {
// this is a new node
current = {
id: node.id
};
if (graph.node) {
// clone default attributes
current.attr = merge(current.attr, graph.node);
} }
} }
else {
// add
graph.nodes.push(node);
if (graph.node) {
var attr = merge({}, graph.node); // clone global attributes
node.attr = merge(attr, node.attr); // merge attributes
// add node to this (sub)graph and all its parent graphs
for (i = graphs.length - 1; i >= 0; i--) {
var g = graphs[i];
if (!g.nodes) {
g.nodes = [];
}
if (g.nodes.indexOf(current) == -1) {
g.nodes.push(current);
} }
} }
// merge attributes
if (node.attr) {
current.attr = merge(current.attr, node.attr);
}
} }
/** /**
@ -182,7 +201,7 @@
} }
graph.edges.push(edge); graph.edges.push(edge);
if (graph.edge) { if (graph.edge) {
var attr = merge({}, graph.edge); // clone global attributes
var attr = merge({}, graph.edge); // clone default attributes
edge.attr = merge(attr, edge.attr); // merge attributes edge.attr = merge(attr, edge.attr); // merge attributes
} }
} }
@ -204,7 +223,7 @@
}; };
if (graph.edge) { if (graph.edge) {
edge.attr = merge({}, graph.edge); // clone global attributes
edge.attr = merge({}, graph.edge); // clone default attributes
} }
edge.attr = merge(edge.attr || {}, attr); // merge attributes edge.attr = merge(edge.attr || {}, attr); // merge attributes
@ -396,9 +415,10 @@
} }
getToken(); getToken();
// remove temporary global properties
// remove temporary default properties
delete graph.node; delete graph.node;
delete graph.edge; delete graph.edge;
delete graph.graph;
return graph; return graph;
} }
@ -448,13 +468,10 @@
if (token == '=') { if (token == '=') {
// id statement // id statement
getToken(); getToken();
if (!graph.attr) {
graph.attr = {};
}
if (tokenType != TOKENTYPE.IDENTIFIER) { if (tokenType != TOKENTYPE.IDENTIFIER) {
throw newSyntaxError('Identifier expected'); throw newSyntaxError('Identifier expected');
} }
graph.attr[id] = token;
graph[id] = token;
getToken(); getToken();
// TODO: implement comma separated list with "a_list: ID=ID [','] [a_list] " // TODO: implement comma separated list with "a_list: ID=ID [','] [a_list] "
} }
@ -489,12 +506,12 @@
getToken(); getToken();
if (!subgraph) { if (!subgraph) {
subgraph = {
type: 'subgraph'
};
subgraph = {};
} }
// TODO: copy global node and edge attributes into subgraph or not?
subgraph.parent = graph;
subgraph.node = graph.node;
subgraph.edge = graph.edge;
subgraph.graph = graph.graph;
// statements // statements
parseStatements(subgraph); parseStatements(subgraph);
@ -505,16 +522,17 @@
} }
getToken(); getToken();
// remove temporary global properties
// remove temporary default properties
delete subgraph.node; delete subgraph.node;
delete subgraph.edge; delete subgraph.edge;
delete subgraph.graph;
delete subgraph.parent;
// register at the parent graph // register at the parent graph
if (!graph.subgraphs) { if (!graph.subgraphs) {
graph.subgraphs = []; graph.subgraphs = [];
} }
graph.subgraphs.push(subgraph); graph.subgraphs.push(subgraph);
graph.nodes = (graph.nodes || []).concat(subgraph.nodes || []);
} }
return subgraph; return subgraph;
@ -522,43 +540,38 @@
/** /**
* parse an attribute statement like "node [shape=circle fontSize=16]". * parse an attribute statement like "node [shape=circle fontSize=16]".
* Available keywords are 'node', 'edge', 'graph'
* Available keywords are 'node', 'edge', 'graph'.
* The previous list with default attributes will be replaced
* @param {Object} graph * @param {Object} graph
* @returns {Object | null} attr
* @returns {String | null} keyword Returns the name of the parsed attribute
* (node, edge, graph), or null if nothing
* is parsed.
*/ */
function parseAttributeStatement (graph) { function parseAttributeStatement (graph) {
var attr = null;
// attribute statements // attribute statements
if (token == 'node') { if (token == 'node') {
getToken(); getToken();
// node attributes // node attributes
attr = parseAttributeList();
if (attr) {
graph.node = merge(graph.node, attr);
}
graph.node = parseAttributeList();
return 'node';
} }
else if (token == 'edge') { else if (token == 'edge') {
getToken(); getToken();
// edge attributes // edge attributes
attr = parseAttributeList();
if (attr) {
graph.edge = merge(graph.edge, attr);
}
graph.edge = parseAttributeList();
return 'edge';
} }
else if (token == 'graph') { else if (token == 'graph') {
getToken(); getToken();
// graph attributes // graph attributes
attr = parseAttributeList();
if (attr) {
graph.attr = merge(graph.attr, attr);
}
graph.graph = parseAttributeList();
return 'graph';
} }
return attr;
return null;
} }
/** /**

+ 28
- 16
test/dotparser.js View File

@ -10,12 +10,9 @@ fs.readFile('test/dot.txt', function (err, data) {
assert.deepEqual(graph, { assert.deepEqual(graph, {
"type": "digraph", "type": "digraph",
"id": "test_graph", "id": "test_graph",
"attr": {
"rankdir": "LR",
"size": "8,5",
"font": "arial",
"attr1": "another\" attr"
},
"rankdir": "LR",
"size": "8,5",
"font": "arial",
"nodes": [ "nodes": [
{ {
"id": "node1", "id": "node1",
@ -63,10 +60,16 @@ fs.readFile('test/dot.txt', function (err, data) {
} }
}, },
{ {
"id": "B"
"id": "B",
"attr": {
"shape": "circle"
}
}, },
{ {
"id": "C"
"id": "C",
"attr": {
"shape": "circle"
}
} }
], ],
"edges": [ "edges": [
@ -131,13 +134,18 @@ fs.readFile('test/dot.txt', function (err, data) {
{ {
"from": "A", "from": "A",
"to": { "to": {
"type": "subgraph",
"nodes": [ "nodes": [
{ {
"id": "B"
"id": "B",
"attr": {
"shape": "circle"
}
}, },
{ {
"id": "C"
"id": "C",
"attr": {
"shape": "circle"
}
} }
] ]
}, },
@ -147,17 +155,21 @@ fs.readFile('test/dot.txt', function (err, data) {
"fontSize": 12 "fontSize": 12
} }
} }
], ],
"subgraphs" : [
"subgraphs": [
{ {
"type": "subgraph",
"nodes": [ "nodes": [
{ {
"id": "B"
"id": "B",
"attr": {
"shape": "circle"
}
}, },
{ {
"id": "C"
"id": "C",
"attr": {
"shape": "circle"
}
} }
] ]
} }

+ 71
- 52
vis.js View File

@ -5,7 +5,7 @@
* A dynamic, browser-based visualization library. * A dynamic, browser-based visualization library.
* *
* @version 0.1.0-SNAPSHOT * @version 0.1.0-SNAPSHOT
* @date 2013-06-14
* @date 2013-06-17
* *
* @license * @license
* Copyright (C) 2011-2013 Almende B.V, http://almende.com * Copyright (C) 2011-2013 Almende B.V, http://almende.com
@ -6949,34 +6949,53 @@ Timeline.prototype.getItemRange = function getItemRange() {
* @param {Object} node * @param {Object} node
*/ */
function addNode(graph, node) { function addNode(graph, node) {
var nodes = graph.nodes;
if (!nodes) {
nodes = [];
graph.nodes = nodes;
var i, len;
var current = null;
// find root graph (in case of subgraph)
var graphs = [graph]; // list with all graphs from current graph to root graph
var root = graph;
while (root.parent) {
graphs.push(root.parent);
root = root.parent;
} }
// find existing node
var current = null;
for (var i = 0, len = nodes.length; i < len; i++) {
if (node.id === nodes[i].id) {
current = nodes[i];
break;
// find existing node (at root level) by its id
if (root.nodes) {
for (i = 0, len = root.nodes.length; i < len; i++) {
if (node.id === root.nodes[i].id) {
current = root.nodes[i];
break;
}
} }
} }
if (current) {
// merge attributes
if (node.attr) {
current.attr = merge(current.attr, node.attr);
if (!current) {
// this is a new node
current = {
id: node.id
};
if (graph.node) {
// clone default attributes
current.attr = merge(current.attr, graph.node);
} }
} }
else {
// add
graph.nodes.push(node);
if (graph.node) {
var attr = merge({}, graph.node); // clone global attributes
node.attr = merge(attr, node.attr); // merge attributes
// add node to this (sub)graph and all its parent graphs
for (i = graphs.length - 1; i >= 0; i--) {
var g = graphs[i];
if (!g.nodes) {
g.nodes = [];
} }
if (g.nodes.indexOf(current) == -1) {
g.nodes.push(current);
}
}
// merge attributes
if (node.attr) {
current.attr = merge(current.attr, node.attr);
} }
} }
@ -6991,7 +7010,7 @@ Timeline.prototype.getItemRange = function getItemRange() {
} }
graph.edges.push(edge); graph.edges.push(edge);
if (graph.edge) { if (graph.edge) {
var attr = merge({}, graph.edge); // clone global attributes
var attr = merge({}, graph.edge); // clone default attributes
edge.attr = merge(attr, edge.attr); // merge attributes edge.attr = merge(attr, edge.attr); // merge attributes
} }
} }
@ -7013,7 +7032,7 @@ Timeline.prototype.getItemRange = function getItemRange() {
}; };
if (graph.edge) { if (graph.edge) {
edge.attr = merge({}, graph.edge); // clone global attributes
edge.attr = merge({}, graph.edge); // clone default attributes
} }
edge.attr = merge(edge.attr || {}, attr); // merge attributes edge.attr = merge(edge.attr || {}, attr); // merge attributes
@ -7205,9 +7224,10 @@ Timeline.prototype.getItemRange = function getItemRange() {
} }
getToken(); getToken();
// remove temporary global properties
// remove temporary default properties
delete graph.node; delete graph.node;
delete graph.edge; delete graph.edge;
delete graph.graph;
return graph; return graph;
} }
@ -7257,13 +7277,10 @@ Timeline.prototype.getItemRange = function getItemRange() {
if (token == '=') { if (token == '=') {
// id statement // id statement
getToken(); getToken();
if (!graph.attr) {
graph.attr = {};
}
if (tokenType != TOKENTYPE.IDENTIFIER) { if (tokenType != TOKENTYPE.IDENTIFIER) {
throw newSyntaxError('Identifier expected'); throw newSyntaxError('Identifier expected');
} }
graph.attr[id] = token;
graph[id] = token;
getToken(); getToken();
// TODO: implement comma separated list with "a_list: ID=ID [','] [a_list] " // TODO: implement comma separated list with "a_list: ID=ID [','] [a_list] "
} }
@ -7298,12 +7315,12 @@ Timeline.prototype.getItemRange = function getItemRange() {
getToken(); getToken();
if (!subgraph) { if (!subgraph) {
subgraph = {
type: 'subgraph'
};
subgraph = {};
} }
// TODO: copy global node and edge attributes into subgraph or not?
subgraph.parent = graph;
subgraph.node = graph.node;
subgraph.edge = graph.edge;
subgraph.graph = graph.graph;
// statements // statements
parseStatements(subgraph); parseStatements(subgraph);
@ -7314,16 +7331,17 @@ Timeline.prototype.getItemRange = function getItemRange() {
} }
getToken(); getToken();
// remove temporary global properties
// remove temporary default properties
delete subgraph.node; delete subgraph.node;
delete subgraph.edge; delete subgraph.edge;
delete subgraph.graph;
delete subgraph.parent;
// register at the parent graph // register at the parent graph
if (!graph.subgraphs) { if (!graph.subgraphs) {
graph.subgraphs = []; graph.subgraphs = [];
} }
graph.subgraphs.push(subgraph); graph.subgraphs.push(subgraph);
graph.nodes = (graph.nodes || []).concat(subgraph.nodes || []);
} }
return subgraph; return subgraph;
@ -7331,43 +7349,38 @@ Timeline.prototype.getItemRange = function getItemRange() {
/** /**
* parse an attribute statement like "node [shape=circle fontSize=16]". * parse an attribute statement like "node [shape=circle fontSize=16]".
* Available keywords are 'node', 'edge', 'graph'
* Available keywords are 'node', 'edge', 'graph'.
* The previous list with default attributes will be replaced
* @param {Object} graph * @param {Object} graph
* @returns {Object | null} attr
* @returns {String | null} keyword Returns the name of the parsed attribute
* (node, edge, graph), or null if nothing
* is parsed.
*/ */
function parseAttributeStatement (graph) { function parseAttributeStatement (graph) {
var attr = null;
// attribute statements // attribute statements
if (token == 'node') { if (token == 'node') {
getToken(); getToken();
// node attributes // node attributes
attr = parseAttributeList();
if (attr) {
graph.node = merge(graph.node, attr);
}
graph.node = parseAttributeList();
return 'node';
} }
else if (token == 'edge') { else if (token == 'edge') {
getToken(); getToken();
// edge attributes // edge attributes
attr = parseAttributeList();
if (attr) {
graph.edge = merge(graph.edge, attr);
}
graph.edge = parseAttributeList();
return 'edge';
} }
else if (token == 'graph') { else if (token == 'graph') {
getToken(); getToken();
// graph attributes // graph attributes
attr = parseAttributeList();
if (attr) {
graph.attr = merge(graph.attr, attr);
}
graph.graph = parseAttributeList();
return 'graph';
} }
return attr;
return null;
} }
/** /**
@ -10406,6 +10419,9 @@ Graph.prototype._setNodes = function(nodes) {
this.nodesData = new DataSet(); this.nodesData = new DataSet();
this.nodesData.add(nodes); this.nodesData.add(nodes);
} }
else if (!nodes) {
this.nodesData = new DataSet();
}
else { else {
throw new TypeError('Array or DataSet expected'); throw new TypeError('Array or DataSet expected');
} }
@ -10529,6 +10545,9 @@ Graph.prototype._setEdges = function(edges) {
this.edgesData = new DataSet(); this.edgesData = new DataSet();
this.edgesData.add(edges); this.edgesData.add(edges);
} }
else if (!edges) {
this.edgesData = new DataSet();
}
else { else {
throw new TypeError('Array or DataSet expected'); throw new TypeError('Array or DataSet expected');
} }

+ 4
- 4
vis.min.js
File diff suppressed because it is too large
View File


Loading…
Cancel
Save