0&&(r+=o.selectorText+" { "+o.style.cssText+" }\n")}}var c="",l="";for(var h in t)t.hasOwnProperty(h)&&"undefined"!=typeof h&&("default"===h?c=".node { "+t[h].styles.join("; ")+"; }\n":l+="."+h+" { "+t[h].styles.join("; ")+"; }\n");if(""!==r||""!==c||""!==l){var d=document.createElement("style");d.setAttribute("type","text/css"),d.setAttribute("title","mermaid-svg-internal-css"),d.innerHTML="/* */\n",e.insertBefore(d,e.firstChild)}}},{}]},{},[110]);
\ No newline at end of file
diff --git a/src/diagrams/flowchart/flowRenderer.js b/src/diagrams/flowchart/flowRenderer.js
index 6c10cf3fa..d831d3af8 100644
--- a/src/diagrams/flowchart/flowRenderer.js
+++ b/src/diagrams/flowchart/flowRenderer.js
@@ -37,7 +37,7 @@ exports.addVertices = function (vert, g) {
* @type {string}
*/
var classStr = '';
-
+
if(vertice.classes.length >0){
classStr = vertice.classes.join(" ");
}
@@ -138,11 +138,34 @@ exports.addEdges = function (edges, g) {
};
/**
- * Returns the default style for nodes.
- * @returns {string} Default style for nodes
+ * Returns the all the styles from classDef statements in the graph definition.
+ * @returns {object} classDef styles
*/
-exports.defaultNodeStyle = function () {
- return ".node {fill:#eaeaea; stroke:#666; stroke-width:1.5px;}";
+exports.getClasses = function (text, isDot) {
+ var parser;
+ graph.clear();
+ if(isDot){
+ parser = dot.parser;
+
+ }else{
+ parser = flow.parser;
+ }
+ parser.yy = graph;
+
+ // Parse the graph definition
+ parser.parse(text);
+
+ var classDefStylesObj = {};
+ var classDefStyleStr = '';
+
+ var classes = graph.getClasses();
+
+ // Add default class if undefined
+ if(typeof classes.default === 'undefined') {
+ classes.default = {id:'default'};
+ classes.default.styles = ['fill:#eaeaea','stroke:#666','stroke-width:1.5px'];
+ }
+ return classes;
};
/**
diff --git a/src/main.js b/src/main.js
index e737d6345..670c86436 100644
--- a/src/main.js
+++ b/src/main.js
@@ -41,27 +41,33 @@ var init = function () {
txt = txt.replace(/' +
'' +
'';
var graphType = utils.detectType(txt);
+ var classes = {};
switch(graphType){
- case 'graph':
+ case 'graph':
console.log('FC');
+ classes = flowRenderer.getClasses(txt, false);
flowRenderer.draw(txt, id, false);
- utils.cloneCssStyles(element.firstChild, flowRenderer.defaultNodeStyle());
+ utils.cloneCssStyles(element.firstChild, classes);
graph.bindFunctions();
- break;
- case 'dotGraph':
- flowRenderer.draw(txt, id,true);
- utils.cloneCssStyles(element.firstChild, flowRenderer.defaultNodeStyle());
break;
- case 'sequenceDiagram':
+ case 'dotGraph':
+ classes = flowRenderer.getClasses(txt, true);
+ flowRenderer.draw(txt, id, true);
+ utils.cloneCssStyles(element.firstChild, classes);
+ break;
+ case 'sequenceDiagram':
seq.draw(txt,id);
- // TODO - Get default styles for sequence diagram
- utils.cloneCssStyles(element.firstChild, flowRenderer.defaultNodeStyle());
+ // TODO - Get styles for sequence diagram
+ utils.cloneCssStyles(element.firstChild, classes);
break;
}
diff --git a/src/utils.js b/src/utils.js
index 13ac60c0c..37cce8abc 100644
--- a/src/utils.js
+++ b/src/utils.js
@@ -30,26 +30,55 @@ module.exports.detectType = function(text,a){
* Copies all relevant CSS content into the graph SVG.
* This allows the SVG to be copied as is while keeping class based styling
* @param {element} svg The root element of the SVG
- * @param {string} defaultStyle Default style definitions (for elements without classes)
+ * @param {object} Hash table of class definitions from the graph definition
*/
-module.exports.cloneCssStyles = function(svg, defaultStyle){
- var used = "";
+module.exports.cloneCssStyles = function(svg, classes){
+ var usedStyles = "";
var sheets = document.styleSheets;
for (var i = 0; i < sheets.length; i++) {
- var rules = sheets[i].cssRules;
- for (var j = 0; j < rules.length; j++) {
- var rule = rules[j];
- if (typeof(rule.style) != "undefined") {
- var elems = svg.querySelectorAll(rule.selectorText);
- if (elems.length > 0) {
- used += rule.selectorText + " { " + rule.style.cssText + " }\n";
+ // Avoid multiple inclusion on pages with multiple graphs
+ if (sheets[i].title != 'mermaid-svg-internal-css') {
+ var rules = sheets[i].cssRules;
+ for (var j = 0; j < rules.length; j++) {
+ var rule = rules[j];
+ if (typeof(rule.style) != "undefined") {
+ var elems = svg.querySelectorAll(rule.selectorText);
+ if (elems.length > 0) {
+ usedStyles += rule.selectorText + " { " + rule.style.cssText + " }\n";
+ }
}
}
+ }
+ }
+
+ var defaultStyles = "";
+ var embeddedStyles = "";
+
+ for (var className in classes) {
+ if (classes.hasOwnProperty(className) && typeof(className) != "undefined") {
+ if (className === 'default') {
+ defaultStyles = '.node' + ' { ' + classes[className].styles.join("; ") + '; }\n';
+ } else {
+ embeddedStyles += '.' + className + ' { ' + classes[className].styles.join("; ") + '; }\n';
+ }
}
}
- var s = document.createElement('style');
- s.setAttribute('type', 'text/css');
- s.innerHTML = "/* */";
- svg.insertBefore(s, svg.firstChild);
+ if (usedStyles !== "" || defaultStyles !== "" || embeddedStyles !== "") {
+ var s = document.createElement('style');
+ s.setAttribute('type', 'text/css');
+ s.setAttribute('title', 'mermaid-svg-internal-css');
+ s.innerHTML = "/* */\n";
+ svg.insertBefore(s, svg.firstChild);
+ }
};
diff --git a/src/utils.spec.js b/src/utils.spec.js
index 78f517919..ddd0d7f87 100644
--- a/src/utils.spec.js
+++ b/src/utils.spec.js
@@ -46,4 +46,165 @@ describe('when detecting chart type ',function() {
var type = utils.detectType(str);
expect(type).toBe('sequence');
});
-});
\ No newline at end of file
+});
+
+describe('when cloning CSS ',function() {
+ var utils = require('./utils');
+
+ beforeEach(function () {
+ var MockBrowser = require('mock-browser').mocks.MockBrowser;
+ var mock = new MockBrowser();
+
+ // and in the run-code inside some object
+ document = mock.getDocument();
+ //document.body.innerHTML = '';
+ //document.body.innerHTML = '';
+ });
+
+ function stylesToArray(svg) {
+ var styleSheets = svg.getElementsByTagName("style");
+ expect(styleSheets.length).toBe(1);
+ var styleSheet = styleSheets[0];
+
+ var innerStyle = styleSheet.innerHTML;
+ var styleArr = innerStyle.split("\n");
+
+ // Remove first and last two lines to remove the CDATA
+ expect(styleArr.length).toBeGreaterThan(2);
+ var styleArrTrim = styleArr.slice(1,-2);
+
+ // Remove all empty lines
+ for (var i = 0; i < styleArrTrim.length; i++) {
+ if (styleArrTrim[i].trim() === '') {
+ styleArrTrim.splice(i,1);
+ i--;
+ }
+ }
+
+ return styleArrTrim;
+ }
+
+ function addStyleToDocument() {
+ var s = document.createElement('style');
+ s.innerHTML = '.node { stroke:#eee; }\n.node-square { stroke:#bbb; }\n';
+ document.body.appendChild(s);
+ }
+
+ function addSecondStyleToDocument() {
+ var s = document.createElement('style');
+ s.innerHTML = '.node2 { stroke:#eee; }\n.node-square { stroke:#beb; }\n';
+ document.body.appendChild(s);
+ }
+
+ function generateSVG() {
+ var svg = document.createElement('svg');
+ var g1 = document.createElement('g');
+ g1.setAttribute('class', 'node');
+ svg.appendChild(g1);
+ var g2 = document.createElement('g');
+ g2.setAttribute('class', 'node-square');
+ svg.appendChild(g2);
+ return svg;
+ }
+
+ function addSVGwithStyleToDocument() {
+ var svg = document.createElement('svg');
+ var s = document.createElement('style');
+ s.innerHTML = '.node2 { stroke:#eee; }\n.node-square { stroke:#bfb; }\n';
+ svg.appendChild(s);
+ document.body.appendChild(svg);
+ }
+
+ function addMermaidSVGwithStyleToDocument(id) {
+ var svg = document.createElement('svg');
+ var s = document.createElement('style');
+ s.setAttribute('type', 'text/css');
+ s.setAttribute('title', 'mermaid-svg-internal-css');
+ s.innerHTML = '.node2 { stroke:#eee; }\n.node-square { stroke:#bfe; }\n';
+ svg.appendChild(s);
+ document.body.appendChild(svg);
+ // The Mock-browser seems not to support stylesheets title attribute, so we add it manually
+ var sheets = document.styleSheets;
+ sheets[id].title = "mermaid-svg-internal-css";
+ }
+
+ it('should handle an empty set of classes', function () {
+ var svg = document.createElement('svg');
+
+ utils.cloneCssStyles(svg, {});
+ // Should not create style element if not needed
+ expect(svg.innerHTML).toBe('');
+ });
+
+ it('should handle a default class', function () {
+ var svg = document.createElement('svg');
+
+ utils.cloneCssStyles(svg, { "default": { "styles": ["stroke:#fff","stroke-width:1.5px"] } });
+ expect(stylesToArray(svg)).toEqual([ '.node { stroke:#fff; stroke-width:1.5px; }']);
+ // Also verify the elements around the styling
+ expect(svg.innerHTML).toBe('');
+ });
+
+ it('should handle stylesheet in document with no classes in SVG', function () {
+ var svg = document.createElement('svg');
+ addStyleToDocument();
+ utils.cloneCssStyles(svg, {});
+ // Should not create style element if not needed
+ expect(svg.innerHTML).toBe('');
+ });
+
+ it('should handle stylesheet in document with classes in SVG', function () {
+ var svg = generateSVG();
+ addStyleToDocument();
+ utils.cloneCssStyles(svg, {});
+ expect(stylesToArray(svg)).toEqual([ '.node { stroke: #eee; }', '.node-square { stroke: #bbb; }']);
+ });
+
+ it('should handle multiple stylesheets in document with classes in SVG', function () {
+ var svg = generateSVG();
+ addStyleToDocument();
+ addSecondStyleToDocument();
+ utils.cloneCssStyles(svg, {});
+ expect(stylesToArray(svg)).toEqual([ '.node { stroke: #eee; }', '.node-square { stroke: #bbb; }', '.node-square { stroke: #beb; }']);
+ });
+
+ it('should handle multiple stylesheets + styles in other SVG', function () {
+ var svg = generateSVG();
+ addStyleToDocument();
+ addSecondStyleToDocument();
+ addSVGwithStyleToDocument();
+ utils.cloneCssStyles(svg, {});
+ expect(stylesToArray(svg)).toEqual([ '.node { stroke: #eee; }', '.node-square { stroke: #bbb; }', '.node-square { stroke: #beb; }', '.node-square { stroke: #bfb; }']);
+ });
+
+ it('should handle multiple stylesheets + ignore styles in mermaid SVG', function () {
+ var svg = generateSVG();
+ addStyleToDocument();
+ addSecondStyleToDocument();
+ addSVGwithStyleToDocument();
+ addMermaidSVGwithStyleToDocument(3);
+ utils.cloneCssStyles(svg, {});
+ expect(stylesToArray(svg)).toEqual([ '.node { stroke: #eee; }', '.node-square { stroke: #bbb; }', '.node-square { stroke: #beb; }', '.node-square { stroke: #bfb; }']);
+ });
+
+ it('should handle a default class together with stylesheet in document with classes in SVG', function () {
+ var svg = generateSVG();
+ addStyleToDocument();
+ utils.cloneCssStyles(svg, { "default": { "styles": ["stroke:#fff","stroke-width:1.5px"] } });
+ expect(stylesToArray(svg)).toEqual([ '.node { stroke:#fff; stroke-width:1.5px; }', '.node { stroke: #eee; }', '.node-square { stroke: #bbb; }']);
+ });
+
+ it('should handle a default class together with stylesheet in document and classDefs', function () {
+ var svg = generateSVG();
+ addStyleToDocument();
+ utils.cloneCssStyles(svg, { "default": { "styles": ["stroke:#fff","stroke-width:1.5px"] },
+ "node-square": { "styles": ["fill:#eee", "stroke:#aaa"] },
+ "node-circle": { "styles": ["fill:#444", "stroke:#111"] } });
+ expect(stylesToArray(svg)).toEqual([ '.node { stroke:#fff; stroke-width:1.5px; }',
+ '.node { stroke: #eee; }',
+ '.node-square { stroke: #bbb; }',
+ '.node-square { fill:#eee; stroke:#aaa; }',
+ '.node-circle { fill:#444; stroke:#111; }' ]);
+ });
+});
+
diff --git a/test/web_style.html b/test/web_style.html
index 07d50cb5b..76c48c622 100644
--- a/test/web_style.html
+++ b/test/web_style.html
@@ -23,41 +23,119 @@
font-family: "Helvetica Neue", Helvetica, Arial, sans-serf;
font-size: 14px; }
.node-circle { stroke-width: 0.5px; stroke: #339999; fill: #009900; }
+ .node-odd-override { stroke-width: 3.5px; stroke: #339900; fill: #009999; }
+ .node-odd { stroke-width: 3.5px; stroke: #339900; fill: #009999; }
+
- Shapes
- Shape examples:
+ Style
+ Styling is applied in the following order:
+
+ - Node default style (see wiki)
+ - CSS on the page
+ - Class definitions inside the graph definition
+ - Inline styling inside the graph definition
+
+ and the last styling applied is the resulting styling. For instance, "Class definitions inside the graph definition" overrides styling from "CSS on the page".
+
+ CSS in the page head:
- graph TD;
- sq[Square shape]-->ci((Circle shape));
- od>Odd shape]---|Two line <br>edge comment|ro;
- od2>Really long text in an Odd shape]-->od3>Really long text with linebreak <br>in an Odd shape];
- di{Diamond is <br> broken}-->ro(Rounded <br>square <br>shape);
+ <style>
+ .node-square {
+ stroke-width: 4px;
+ stroke: #339933;
+ fill: #999900;
+ font-weight: 300;
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serf;
+ font-size: 14px; }
+ .node-circle { stroke-width: 0.5px; stroke: #339999; fill: #009900; }
+ .node-odd-override { stroke-width: 3.5px; stroke: #339900; fill: #009999; }
+ .node-odd { stroke-width: 3.5px; stroke: #339900; fill: #009999; }
+
+ </style>
+
+ Graph definition
+
+ graph TD;
- %% Comments after double percent signs
- di-->ro2(Rounded square shape);
- e((Inner circle))-->f(,.?!+-*ز);
- style e red;
+ noc[No class<br />using default];
+ cyr2((Class node-cyr-undefined<br />is undefined, using default));
+ class cyr2 node-cyr-undefined;
+ ndef[Default style];
+ noc-->ndef;
+ cyr2-->ndef;
- class sq node-square;
- class e node-circle;
+ sq[Class node-square<br />defined in page CSS];
+ class sq node-square;
+ ncss[Page CSS style];
+ sq-->ncss;
+
+ cyr[Class node-cyr<br />defined by classDef];
+ od2>Class node-odd-override<br />defined in page CSS<br />and defined by classDef];
+ ncdef[classDef style];
+ od2-->ncdef;
+ cyr-->ncdef;
+ class cyr node-cyr;
+ class od2 node-odd-override;
+ classDef node-odd-override fill:#BB00BB,stroke:#666622;
+ classDef node-cyr fill:#BB0099,stroke:#666622;
+
+ e1[Class node-cyr<br />defined by classDef<br />and inline style];
+ class e1 node-e1;
+ style e1 fill:#FF0000;
+ e2>Class node-odd-override<br />defined in page CSS<br />and defined by classDef<br />and inline style];
+ class e2 node-e2;
+ style e2 fill:#FF0000;
+ e((Inline style in<br />graph definition));
+ style e fill:#FF0000;
+ ninl[Inline style];
+ e-->ninl;
+ e1-->ninl;
+ e2-->ninl;
+ classDef node-e1 fill:#990066,stroke:#666622;
+ classDef node-e2 fill:#990066,stroke:#666622;
graph TD;
- sq[Square shape]-->ci((Circle shape));
- od>Odd shape]---|Two line
edge comment|ro;
- od2>Really long text in an Odd shape]-->od3>Really long text with linebreak
in an Odd shape];
- di{Diamond is
broken}-->ro(Rounded
square
shape);
- di-->ro2(Rounded square shape);
- %% Comments after double percent signs
- e((Inner circle))-->f(,.?!+-*ز);
- cyr[Cyrillic]-->cyr2((Circle shape Начало));
- style e red;
+ noc[No class
using default];
+ cyr2((Class node-cyr-undefined
is undefined, using default));
+ class cyr2 node-cyr-undefined;
+ ndef[Default style];
+ noc-->ndef;
+ cyr2-->ndef;
+
+ sq[Class node-square
defined in page CSS];
class sq node-square;
- class e node-circle;
+ ncss[Page CSS style];
+ sq-->ncss;
+
+ cyr[Class node-cyr
defined by classDef];
+ od2>Class node-odd-override
defined in page CSS
and defined by classDef];
+ ncdef[classDef style];
+ od2-->ncdef;
+ cyr-->ncdef;
+ class cyr node-cyr;
+ class od2 node-odd-override;
+ classDef node-odd-override fill:#BB00BB,stroke:#666622;
+ classDef node-cyr fill:#BB0099,stroke:#666622;
+
+ e1[Class node-cyr
defined by classDef
and inline style];
+ class e1 node-e1;
+ style e1 fill:#FF0000;
+ e2>Class node-odd-override
defined in page CSS
and defined by classDef
and inline style];
+ class e2 node-e2;
+ style e2 fill:#FF0000;
+ e((Inline style in
graph definition));
+ style e fill:#FF0000;
+ ninl[Inline style];
+ e-->ninl;
+ e1-->ninl;
+ e2-->ninl;
+ classDef node-e1 fill:#990066,stroke:#666622;
+ classDef node-e2 fill:#990066,stroke:#666622;
diff --git a/test/web_style.svg b/test/web_style.svg
index 5a36691ad..eef22e093 100644
--- a/test/web_style.svg
+++ b/test/web_style.svg
@@ -1,9 +1,10 @@
-
\ No newline at end of file
+
No class .. using default
Class node-cyr-undefined .. is undefined, using default
Default style
Class node-square .. defined in page CSS
Page CSS style
Class node-cyr .. defined by classDef
Class node-odd-override .. defined in page CSS .. and defined by classDef
classDef style
Class node-cyr .. defined by classDef .. and inline style
Class node-odd-override .. defined in page CSS .. and defined by classDef .. and inline style
Inline style in .. graph definition
Inline style
\ No newline at end of file