Browse Source

First steps in supporting subgraphs in DOT parser

css_transitions
josdejong 11 years ago
parent
commit
d34b93d16f
4 changed files with 178 additions and 144 deletions
  1. +87
    -70
      src/graph/dotparser.js
  2. +3
    -3
      test/dotparser.js
  3. +87
    -70
      vis.js
  4. +1
    -1
      vis.min.js

+ 87
- 70
src/graph/dotparser.js View File

@ -2,6 +2,9 @@
/** /**
* Parse a text source containing data in DOT language into a JSON object. * Parse a text source containing data in DOT language into a JSON object.
* The object contains two lists: one with nodes and one with edges. * The object contains two lists: one with nodes and one with edges.
*
* DOT language reference: http://www.graphviz.org/doc/info/lang.html
*
* @param {String} data Text containing a graph in DOT-notation * @param {String} data Text containing a graph in DOT-notation
* @return {Object} graph An object containing two parameters: * @return {Object} graph An object containing two parameters:
* {Object[]} nodes * {Object[]} nodes
@ -40,10 +43,6 @@
var token = ''; // current token var token = ''; // current token
var tokenType = TOKENTYPE.NULL; // type of the token var tokenType = TOKENTYPE.NULL; // type of the token
var graph = null; // object with the graph to be build
var nodeAttr = null; // global node attributes
var edgeAttr = null; // global edge attributes
/** /**
* Get the first character from the dot file. * Get the first character from the dot file.
* The character is stored into the char c. If the end of the dot file is * The character is stored into the char c. If the end of the dot file is
@ -135,11 +134,12 @@
} }
/** /**
* Add a node to the current graph object. If there is already a node with
* Add a node to a graph object. If there is already a node with
* the same id, their attributes will be merged. * the same id, their attributes will be merged.
* @param {Object} graph
* @param {Object} node * @param {Object} node
*/ */
function addNode(node) {
function addNode(graph, node) {
var nodes = graph.nodes; var nodes = graph.nodes;
if (!nodes) { if (!nodes) {
nodes = []; nodes = [];
@ -164,24 +164,25 @@
else { else {
// add // add
graph.nodes.push(node); graph.nodes.push(node);
if (nodeAttr) {
var attr = merge({}, nodeAttr); // clone global attributes
if (graph.node) {
var attr = merge({}, graph.node); // clone global attributes
node.attr = merge(attr, node.attr); // merge attributes node.attr = merge(attr, node.attr); // merge attributes
} }
} }
} }
/** /**
* Add an edge to the current graph object
* Add an edge to a graph object
* @param {Object} graph
* @param {Object} edge * @param {Object} edge
*/ */
function addEdge(edge) {
function addEdge(graph, edge) {
if (!graph.edges) { if (!graph.edges) {
graph.edges = []; graph.edges = [];
} }
graph.edges.push(edge); graph.edges.push(edge);
if (edgeAttr) {
var attr = merge({}, edgeAttr); // clone global attributes
if (graph.edge) {
var attr = merge({}, graph.edge); // clone global attributes
edge.attr = merge(attr, edge.attr); // merge attributes edge.attr = merge(attr, edge.attr); // merge attributes
} }
} }
@ -327,9 +328,7 @@
* @returns {Object} graph * @returns {Object} graph
*/ */
function parseGraph() { function parseGraph() {
graph = {};
nodeAttr = null;
edgeAttr = null;
var graph = {};
first(); first();
getToken(); getToken();
@ -359,7 +358,7 @@
getToken(); getToken();
// statements // statements
parseStatements();
parseStatements(graph);
// close angle bracket // close angle bracket
if (token != '}') { if (token != '}') {
@ -373,19 +372,24 @@
} }
getToken(); getToken();
// remove temporary global properties
delete graph.node;
delete graph.edge;
return graph; return graph;
} }
/** /**
* Parse a list with statements. * Parse a list with statements.
* @param {Object} graph
*/ */
function parseStatements () {
function parseStatements (graph) {
while (token !== '' && token != '}') { while (token !== '' && token != '}') {
if (tokenType != TOKENTYPE.IDENTIFIER) { if (tokenType != TOKENTYPE.IDENTIFIER) {
throw newSyntaxError('Identifier expected'); throw newSyntaxError('Identifier expected');
} }
parseStatement();
parseStatement(graph);
if (token == ';') { if (token == ';') {
getToken(); getToken();
} }
@ -396,47 +400,39 @@
* Parse a single statement. Can be a an attribute statement, node * Parse a single statement. Can be a an attribute statement, node
* statement, a series of node statements and edge statements, or a * statement, a series of node statements and edge statements, or a
* parameter. * parameter.
* @param {Object} graph
*/ */
function parseStatement() {
var attr;
function parseStatement(graph) {
// TODO: parse subgraph
attr = parseAttributeStatement();
if (!attr) {
var id = token; // can be a string or a number
getToken();
// parse an attribute statement
var attr = parseAttributeStatement(graph);
if (attr) {
return;
}
if (token == '=') {
// id statement
getToken();
if (!graph.attr) {
graph.attr = {};
}
graph.attr[id] = token;
getToken();
}
else {
// node statement
var node = {
id: id
};
attr = parseAttributes();
if (attr) {
node.attr = attr;
}
addNode(node);
// parse node
var id = token; // id can be a string or a number
getToken();
// edge statements
parseEdge(id);
}
if (token == '=') {
// id statement
getToken();
graph[id] = token;
getToken();
}
else {
parseNodeStatement(graph, id);
} }
} }
/** /**
* 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'
* @param {Object} graph
* @returns {Object | null} attr * @returns {Object | null} attr
*/ */
function parseAttributeStatement () {
function parseAttributeStatement (graph) {
var attr = null; var attr = null;
// attribute statements // attribute statements
@ -444,25 +440,25 @@
getToken(); getToken();
// node attributes // node attributes
attr = parseAttributes();
attr = parseAttributeList();
if (attr) { if (attr) {
nodeAttr = merge(nodeAttr, attr);
graph.node = merge(graph.node, attr);
} }
} }
else if (token == 'edge') { else if (token == 'edge') {
getToken(); getToken();
// edge attributes // edge attributes
attr = parseAttributes();
attr = parseAttributeList();
if (attr) { if (attr) {
edgeAttr = merge(edgeAttr, attr);
graph.edge = merge(graph.edge, attr);
} }
} }
else if (token == 'graph') { else if (token == 'graph') {
getToken(); getToken();
// graph attributes // graph attributes
attr = parseAttributes();
attr = parseAttributeList();
if (attr) { if (attr) {
graph.attr = merge(graph.attr, attr); graph.attr = merge(graph.attr, attr);
} }
@ -471,28 +467,49 @@
return attr; return attr;
} }
/**
* parse a node statement
* @param {Object} graph
* @param {String | Number} id
*/
function parseNodeStatement(graph, id) {
// node statement
var node = {
id: id
};
var attr = parseAttributeList();
if (attr) {
node.attr = attr;
}
addNode(graph, node);
// edge statements
parseEdge(graph, id);
}
/** /**
* Parse an edge or a series of edges * Parse an edge or a series of edges
* @param {Object} graph
* @param {String | Number} from Id of the from node * @param {String | Number} from Id of the from node
*/ */
function parseEdge(from) {
function parseEdge(graph, from) {
while (token == '->' || token == '--') { while (token == '->' || token == '--') {
var type = token; var type = token;
getToken(); getToken();
if (token == '{') { if (token == '{') {
// parse a set of nodes, like "node1 -> {node2, node3}" // parse a set of nodes, like "node1 -> {node2, node3}"
parseEdgeSet(from, type);
parseEdgeSet(graph, from, type);
break; break;
} }
else { else {
// parse a single edge, like "node1 -> node2 -> node3" // parse a single edge, like "node1 -> node2 -> node3"
var to = token; var to = token;
addNode({
addNode(graph, {
id: to id: to
}); });
getToken(); getToken();
var attr = parseAttributes();
var attr = parseAttributeList();
// create edge // create edge
var edge = { var edge = {
@ -503,7 +520,7 @@
if (attr) { if (attr) {
edge.attr = attr; edge.attr = attr;
} }
addEdge(edge);
addEdge(graph, edge);
from = to; from = to;
} }
@ -512,11 +529,12 @@
/** /**
* Parse a set of nodes, like "{node1; node2; node3}" * Parse a set of nodes, like "{node1; node2; node3}"
* @param {Object} graph
* @param {String | Number} from Id of the from node * @param {String | Number} from Id of the from node
* @param {String} type Edge type, '--' or '->' * @param {String} type Edge type, '--' or '->'
* @return {Node[] | null} nodes * @return {Node[] | null} nodes
*/ */
function parseEdgeSet(from, type) {
function parseEdgeSet(graph, from, type) {
var nodes = null; var nodes = null;
if (token == '{') { if (token == '{') {
@ -528,7 +546,7 @@
throw newSyntaxError('Identifier expected'); throw newSyntaxError('Identifier expected');
} }
var to = token; var to = token;
addNode({
addNode(graph, {
id: to id: to
}); });
getToken(); getToken();
@ -539,11 +557,11 @@
to: to, to: to,
type: type type: type
}; };
var attr = parseAttributes();
var attr = parseAttributeList();
if (attr) { if (attr) {
edge.attr = attr; edge.attr = attr;
} }
addEdge(edge);
addEdge(graph, edge);
// separator // separator
if (token == ';') { if (token == ';') {
@ -564,12 +582,14 @@
/** /**
* Parse a set with attributes, * Parse a set with attributes,
* for example [label="1.000", shape=solid] * for example [label="1.000", shape=solid]
* @return {Object | undefined} attr
* @return {Object | null} attr
*/ */
function parseAttributes() {
if (token == '[') {
function parseAttributeList() {
var attr = null;
while (token == '[') {
getToken(); getToken();
var attr = {};
attr = {};
while (token !== '' && token != ']') { while (token !== '' && token != ']') {
if (tokenType != TOKENTYPE.IDENTIFIER) { if (tokenType != TOKENTYPE.IDENTIFIER) {
throw newSyntaxError('Attribute name expected'); throw newSyntaxError('Attribute name expected');
@ -598,12 +618,9 @@
throw newSyntaxError('Bracket ] expected'); throw newSyntaxError('Bracket ] expected');
} }
getToken(); getToken();
return attr;
}
else {
return undefined;
} }
return attr;
} }
/** /**

+ 3
- 3
test/dotparser.js View File

@ -10,10 +10,10 @@ fs.readFile('test/dot.txt', function (err, data) {
assert.deepEqual(graph, { assert.deepEqual(graph, {
"type": "digraph", "type": "digraph",
"id": "test_graph", "id": "test_graph",
"rankdir": "LR",
"size": "8,5",
"font": "arial",
"attr": { "attr": {
"rankdir": "LR",
"size": "8,5",
"font": "arial",
"attr1": "another\" attr" "attr1": "another\" attr"
}, },
"nodes": [ "nodes": [

+ 87
- 70
vis.js View File

@ -6810,6 +6810,9 @@ Timeline.prototype.getItemRange = function getItemRange() {
/** /**
* Parse a text source containing data in DOT language into a JSON object. * Parse a text source containing data in DOT language into a JSON object.
* The object contains two lists: one with nodes and one with edges. * The object contains two lists: one with nodes and one with edges.
*
* DOT language reference: http://www.graphviz.org/doc/info/lang.html
*
* @param {String} data Text containing a graph in DOT-notation * @param {String} data Text containing a graph in DOT-notation
* @return {Object} graph An object containing two parameters: * @return {Object} graph An object containing two parameters:
* {Object[]} nodes * {Object[]} nodes
@ -6848,10 +6851,6 @@ Timeline.prototype.getItemRange = function getItemRange() {
var token = ''; // current token var token = ''; // current token
var tokenType = TOKENTYPE.NULL; // type of the token var tokenType = TOKENTYPE.NULL; // type of the token
var graph = null; // object with the graph to be build
var nodeAttr = null; // global node attributes
var edgeAttr = null; // global edge attributes
/** /**
* Get the first character from the dot file. * Get the first character from the dot file.
* The character is stored into the char c. If the end of the dot file is * The character is stored into the char c. If the end of the dot file is
@ -6943,11 +6942,12 @@ Timeline.prototype.getItemRange = function getItemRange() {
} }
/** /**
* Add a node to the current graph object. If there is already a node with
* Add a node to a graph object. If there is already a node with
* the same id, their attributes will be merged. * the same id, their attributes will be merged.
* @param {Object} graph
* @param {Object} node * @param {Object} node
*/ */
function addNode(node) {
function addNode(graph, node) {
var nodes = graph.nodes; var nodes = graph.nodes;
if (!nodes) { if (!nodes) {
nodes = []; nodes = [];
@ -6972,24 +6972,25 @@ Timeline.prototype.getItemRange = function getItemRange() {
else { else {
// add // add
graph.nodes.push(node); graph.nodes.push(node);
if (nodeAttr) {
var attr = merge({}, nodeAttr); // clone global attributes
if (graph.node) {
var attr = merge({}, graph.node); // clone global attributes
node.attr = merge(attr, node.attr); // merge attributes node.attr = merge(attr, node.attr); // merge attributes
} }
} }
} }
/** /**
* Add an edge to the current graph object
* Add an edge to a graph object
* @param {Object} graph
* @param {Object} edge * @param {Object} edge
*/ */
function addEdge(edge) {
function addEdge(graph, edge) {
if (!graph.edges) { if (!graph.edges) {
graph.edges = []; graph.edges = [];
} }
graph.edges.push(edge); graph.edges.push(edge);
if (edgeAttr) {
var attr = merge({}, edgeAttr); // clone global attributes
if (graph.edge) {
var attr = merge({}, graph.edge); // clone global attributes
edge.attr = merge(attr, edge.attr); // merge attributes edge.attr = merge(attr, edge.attr); // merge attributes
} }
} }
@ -7135,9 +7136,7 @@ Timeline.prototype.getItemRange = function getItemRange() {
* @returns {Object} graph * @returns {Object} graph
*/ */
function parseGraph() { function parseGraph() {
graph = {};
nodeAttr = null;
edgeAttr = null;
var graph = {};
first(); first();
getToken(); getToken();
@ -7167,7 +7166,7 @@ Timeline.prototype.getItemRange = function getItemRange() {
getToken(); getToken();
// statements // statements
parseStatements();
parseStatements(graph);
// close angle bracket // close angle bracket
if (token != '}') { if (token != '}') {
@ -7181,19 +7180,24 @@ Timeline.prototype.getItemRange = function getItemRange() {
} }
getToken(); getToken();
// remove temporary global properties
delete graph.node;
delete graph.edge;
return graph; return graph;
} }
/** /**
* Parse a list with statements. * Parse a list with statements.
* @param {Object} graph
*/ */
function parseStatements () {
function parseStatements (graph) {
while (token !== '' && token != '}') { while (token !== '' && token != '}') {
if (tokenType != TOKENTYPE.IDENTIFIER) { if (tokenType != TOKENTYPE.IDENTIFIER) {
throw newSyntaxError('Identifier expected'); throw newSyntaxError('Identifier expected');
} }
parseStatement();
parseStatement(graph);
if (token == ';') { if (token == ';') {
getToken(); getToken();
} }
@ -7204,47 +7208,39 @@ Timeline.prototype.getItemRange = function getItemRange() {
* Parse a single statement. Can be a an attribute statement, node * Parse a single statement. Can be a an attribute statement, node
* statement, a series of node statements and edge statements, or a * statement, a series of node statements and edge statements, or a
* parameter. * parameter.
* @param {Object} graph
*/ */
function parseStatement() {
var attr;
function parseStatement(graph) {
// TODO: parse subgraph
attr = parseAttributeStatement();
if (!attr) {
var id = token; // can be a string or a number
getToken();
// parse an attribute statement
var attr = parseAttributeStatement(graph);
if (attr) {
return;
}
if (token == '=') {
// id statement
getToken();
if (!graph.attr) {
graph.attr = {};
}
graph.attr[id] = token;
getToken();
}
else {
// node statement
var node = {
id: id
};
attr = parseAttributes();
if (attr) {
node.attr = attr;
}
addNode(node);
// parse node
var id = token; // id can be a string or a number
getToken();
// edge statements
parseEdge(id);
}
if (token == '=') {
// id statement
getToken();
graph[id] = token;
getToken();
}
else {
parseNodeStatement(graph, id);
} }
} }
/** /**
* 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'
* @param {Object} graph
* @returns {Object | null} attr * @returns {Object | null} attr
*/ */
function parseAttributeStatement () {
function parseAttributeStatement (graph) {
var attr = null; var attr = null;
// attribute statements // attribute statements
@ -7252,25 +7248,25 @@ Timeline.prototype.getItemRange = function getItemRange() {
getToken(); getToken();
// node attributes // node attributes
attr = parseAttributes();
attr = parseAttributeList();
if (attr) { if (attr) {
nodeAttr = merge(nodeAttr, attr);
graph.node = merge(graph.node, attr);
} }
} }
else if (token == 'edge') { else if (token == 'edge') {
getToken(); getToken();
// edge attributes // edge attributes
attr = parseAttributes();
attr = parseAttributeList();
if (attr) { if (attr) {
edgeAttr = merge(edgeAttr, attr);
graph.edge = merge(graph.edge, attr);
} }
} }
else if (token == 'graph') { else if (token == 'graph') {
getToken(); getToken();
// graph attributes // graph attributes
attr = parseAttributes();
attr = parseAttributeList();
if (attr) { if (attr) {
graph.attr = merge(graph.attr, attr); graph.attr = merge(graph.attr, attr);
} }
@ -7279,28 +7275,49 @@ Timeline.prototype.getItemRange = function getItemRange() {
return attr; return attr;
} }
/**
* parse a node statement
* @param {Object} graph
* @param {String | Number} id
*/
function parseNodeStatement(graph, id) {
// node statement
var node = {
id: id
};
var attr = parseAttributeList();
if (attr) {
node.attr = attr;
}
addNode(graph, node);
// edge statements
parseEdge(graph, id);
}
/** /**
* Parse an edge or a series of edges * Parse an edge or a series of edges
* @param {Object} graph
* @param {String | Number} from Id of the from node * @param {String | Number} from Id of the from node
*/ */
function parseEdge(from) {
function parseEdge(graph, from) {
while (token == '->' || token == '--') { while (token == '->' || token == '--') {
var type = token; var type = token;
getToken(); getToken();
if (token == '{') { if (token == '{') {
// parse a set of nodes, like "node1 -> {node2, node3}" // parse a set of nodes, like "node1 -> {node2, node3}"
parseEdgeSet(from, type);
parseEdgeSet(graph, from, type);
break; break;
} }
else { else {
// parse a single edge, like "node1 -> node2 -> node3" // parse a single edge, like "node1 -> node2 -> node3"
var to = token; var to = token;
addNode({
addNode(graph, {
id: to id: to
}); });
getToken(); getToken();
var attr = parseAttributes();
var attr = parseAttributeList();
// create edge // create edge
var edge = { var edge = {
@ -7311,7 +7328,7 @@ Timeline.prototype.getItemRange = function getItemRange() {
if (attr) { if (attr) {
edge.attr = attr; edge.attr = attr;
} }
addEdge(edge);
addEdge(graph, edge);
from = to; from = to;
} }
@ -7320,11 +7337,12 @@ Timeline.prototype.getItemRange = function getItemRange() {
/** /**
* Parse a set of nodes, like "{node1; node2; node3}" * Parse a set of nodes, like "{node1; node2; node3}"
* @param {Object} graph
* @param {String | Number} from Id of the from node * @param {String | Number} from Id of the from node
* @param {String} type Edge type, '--' or '->' * @param {String} type Edge type, '--' or '->'
* @return {Node[] | null} nodes * @return {Node[] | null} nodes
*/ */
function parseEdgeSet(from, type) {
function parseEdgeSet(graph, from, type) {
var nodes = null; var nodes = null;
if (token == '{') { if (token == '{') {
@ -7336,7 +7354,7 @@ Timeline.prototype.getItemRange = function getItemRange() {
throw newSyntaxError('Identifier expected'); throw newSyntaxError('Identifier expected');
} }
var to = token; var to = token;
addNode({
addNode(graph, {
id: to id: to
}); });
getToken(); getToken();
@ -7347,11 +7365,11 @@ Timeline.prototype.getItemRange = function getItemRange() {
to: to, to: to,
type: type type: type
}; };
var attr = parseAttributes();
var attr = parseAttributeList();
if (attr) { if (attr) {
edge.attr = attr; edge.attr = attr;
} }
addEdge(edge);
addEdge(graph, edge);
// separator // separator
if (token == ';') { if (token == ';') {
@ -7372,12 +7390,14 @@ Timeline.prototype.getItemRange = function getItemRange() {
/** /**
* Parse a set with attributes, * Parse a set with attributes,
* for example [label="1.000", shape=solid] * for example [label="1.000", shape=solid]
* @return {Object | undefined} attr
* @return {Object | null} attr
*/ */
function parseAttributes() {
if (token == '[') {
function parseAttributeList() {
var attr = null;
while (token == '[') {
getToken(); getToken();
var attr = {};
attr = {};
while (token !== '' && token != ']') { while (token !== '' && token != ']') {
if (tokenType != TOKENTYPE.IDENTIFIER) { if (tokenType != TOKENTYPE.IDENTIFIER) {
throw newSyntaxError('Attribute name expected'); throw newSyntaxError('Attribute name expected');
@ -7406,12 +7426,9 @@ Timeline.prototype.getItemRange = function getItemRange() {
throw newSyntaxError('Bracket ] expected'); throw newSyntaxError('Bracket ] expected');
} }
getToken(); getToken();
return attr;
}
else {
return undefined;
} }
return attr;
} }
/** /**

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


Loading…
Cancel
Save