diff --git a/src/dot.js b/src/dot.js index 19f9c3e4..0b8e25ef 100644 --- a/src/dot.js +++ b/src/dot.js @@ -30,24 +30,57 @@ dagre.dot.toGraph = function(str) { g.addEdge(id, source, target, edge); } - function handleStmt(stmt) { + var defaultAttrs = { + _default: {}, + + get: function get(type, attrs) { + if (typeof this._default[type] !== "undefined") { + var mergedAttrs = {}; + // clone default attributes so they won't get overwritten in the next step + mergeAttributes(this._default[type], mergedAttrs); + // merge statement attributes with default attributes, precedence give to stmt attributes + mergeAttributes(attrs, mergedAttrs); + return mergedAttrs; + } else { + return attrs; + } + }, + + set: function set(type, attrs) { + this._default[type] = this.get(type, attrs); + } + }; + + function handleStmt(stmt, skipDefaultAttributes) { + var attrs; + if (typeof skipDefaultAttributes === "undefined" || skipDefaultAttributes !== true) { + attrs = defaultAttrs.get(stmt.type, stmt.attrs); + } else { + attrs = stmt.attrs; + } + switch (stmt.type) { case "node": - createNode(stmt.id, stmt.attrs); + createNode(stmt.id, attrs); break; case "edge": var prev; stmt.elems.forEach(function(elem) { - handleStmt(elem); + // Default attribute inclusion has to be skipped for nested calls + // to not overwrite attributes that have been defined through preceding + // default attribute statements. + // + // Test "overrides redefined default attributes" in test/dot-test.js checks that. + handleStmt(elem, true); switch(elem.type) { case "node": var curr = elem.id; if (prev) { - createEdge(prev, curr, stmt.attrs); + createEdge(prev, curr, attrs); if (undir) { - createEdge(curr, prev, stmt.attrs); + createEdge(curr, prev, attrs); } } prev = curr; @@ -59,7 +92,7 @@ dagre.dot.toGraph = function(str) { }); break; case "attr": - // Ignore for now + defaultAttrs.set(stmt.attrType, stmt.attrs); break; default: throw new Error("Unsupported statement type: " + stmt.type); diff --git a/test/dot-test.js b/test/dot-test.js index 494bf782..7f1d642c 100644 --- a/test/dot-test.js +++ b/test/dot-test.js @@ -3,4 +3,49 @@ describe("dagre.dot.toGraph", function() { var g = dagre.dot.toGraph("digraph { a [label=\"\"]; }"); assert.equal(g.node("a").label, ""); }); + it("adds default attributes to nodes", function() { + var dot = "digraph { node [color=black shape=box]; n1 [label=\"n1\"]; n2 [label=\"n2\"]; n1 -> n2; }"; + var g = dagre.dot.toGraph(dot); + assert.equal(g.node("n1").color, "black"); + assert.equal(g.node("n1").shape, "box"); + assert.equal(g.node("n1").label, "n1"); + + assert.equal(g.node("n2").color, "black"); + assert.equal(g.node("n2").shape, "box"); + assert.equal(g.node("n2").label, "n2"); + }); + it("combines multiple default attribute statements", function() { + var dot = "digraph { node [color=black]; node [shape=box]; n1 [label=\"n1\"]; }"; + var g = dagre.dot.toGraph(dot); + assert.equal(g.node("n1").color, "black"); + assert.equal(g.node("n1").shape, "box"); + }); + it("takes statement order into account when applying default attributes", function() { + var dot = "digraph { node [color=black]; n1 [label=\"n1\"]; node [shape=box]; n2 [label=\"n2\"]; }"; + var g = dagre.dot.toGraph(dot); + assert.equal(g.node("n1").color, "black"); + assert.equal(g.node("n1").shape, undefined); + + assert.equal(g.node("n2").color, "black"); + assert.equal(g.node("n2").shape, "box"); + }); + it("overrides redefined default attributes", function() { + var dot = "digraph { node [color=black]; n1 [label=\"n1\"]; node [color=green]; n2 [label=\"n2\"]; n1 -> n2; }"; + var g = dagre.dot.toGraph(dot); + assert.equal(g.node("n1").color, "black"); + assert.equal(g.node("n2").color, "green"); + + // Implementation detail: + // toGraph::handleStmt wants to assure that nodes used in an edge definition + // are defined by calling createNode for those nodes. If these nested createNode + // calls don't skip merging the default attributes, the attributes of already + // defined nodes could be overwritten, causing both nodes in this test case to + // have "color" set to green. + }); + it("does not carry attributes from one node over to the next", function() { + var dot = "digraph { node [color=black]; n1 [label=\"n1\" fontsize=12]; n2 [label=\"n2\"]; n1 -> n2; }"; + var g = dagre.dot.toGraph(dot); + assert.equal(g.node("n1").fontsize, 12); + assert.equal(g.node("n2").fontsize, undefined, "n2.fontsize should not be defined"); + }); });