Add title support using YAML frontmatter

This commit is contained in:
Mason Malone 2022-11-16 19:36:51 -08:00
parent 09ed41b7d2
commit 70f024735b
48 changed files with 477 additions and 5 deletions

View File

@ -53,6 +53,18 @@ export const MockD3 = (name, parent) => {
get __parent() {
return parent;
},
node() {
return {
getBBox() {
return {
x: 5,
y: 10,
height: 15,
width: 20,
};
},
};
},
};
elem.append = (name) => {
const mockElem = MockD3(name, elem);

View File

@ -496,4 +496,16 @@ describe('Class diagram V2', () => {
);
cy.get('svg');
});
it('1433: should render a simple class with a title', () => {
imgSnapshotTest(
`---
title: simple class diagram
---
classDiagram-v2
class Class10
`,
{}
);
});
});

View File

@ -273,4 +273,17 @@ describe('Entity Relationship Diagram', () => {
);
cy.get('svg');
});
it('1433: should render a simple ER diagram with a title', () => {
imgSnapshotTest(
`---
title: simple ER diagram
---
erDiagram
CUSTOMER ||--o{ ORDER : places
ORDER ||--|{ LINE-ITEM : contains
`,
{}
);
});
});

View File

@ -663,4 +663,15 @@ flowchart RL
{ htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' }
);
});
it('1433: should render a titled flowchart with titleTopMargin set to 0', () => {
imgSnapshotTest(
`---
title: Simple flowchart
---
flowchart TD
A --> B
`,
{ titleTopMargin: 0 }
);
});
});

View File

@ -322,4 +322,15 @@ describe('Git Graph diagram', () => {
{}
);
});
it('1433: should render a simple gitgraph with a title', () => {
imgSnapshotTest(
`---
title: simple gitGraph
---
gitGraph
commit
`,
{}
);
});
});

View File

@ -559,4 +559,16 @@ stateDiagram-v2
);
});
});
it('1433: should render a simple state diagram with a title', () => {
imgSnapshotTest(
`---
title: simple state diagram
---
stateDiagram-v2
[*] --> State1
State1 --> [*]
`,
{}
);
});
});

View File

@ -17,6 +17,9 @@
<h1>Class diagram demos</h1>
<pre class="mermaid">
---
title: Demo Class Diagram
---
classDiagram
accTitle: Demo Class Diagram
accDescr: This class diagram show the abstract Animal class, and 3 classes that inherit from it: Duck, Fish, and Zebra.

View File

@ -20,6 +20,9 @@
<body>
<pre class="mermaid">
---
title: This is a title
---
erDiagram
%% title This is a title
%% accDescription Test a description

View File

@ -17,6 +17,9 @@
<h2>Sample 1</h2>
<h3>graph</h3>
<pre class="mermaid">
---
title: This is a complicated flow
---
graph LR
accTitle: This is a complicated flow
accDescr: This is the descriptoin for the complicated flow.
@ -221,6 +224,9 @@
<h2>Sample 2</h2>
<h3>graph</h3>
<pre class="mermaid">
---
title: What to buy
---
graph TD
accTitle: What to buy
accDescr: Options of what to buy with Christmas money

View File

@ -16,6 +16,9 @@
<body>
<h1>Git diagram demo</h1>
<pre class="mermaid">
---
title: Simple Git diagram
---
gitGraph:
options
{

View File

@ -16,8 +16,10 @@
<body>
<h1>Journey diagram demo</h1>
<pre class="mermaid">
journey
title My working day
---
title: My working day
---
journey
accTitle: Very simple journey demo
accDescr: 2 main sections: work and home, each with just a few tasks

View File

@ -17,6 +17,9 @@
<h1>State diagram demos</h1>
<h2>Very simple showing change from State1 to State2</h2>
<pre class="mermaid">
---
title: Very simple diagram
---
stateDiagram
accTitle: This is the accessible title
accDescr:This is an accessible description
@ -43,6 +46,9 @@
</code>
</p>
<pre class="mermaid">
---
title: Very simple diagram
---
stateDiagram-v2
accTitle: This is the accessible title
accDescr: This is an accessible description

View File

@ -14,7 +14,7 @@
#### Defined in
[defaultConfig.ts:1881](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L1881)
[defaultConfig.ts:1933](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L1933)
---

View File

@ -14,6 +14,9 @@ The class diagram is the main building block of object-oriented modeling. It is
Mermaid can render class diagrams.
```mermaid-example
---
title: Animal example
---
classDiagram
note "From Duck till Zebra"
Animal <|-- Duck
@ -40,6 +43,9 @@ classDiagram
```
```mermaid
---
title: Animal example
---
classDiagram
note "From Duck till Zebra"
Animal <|-- Duck
@ -77,6 +83,9 @@ A single instance of a class in the diagram contains three compartments:
- The bottom compartment contains the operations the class can execute. They are also left-aligned and the first letter is lowercase.
```mermaid-example
---
title: Bank example
---
classDiagram
class BankAccount
BankAccount : +String owner
@ -87,6 +96,9 @@ classDiagram
```
```mermaid
---
title: Bank example
---
classDiagram
class BankAccount
BankAccount : +String owner

View File

@ -13,6 +13,9 @@ Note that practitioners of ER modelling almost always refer to _entity types_ si
Mermaid can render ER diagrams
```mermaid-example
---
title: Order example
---
erDiagram
CUSTOMER ||--o{ ORDER : places
ORDER ||--|{ LINE-ITEM : contains
@ -20,6 +23,9 @@ erDiagram
```
```mermaid
---
title: Order example
---
erDiagram
CUSTOMER ||--o{ ORDER : places
ORDER ||--|{ LINE-ITEM : contains

View File

@ -15,11 +15,17 @@ It can also accommodate different arrow types, multi directional arrows, and lin
### A node (default)
```mermaid-example
---
title: Node
---
flowchart LR
id
```
```mermaid
---
title: Node
---
flowchart LR
id
```
@ -33,11 +39,17 @@ found for the node that will be used. Also if you define edges for the node late
one previously defined will be used when rendering the box.
```mermaid-example
---
title: Node with text
---
flowchart LR
id1[This is the text in the box]
```
```mermaid
---
title: Node with text
---
flowchart LR
id1[This is the text in the box]
```

View File

@ -13,6 +13,9 @@ These kind of diagram are particularly helpful to developers and devops teams to
Mermaid can render Git diagrams
```mermaid-example
---
title: Example Git diagram
---
gitGraph
commit
commit
@ -27,6 +30,9 @@ Mermaid can render Git diagrams
```
```mermaid
---
title: Example Git diagram
---
gitGraph
commit
commit

View File

@ -11,6 +11,9 @@
Mermaid can render state diagrams. The syntax tries to be compliant with the syntax used in plantUml as this will make it easier for users to share diagrams between mermaid and plantUml.
```mermaid-example
---
title: Simple sample
---
stateDiagram-v2
[*] --> Still
Still --> [*]
@ -22,6 +25,9 @@ stateDiagram-v2
```
```mermaid
---
title: Simple sample
---
stateDiagram-v2
[*] --> Still
Still --> [*]

View File

@ -60,6 +60,7 @@
"@types/eslint": "^8.4.10",
"@types/express": "^4.17.14",
"@types/jsdom": "^20.0.1",
"@types/js-yaml": "^4.0.5",
"@types/lodash": "^4.14.188",
"@types/mdast": "^3.0.10",
"@types/node": "^18.11.9",
@ -90,6 +91,7 @@
"jest": "^29.3.1",
"jison": "^0.4.18",
"jsdom": "^20.0.2",
"js-yaml": "^4.1.0",
"lint-staged": "^13.0.3",
"path-browserify": "^1.0.1",
"pnpm": "^7.15.0",

View File

@ -2,6 +2,7 @@ import * as configApi from './config';
import { log } from './logger';
import { getDiagram, registerDiagram } from './diagram-api/diagramAPI';
import { detectType, getDiagramLoader } from './diagram-api/detectType';
import { extractFrontMatter } from './diagram-api/frontmatter';
import { isDetailedError, type DetailedError } from './utils';
export type ParseErrorFunction = (err: string | DetailedError, hash?: any) => void;
@ -29,6 +30,16 @@ export class Diagram {
this.db.clear?.();
this.renderer = diagram.renderer;
this.parser = diagram.parser;
const originalParse = this.parser.parse.bind(this.parser);
// Wrap the jison parse() method to handle extracting frontmatter.
//
// This can't be done in this.parse() because some code
// directly calls diagram.parser.parse(), bypassing this.parse().
//
// Similarly, we can't do this in getDiagramFromText() because some code
// calls diagram.db.clear(), which would reset anything set by
// extractFrontMatter().
this.parser.parse = (text: string) => originalParse(extractFrontMatter(text, this.db));
this.parser.parser.yy = this.db;
if (diagram.init) {
diagram.init(cnf);

View File

@ -189,6 +189,7 @@ export interface C4DiagramConfig extends BaseDiagramConfig {
}
export interface GitGraphDiagramConfig extends BaseDiagramConfig {
titleTopMargin?: number;
diagramPadding?: number;
nodeLabel?: NodeLabel;
mainBranchName?: string;
@ -227,6 +228,7 @@ export interface MindmapDiagramConfig extends BaseDiagramConfig {
export type PieDiagramConfig = BaseDiagramConfig;
export interface ErDiagramConfig extends BaseDiagramConfig {
titleTopMargin?: number;
diagramPadding?: number;
layoutDirection?: string;
minEntityWidth?: number;
@ -238,6 +240,7 @@ export interface ErDiagramConfig extends BaseDiagramConfig {
}
export interface StateDiagramConfig extends BaseDiagramConfig {
titleTopMargin?: number;
arrowMarkerAbsolute?: boolean;
dividerMargin?: number;
sizeUnit?: number;
@ -258,6 +261,7 @@ export interface StateDiagramConfig extends BaseDiagramConfig {
}
export interface ClassDiagramConfig extends BaseDiagramConfig {
titleTopMargin?: number;
arrowMarkerAbsolute?: boolean;
dividerMargin?: number;
padding?: number;
@ -343,6 +347,7 @@ export interface SequenceDiagramConfig extends BaseDiagramConfig {
}
export interface FlowchartDiagramConfig extends BaseDiagramConfig {
titleTopMargin?: number;
arrowMarkerAbsolute?: boolean;
diagramPadding?: number;
htmlLabels?: boolean;

View File

@ -154,6 +154,17 @@ const config: Partial<MermaidConfig> = {
/** The object containing configurations specific for flowcharts */
flowchart: {
/**
* ### titleTopMargin
*
* | Parameter | Description | Type | Required | Values |
* | -------------- | ---------------------------------------------- | ------- | -------- | ------------------ |
* | titleTopMargin | Margin top for the text over the flowchart | Integer | Required | Any Positive Value |
*
* **Notes:** Default value: 25
*/
titleTopMargin: 25,
/**
* | Parameter | Description | Type | Required | Values |
* | -------------- | ----------------------------------------------- | ------- | -------- | ------------------ |
@ -851,6 +862,16 @@ const config: Partial<MermaidConfig> = {
sectionColours: ['#fff'],
},
class: {
/**
* ### titleTopMargin
*
* | Parameter | Description | Type | Required | Values |
* | -------------- | ---------------------------------------------- | ------- | -------- | ------------------ |
* | titleTopMargin | Margin top for the text over the class diagram | Integer | Required | Any Positive Value |
*
* **Notes:** Default value: 25
*/
titleTopMargin: 25,
arrowMarkerAbsolute: false,
dividerMargin: 10,
padding: 5,
@ -884,6 +905,16 @@ const config: Partial<MermaidConfig> = {
defaultRenderer: 'dagre-wrapper',
},
state: {
/**
* ### titleTopMargin
*
* | Parameter | Description | Type | Required | Values |
* | -------------- | ---------------------------------------------- | ------- | -------- | ------------------ |
* | titleTopMargin | Margin top for the text over the state diagram | Integer | Required | Any Positive Value |
*
* **Notes:** Default value: 25
*/
titleTopMargin: 25,
dividerMargin: 10,
sizeUnit: 5,
padding: 8,
@ -932,6 +963,17 @@ const config: Partial<MermaidConfig> = {
/** The object containing configurations specific for entity relationship diagrams */
er: {
/**
* ### titleTopMargin
*
* | Parameter | Description | Type | Required | Values |
* | -------------- | ---------------------------------------------- | ------- | -------- | ------------------ |
* | titleTopMargin | Margin top for the text over the diagram | Integer | Required | Any Positive Value |
*
* **Notes:** Default value: 25
*/
titleTopMargin: 25,
/**
* | Parameter | Description | Type | Required | Values |
* | -------------- | ----------------------------------------------- | ------- | -------- | ------------------ |
@ -1085,6 +1127,16 @@ const config: Partial<MermaidConfig> = {
line_height: 20,
},
gitGraph: {
/**
* ### titleTopMargin
*
* | Parameter | Description | Type | Required | Values |
* | -------------- | ---------------------------------------------- | ------- | -------- | ------------------ |
* | titleTopMargin | Margin top for the text over the Git diagram | Integer | Required | Any Positive Value |
*
* **Notes:** Default value: 25
*/
titleTopMargin: 25,
diagramPadding: 8,
nodeLabel: {
width: 75,

View File

@ -1,6 +1,7 @@
import { MermaidConfig } from '../config.type';
import { log } from '../logger';
import { DetectorRecord, DiagramDetector, DiagramLoader } from './types';
import { frontMatterRegex } from './frontmatter';
const directive =
/[%]{2}[{]\s*(?:(?:(\w+)\s*:|(\w+))\s*(?:(?:(\w+))|((?:(?![}][%]{2}).|\r?\n)*))?\s*)(?:[}][%]{2})?/gi;
@ -31,7 +32,7 @@ const detectors: Record<string, DetectorRecord> = {};
* @returns A graph definition key
*/
export const detectType = function (text: string, config?: MermaidConfig): string {
text = text.replace(directive, '').replace(anyComment, '\n');
text = text.replace(frontMatterRegex, '').replace(directive, '').replace(anyComment, '\n');
for (const [key, { detector }] of Object.entries(detectors)) {
const diagram = detector(text, config);
if (diagram) {

View File

@ -0,0 +1,71 @@
import { vi } from 'vitest';
import { extractFrontMatter } from './frontmatter';
const dbMock = () => ({ setDiagramTitle: vi.fn() });
describe('extractFrontmatter', () => {
it('returns text unchanged if no frontmatter', () => {
expect(extractFrontMatter('diagram', null)).toEqual('diagram');
});
it('returns text unchanged if frontmatter lacks closing delimiter', () => {
const text = `---\ntitle: foo\ndiagram`;
expect(extractFrontMatter(text, null)).toEqual(text);
});
it('handles empty frontmatter', () => {
const db = dbMock();
const text = `---\n\n---\ndiagram`;
expect(extractFrontMatter(text, db)).toEqual('diagram');
expect(db.setDiagramTitle).not.toHaveBeenCalled();
});
it('handles frontmatter without mappings', () => {
const db = dbMock();
const text = `---\n1\n---\ndiagram`;
expect(extractFrontMatter(text, db)).toEqual('diagram');
expect(db.setDiagramTitle).not.toHaveBeenCalled();
});
it('does not try to parse frontmatter at the end', () => {
const db = dbMock();
const text = `diagram\n---\ntitle: foo\n---\n`;
expect(extractFrontMatter(text, db)).toEqual(text);
expect(db.setDiagramTitle).not.toHaveBeenCalled();
});
it('handles frontmatter with multiple delimiters', () => {
const db = dbMock();
const text = `---\ntitle: foo---bar\n---\ndiagram\n---\ntest`;
expect(extractFrontMatter(text, db)).toEqual('diagram\n---\ntest');
expect(db.setDiagramTitle).toHaveBeenCalledWith('foo---bar');
});
it('handles frontmatter with title', () => {
const db = dbMock();
const text = `---\ntitle: foo\n---\ndiagram`;
expect(extractFrontMatter(text, db)).toEqual('diagram');
expect(db.setDiagramTitle).toHaveBeenCalledWith('foo');
});
it('handles booleans in frontmatter properly', () => {
const db = dbMock();
const text = `---\ntitle: true\n---\ndiagram`;
expect(extractFrontMatter(text, db)).toEqual('diagram');
expect(db.setDiagramTitle).toHaveBeenCalledWith('true');
});
it('ignores unspecified frontmatter keys', () => {
const db = dbMock();
const text = `---\ninvalid: true\ntitle: foo\ntest: bar\n---\ndiagram`;
expect(extractFrontMatter(text, db)).toEqual('diagram');
expect(db.setDiagramTitle).toHaveBeenCalledWith('foo');
});
it('throws exception for invalid YAML syntax', () => {
const text = `---\n!!!\n---\ndiagram`;
expect(() => extractFrontMatter(text, null)).toThrow(
'tag suffix cannot contain exclamation marks'
);
});
});

View File

@ -0,0 +1,39 @@
// The "* as yaml" part is necessary for tree-shaking
import * as yaml from 'js-yaml';
// Match Jekyll-style front matter blocks (https://jekyllrb.com/docs/front-matter/).
// Based on regex used by Jekyll: https://github.com/jekyll/jekyll/blob/6dd3cc21c40b98054851846425af06c64f9fb466/lib/jekyll/document.rb#L10
// Note that JS doesn't support the "\A" anchor, which means we can't use
// multiline mode.
// Relevant YAML spec: https://yaml.org/spec/1.2.2/#914-explicit-documents
export const frontMatterRegex = /^(?:\s*---\s*[\r\n])(.*?)(?:[\r\n]\s*---\s*[\r\n]+)/s;
type FrontMatterMetadata = {
title?: string;
};
/**
* Extract and parse frontmatter from text, if present, and sets appropriate
* properties in the provided db.
* @param text -
* @param db -
* @returns text with frontmatter stripped out
*/
export function extractFrontMatter(text: string, db: any): string {
const matches = text.match(frontMatterRegex);
if (matches) {
const parsed: FrontMatterMetadata = yaml.load(matches[1], {
// To keep things simple, only allow strings, arrays, and plain objects.
// https://www.yaml.org/spec/1.2/spec.html#id2802346
schema: yaml.FAILSAFE_SCHEMA,
}) as FrontMatterMetadata;
if (parsed && parsed.title) {
db?.setDiagramTitle(parsed.title);
}
return text.slice(matches[0].length);
} else {
return text;
}
}

View File

@ -10,6 +10,8 @@ import {
getAccDescription,
setAccDescription,
clear as commonClear,
setDiagramTitle,
getDiagramTitle,
} from '../../commonDb';
const MERMAID_DOM_ID_PREFIX = 'classid-';
@ -408,4 +410,6 @@ export default {
getTooltip,
setTooltip,
lookUpDomId,
setDiagramTitle,
getDiagramTitle,
};

View File

@ -3,6 +3,7 @@ import graphlib from 'graphlib';
import { log } from '../../logger';
import { getConfig } from '../../config';
import { render } from '../../dagre-wrapper/index.js';
import utils from '../../utils';
import { curveLinear } from 'd3';
import { interpolateToCurve, getStylesFromArray } from '../../utils';
import { setupGraphViewbox } from '../../setupGraphViewbox';
@ -429,6 +430,8 @@ export const draw = function (text, id, _version, diagObj) {
id
);
utils.insertTitle(svg, 'classTitleText', conf.titleTopMargin, diagObj.db.getDiagramTitle());
setupGraphViewbox(g, svg, conf.diagramPadding, conf.useMaxWidth);
// Add label rects for non html labels

View File

@ -148,6 +148,11 @@ g.classGroup line {
font-size: 11px;
}
.classTitleText {
text-anchor: middle;
font-size: 18px;
fill: ${options.textColor};
}
`;
export default getStyles;

View File

@ -8,6 +8,8 @@ import {
getAccDescription,
setAccDescription,
clear as commonClear,
setDiagramTitle,
getDiagramTitle,
} from '../../commonDb';
let entities = {};
@ -94,4 +96,6 @@ export default {
getAccTitle,
setAccDescription,
getAccDescription,
setDiagramTitle,
getDiagramTitle,
};

View File

@ -3,6 +3,7 @@ import { line, curveBasis, select } from 'd3';
import dagre from 'dagre';
import { getConfig } from '../../config';
import { log } from '../../logger';
import utils from '../../utils';
import erMarkers from './erMarkers';
import { configureSvgSize } from '../../setupGraphViewbox';
import addSVGAccessibilityFields from '../../accessibility';
@ -649,6 +650,8 @@ export const draw = function (text, id, _version, diagObj) {
const padding = conf.diagramPadding;
utils.insertTitle(svg, 'entityTitleText', conf.titleTopMargin, diagObj.db.getDiagramTitle());
const svgBounds = svg.node().getBBox();
const width = svgBounds.width + padding * 2;
const height = svgBounds.height + padding * 2;

View File

@ -27,6 +27,12 @@ const getStyles = (options) =>
.relationshipLine {
stroke: ${options.lineColor};
}
.entityTitleText {
text-anchor: middle;
font-size: 18px;
fill: ${options.textColor};
}
`;
export default getStyles;

View File

@ -10,6 +10,8 @@ import {
getAccDescription,
setAccDescription,
clear as commonClear,
setDiagramTitle,
getDiagramTitle,
} from '../../commonDb';
const MERMAID_DOM_ID_PREFIX = 'flowchart-';
@ -785,4 +787,6 @@ export default {
},
exists,
makeUniq,
setDiagramTitle,
getDiagramTitle,
};

View File

@ -3,6 +3,7 @@ import { select, curveLinear, selectAll } from 'd3';
import flowDb from './flowDb';
import { getConfig } from '../../config';
import utils from '../../utils';
import { render } from '../../dagre-wrapper/index.js';
import addHtmlLabel from 'dagre-d3/lib/label/add-html-label.js';
@ -437,6 +438,8 @@ export const draw = function (text, id, _version, diagObj) {
const element = root.select('#' + id + ' g');
render(element, g, ['point', 'circle', 'cross'], 'flowchart', id);
utils.insertTitle(svg, 'flowchartTitleText', conf.titleTopMargin, diagObj.db.getDiagramTitle());
setupGraphViewbox(g, svg, conf.diagramPadding, conf.useMaxWidth);
// Index nodes

View File

@ -103,6 +103,12 @@ const getStyles = (options: FlowChartStyleOptions) =>
pointer-events: none;
z-index: 100;
}
.flowchartTitleText {
text-anchor: middle;
font-size: 18px;
fill: ${options.textColor};
}
`;
export default getStyles;

View File

@ -10,6 +10,8 @@ import {
getAccDescription,
setAccDescription,
clear as commonClear,
setDiagramTitle,
getDiagramTitle,
} from '../../commonDb';
let mainBranchName = getConfig().gitGraph.mainBranchName;
@ -529,5 +531,7 @@ export default {
getAccTitle,
getAccDescription,
setAccDescription,
setDiagramTitle,
getDiagramTitle,
commitType,
};

View File

@ -1,6 +1,7 @@
import { select } from 'd3';
import { getConfig, setupGraphViewbox } from '../../diagram-api/diagramAPI';
import { log } from '../../logger';
import utils from '../../utils';
import addSVGAccessibilityFields from '../../accessibility';
let allCommitsDict = {};
@ -521,6 +522,12 @@ export const draw = function (txt, id, ver, diagObj) {
}
drawArrows(diagram, allCommitsDict);
drawCommits(diagram, allCommitsDict, true);
utils.insertTitle(
diagram,
'gitTitleText',
gitGraphConfig.titleTopMargin,
diagObj.db.getDiagramTitle()
);
// Setup the view box and size of the svg element
setupGraphViewbox(

View File

@ -51,6 +51,11 @@ const getStyles = (options) =>
}
.arrow { stroke-width: 8; stroke-linecap: round; fill: none}
.gitTitleText {
text-anchor: middle;
font-size: 18px;
fill: ${options.textColor};
}
}
`;

View File

@ -9,6 +9,8 @@ import {
getAccDescription,
setAccDescription,
clear as commonClear,
setDiagramTitle,
getDiagramTitle,
} from '../../commonDb';
import {
@ -571,4 +573,6 @@ export default {
addStyleClass,
setCssClass,
addDescription,
setDiagramTitle,
getDiagramTitle,
};

View File

@ -5,6 +5,7 @@ import { render } from '../../dagre-wrapper/index.js';
import { log } from '../../logger';
import { configureSvgSize } from '../../setupGraphViewbox';
import common from '../common/common';
import utils from '../../utils';
import addSVGAccessibilityFields from '../../accessibility';
import {
DEFAULT_DIAGRAM_DIRECTION,
@ -437,8 +438,9 @@ export const draw = function (text, id, _version, diag) {
const padding = 8;
const bounds = svg.node().getBBox();
utils.insertTitle(svg, 'statediagramTitleText', conf.titleTopMargin, diag.db.getDiagramTitle());
const bounds = svg.node().getBBox();
const width = bounds.width + padding * 2;
const height = bounds.height + padding * 2;

View File

@ -194,6 +194,12 @@ g.stateGroup line {
stroke: ${options.lineColor};
stroke-width: 1;
}
.statediagramTitleText {
text-anchor: middle;
font-size: 18px;
fill: ${options.textColor};
}
`;
export default getStyles;

View File

@ -8,6 +8,9 @@ The class diagram is the main building block of object-oriented modeling. It is
Mermaid can render class diagrams.
```mermaid-example
---
title: Animal example
---
classDiagram
note "From Duck till Zebra"
Animal <|-- Duck
@ -45,6 +48,9 @@ A single instance of a class in the diagram contains three compartments:
- The bottom compartment contains the operations the class can execute. They are also left-aligned and the first letter is lowercase.
```mermaid-example
---
title: Bank example
---
classDiagram
class BankAccount
BankAccount : +String owner

View File

@ -7,6 +7,9 @@ Note that practitioners of ER modelling almost always refer to _entity types_ si
Mermaid can render ER diagrams
```mermaid-example
---
title: Order example
---
erDiagram
CUSTOMER ||--o{ ORDER : places
ORDER ||--|{ LINE-ITEM : contains

View File

@ -9,6 +9,9 @@ It can also accommodate different arrow types, multi directional arrows, and lin
### A node (default)
```mermaid-example
---
title: Node
---
flowchart LR
id
```
@ -22,6 +25,9 @@ found for the node that will be used. Also if you define edges for the node late
one previously defined will be used when rendering the box.
```mermaid-example
---
title: Node with text
---
flowchart LR
id1[This is the text in the box]
```

View File

@ -7,6 +7,9 @@ These kind of diagram are particularly helpful to developers and devops teams to
Mermaid can render Git diagrams
```mermaid-example
---
title: Example Git diagram
---
gitGraph
commit
commit

View File

@ -5,6 +5,9 @@
Mermaid can render state diagrams. The syntax tries to be compliant with the syntax used in plantUml as this will make it easier for users to share diagrams between mermaid and plantUml.
```mermaid-example
---
title: Simple sample
---
stateDiagram-v2
[*] --> Still
Still --> [*]

View File

@ -4,6 +4,7 @@ import assignWithDepth from './assignWithDepth';
import { detectType } from './diagram-api/detectType';
import { addDiagrams } from './diagram-api/diagram-orchestration';
import memoize from 'lodash/memoize';
import { MockD3 } from 'd3';
addDiagrams();
describe('when assignWithDepth: should merge objects within objects', function () {
@ -232,6 +233,16 @@ Alice->Bob: hi`;
const type = detectType(str);
expect(type).toBe('gitGraph');
});
it('should handle frontmatter', function () {
const str = '---\ntitle: foo\n---\n gitGraph TB:\nbfs1:queue';
const type = detectType(str);
expect(type).toBe('gitGraph');
});
it('should handle frontmatter with leading spaces', function () {
const str = ' ---\ntitle: foo\n---\n gitGraph TB:\nbfs1:queue';
const type = detectType(str);
expect(type).toBe('gitGraph');
});
});
describe('when finding substring in array ', function () {
it('should return the array index that contains the substring', function () {
@ -340,3 +351,23 @@ describe('when initializing the id generator', function () {
expect(idGenerator.next()).toEqual(lastId + 1);
});
});
describe('when inserting titles', function () {
it('should do nothing when title is empty', function () {
const svg = MockD3('svg');
utils.insertTitle(svg, 'testClass', 0, '');
expect(svg.__children.length).toBe(0);
});
it('should insert title centered', function () {
const svg = MockD3('svg');
utils.insertTitle(svg, 'testClass', 5, 'test title');
expect(svg.__children.length).toBe(1);
const text = svg.__children[0];
expect(text.__name).toBe('text');
expect(text.text).toHaveBeenCalledWith('test title');
expect(text.attr).toHaveBeenCalledWith('x', 15);
expect(text.attr).toHaveBeenCalledWith('y', -5);
expect(text.attr).toHaveBeenCalledWith('class', 'testClass');
});
});

View File

@ -885,6 +885,32 @@ export function getErrorMessage(error: unknown): string {
return String(error);
}
/**
* Appends <text> element with the given title, centered.
*
* @param parent - d3 svg object to append title to
* @param cssClass - CSS class for the <text> element containing the title
* @param titleTopMargin - Margin in pixels between title and rest of the graph
* @param title - The title. If empty, returns immediately.
*/
export const insertTitle = (
parent,
cssClass: string,
titleTopMargin: number,
title?: string
): void => {
if (!title) {
return;
}
const bounds = parent.node().getBBox();
parent
.append('text')
.text(title)
.attr('x', bounds.x + bounds.width / 2)
.attr('y', -titleTopMargin)
.attr('class', cssClass);
};
export default {
assignWithDepth,
wrapLabel,
@ -907,4 +933,5 @@ export default {
initIdGenerator: initIdGenerator,
directiveSanitizer,
sanitizeCss,
insertTitle,
};

View File

@ -25,6 +25,9 @@ importers:
'@types/express':
specifier: ^4.17.14
version: 4.17.14
'@types/js-yaml':
specifier: ^4.0.5
version: 4.0.5
'@types/jsdom':
specifier: ^20.0.1
version: 20.0.1
@ -115,6 +118,9 @@ importers:
jison:
specifier: ^0.4.18
version: 0.4.18
js-yaml:
specifier: ^4.1.0
version: 4.1.0
jsdom:
specifier: ^20.0.2
version: 20.0.2
@ -2466,6 +2472,10 @@ packages:
'@types/istanbul-lib-report': 3.0.0
dev: true
/@types/js-yaml/4.0.5:
resolution: {integrity: sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA==}
dev: true
/@types/jsdom/20.0.1:
resolution: {integrity: sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==}
dependencies: