Improved class styling for nodes

Added unit testing
classDefs are now exported to the common style of the SVG
Styling is no longer copied from other mermaid SVGs on the same webpage
This commit is contained in:
Björn Weström
2014-12-14 22:15:47 +01:00
parent 6ef6d79b48
commit f656269a50
10 changed files with 539 additions and 125 deletions

View File

@@ -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;
};
/**

View File

@@ -41,27 +41,33 @@ var init = function () {
txt = txt.replace(/</g,'&lt;');
txt = he.decode(txt).trim();
var classDefStyles = '';
var defaultStyles = '';
element.innerHTML = '<svg id="' + id + '" width="100%" xmlns="http://www.w3.org/2000/svg">' +
'<g />' +
'</svg>';
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;
}

View File

@@ -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 = "/* <![CDATA[ */\n" + defaultStyle + "\n" + used + "\n/* ]]> */";
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 = "/* <![CDATA[ */\n";
if (defaultStyles !== "") {
s.innerHTML += defaultStyles;
}
if (usedStyles !== "") {
s.innerHTML += usedStyles;
}
if (embeddedStyles !== "") {
s.innerHTML += embeddedStyles;
}
s.innerHTML += "/* ]]> */\n";
svg.insertBefore(s, svg.firstChild);
}
};

View File

@@ -46,4 +46,165 @@ describe('when detecting chart type ',function() {
var type = utils.detectType(str);
expect(type).toBe('sequence');
});
});
});
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('<style type="text/css" title="mermaid-svg-internal-css">/* <![CDATA[ */\n.node { stroke:#fff; stroke-width:1.5px; }\n/* ]]> */\n</style>');
});
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; }' ]);
});
});