From 184fcab0b78bfa0dea84432245663c5ccbb692b6 Mon Sep 17 00:00:00 2001 From: Guillaume Grossetie Date: Sun, 23 Aug 2020 15:45:23 +0200 Subject: [PATCH 1/2] resolves #1490 consistent SVG height and width between diagrams --- .../rendering/classDiagram.spec.js | 150 +++++++++++------- src/defaultConfig.js | 75 ++++++++- src/diagrams/class/classRenderer-v2.js | 18 +-- src/diagrams/class/classRenderer.js | 9 +- src/diagrams/er/erRenderer.js | 9 +- src/diagrams/flowchart/flowRenderer-v2.js | 10 +- src/diagrams/flowchart/flowRenderer.js | 10 +- src/diagrams/gantt/ganttRenderer.js | 7 +- src/diagrams/pie/pieRenderer.js | 24 +-- src/diagrams/sequence/sequenceRenderer.js | 13 +- src/diagrams/state/stateRenderer-v2.js | 43 +---- src/diagrams/state/stateRenderer.js | 13 +- src/diagrams/user-journey/journeyRenderer.js | 11 +- src/utils.js | 25 +++ src/utils.spec.js | 17 +- 15 files changed, 242 insertions(+), 192 deletions(-) diff --git a/cypress/integration/rendering/classDiagram.spec.js b/cypress/integration/rendering/classDiagram.spec.js index 84de183c8..139ce1cb9 100644 --- a/cypress/integration/rendering/classDiagram.spec.js +++ b/cypress/integration/rendering/classDiagram.spec.js @@ -1,5 +1,5 @@ /* eslint-env jest */ -import { imgSnapshotTest } from '../../helpers/util'; +import { imgSnapshotTest, renderGraph } from '../../helpers/util'; describe('Class diagram', () => { it('1: should render a simple class diagram', () => { @@ -65,7 +65,7 @@ describe('Class diagram', () => { cy.get('svg'); }); - it('should render a simple class diagram with different visibilities', () => { + it('3: should render a simple class diagram with different visibilities', () => { imgSnapshotTest( ` classDiagram @@ -83,59 +83,6 @@ describe('Class diagram', () => { cy.get('svg'); }); - it('should render multiple class diagrams', () => { - imgSnapshotTest( - [ - ` - classDiagram - Class01 "1" <|--|> "*" AveryLongClass : Cool - <<interface>> Class01 - Class03 "1" *-- "*" Class04 - Class05 "1" o-- "many" Class06 - Class07 "1" .. "*" Class08 - Class09 "1" --> "*" C2 : Where am i? - Class09 "*" --* "*" C3 - Class09 "1" --|> "1" Class07 - Class07 : equals() - Class07 : Object[] elementData - Class01 : size() - Class01 : int chimp - Class01 : int gorilla - Class08 "1" <--> "*" C2: Cool label - class Class10 { - <<service>> - int id - test() - } - `, - ` - classDiagram - Class01 "1" <|--|> "*" AveryLongClass : Cool - <<interface>> Class01 - Class03 "1" *-- "*" Class04 - Class05 "1" o-- "many" Class06 - Class07 "1" .. "*" Class08 - Class09 "1" --> "*" C2 : Where am i? - Class09 "*" --* "*" C3 - Class09 "1" --|> "1" Class07 - Class07 : equals() - Class07 : Object[] elementData - Class01 : size() - Class01 : int chimp - Class01 : int gorilla - Class08 "1" <--> "*" C2: Cool label - class Class10 { - <<service>> - int id - test() - } - `, - ], - {} - ); - cy.get('svg'); - }); - it('4: should render a simple class diagram with comments', () => { imgSnapshotTest( ` @@ -353,4 +300,97 @@ describe('Class diagram', () => { ); cy.get('svg'); }); + + it('16: should render multiple class diagrams', () => { + imgSnapshotTest( + [ + ` + classDiagram + Class01 "1" <|--|> "*" AveryLongClass : Cool + <<interface>> Class01 + Class03 "1" *-- "*" Class04 + Class05 "1" o-- "many" Class06 + Class07 "1" .. "*" Class08 + Class09 "1" --> "*" C2 : Where am i? + Class09 "*" --* "*" C3 + Class09 "1" --|> "1" Class07 + Class07 : equals() + Class07 : Object[] elementData + Class01 : size() + Class01 : int chimp + Class01 : int gorilla + Class08 "1" <--> "*" C2: Cool label + class Class10 { + <<service>> + int id + test() + } + `, + ` + classDiagram + Class01 "1" <|--|> "*" AveryLongClass : Cool + <<interface>> Class01 + Class03 "1" *-- "*" Class04 + Class05 "1" o-- "many" Class06 + Class07 "1" .. "*" Class08 + Class09 "1" --> "*" C2 : Where am i? + Class09 "*" --* "*" C3 + Class09 "1" --|> "1" Class07 + Class07 : equals() + Class07 : Object[] elementData + Class01 : size() + Class01 : int chimp + Class01 : int gorilla + Class08 "1" <--> "*" C2: Cool label + class Class10 { + <<service>> + int id + test() + } + `, + ], + {} + ); + cy.get('svg'); + }); + + it('17: should render a class diagrams when useMaxWidth is true (default)', () => { + renderGraph( + ` + classDiagram + Class01 <|-- AveryLongClass : Cool + Class01 : size() + Class01 : int chimp + Class01 : int gorilla + Class01 : -int privateChimp + Class01 : +int publicGorilla + Class01 : #int protectedMarmoset + `, + { class: { useMaxWidth: true } } + ); + cy.get('svg') + .should('have.attr', 'width', '100%') + .should('have.attr', 'height', '218') + .should('have.attr', 'style', 'max-width: 162.28125px;') + }); + + it('18: should render a class diagrams when useMaxWidth is false', () => { + renderGraph( + ` + classDiagram + Class01 <|-- AveryLongClass : Cool + Class01 : size() + Class01 : int chimp + Class01 : int gorilla + Class01 : -int privateChimp + Class01 : +int publicGorilla + Class01 : #int protectedMarmoset + `, + { class: { useMaxWidth: false } } + ); + cy.get('svg') + .should('have.attr', 'width', '162.28125') + .should('have.attr', 'height', '218') + .should('not.have.attr', 'style') + }); }); diff --git a/src/defaultConfig.js b/src/defaultConfig.js index 8d1348b5b..7c44d5a95 100644 --- a/src/defaultConfig.js +++ b/src/defaultConfig.js @@ -173,8 +173,20 @@ const config = { */ curve: 'linear', // Only used in new experimental rendering - // repreesents the padding between the labels and the shape - padding: 15 + // represents the padding between the labels and the shape + padding: 15, + + /** + *| Parameter | Description |Type | Required | Values| + *| --- | --- | --- | --- | --- | + *| useMaxWidth | See notes | Boolean | 4 | True, False | + * + ***Notes:**when this flag is set the height and width is set to 100% and is then scaling with the + *available space if not the absolute space required is used. + * + ***Default value true**. + */ + useMaxWidth: true }, /** @@ -572,8 +584,23 @@ const config = { * This might need adjustment to match your locale and preferences ***Default value '%Y-%m-%d'**. */ - axisFormat: '%Y-%m-%d' + axisFormat: '%Y-%m-%d', + + /** + *| Parameter | Description |Type | Required | Values| + *| --- | --- | --- | --- | --- | + *| useMaxWidth | See notes | Boolean | 4 | True, False | + * + ***Notes:**when this flag is set the height and width is set to 100% and is then scaling with the + *available space if not the absolute space required is used. + * + ***Default value true**. + */ + useMaxWidth: true, + + useWidth: undefined }, + /** * The object containing configurations specific for journey diagrams */ @@ -711,10 +738,35 @@ const config = { rightAngles: false }, class: { - arrowMarkerAbsolute: false + arrowMarkerAbsolute: false, + /** + *| Parameter | Description |Type | Required | Values| + *| --- | --- | --- | --- | --- | + *| useMaxWidth | See notes | Boolean | 4 | True, False | + * + ***Notes:**when this flag is set the height and width is set to 100% and is then scaling with the + *available space if not the absolute space required is used. + * + ***Default value true**. + */ + useMaxWidth: true }, git: { - arrowMarkerAbsolute: false + arrowMarkerAbsolute: false, + + useWidth: undefined, + + /** + *| Parameter | Description |Type | Required | Values| + *| --- | --- | --- | --- | --- | + *| useMaxWidth | See notes | Boolean | 4 | True, False | + * + ***Notes:**when this flag is set the height and width is set to 100% and is then scaling with the + *available space if not the absolute space required is used. + * + ***Default value true**. + */ + useMaxWidth: true }, state: { dividerMargin: 10, @@ -734,7 +786,18 @@ const config = { labelHeight: 16, edgeLengthFactor: '20', compositTitleSize: 35, - radius: 5 + radius: 5, + /** + *| Parameter | Description |Type | Required | Values| + *| --- | --- | --- | --- | --- | + *| useMaxWidth | See notes | Boolean | 4 | True, False | + * + ***Notes:**when this flag is set the height and width is set to 100% and is then scaling with the + *available space if not the absolute space required is used. + * + ***Default value true**. + */ + useMaxWidth: true }, /** diff --git a/src/diagrams/class/classRenderer-v2.js b/src/diagrams/class/classRenderer-v2.js index 07ed68d2a..3419b6b7d 100644 --- a/src/diagrams/class/classRenderer-v2.js +++ b/src/diagrams/class/classRenderer-v2.js @@ -9,7 +9,7 @@ import { getConfig } from '../../config'; import { render } from '../../dagre-wrapper/index.js'; // import addHtmlLabel from 'dagre-d3/lib/label/add-html-label.js'; import { curveLinear } from 'd3'; -import { interpolateToCurve, getStylesFromArray } from '../../utils'; +import { interpolateToCurve, getStylesFromArray, configureSvgSize } from '../../utils'; import common from '../common/common'; parser.yy = classDb; @@ -325,13 +325,7 @@ export const drawOld = function(text, id) { const width = svgBounds.width + padding * 2; const height = svgBounds.height + padding * 2; - if (conf.useMaxWidth) { - diagram.attr('width', '100%'); - diagram.attr('style', `max-width: ${width}px;`); - } else { - diagram.attr('height', height); - diagram.attr('width', width); - } + configureSvgSize(diagram, height, width, conf.useMaxWidth); // Ensure the viewBox includes the whole svgBounds area with extra space for padding const vBox = `${svgBounds.x - padding} ${svgBounds.y - padding} ${width} ${height}`; @@ -427,13 +421,7 @@ export const draw = function(text, id) { `translate(${padding - g._label.marginx}, ${padding - g._label.marginy})` ); - if (conf.useMaxWidth) { - svg.attr('width', '100%'); - svg.attr('style', `max-width: ${width}px;`); - } else { - svg.attr('height', height); - svg.attr('width', width); - } + configureSvgSize(svg, height, width, conf.useMaxWidth); svg.attr('viewBox', `0 0 ${width} ${height}`); svg diff --git a/src/diagrams/class/classRenderer.js b/src/diagrams/class/classRenderer.js index 93e0a8dfc..727ecca20 100644 --- a/src/diagrams/class/classRenderer.js +++ b/src/diagrams/class/classRenderer.js @@ -5,6 +5,7 @@ import { logger } from '../../logger'; import classDb, { lookUpDomId } from './classDb'; import { parser } from './parser/classDiagram'; import svgDraw from './svgDraw'; +import { configureSvgSize } from '../../utils'; parser.yy = classDb; @@ -232,13 +233,7 @@ export const draw = function(text, id) { const width = svgBounds.width + padding * 2; const height = svgBounds.height + padding * 2; - if (conf.useMaxWidth) { - diagram.attr('width', '100%'); - diagram.attr('style', `max-width: ${width}px;`); - } else { - diagram.attr('height', height); - diagram.attr('width', width); - } + configureSvgSize(diagram, height, width, conf.useMaxWidth); // Ensure the viewBox includes the whole svgBounds area with extra space for padding const vBox = `${svgBounds.x - padding} ${svgBounds.y - padding} ${width} ${height}`; diff --git a/src/diagrams/er/erRenderer.js b/src/diagrams/er/erRenderer.js index e73dcd667..ead9612db 100644 --- a/src/diagrams/er/erRenderer.js +++ b/src/diagrams/er/erRenderer.js @@ -6,6 +6,7 @@ import dagre from 'dagre'; import { getConfig } from '../../config'; import { logger } from '../../logger'; import erMarkers from './erMarkers'; +import { configureSvgSize } from '../../utils'; const conf = {}; @@ -344,13 +345,7 @@ export const draw = function(text, id) { const width = svgBounds.width + padding * 2; const height = svgBounds.height + padding * 2; - if (conf.useMaxWidth) { - svg.attr('width', '100%'); - svg.attr('style', `max-width: ${width}px;`); - } else { - svg.attr('height', height); - svg.attr('width', width); - } + configureSvgSize(svg, height, width, conf.useMaxWidth); svg.attr('viewBox', `${svgBounds.x - padding} ${svgBounds.y - padding} ${width} ${height}`); }; // draw diff --git a/src/diagrams/flowchart/flowRenderer-v2.js b/src/diagrams/flowchart/flowRenderer-v2.js index 03c795e3f..d89ef118b 100644 --- a/src/diagrams/flowchart/flowRenderer-v2.js +++ b/src/diagrams/flowchart/flowRenderer-v2.js @@ -9,7 +9,7 @@ import { render } from '../../dagre-wrapper/index.js'; import addHtmlLabel from 'dagre-d3/lib/label/add-html-label.js'; import { logger } from '../../logger'; import common from '../common/common'; -import { interpolateToCurve, getStylesFromArray } from '../../utils'; +import { interpolateToCurve, getStylesFromArray, configureSvgSize } from '../../utils'; const conf = {}; export const setConf = function(cnf) { @@ -418,13 +418,7 @@ export const draw = function(text, id) { `translate(${padding - g._label.marginx}, ${padding - g._label.marginy})` ); - if (conf.useMaxWidth) { - svg.attr('width', '100%'); - svg.attr('style', `max-width: ${width}px;`); - } else { - svg.attr('height', height); - svg.attr('width', width); - } + configureSvgSize(svg, height, width, conf.useMaxWidth); svg.attr('viewBox', `0 0 ${width} ${height}`); svg diff --git a/src/diagrams/flowchart/flowRenderer.js b/src/diagrams/flowchart/flowRenderer.js index 5ab9ec293..be60bf4b3 100644 --- a/src/diagrams/flowchart/flowRenderer.js +++ b/src/diagrams/flowchart/flowRenderer.js @@ -9,7 +9,7 @@ import dagreD3 from 'dagre-d3'; import addHtmlLabel from 'dagre-d3/lib/label/add-html-label.js'; import { logger } from '../../logger'; import common from '../common/common'; -import { interpolateToCurve, getStylesFromArray } from '../../utils'; +import { interpolateToCurve, getStylesFromArray, configureSvgSize } from '../../utils'; import flowChartShapes from './flowChartShapes'; const conf = {}; @@ -401,13 +401,7 @@ export const draw = function(text, id) { const width = svgBounds.width + padding * 2; const height = svgBounds.height + padding * 2; - if (conf.useMaxWidth) { - svg.attr('width', '100%'); - svg.attr('style', `max-width: ${width}px;`); - } else { - svg.attr('height', height); - svg.attr('width', width); - } + configureSvgSize(svg, height, width, conf.useMaxWidth); // Ensure the viewBox includes the whole svgBounds area with extra space for padding const vBox = `${svgBounds.x - padding} ${svgBounds.y - padding} ${width} ${height}`; diff --git a/src/diagrams/gantt/ganttRenderer.js b/src/diagrams/gantt/ganttRenderer.js index 98e6391a9..95711f85c 100644 --- a/src/diagrams/gantt/ganttRenderer.js +++ b/src/diagrams/gantt/ganttRenderer.js @@ -11,6 +11,7 @@ import { import { parser } from './parser/gantt'; import common from '../common/common'; import ganttDb from './ganttDb'; +import { configureSvgSize } from '../../utils'; parser.yy = ganttDb; @@ -53,7 +54,6 @@ export const draw = function(text, id) { // Set height based on number of tasks const h = taskArray.length * (conf.barHeight + conf.barGap) + 2 * conf.topPadding; - elem.setAttribute('height', '100%'); // Set viewBox elem.setAttribute('viewBox', '0 0 ' + w + ' ' + h); const svg = select(`[id="${id}"]`); @@ -97,9 +97,8 @@ export const draw = function(text, id) { taskArray.sort(taskCompare); makeGant(taskArray, w, h); - if (typeof conf.useWidth !== 'undefined') { - elem.setAttribute('width', w); - } + + configureSvgSize(elem, h, w, conf.useMaxWidth); svg .append('text') diff --git a/src/diagrams/pie/pieRenderer.js b/src/diagrams/pie/pieRenderer.js index ee725b9f5..3dc4352fe 100644 --- a/src/diagrams/pie/pieRenderer.js +++ b/src/diagrams/pie/pieRenderer.js @@ -5,6 +5,7 @@ import { select, scaleOrdinal, schemeSet2, pie as d3pie, entries, arc } from 'd3 import pieData from './pieDb'; import pieParser from './parser/pie'; import { logger } from '../../logger'; +import { configureSvgSize } from '../../utils'; const conf = {}; export const setConf = function(cnf) { @@ -20,7 +21,8 @@ export const setConf = function(cnf) { * @param text * @param id */ -let w; +let width; +const height = 450; export const draw = (txt, id) => { try { const parser = pieParser.parser; @@ -31,24 +33,22 @@ export const draw = (txt, id) => { parser.parse(txt); logger.debug('Parsed info diagram'); const elem = document.getElementById(id); - w = elem.parentElement.offsetWidth; + width = elem.parentElement.offsetWidth; - if (typeof w === 'undefined') { - w = 1200; + if (typeof width === 'undefined') { + width = 1200; } if (typeof conf.useWidth !== 'undefined') { - w = conf.useWidth; + width = conf.useWidth; } - const h = 450; - elem.setAttribute('height', '100%'); + + configureSvgSize(elem, height, width, conf.useMaxWidth); + // Set viewBox - elem.setAttribute('viewBox', '0 0 ' + w + ' ' + h); + elem.setAttribute('viewBox', '0 0 ' + width + ' ' + height); // Fetch the default direction, use TD if none was found - - var width = w; // 450 - var height = 450; var margin = 40; var legendRectSize = 18; var legendSpacing = 4; @@ -119,7 +119,7 @@ export const draw = (txt, id) => { .append('text') .text(parser.yy.getTitle()) .attr('x', 0) - .attr('y', -(h - 50) / 2) + .attr('y', -(height - 50) / 2) .attr('class', 'pieTitleText'); //Add the slegend/annotations for each section diff --git a/src/diagrams/sequence/sequenceRenderer.js b/src/diagrams/sequence/sequenceRenderer.js index cc2e140f2..b4fda5400 100644 --- a/src/diagrams/sequence/sequenceRenderer.js +++ b/src/diagrams/sequence/sequenceRenderer.js @@ -5,7 +5,7 @@ import { parser } from './parser/sequenceDiagram'; import common from '../common/common'; import sequenceDb from './sequenceDb'; import * as configApi from '../../config'; -import utils, { assignWithDepth } from '../../utils'; +import utils, { assignWithDepth, configureSvgSize } from '../../utils'; parser.yy = sequenceDb; @@ -706,15 +706,8 @@ export const draw = function(text, id) { .attr('y', -25); } - if (conf.useMaxWidth) { - diagram.attr('height', '100%'); - diagram.attr('width', '100%'); - diagram.attr('style', 'max-width:' + width + 'px;'); - // diagram.attr('style', 'max-width:100%;'); - } else { - diagram.attr('height', height); - diagram.attr('width', width); - } + configureSvgSize(diagram, height, width, conf.useMaxWidth); + const extraVertForTitle = title ? 40 : 0; diagram.attr( 'viewBox', diff --git a/src/diagrams/state/stateRenderer-v2.js b/src/diagrams/state/stateRenderer-v2.js index 90683d2d4..057307fbb 100644 --- a/src/diagrams/state/stateRenderer-v2.js +++ b/src/diagrams/state/stateRenderer-v2.js @@ -6,6 +6,7 @@ import { getConfig } from '../../config'; import { render } from '../../dagre-wrapper/index.js'; import { logger } from '../../logger'; +import { configureSvgSize } from '../../utils'; const conf = {}; export const setConf = function(cnf) { @@ -211,11 +212,7 @@ export const draw = function(text, id) { parser.yy = stateDb; // Parse the graph definition - // try { parser.parse(text); - // } catch (err) { - // logger.error('Parsing failed', err); - // } // Fetch the default direction, use TD if none was found let dir = stateDb.getDirection(); @@ -256,54 +253,18 @@ export const draw = function(text, id) { render(element, g, ['barb'], 'statediagram', id); const padding = 8; - // const svgBounds = svg.node().getBBox(); - // const width = svgBounds.width + padding * 2; - // const height = svgBounds.height + padding * 2; - // logger.debug( - // `new ViewBox 0 0 ${width} ${height}`, - // `translate(${padding + g._label.marginx}, ${padding + g._label.marginy})` - // ); - - // if (conf.useMaxWidth) { - // svg.attr('width', '100%'); - // svg.attr('style', `max-width: ${width}px;`); - // } else { - // svg.attr('height', height); - // svg.attr('width', width); - // } - - // svg.attr('viewBox', `0 0 ${width} ${height}`); - // svg - // .select('g') - // .attr('transform', `translate(${padding - g._label.marginx}, ${padding - svgBounds.y})`); const bounds = svg.node().getBBox(); const width = bounds.width + padding * 2; const height = bounds.height + padding * 2; - // diagram.attr('height', '100%'); - // diagram.attr('style', `width: ${bounds.width * 3 + conf.padding * 2};`); - // diagram.attr('height', height); - // Zoom in a bit - svg.attr('width', width * 1.75); svg.attr('class', 'statediagram'); - // diagram.attr('height', bounds.height * 3 + conf.padding * 2); - // svg.attr( - // 'viewBox', - // `${bounds.x - conf.padding} ${bounds.y - conf.padding} ` + width + ' ' + height - // ); const svgBounds = svg.node().getBBox(); - if (conf.useMaxWidth) { - svg.attr('width', '100%'); - svg.attr('style', `max-width: ${width}px;`); - } else { - svg.attr('height', height); - svg.attr('width', width); - } + configureSvgSize(svg, height, width * 1.75, conf.useMaxWidth); // Ensure the viewBox includes the whole svgBounds area with extra space for padding const vBox = `${svgBounds.x - padding} ${svgBounds.y - padding} ${width} ${height}`; diff --git a/src/diagrams/state/stateRenderer.js b/src/diagrams/state/stateRenderer.js index f6489851a..e52969ee7 100644 --- a/src/diagrams/state/stateRenderer.js +++ b/src/diagrams/state/stateRenderer.js @@ -8,6 +8,7 @@ import { parser } from './parser/stateDiagram'; // import idCache from './id-cache'; import { drawState, addTitleAndBox, drawEdge } from './shapes'; import { getConfig } from '../../config'; +import { configureSvgSize } from '../../utils'; parser.yy = stateDb; @@ -75,14 +76,10 @@ export const draw = function(text, id) { const width = bounds.width + padding * 2; const height = bounds.height + padding * 2; - if (conf.useMaxWidth) { - diagram.attr('width', '100%'); - diagram.attr('style', `max-width: ${width * 1.75}px;`); - } else { - // Zoom in a bit - diagram.attr('width', width * 1.75); - } - // diagram.attr('height', bounds.height * 3 + conf.padding * 2); + // zoom in a bit + const svgWidth = width * 1.75; + configureSvgSize(diagram, height, svgWidth, conf.useMaxWidth); + diagram.attr( 'viewBox', `${bounds.x - conf.padding} ${bounds.y - conf.padding} ` + width + ' ' + height diff --git a/src/diagrams/user-journey/journeyRenderer.js b/src/diagrams/user-journey/journeyRenderer.js index e871a4693..854f2ce37 100644 --- a/src/diagrams/user-journey/journeyRenderer.js +++ b/src/diagrams/user-journey/journeyRenderer.js @@ -2,6 +2,7 @@ import { select } from 'd3'; import { parser } from './parser/journey'; import journeyDb from './journeyDb'; import svgDraw from './svgDraw'; +import { configureSvgSize } from '../../utils'; parser.yy = journeyDb; @@ -118,14 +119,8 @@ export const draw = function(text, id) { } const height = box.stopy - box.starty + 2 * conf.diagramMarginY; const width = LEFT_MARGIN + box.stopx + 2 * conf.diagramMarginX; - if (conf.useMaxWidth) { - diagram.attr('height', '100%'); - diagram.attr('width', '100%'); - diagram.attr('style', 'max-width:' + width + 'px;'); - } else { - diagram.attr('height', height); - diagram.attr('width', width); - } + + configureSvgSize(diagram, height, width, conf.useMaxWidth); // Draw activity line diagram diff --git a/src/utils.js b/src/utils.js index 8fe374b00..0f54c4212 100644 --- a/src/utils.js +++ b/src/utils.js @@ -695,12 +695,37 @@ export const calculateTextDimensions = memoize( (text, config) => `${text}-${config.fontSize}-${config.fontWeight}-${config.fontFamily}` ); +const d3Attrs = function(d3Elem, attrs) { + for (let attr of attrs) { + d3Elem.attr(attr[0], attr[1]); + } +}; + +export const calculateSvgSizeAttrs = function(height, width, useMaxWidth) { + let attrs = new Map(); + attrs.set('height', height); + if (useMaxWidth) { + attrs.set('width', '100%'); + attrs.set('style', `max-width: ${width}px;`); + } else { + attrs.set('width', width); + } + return attrs; +}; + +export const configureSvgSize = function(svgElem, height, width, useMaxWidth) { + const attrs = calculateSvgSizeAttrs(height, width, useMaxWidth); + d3Attrs(svgElem, attrs); +}; + export default { assignWithDepth, wrapLabel, calculateTextHeight, calculateTextWidth, calculateTextDimensions, + calculateSvgSizeAttrs, + configureSvgSize, detectInit, detectDirective, detectType, diff --git a/src/utils.spec.js b/src/utils.spec.js index 08c4654e0..c3fd94f93 100644 --- a/src/utils.spec.js +++ b/src/utils.spec.js @@ -1,5 +1,5 @@ /* eslint-env jasmine */ -import utils from './utils'; +import utils from './utils'; describe('when assignWithDepth: should merge objects within objects', function() { it('should handle simple, depth:1 types (identity)', function() { @@ -171,7 +171,6 @@ Alice->Bob: hi`; expect(type).toBe('git'); }); }); - describe('when finding substring in array ', function() { it('should return the array index that contains the substring', function() { const arr = ['stroke:val1', 'fill:val2']; @@ -184,7 +183,6 @@ describe('when finding substring in array ', function() { expect(result).toEqual(-1); }); }); - describe('when formatting urls', function() { it('should handle links', function() { const url = 'https://mermaid-js.github.io/mermaid/#/'; @@ -242,3 +240,16 @@ describe('when formatting urls', function() { expect(result).toEqual('about:blank'); }); }); +describe('when calculating SVG size', function() { + it('should return width 100% when useMaxWidth is true', function () { + const attrs = utils.calculateSvgSizeAttrs(100, 200, true); + expect(attrs.get('height')).toEqual(100); + expect(attrs.get('style')).toEqual('max-width: 200px;'); + expect(attrs.get('width')).toEqual('100%'); + }); + it('should return absolute width when useMaxWidth is false', function () { + const attrs = utils.calculateSvgSizeAttrs(100, 200, false); + expect(attrs.get('height')).toEqual(100); + expect(attrs.get('width')).toEqual(200); + }); +}); From 99844b4ca3227cc401989df77b2a5fe64844f6b5 Mon Sep 17 00:00:00 2001 From: Guillaume Grossetie Date: Tue, 25 Aug 2020 17:05:01 +0200 Subject: [PATCH 2/2] Add e2e tests on all diagrams --- cypress/helpers/util.js | 1 + .../rendering/classDiagram.spec.js | 26 +++-- .../integration/rendering/erDiagram.spec.js | 42 +++++++- .../rendering/flowchart-v2.spec.js | 54 ++++++++++- .../integration/rendering/flowchart.spec.js | 47 ++++++++- cypress/integration/rendering/gantt.spec.js | 97 ++++++++++++++++++- cypress/integration/rendering/journey.spec.js | 41 +++++++- cypress/integration/rendering/pie.spec.js | 43 +++++++- .../rendering/sequencediagram.spec.js | 87 ++++++++++++++++- .../rendering/stateDiagram-v2.spec.js | 56 +++++++++-- .../rendering/stateDiagram.spec.js | 54 +++++++++-- src/defaultConfig.js | 20 ++++ src/diagrams/gantt/ganttRenderer.js | 2 +- src/diagrams/pie/pieRenderer.js | 21 ++-- src/mermaidAPI.js | 2 +- src/utils.js | 1 + src/utils.spec.js | 2 +- 17 files changed, 547 insertions(+), 49 deletions(-) diff --git a/cypress/helpers/util.js b/cypress/helpers/util.js index aa04f7ae6..b075659f0 100644 --- a/cypress/helpers/util.js +++ b/cypress/helpers/util.js @@ -1,4 +1,5 @@ /* eslint-env jest */ +/* global cy */ import { Base64 } from 'js-base64'; export const mermaidUrl = (graphStr, options, api) => { diff --git a/cypress/integration/rendering/classDiagram.spec.js b/cypress/integration/rendering/classDiagram.spec.js index 139ce1cb9..3882b9e99 100644 --- a/cypress/integration/rendering/classDiagram.spec.js +++ b/cypress/integration/rendering/classDiagram.spec.js @@ -354,7 +354,7 @@ describe('Class diagram', () => { cy.get('svg'); }); - it('17: should render a class diagrams when useMaxWidth is true (default)', () => { + it('17: should render a class diagram when useMaxWidth is true (default)', () => { renderGraph( ` classDiagram @@ -369,12 +369,18 @@ describe('Class diagram', () => { { class: { useMaxWidth: true } } ); cy.get('svg') - .should('have.attr', 'width', '100%') - .should('have.attr', 'height', '218') - .should('have.attr', 'style', 'max-width: 162.28125px;') + .should((svg) => { + expect(svg).to.have.attr('width', '100%'); + expect(svg).to.have.attr('height', '218'); + const style = svg.attr('style'); + expect(style).to.match(/^max-width: [\d.]+px;$/); + const maxWidthValue = parseInt(style.match(/[\d.]+/g).join('')); + // use within because the absolute value can be slightly different depending on the environment ±5% + expect(maxWidthValue).to.be.within(160 * .95, 160 * 1.05); + }); }); - it('18: should render a class diagrams when useMaxWidth is false', () => { + it('18: should render a class diagram when useMaxWidth is false', () => { renderGraph( ` classDiagram @@ -389,8 +395,12 @@ describe('Class diagram', () => { { class: { useMaxWidth: false } } ); cy.get('svg') - .should('have.attr', 'width', '162.28125') - .should('have.attr', 'height', '218') - .should('not.have.attr', 'style') + .should((svg) => { + const width = parseFloat(svg.attr('width')); + // use within because the absolute value can be slightly different depending on the environment ±5% + expect(width).to.be.within(160 * .95, 160 * 1.05); + expect(svg).to.have.attr('height', '218'); + expect(svg).to.not.have.attr('style'); + }); }); }); diff --git a/cypress/integration/rendering/erDiagram.spec.js b/cypress/integration/rendering/erDiagram.spec.js index e965ca57d..a387bf254 100644 --- a/cypress/integration/rendering/erDiagram.spec.js +++ b/cypress/integration/rendering/erDiagram.spec.js @@ -1,5 +1,5 @@ /* eslint-env jest */ -import { imgSnapshotTest } from '../../helpers/util'; +import { imgSnapshotTest, renderGraph } from '../../helpers/util'; describe('Entity Relationship Diagram', () => { it('should render a simple ER diagram', () => { @@ -101,4 +101,44 @@ describe('Entity Relationship Diagram', () => { ); cy.get('svg'); }); + + it('should render an ER diagrams when useMaxWidth is true (default)', () => { + renderGraph( + ` + erDiagram + CUSTOMER ||--o{ ORDER : places + ORDER ||--|{ LINE-ITEM : contains + `, + { er: { useMaxWidth: true } } + ); + cy.get('svg') + .should((svg) => { + expect(svg).to.have.attr('width', '100%'); + expect(svg).to.have.attr('height', '465'); + const style = svg.attr('style'); + expect(style).to.match(/^max-width: [\d.]+px;$/); + const maxWidthValue = parseFloat(style.match(/[\d.]+/g).join('')); + // use within because the absolute value can be slightly different depending on the environment ±5% + expect(maxWidthValue).to.be.within(140 * .95, 140 * 1.05); + }); + }); + + it('should render an ER when useMaxWidth is false', () => { + renderGraph( + ` + erDiagram + CUSTOMER ||--o{ ORDER : places + ORDER ||--|{ LINE-ITEM : contains + `, + { er: { useMaxWidth: false } } + ); + cy.get('svg') + .should((svg) => { + const width = parseFloat(svg.attr('width')); + // use within because the absolute value can be slightly different depending on the environment ±5% + expect(width).to.be.within(140 * .95, 140 * 1.05); + expect(svg).to.have.attr('height', '465'); + expect(svg).to.not.have.attr('style'); + }); + }); }); diff --git a/cypress/integration/rendering/flowchart-v2.spec.js b/cypress/integration/rendering/flowchart-v2.spec.js index 29f2ef5b0..7374dff53 100644 --- a/cypress/integration/rendering/flowchart-v2.spec.js +++ b/cypress/integration/rendering/flowchart-v2.spec.js @@ -1,5 +1,5 @@ /* eslint-env jest */ -import { imgSnapshotTest } from '../../helpers/util'; +import { imgSnapshotTest, renderGraph } from '../../helpers/util'; describe('Flowchart v2', () => { it('1: should render a simple flowchart', () => { @@ -47,7 +47,7 @@ describe('Flowchart v2', () => { ); }); - it('Length of edges', () => { + it('4: Length of edges', () => { imgSnapshotTest( `flowchart TD L1 --- L2 @@ -64,7 +64,7 @@ describe('Flowchart v2', () => { { flowchart: { diagramPadding: 0 } } ); }); - it('36: should render escaped without html labels', () => { + it('5: should render escaped without html labels', () => { imgSnapshotTest( `flowchart TD a["Haiya"]---->b @@ -72,13 +72,57 @@ describe('Flowchart v2', () => { {htmlLabels: false, flowchart: {htmlLabels: false}} ); }); - it('37: should render non-escaped with html labels', () => { + it('6: should render non-escaped with html labels', () => { imgSnapshotTest( `flowchart TD a["Haiya"]===>b `, {htmlLabels: true, flowchart: {htmlLabels: true}, securityLevel: 'loose'} - ); }); + it('7: should render a flowchart when useMaxWidth is true (default)', () => { + renderGraph( + `flowchart TD + A[Christmas] -->|Get money| B(Go shopping) + B --> C{Let me think} + C -->|One| D[Laptop] + C -->|Two| E[iPhone] + C -->|Three| F[fa:fa-car Car] + `, + { flowchart: { useMaxWidth: true } } + ); + cy.get('svg') + .should((svg) => { + expect(svg).to.have.attr('width', '100%'); + expect(svg).to.have.attr('height'); + // use within because the absolute value can be slightly different depending on the environment ±5% + const height = parseFloat(svg.attr('height')); + expect(height).to.be.within(446 * .95, 446 * 1.05); + const style = svg.attr('style'); + expect(style).to.match(/^max-width: [\d.]+px;$/); + const maxWidthValue = parseFloat(style.match(/[\d.]+/g).join('')); + expect(maxWidthValue).to.be.within(300 * .95, 300 * 1.05); + }); + }); + it('8: should render a flowchart when useMaxWidth is false', () => { + renderGraph( + `flowchart TD + A[Christmas] -->|Get money| B(Go shopping) + B --> C{Let me think} + C -->|One| D[Laptop] + C -->|Two| E[iPhone] + C -->|Three| F[fa:fa-car Car] + `, + { flowchart: { useMaxWidth: false } } + ); + cy.get('svg') + .should((svg) => { + const height = parseFloat(svg.attr('height')); + const width = parseFloat(svg.attr('width')); + // use within because the absolute value can be slightly different depending on the environment ±5% + expect(height).to.be.within(446 * .95, 446 * 1.05); + expect(width).to.be.within(300 * .95, 300 * 1.05); + expect(svg).to.not.have.attr('style'); + }); + }); }); diff --git a/cypress/integration/rendering/flowchart.spec.js b/cypress/integration/rendering/flowchart.spec.js index 804b49b7c..bf03696a2 100644 --- a/cypress/integration/rendering/flowchart.spec.js +++ b/cypress/integration/rendering/flowchart.spec.js @@ -1,5 +1,5 @@ /* eslint-env jest */ -import { imgSnapshotTest } from '../../helpers/util'; +import { imgSnapshotTest, renderGraph } from '../../helpers/util'; describe('Flowchart', () => { it('1: should render a simple flowchart no htmlLabels', () => { @@ -731,4 +731,49 @@ describe('Flowchart', () => { {htmlLabels: true, flowchart: {htmlLabels: true}, securityLevel: 'loose'} ); }); + it('38: should render a flowchart when useMaxWidth is true (default)', () => { + renderGraph( + `graph TD + A[Christmas] -->|Get money| B(Go shopping) + B --> C{Let me think} + C -->|One| D[Laptop] + C -->|Two| E[iPhone] + C -->|Three| F[fa:fa-car Car] + `, + { flowchart: { useMaxWidth: true } } + ); + cy.get('svg') + .should((svg) => { + expect(svg).to.have.attr('width', '100%'); + expect(svg).to.have.attr('height'); + // use within because the absolute value can be slightly different depending on the environment ±5% + const height = parseFloat(svg.attr('height')); + expect(height).to.be.within(446 * .95, 446 * 1.05); + const style = svg.attr('style'); + expect(style).to.match(/^max-width: [\d.]+px;$/); + const maxWidthValue = parseFloat(style.match(/[\d.]+/g).join('')); + expect(maxWidthValue).to.be.within(300 * .95, 300 * 1.05); + }); + }); + it('39: should render a flowchart when useMaxWidth is false', () => { + renderGraph( + `graph TD + A[Christmas] -->|Get money| B(Go shopping) + B --> C{Let me think} + C -->|One| D[Laptop] + C -->|Two| E[iPhone] + C -->|Three| F[fa:fa-car Car] + `, + { flowchart: { useMaxWidth: false } } + ); + cy.get('svg') + .should((svg) => { + const height = parseFloat(svg.attr('height')); + const width = parseFloat(svg.attr('width')); + // use within because the absolute value can be slightly different depending on the environment ±5% + expect(height).to.be.within(446 * .95, 446 * 1.05); + expect(width).to.be.within(300 * .95, 300 * 1.05); + expect(svg).to.not.have.attr('style'); + }); + }); }); diff --git a/cypress/integration/rendering/gantt.spec.js b/cypress/integration/rendering/gantt.spec.js index e655681bb..422af1165 100644 --- a/cypress/integration/rendering/gantt.spec.js +++ b/cypress/integration/rendering/gantt.spec.js @@ -1,5 +1,5 @@ /* eslint-env jest */ -import { imgSnapshotTest } from '../../helpers/util.js'; +import { imgSnapshotTest, renderGraph } from '../../helpers/util.js'; describe('Gantt diagram', () => { beforeEach(()=>{ @@ -163,4 +163,99 @@ describe('Gantt diagram', () => { {} ); }); + + it('should render a gantt diagram when useMaxWidth is true (default)', () => { + renderGraph( + ` + gantt + dateFormat YYYY-MM-DD + axisFormat %d/%m + title Adding GANTT diagram to mermaid + excludes weekdays 2014-01-10 + + section A section + Completed task :done, des1, 2014-01-06,2014-01-08 + Active task :active, des2, 2014-01-09, 3d + Future task : des3, after des2, 5d + Future task2 : des4, after des3, 5d + + section Critical tasks + Completed task in the critical line :crit, done, 2014-01-06,24h + Implement parser and jison :crit, done, after des1, 2d + Create tests for parser :crit, active, 3d + Future task in critical line :crit, 5d + Create tests for renderer :2d + Add to mermaid :1d + + section Documentation + Describe gantt syntax :active, a1, after des1, 3d + Add gantt diagram to demo page :after a1 , 20h + Add another diagram to demo page :doc1, after a1 , 48h + + section Last section + Describe gantt syntax :after doc1, 3d + Add gantt diagram to demo page : 20h + Add another diagram to demo page : 48h + `, + { gantt: { useMaxWidth: true } } + ); + cy.get('svg') + .should((svg) => { + expect(svg).to.have.attr('width', '100%'); + expect(svg).to.have.attr('height'); + // use within because the absolute value can be slightly different depending on the environment ±5% + const height = parseFloat(svg.attr('height')); + expect(height).to.be.within(484 * .95, 484 * 1.05); + const style = svg.attr('style'); + expect(style).to.match(/^max-width: [\d.]+px;$/); + const maxWidthValue = parseFloat(style.match(/[\d.]+/g).join('')); + expect(maxWidthValue).to.be.within(984 * .95, 984 * 1.05); + }); + }); + + it('should render a gantt diagram when useMaxWidth is false', () => { + renderGraph( + ` + gantt + dateFormat YYYY-MM-DD + axisFormat %d/%m + title Adding GANTT diagram to mermaid + excludes weekdays 2014-01-10 + + section A section + Completed task :done, des1, 2014-01-06,2014-01-08 + Active task :active, des2, 2014-01-09, 3d + Future task : des3, after des2, 5d + Future task2 : des4, after des3, 5d + + section Critical tasks + Completed task in the critical line :crit, done, 2014-01-06,24h + Implement parser and jison :crit, done, after des1, 2d + Create tests for parser :crit, active, 3d + Future task in critical line :crit, 5d + Create tests for renderer :2d + Add to mermaid :1d + + section Documentation + Describe gantt syntax :active, a1, after des1, 3d + Add gantt diagram to demo page :after a1 , 20h + Add another diagram to demo page :doc1, after a1 , 48h + + section Last section + Describe gantt syntax :after doc1, 3d + Add gantt diagram to demo page : 20h + Add another diagram to demo page : 48h + `, + { gantt: { useMaxWidth: false } } + ); + cy.get('svg') + .should((svg) => { + const height = parseFloat(svg.attr('height')); + const width = parseFloat(svg.attr('width')); + // use within because the absolute value can be slightly different depending on the environment ±5% + expect(height).to.be.within(484 * .95, 484 * 1.05); + expect(width).to.be.within(984 * .95, 984 * 1.05); + expect(svg).to.not.have.attr('style'); + }); + }); }); diff --git a/cypress/integration/rendering/journey.spec.js b/cypress/integration/rendering/journey.spec.js index 8bcf58412..6dd5f6c25 100644 --- a/cypress/integration/rendering/journey.spec.js +++ b/cypress/integration/rendering/journey.spec.js @@ -1,5 +1,5 @@ /* eslint-env jest */ -import { imgSnapshotTest } from '../../helpers/util.js'; +import { imgSnapshotTest, renderGraph } from '../../helpers/util.js'; describe('User journey diagram', () => { it('Simple test', () => { @@ -28,4 +28,43 @@ section Order from website {} ); }); + + it('should render a user journey diagram when useMaxWidth is true (default)', () => { + renderGraph( + `journey +title Adding journey diagram functionality to mermaid +section Order from website + `, + { journey: { useMaxWidth: true } } + ); + cy.get('svg') + .should((svg) => { + expect(svg).to.have.attr('width', '100%'); + expect(svg).to.have.attr('height'); + const height = parseFloat(svg.attr('height')); + expect(height).to.eq(20); + const style = svg.attr('style'); + expect(style).to.match(/^max-width: [\d.]+px;$/); + const maxWidthValue = parseFloat(style.match(/[\d.]+/g).join('')); + expect(maxWidthValue).to.eq(400); + }); + }); + + it('should render a user journey diagram when useMaxWidth is false', () => { + renderGraph( + `journey +title Adding journey diagram functionality to mermaid +section Order from website + `, + { journey: { useMaxWidth: false } } + ); + cy.get('svg') + .should((svg) => { + const height = parseFloat(svg.attr('height')); + const width = parseFloat(svg.attr('width')); + expect(height).to.eq(20); + expect(width).to.eq(400); + expect(svg).to.not.have.attr('style'); + }); + }); }); diff --git a/cypress/integration/rendering/pie.spec.js b/cypress/integration/rendering/pie.spec.js index b69aad631..bbedb9ebd 100644 --- a/cypress/integration/rendering/pie.spec.js +++ b/cypress/integration/rendering/pie.spec.js @@ -1,5 +1,5 @@ /* eslint-env jest */ -import { imgSnapshotTest } from '../../helpers/util.js'; +import { imgSnapshotTest, renderGraph } from '../../helpers/util.js'; describe('Pie Chart', () => { it('should render a simple pie diagram', () => { @@ -37,4 +37,45 @@ describe('Pie Chart', () => { ); cy.get('svg'); }); + it('should render a pie diagram when useMaxWidth is true (default)', () => { + renderGraph( + ` + pie title Sports in Sweden + "Bandy" : 40 + "Ice-Hockey" : 80 + "Football" : 90 + `, + { pie: { useMaxWidth: true } } + ); + cy.get('svg') + .should((svg) => { + expect(svg).to.have.attr('width', '100%'); + expect(svg).to.have.attr('height'); + const height = parseFloat(svg.attr('height')); + expect(height).to.eq(450); + const style = svg.attr('style'); + expect(style).to.match(/^max-width: [\d.]+px;$/); + const maxWidthValue = parseFloat(style.match(/[\d.]+/g).join('')); + expect(maxWidthValue).to.eq(984); + }); + }); + it('should render a pie diagram when useMaxWidth is false', () => { + renderGraph( + ` + pie title Sports in Sweden + "Bandy" : 40 + "Ice-Hockey" : 80 + "Football" : 90 + `, + { pie: { useMaxWidth: false } } + ); + cy.get('svg') + .should((svg) => { + const height = parseFloat(svg.attr('height')); + const width = parseFloat(svg.attr('width')); + expect(height).to.eq(450); + expect(width).to.eq(984); + expect(svg).to.not.have.attr('style'); + }); + }); }); diff --git a/cypress/integration/rendering/sequencediagram.spec.js b/cypress/integration/rendering/sequencediagram.spec.js index e20c6104b..bb460418f 100644 --- a/cypress/integration/rendering/sequencediagram.spec.js +++ b/cypress/integration/rendering/sequencediagram.spec.js @@ -1,6 +1,6 @@ /// -import { imgSnapshotTest } from '../../helpers/util'; +import { imgSnapshotTest, renderGraph } from '../../helpers/util'; context('Sequence diagram', () => { it('should render a simple sequence diagram', () => { @@ -505,7 +505,7 @@ context('Sequence diagram', () => { }); }); context('directives', () => { - it('should overide config with directive settings', () => { + it('should override config with directive settings', () => { imgSnapshotTest( ` %%{init: { "config": { "mirrorActors": true }}}%% @@ -517,7 +517,7 @@ context('Sequence diagram', () => { { logLevel:0, sequence: { mirrorActors: false, noteFontSize: 18, noteFontFamily: 'Arial' } } ); }); - it('should overide config with directive settings', () => { + it('should override config with directive settings', () => { imgSnapshotTest( ` %%{init: { "config": { "mirrorActors": false, "wrap": true }}}%% @@ -530,4 +530,85 @@ context('Sequence diagram', () => { ); }); }); + context('svg size', () => { + it('should render a sequence diagram when useMaxWidth is true (default)', () => { + renderGraph( + ` + sequenceDiagram + participant Alice + participant Bob + participant John as John
Second Line + Alice ->> Bob: Hello Bob, how are you? + Bob-->>John: How about you John? + Bob--x Alice: I am good thanks! + Bob-x John: I am good thanks! + Note right of John: Bob thinks a long
long time, so long
that the text does
not fit on a row. + Bob-->Alice: Checking with John... + alt either this + Alice->>John: Yes + else or this + Alice->>John: No + else or this will happen + Alice->John: Maybe + end + par this happens in parallel + Alice -->> Bob: Parallel message 1 + and + Alice -->> John: Parallel message 2 + end + `, + { sequence: { useMaxWidth: true } } + ); + cy.get('svg') + .should((svg) => { + expect(svg).to.have.attr('width', '100%'); + expect(svg).to.have.attr('height'); + const height = parseFloat(svg.attr('height')); + expect(height).to.eq(920); + const style = svg.attr('style'); + expect(style).to.match(/^max-width: [\d.]+px;$/); + const maxWidthValue = parseFloat(style.match(/[\d.]+/g).join('')); + // use within because the absolute value can be slightly different depending on the environment ±5% + expect(maxWidthValue).to.be.within(820 * .95, 820 * 1.05); + }); + }); + it('should render a sequence diagram when useMaxWidth is false', () => { + renderGraph( + ` + sequenceDiagram + participant Alice + participant Bob + participant John as John
Second Line + Alice ->> Bob: Hello Bob, how are you? + Bob-->>John: How about you John? + Bob--x Alice: I am good thanks! + Bob-x John: I am good thanks! + Note right of John: Bob thinks a long
long time, so long
that the text does
not fit on a row. + Bob-->Alice: Checking with John... + alt either this + Alice->>John: Yes + else or this + Alice->>John: No + else or this will happen + Alice->John: Maybe + end + par this happens in parallel + Alice -->> Bob: Parallel message 1 + and + Alice -->> John: Parallel message 2 + end + `, + { sequence: { useMaxWidth: false } } + ); + cy.get('svg') + .should((svg) => { + const height = parseFloat(svg.attr('height')); + const width = parseFloat(svg.attr('width')); + expect(height).to.eq(920); + // use within because the absolute value can be slightly different depending on the environment ±5% + expect(width).to.be.within(820 * .95, 820 * 1.05); + expect(svg).to.not.have.attr('style'); + }); + }); + }); }); diff --git a/cypress/integration/rendering/stateDiagram-v2.spec.js b/cypress/integration/rendering/stateDiagram-v2.spec.js index bc3cb8816..9013ec64f 100644 --- a/cypress/integration/rendering/stateDiagram-v2.spec.js +++ b/cypress/integration/rendering/stateDiagram-v2.spec.js @@ -1,5 +1,5 @@ /* eslint-env jest */ -import { imgSnapshotTest } from '../../helpers/util'; +import { imgSnapshotTest, renderGraph } from '../../helpers/util'; describe('State diagram', () => { it('v2 should render a simple info', () => { @@ -47,7 +47,7 @@ describe('State diagram', () => { ); cy.get('svg'); }); - it('v2 should render a single state with short descr', () => { + it('v2 should render a single state with short descriptions', () => { imgSnapshotTest( ` stateDiagram-v2 @@ -58,7 +58,7 @@ describe('State diagram', () => { ); cy.get('svg'); }); - it('v2 should render a transition descrions with new lines', () => { + it('v2 should render a transition descriptions with new lines', () => { imgSnapshotTest( ` stateDiagram-v2 @@ -201,7 +201,7 @@ describe('State diagram', () => { ); cy.get('svg'); }); - it('v2 should render composit states', () => { + it('v2 should render composite states', () => { imgSnapshotTest( ` stateDiagram-v2 @@ -220,7 +220,7 @@ describe('State diagram', () => { ); cy.get('svg'); }); - it('v2 should render multiple composit states', () => { + it('v2 should render multiple composite states', () => { imgSnapshotTest( ` stateDiagram-v2 @@ -249,7 +249,7 @@ describe('State diagram', () => { { logLevel: 0, fontFamily: 'courier' } ); }); - it('v2 should render forks in composit states', () => { + it('v2 should render forks in composite states', () => { imgSnapshotTest( ` stateDiagram-v2 @@ -329,7 +329,7 @@ describe('State diagram', () => { } ); }); - it('v2 Simplest composit state', () => { + it('v2 Simplest composite state', () => { imgSnapshotTest( ` stateDiagram-v2 @@ -354,5 +354,47 @@ describe('State diagram', () => { } ); }); + it('v2 should render a state diagram when useMaxWidth is true (default)', () => { + renderGraph( + ` + stateDiagram-v2 + [*] --> State1 + State1 --> [*] + `, + { state: { useMaxWidth: true } } + ); + cy.get('svg') + .should((svg) => { + expect(svg).to.have.attr('width', '100%'); + expect(svg).to.have.attr('height'); + const height = parseFloat(svg.attr('height')); + expect(height).to.eq(177); + const style = svg.attr('style'); + expect(style).to.match(/^max-width: [\d.]+px;$/); + const maxWidthValue = parseFloat(style.match(/[\d.]+/g).join('')); + // use within because the absolute value can be slightly different depending on the environment ±5% + expect(maxWidthValue).to.be.within(135 * .95, 135 * 1.05); + }); + }); + it('v2 should render a state diagram when useMaxWidth is false', () => { + renderGraph( + ` + stateDiagram-v2 + + [*] --> State1 + State1 --> [*] + `, + { state: { useMaxWidth: false } } + ); + cy.get('svg') + .should((svg) => { + const height = parseFloat(svg.attr('height')); + const width = parseFloat(svg.attr('width')); + expect(height).to.eq(177); + // use within because the absolute value can be slightly different depending on the environment ±5% + expect(width).to.be.within(135 * .95, 135 * 1.05); + expect(svg).to.not.have.attr('style'); + }); + }); }); diff --git a/cypress/integration/rendering/stateDiagram.spec.js b/cypress/integration/rendering/stateDiagram.spec.js index ac1000858..2d10a6a9e 100644 --- a/cypress/integration/rendering/stateDiagram.spec.js +++ b/cypress/integration/rendering/stateDiagram.spec.js @@ -1,5 +1,5 @@ /* eslint-env jest */ -import { imgSnapshotTest } from '../../helpers/util'; +import { imgSnapshotTest, renderGraph } from '../../helpers/util'; describe('State diagram', () => { it('should render a simple state diagrams', () => { @@ -37,7 +37,7 @@ describe('State diagram', () => { ); cy.get('svg'); }); - it('should render a single state with short descr', () => { + it('should render a single state with short descriptions', () => { imgSnapshotTest( ` stateDiagram @@ -48,7 +48,7 @@ describe('State diagram', () => { ); cy.get('svg'); }); - it('should render a transition descrions with new lines', () => { + it('should render a transition descriptions with new lines', () => { imgSnapshotTest( ` stateDiagram @@ -191,7 +191,7 @@ describe('State diagram', () => { ); cy.get('svg'); }); - it('should render composit states', () => { + it('should render composite states', () => { imgSnapshotTest( ` stateDiagram @@ -280,7 +280,7 @@ describe('State diagram', () => { ); cy.get('svg'); }); - it('should render conurrency states', () => { + it('should render concurrency states', () => { imgSnapshotTest( ` stateDiagram @@ -319,7 +319,7 @@ describe('State diagram', () => { } ); }); - it('Simplest composit state', () => { + it('Simplest composite state', () => { imgSnapshotTest( ` stateDiagram @@ -344,5 +344,45 @@ describe('State diagram', () => { } ); }); - + it('should render a state diagram when useMaxWidth is true (default)', () => { + renderGraph( + ` + stateDiagram + [*] --> State1 + State1 --> [*] + `, + { state: { useMaxWidth: true } } + ); + cy.get('svg') + .should((svg) => { + expect(svg).to.have.attr('width', '100%'); + expect(svg).to.have.attr('height'); + const height = parseFloat(svg.attr('height')); + expect(height).to.eq(139); + const style = svg.attr('style'); + expect(style).to.match(/^max-width: [\d.]+px;$/); + const maxWidthValue = parseFloat(style.match(/[\d.]+/g).join('')); + // use within because the absolute value can be slightly different depending on the environment ±5% + expect(maxWidthValue).to.be.within(112 * .95, 112 * 1.05); + }); + }); + it('should render a state diagram when useMaxWidth is false', () => { + renderGraph( + ` + stateDiagram + [*] --> State1 + State1 --> [*] + `, + { state: { useMaxWidth: false } } + ); + cy.get('svg') + .should((svg) => { + const height = parseFloat(svg.attr('height')); + const width = parseFloat(svg.attr('width')); + expect(height).to.eq(139); + // use within because the absolute value can be slightly different depending on the environment ±5% + expect(width).to.be.within(112 * .95, 112 * 1.05); + expect(svg).to.not.have.attr('style'); + }); + }); }); diff --git a/src/defaultConfig.js b/src/defaultConfig.js index 7c44d5a95..88b441272 100644 --- a/src/defaultConfig.js +++ b/src/defaultConfig.js @@ -884,6 +884,26 @@ const config = { */ fontSize: 12, + /** + *| Parameter | Description |Type | Required | Values| + *| --- | --- | --- | --- | --- | + *| useMaxWidth | See Notes | Boolean | Required | true, false | + * + ***Notes:** + *When this flag is set to true, the diagram width is locked to 100% and + *scaled based on available space. If set to false, the diagram reserves its + *absolute width. + ***Default value: true**. + */ + useMaxWidth: true + }, + + /** + * The object containing configurations specific for pie diagrams + */ + pie: { + useWidth: undefined, + /** *| Parameter | Description |Type | Required | Values| *| --- | --- | --- | --- | --- | diff --git a/src/diagrams/gantt/ganttRenderer.js b/src/diagrams/gantt/ganttRenderer.js index 95711f85c..0d3220bc9 100644 --- a/src/diagrams/gantt/ganttRenderer.js +++ b/src/diagrams/gantt/ganttRenderer.js @@ -98,7 +98,7 @@ export const draw = function(text, id) { makeGant(taskArray, w, h); - configureSvgSize(elem, h, w, conf.useMaxWidth); + configureSvgSize(svg, h, w, conf.useMaxWidth); svg .append('text') diff --git a/src/diagrams/pie/pieRenderer.js b/src/diagrams/pie/pieRenderer.js index 3dc4352fe..9cc8ac638 100644 --- a/src/diagrams/pie/pieRenderer.js +++ b/src/diagrams/pie/pieRenderer.js @@ -43,7 +43,9 @@ export const draw = (txt, id) => { width = conf.useWidth; } - configureSvgSize(elem, height, width, conf.useMaxWidth); + const diagram = select('#' + id); + console.log('conf', conf); + configureSvgSize(diagram, height, width, conf.useMaxWidth); // Set viewBox elem.setAttribute('viewBox', '0 0 ' + width + ' ' + height); @@ -55,10 +57,7 @@ export const draw = (txt, id) => { var radius = Math.min(width, height) / 2 - margin; - var svg = select('#' + id) - .append('svg') - .attr('width', width) - .attr('height', height) + var svg = diagram .append('g') .attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')'); @@ -67,9 +66,8 @@ export const draw = (txt, id) => { Object.keys(data).forEach(function(key) { sum += data[key]; }); - logger.info(data); - // set the color scale + // Set the color scale var color = scaleOrdinal() .domain(data) .range(schemeSet2); @@ -80,12 +78,12 @@ export const draw = (txt, id) => { }); var dataReady = pie(entries(data)); - // shape helper to build arcs: + // Shape helper to build arcs: var arcGenerator = arc() .innerRadius(0) .outerRadius(radius); - // Build the pie chart: Basically, each part of the pie is a path that we build using the arc function. + // Build the pie chart: each part of the pie is a path that we build using the arc function. svg .selectAll('mySlices') .data(dataReady) @@ -99,7 +97,8 @@ export const draw = (txt, id) => { .style('stroke-width', '2px') .style('opacity', 0.7); - // Now add the Percentage. Use the centroid method to get the best coordinates + // Now add the percentage. + // Use the centroid method to get the best coordinates. svg .selectAll('mySlices') .data(dataReady) @@ -122,7 +121,7 @@ export const draw = (txt, id) => { .attr('y', -(height - 50) / 2) .attr('class', 'pieTitleText'); - //Add the slegend/annotations for each section + // Add the legends/annotations for each section var legend = svg .selectAll('.legend') .data(color.domain()) diff --git a/src/mermaidAPI.js b/src/mermaidAPI.js index 096730791..3e9c76ec0 100644 --- a/src/mermaidAPI.js +++ b/src/mermaidAPI.js @@ -385,7 +385,7 @@ const render = function(id, _txt, cb, container) { break; case 'pie': cnf.class.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute; - pieRenderer.setConf(cnf.class); + pieRenderer.setConf(cnf.pie); pieRenderer.draw(txt, id, pkg.version); break; case 'er': diff --git a/src/utils.js b/src/utils.js index 0f54c4212..5344afc04 100644 --- a/src/utils.js +++ b/src/utils.js @@ -715,6 +715,7 @@ export const calculateSvgSizeAttrs = function(height, width, useMaxWidth) { export const configureSvgSize = function(svgElem, height, width, useMaxWidth) { const attrs = calculateSvgSizeAttrs(height, width, useMaxWidth); + console.log('svgElem', svgElem); d3Attrs(svgElem, attrs); }; diff --git a/src/utils.spec.js b/src/utils.spec.js index c3fd94f93..756ad3794 100644 --- a/src/utils.spec.js +++ b/src/utils.spec.js @@ -1,5 +1,5 @@ /* eslint-env jasmine */ -import utils from './utils'; +import utils from './utils'; describe('when assignWithDepth: should merge objects within objects', function() { it('should handle simple, depth:1 types (identity)', function() {