diff --git a/README.zh-CN.md b/README.zh-CN.md
index fcaa1f523..e88c54e7f 100644
--- a/README.zh-CN.md
+++ b/README.zh-CN.md
@@ -39,7 +39,7 @@ Mermaid 甚至能让非程序员也能通过 [Mermaid Live Editor](https://merma
-### 流程图 [文档 - live editor]
+### 流程图 [文档 - live editor]
```
flowchart LR
@@ -57,7 +57,7 @@ C -->|One| D[Result 1]
C -->|Two| E[Result 2]
```
-### 时序图 [文档 - live editor]
+### 时序图 [文档 - live editor]
```
sequenceDiagram
@@ -83,7 +83,7 @@ John->>Bob: How about you?
Bob-->>John: Jolly good!
```
-### 甘特图 [文档 - live editor]
+### 甘特图 [文档 - live editor]
```
gantt
@@ -107,7 +107,7 @@ gantt
Parallel 4 : des6, after des4, 1d
```
-### 类图 [文档 - live editor]
+### 类图 [文档 - live editor]
```
classDiagram
@@ -147,7 +147,7 @@ class Class10 {
}
```
-### 状态图 [[docs - live editor]
+### 状态图 [[docs - live editor]
```
stateDiagram-v2
@@ -169,7 +169,7 @@ Moving --> Crash
Crash --> [*]
```
-### 饼图 [文档 - live editor]
+### 饼图 [文档 - live editor]
```
pie
@@ -185,9 +185,9 @@ pie
"Rats" : 15
```
-### Git 图 [实验特性 - live editor]
+### Git 图 [实验特性 - live editor]
-### 用户体验旅程图 [文档 - live editor]
+### 用户体验旅程图 [文档 - live editor]
```
journey
diff --git a/cypress/integration/rendering/gantt.spec.js b/cypress/integration/rendering/gantt.spec.js
index 16a70ece0..b75e682c6 100644
--- a/cypress/integration/rendering/gantt.spec.js
+++ b/cypress/integration/rendering/gantt.spec.js
@@ -341,4 +341,130 @@ describe('Gantt diagram', () => {
expect(descriptionEl.textContent).to.equal(expectedAccDescription);
});
});
+
+ it('should render a gantt diagram with tick is 15 minutes', () => {
+ imgSnapshotTest(
+ `
+ gantt
+ title A Gantt Diagram
+ dateFormat YYYY-MM-DD
+ axisFormat %H:%M
+ tickInterval 15minute
+ excludes weekends
+
+ section Section
+ A task : a1, 2022-10-03, 6h
+ Another task : after a1, 6h
+ section Another
+ Task in sec : 2022-10-03, 3h
+ another task : 3h
+ `,
+ {}
+ );
+ });
+
+ it('should render a gantt diagram with tick is 6 hours', () => {
+ imgSnapshotTest(
+ `
+ gantt
+ title A Gantt Diagram
+ dateFormat YYYY-MM-DD
+ axisFormat %d %H:%M
+ tickInterval 6hour
+ excludes weekends
+
+ section Section
+ A task : a1, 2022-10-03, 1d
+ Another task : after a1, 2d
+ section Another
+ Task in sec : 2022-10-04, 2d
+ another task : 2d
+ `,
+ {}
+ );
+ });
+
+ it('should render a gantt diagram with tick is 1 day', () => {
+ imgSnapshotTest(
+ `
+ gantt
+ title A Gantt Diagram
+ dateFormat YYYY-MM-DD
+ axisFormat %m-%d
+ tickInterval 1day
+ excludes weekends
+
+ section Section
+ A task : a1, 2022-10-01, 30d
+ Another task : after a1, 20d
+ section Another
+ Task in sec : 2022-10-20, 12d
+ another task : 24d
+ `,
+ {}
+ );
+ });
+
+ it('should render a gantt diagram with tick is 1 week', () => {
+ imgSnapshotTest(
+ `
+ gantt
+ title A Gantt Diagram
+ dateFormat YYYY-MM-DD
+ axisFormat %m-%d
+ tickInterval 1week
+ excludes weekends
+
+ section Section
+ A task : a1, 2022-10-01, 30d
+ Another task : after a1, 20d
+ section Another
+ Task in sec : 2022-10-20, 12d
+ another task : 24d
+ `,
+ {}
+ );
+ });
+
+ it('should render a gantt diagram with tick is 1 month', () => {
+ imgSnapshotTest(
+ `
+ gantt
+ title A Gantt Diagram
+ dateFormat YYYY-MM-DD
+ axisFormat %m-%d
+ tickInterval 1month
+ excludes weekends
+
+ section Section
+ A task : a1, 2022-10-01, 30d
+ Another task : after a1, 20d
+ section Another
+ Task in sec : 2022-10-20, 12d
+ another task : 24d
+ `,
+ {}
+ );
+ });
+
+ it('should render a gantt diagram with tick is 1 day and topAxis is true', () => {
+ imgSnapshotTest(
+ `
+ gantt
+ title A Gantt Diagram
+ dateFormat YYYY-MM-DD
+ axisFormat %m-%d
+ tickInterval 1day
+ excludes weekends
+
+ section Section
+ A task : a1, 2022-10-01, 30d
+ Another task : after a1, 20d
+ section Another
+ Task in sec : 2022-10-20, 12d
+ another task : 24d
+ `,
+ { gantt: { topAxis: true } }
+ );
+ });
});
diff --git a/docs/config/setup/modules/defaultConfig.md b/docs/config/setup/modules/defaultConfig.md
index e4b34e9bc..aea949fab 100644
--- a/docs/config/setup/modules/defaultConfig.md
+++ b/docs/config/setup/modules/defaultConfig.md
@@ -14,7 +14,7 @@
#### Defined in
-[defaultConfig.ts:1869](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L1869)
+[defaultConfig.ts:1882](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L1882)
---
diff --git a/docs/syntax/gantt.md b/docs/syntax/gantt.md
index 07907981f..11034d2e7 100644
--- a/docs/syntax/gantt.md
+++ b/docs/syntax/gantt.md
@@ -234,6 +234,18 @@ The following formatting strings are supported:
More info in: https://github.com/mbostock/d3/wiki/Time-Formatting
+### Axis ticks
+
+The default output ticks are auto. You can custom your `tickInterval`, like `1day` or `1week`.
+
+ tickInterval 1day
+
+The pattern is:
+
+ /^([1-9][0-9]*)(minute|hour|day|week|month)$/
+
+More info in:
+
## Comments
Comments can be entered within a gantt chart, which will be ignored by the parser. Comments need to be on their own line and must be prefaced with `%%` (double percent signs). Any text after the start of the comment to the next newline will be treated as a comment, including any diagram syntax
diff --git a/packages/mermaid/src/config.type.ts b/packages/mermaid/src/config.type.ts
index 2343bdd34..540fc6760 100644
--- a/packages/mermaid/src/config.type.ts
+++ b/packages/mermaid/src/config.type.ts
@@ -297,6 +297,7 @@ export interface GanttDiagramConfig extends BaseDiagramConfig {
sectionFontSize?: string | number;
numberSectionStyles?: number;
axisFormat?: string;
+ tickInterval?: string;
topAxis?: boolean;
}
diff --git a/packages/mermaid/src/defaultConfig.ts b/packages/mermaid/src/defaultConfig.ts
index 8b09eed1a..5a7f6c071 100644
--- a/packages/mermaid/src/defaultConfig.ts
+++ b/packages/mermaid/src/defaultConfig.ts
@@ -661,6 +661,19 @@ const config: Partial = {
*/
axisFormat: '%Y-%m-%d',
+ /**
+ * | Parameter | Description | Type | Required | Values |
+ * | ------------ | ------------| ------ | -------- | ------- |
+ * | tickInterval | axis ticks | string | Optional | string |
+ *
+ * **Notes:**
+ *
+ * Pattern is /^([1-9][0-9]*)(minute|hour|day|week|month)$/
+ *
+ * Default value: undefined
+ */
+ tickInterval: undefined,
+
/**
* | Parameter | Description | Type | Required | Values |
* | ----------- | ----------- | ------- | -------- | ----------- |
diff --git a/packages/mermaid/src/diagrams/er/erRenderer.js b/packages/mermaid/src/diagrams/er/erRenderer.js
index a4d5c8bd1..323bb4607 100644
--- a/packages/mermaid/src/diagrams/er/erRenderer.js
+++ b/packages/mermaid/src/diagrams/er/erRenderer.js
@@ -77,31 +77,27 @@ const drawAttributes = (groupNode, entityTextNode, attributes) => {
// Add a text node for the attribute type
const typeNode = groupNode
.append('text')
- .attr('class', 'er entityLabel')
+ .classed('er entityLabel', true)
.attr('id', `${attrPrefix}-type`)
.attr('x', 0)
.attr('y', 0)
- .attr('dominant-baseline', 'middle')
- .attr('text-anchor', 'left')
- .attr(
- 'style',
- 'font-family: ' + getConfig().fontFamily + '; font-size: ' + attrFontSize + 'px'
- )
+ .style('dominant-baseline', 'middle')
+ .style('text-anchor', 'left')
+ .style('font-family', getConfig().fontFamily)
+ .style('font-size', attrFontSize + 'px')
.text(attributeType);
// Add a text node for the attribute name
const nameNode = groupNode
.append('text')
- .attr('class', 'er entityLabel')
+ .classed('er entityLabel', true)
.attr('id', `${attrPrefix}-name`)
.attr('x', 0)
.attr('y', 0)
- .attr('dominant-baseline', 'middle')
- .attr('text-anchor', 'left')
- .attr(
- 'style',
- 'font-family: ' + getConfig().fontFamily + '; font-size: ' + attrFontSize + 'px'
- )
+ .style('dominant-baseline', 'middle')
+ .style('text-anchor', 'left')
+ .style('font-family', getConfig().fontFamily)
+ .style('font-size', attrFontSize + 'px')
.text(item.attributeName);
const attributeNode = {};
@@ -118,16 +114,14 @@ const drawAttributes = (groupNode, entityTextNode, attributes) => {
if (hasKeyType) {
const keyTypeNode = groupNode
.append('text')
- .attr('class', 'er entityLabel')
+ .classed('er entityLabel', true)
.attr('id', `${attrPrefix}-key`)
.attr('x', 0)
.attr('y', 0)
- .attr('dominant-baseline', 'middle')
- .attr('text-anchor', 'left')
- .attr(
- 'style',
- 'font-family: ' + getConfig().fontFamily + '; font-size: ' + attrFontSize + 'px'
- )
+ .style('dominant-baseline', 'middle')
+ .style('text-anchor', 'left')
+ .style('font-family', getConfig().fontFamily)
+ .style('font-size', attrFontSize + 'px')
.text(item.attributeKeyType || '');
attributeNode.kn = keyTypeNode;
@@ -139,16 +133,14 @@ const drawAttributes = (groupNode, entityTextNode, attributes) => {
if (hasComment) {
const commentNode = groupNode
.append('text')
- .attr('class', 'er entityLabel')
+ .classed('er entityLabel', true)
.attr('id', `${attrPrefix}-comment`)
.attr('x', 0)
.attr('y', 0)
- .attr('dominant-baseline', 'middle')
- .attr('text-anchor', 'left')
- .attr(
- 'style',
- 'font-family: ' + getConfig().fontFamily + '; font-size: ' + attrFontSize + 'px'
- )
+ .style('dominant-baseline', 'middle')
+ .style('text-anchor', 'left')
+ .style('font-family', getConfig().fontFamily)
+ .style('font-size', attrFontSize + 'px')
.text(item.attributeComment || '');
attributeNode.cn = commentNode;
@@ -217,10 +209,10 @@ const drawAttributes = (groupNode, entityTextNode, attributes) => {
// Insert a rectangle for the type
const typeRect = groupNode
.insert('rect', '#' + attributeNode.tn.node().id)
- .attr('class', `er ${attribStyle}`)
- .attr('fill', conf.fill)
- .attr('fill-opacity', '100%')
- .attr('stroke', conf.stroke)
+ .classed(`er ${attribStyle}`, true)
+ .style('fill', conf.fill)
+ .style('fill-opacity', '100%')
+ .style('stroke', conf.stroke)
.attr('x', 0)
.attr('y', heightOffset)
.attr('width', maxTypeWidth + widthPadding * 2 + spareColumnWidth)
@@ -237,10 +229,10 @@ const drawAttributes = (groupNode, entityTextNode, attributes) => {
// Insert a rectangle for the name
const nameRect = groupNode
.insert('rect', '#' + attributeNode.nn.node().id)
- .attr('class', `er ${attribStyle}`)
- .attr('fill', conf.fill)
- .attr('fill-opacity', '100%')
- .attr('stroke', conf.stroke)
+ .classed(`er ${attribStyle}`, true)
+ .style('fill', conf.fill)
+ .style('fill-opacity', '100%')
+ .style('stroke', conf.stroke)
.attr('x', nameXOffset)
.attr('y', heightOffset)
.attr('width', maxNameWidth + widthPadding * 2 + spareColumnWidth)
@@ -259,10 +251,10 @@ const drawAttributes = (groupNode, entityTextNode, attributes) => {
// Insert a rectangle for the key type
const keyTypeRect = groupNode
.insert('rect', '#' + attributeNode.kn.node().id)
- .attr('class', `er ${attribStyle}`)
- .attr('fill', conf.fill)
- .attr('fill-opacity', '100%')
- .attr('stroke', conf.stroke)
+ .classed(`er ${attribStyle}`, true)
+ .style('fill', conf.fill)
+ .style('fill-opacity', '100%')
+ .style('stroke', conf.stroke)
.attr('x', keyTypeAndCommentXOffset)
.attr('y', heightOffset)
.attr('width', maxKeyWidth + widthPadding * 2 + spareColumnWidth)
@@ -282,10 +274,10 @@ const drawAttributes = (groupNode, entityTextNode, attributes) => {
// Insert a rectangle for the comment
groupNode
.insert('rect', '#' + attributeNode.cn.node().id)
- .attr('class', `er ${attribStyle}`)
- .attr('fill', conf.fill)
- .attr('fill-opacity', '100%')
- .attr('stroke', conf.stroke)
+ .classed(`er ${attribStyle}`, 'true')
+ .style('fill', conf.fill)
+ .style('fill-opacity', '100%')
+ .style('stroke', conf.stroke)
.attr('x', keyTypeAndCommentXOffset)
.attr('y', heightOffset)
.attr('width', maxCommentWidth + widthPadding * 2 + spareColumnWidth)
@@ -335,16 +327,14 @@ const drawEntities = function (svgNode, entities, graph) {
const textId = 'text-' + entityId;
const textNode = groupNode
.append('text')
- .attr('class', 'er entityLabel')
+ .classed('er entityLabel', true)
.attr('id', textId)
.attr('x', 0)
.attr('y', 0)
- .attr('dominant-baseline', 'middle')
- .attr('text-anchor', 'middle')
- .attr(
- 'style',
- 'font-family: ' + getConfig().fontFamily + '; font-size: ' + conf.fontSize + 'px'
- )
+ .style('dominant-baseline', 'middle')
+ .style('text-anchor', 'middle')
+ .style('font-family', getConfig().fontFamily)
+ .style('font-size', conf.fontSize + 'px')
.text(entityName);
const { width: entityWidth, height: entityHeight } = drawAttributes(
@@ -356,10 +346,10 @@ const drawEntities = function (svgNode, entities, graph) {
// Draw the rectangle - insert it before the text so that the text is not obscured
const rectNode = groupNode
.insert('rect', '#' + textId)
- .attr('class', 'er entityBox')
+ .classed('er entityBox', true)
.style('fill', conf.fill)
- .attr('fill-opacity', '100%')
- .attr('stroke', conf.stroke)
+ .style('fill-opacity', '100%')
+ .style('stroke', conf.stroke)
.attr('x', 0)
.attr('y', 0)
.attr('width', entityWidth)
@@ -460,10 +450,10 @@ const drawRelationshipFromLayout = function (svg, rel, g, insert, diagObj) {
// Insert the line at the right place
const svgPath = svg
.insert('path', '#' + insert)
- .attr('class', 'er relationshipLine')
+ .classed('er relationshipLine', true)
.attr('d', lineFunction(edge.points))
- .attr('stroke', conf.stroke)
- .attr('fill', 'none');
+ .style('stroke', conf.stroke)
+ .style('fill', 'none');
// ...and with dashes if necessary
if (rel.relSpec.relType === diagObj.db.Identification.NON_IDENTIFYING) {
@@ -537,16 +527,14 @@ const drawRelationshipFromLayout = function (svg, rel, g, insert, diagObj) {
const labelNode = svg
.append('text')
- .attr('class', 'er relationshipLabel')
+ .classed('er relationshipLabel', true)
.attr('id', labelId)
.attr('x', labelPoint.x)
.attr('y', labelPoint.y)
- .attr('text-anchor', 'middle')
- .attr('dominant-baseline', 'middle')
- .attr(
- 'style',
- 'font-family: ' + getConfig().fontFamily + '; font-size: ' + conf.fontSize + 'px'
- )
+ .style('text-anchor', 'middle')
+ .style('dominant-baseline', 'middle')
+ .style('font-family', getConfig().fontFamily)
+ .style('font-size', conf.fontSize + 'px')
.text(rel.roleA);
// Figure out how big the opaque 'container' rectangle needs to be
@@ -555,13 +543,13 @@ const drawRelationshipFromLayout = function (svg, rel, g, insert, diagObj) {
// Insert the opaque rectangle before the text label
svg
.insert('rect', '#' + labelId)
- .attr('class', 'er relationshipLabelBox')
+ .classed('er relationshipLabelBox', true)
.attr('x', labelPoint.x - labelBBox.width / 2)
.attr('y', labelPoint.y - labelBBox.height / 2)
.attr('width', labelBBox.width)
.attr('height', labelBBox.height)
- .attr('fill', 'white')
- .attr('fill-opacity', '85%');
+ .style('fill', 'white')
+ .style('fill-opacity', '85%');
};
/**
diff --git a/packages/mermaid/src/diagrams/gantt/ganttDb.js b/packages/mermaid/src/diagrams/gantt/ganttDb.js
index 99c93ea04..a0f18c3b8 100644
--- a/packages/mermaid/src/diagrams/gantt/ganttDb.js
+++ b/packages/mermaid/src/diagrams/gantt/ganttDb.js
@@ -17,6 +17,7 @@ import {
let dateFormat = '';
let axisFormat = '';
+let tickInterval = undefined;
let todayMarker = '';
let includes = [];
let excludes = [];
@@ -47,6 +48,7 @@ export const clear = function () {
rawTasks = [];
dateFormat = '';
axisFormat = '';
+ tickInterval = undefined;
todayMarker = '';
includes = [];
excludes = [];
@@ -65,6 +67,14 @@ export const getAxisFormat = function () {
return axisFormat;
};
+export const setTickInterval = function (txt) {
+ tickInterval = txt;
+};
+
+export const getTickInterval = function () {
+ return tickInterval;
+};
+
export const setTodayMarker = function (txt) {
todayMarker = txt;
};
@@ -647,6 +657,8 @@ export default {
topAxisEnabled,
setAxisFormat,
getAxisFormat,
+ setTickInterval,
+ getTickInterval,
setTodayMarker,
getTodayMarker,
setAccTitle,
diff --git a/packages/mermaid/src/diagrams/gantt/ganttRenderer.js b/packages/mermaid/src/diagrams/gantt/ganttRenderer.js
index c9f6836a5..9501dd024 100644
--- a/packages/mermaid/src/diagrams/gantt/ganttRenderer.js
+++ b/packages/mermaid/src/diagrams/gantt/ganttRenderer.js
@@ -10,6 +10,11 @@ import {
axisBottom,
axisTop,
timeFormat,
+ timeMinute,
+ timeHour,
+ timeDay,
+ timeWeek,
+ timeMonth,
} from 'd3';
import common from '../common/common';
import { getConfig } from '../../config';
@@ -495,6 +500,33 @@ export const draw = function (text, id, version, diagObj) {
.tickSize(-h + theTopPad + conf.gridLineStartPadding)
.tickFormat(timeFormat(diagObj.db.getAxisFormat() || conf.axisFormat || '%Y-%m-%d'));
+ const reTickInterval = /^([1-9][0-9]*)(minute|hour|day|week|month)$/;
+ const resultTickInterval = reTickInterval.exec(
+ diagObj.db.getTickInterval() || conf.tickInterval
+ );
+
+ if (resultTickInterval !== null) {
+ const every = resultTickInterval[1];
+ const interval = resultTickInterval[2];
+ switch (interval) {
+ case 'minute':
+ bottomXAxis.ticks(timeMinute.every(every));
+ break;
+ case 'hour':
+ bottomXAxis.ticks(timeHour.every(every));
+ break;
+ case 'day':
+ bottomXAxis.ticks(timeDay.every(every));
+ break;
+ case 'week':
+ bottomXAxis.ticks(timeWeek.every(every));
+ break;
+ case 'month':
+ bottomXAxis.ticks(timeMonth.every(every));
+ break;
+ }
+ }
+
svg
.append('g')
.attr('class', 'grid')
@@ -512,6 +544,28 @@ export const draw = function (text, id, version, diagObj) {
.tickSize(-h + theTopPad + conf.gridLineStartPadding)
.tickFormat(timeFormat(diagObj.db.getAxisFormat() || conf.axisFormat || '%Y-%m-%d'));
+ if (resultTickInterval !== null) {
+ const every = resultTickInterval[1];
+ const interval = resultTickInterval[2];
+ switch (interval) {
+ case 'minute':
+ topXAxis.ticks(timeMinute.every(every));
+ break;
+ case 'hour':
+ topXAxis.ticks(timeHour.every(every));
+ break;
+ case 'day':
+ topXAxis.ticks(timeDay.every(every));
+ break;
+ case 'week':
+ topXAxis.ticks(timeWeek.every(every));
+ break;
+ case 'month':
+ topXAxis.ticks(timeMonth.every(every));
+ break;
+ }
+ }
+
svg
.append('g')
.attr('class', 'grid')
diff --git a/packages/mermaid/src/diagrams/gantt/parser/gantt.jison b/packages/mermaid/src/diagrams/gantt/parser/gantt.jison
index f25656453..2223aa378 100644
--- a/packages/mermaid/src/diagrams/gantt/parser/gantt.jison
+++ b/packages/mermaid/src/diagrams/gantt/parser/gantt.jison
@@ -82,6 +82,7 @@ that id.
"inclusiveEndDates" return 'inclusiveEndDates';
"topAxis" return 'topAxis';
"axisFormat"\s[^#\n;]+ return 'axisFormat';
+"tickInterval"\s[^#\n;]+ return 'tickInterval';
"includes"\s[^#\n;]+ return 'includes';
"excludes"\s[^#\n;]+ return 'excludes';
"todayMarker"\s[^\n;]+ return 'todayMarker';
@@ -125,6 +126,7 @@ statement
| inclusiveEndDates {yy.enableInclusiveEndDates();$$=$1.substr(18);}
| topAxis {yy.TopAxis();$$=$1.substr(8);}
| axisFormat {yy.setAxisFormat($1.substr(11));$$=$1.substr(11);}
+ | tickInterval {yy.setTickInterval($1.substr(13));$$=$1.substr(13);}
| excludes {yy.setExcludes($1.substr(9));$$=$1.substr(9);}
| includes {yy.setIncludes($1.substr(9));$$=$1.substr(9);}
| todayMarker {yy.setTodayMarker($1.substr(12));$$=$1.substr(12);}
diff --git a/packages/mermaid/src/docs/syntax/gantt.md b/packages/mermaid/src/docs/syntax/gantt.md
index 755f50b1e..8b200da7d 100644
--- a/packages/mermaid/src/docs/syntax/gantt.md
+++ b/packages/mermaid/src/docs/syntax/gantt.md
@@ -174,6 +174,22 @@ The following formatting strings are supported:
More info in: https://github.com/mbostock/d3/wiki/Time-Formatting
+### Axis ticks
+
+The default output ticks are auto. You can custom your `tickInterval`, like `1day` or `1week`.
+
+```
+tickInterval 1day
+```
+
+The pattern is:
+
+```
+/^([1-9][0-9]*)(minute|hour|day|week|month)$/
+```
+
+More info in: [https://github.com/d3/d3-time#interval_every](https://github.com/d3/d3-time#interval_every)
+
## Comments
Comments can be entered within a gantt chart, which will be ignored by the parser. Comments need to be on their own line and must be prefaced with `%%` (double percent signs). Any text after the start of the comment to the next newline will be treated as a comment, including any diagram syntax