diff --git a/cSpell.json b/cSpell.json
index abcfa0383..a99be67da 100644
--- a/cSpell.json
+++ b/cSpell.json
@@ -149,6 +149,7 @@
"vueuse",
"xlink",
"yash",
+ "xychart",
"yokozuna",
"zenuml"
],
diff --git a/demos/index.html b/demos/index.html
index 24c4fbf3b..c634aad2d 100644
--- a/demos/index.html
+++ b/demos/index.html
@@ -60,6 +60,9 @@
+
+
+
diff --git a/demos/xychart.html b/demos/xychart.html
new file mode 100644
index 000000000..3fffd379a
--- /dev/null
+++ b/demos/xychart.html
@@ -0,0 +1,33 @@
+
+
+
+
+
+ Mermaid Quick Test Page
+
+
+
+
+
+ XY Charts demos
+
+ xychart
+
+
+
+
+
+
+
diff --git a/packages/mermaid/src/diagram-api/diagram-orchestration.ts b/packages/mermaid/src/diagram-api/diagram-orchestration.ts
index 9c03e27f3..b4dd2fd95 100644
--- a/packages/mermaid/src/diagram-api/diagram-orchestration.ts
+++ b/packages/mermaid/src/diagram-api/diagram-orchestration.ts
@@ -7,6 +7,7 @@ import gantt from '../diagrams/gantt/ganttDetector.js';
import { info } from '../diagrams/info/infoDetector.js';
import pie from '../diagrams/pie/pieDetector.js';
import quadrantChart from '../diagrams/quadrant-chart/quadrantDetector.js';
+import xychart from '../diagrams/xychart/xychartDetector.js';
import requirement from '../diagrams/requirement/requirementDetector.js';
import sequence from '../diagrams/sequence/sequenceDetector.js';
import classDiagram from '../diagrams/class/classDetector.js';
@@ -82,5 +83,6 @@ export const addDiagrams = () => {
journey,
quadrantChart,
sankey
+ xychart
);
};
diff --git a/packages/mermaid/src/diagrams/xychart/parser/xychart.jison b/packages/mermaid/src/diagrams/xychart/parser/xychart.jison
new file mode 100644
index 000000000..d8e0d08d0
--- /dev/null
+++ b/packages/mermaid/src/diagrams/xychart/parser/xychart.jison
@@ -0,0 +1,143 @@
+%lex
+%options case-insensitive
+
+%x string
+%x string
+%x md_string
+%x title
+%x open_directive
+%x type_directive
+%x arg_directive
+%x close_directive
+%x acc_title
+%x acc_descr
+%x acc_descr_multiline
+%%
+\%\%\{ { this.begin('open_directive'); return 'open_directive'; }
+((?:(?!\}\%\%)[^:.])*) { this.begin('type_directive'); return 'type_directive'; }
+":" { this.popState(); this.begin('arg_directive'); return ':'; }
+\}\%\% { this.popState(); this.popState(); return 'close_directive'; }
+((?:(?!\}\%\%).|\n)*) return 'arg_directive';
+\%\%(?!\{)[^\n]* /* skip comments */
+[^\}]\%\%[^\n]* /* skip comments */
+[\n\r]+ return 'NEWLINE';
+\%\%[^\n]* /* do nothing */
+
+title { this.begin("title");return 'title'; }
+(?!\n|;|#)*[^\n]* { this.popState(); return "title_value"; }
+
+accTitle\s*":"\s* { this.begin("acc_title");return 'acc_title'; }
+(?!\n|;|#)*[^\n]* { this.popState(); return "acc_title_value"; }
+accDescr\s*":"\s* { this.begin("acc_descr");return 'acc_descr'; }
+(?!\n|;|#)*[^\n]* { this.popState(); return "acc_descr_value"; }
+accDescr\s*"{"\s* { this.begin("acc_descr_multiline");}
+[\}] { this.popState(); }
+[^\}]* return "acc_descr_multiline_value";
+
+
+["][`] { this.begin("md_string");}
+[^`"]+ { return "MD_STR";}
+[`]["] { this.popState();}
+["] this.begin("string");
+["] this.popState();
+[^"]* return "STR";
+
+" "*"xychart"" "* return 'XYCHART';
+
+[A-Za-z]+ return 'ALPHA';
+":" return 'COLON';
+\+ return 'PLUS';
+"," return 'COMMA';
+"=" return 'EQUALS';
+\= return 'EQUALS';
+"*" return 'MULT';
+\# return 'BRKT';
+[\_] return 'UNDERSCORE';
+"." return 'DOT';
+"&" return 'AMP';
+\- return 'MINUS';
+[0-9]+ return 'NUM';
+\s return 'SPACE';
+";" return 'SEMI';
+[!"#$%&'*+,-.`?\\_/] return 'PUNCTUATION';
+<> return 'EOF';
+
+/lex
+
+%start start
+
+%% /* language grammar */
+
+start
+ : eol start
+ | SPACE start
+ | directive start
+ | XYCHART document
+ ;
+
+document
+ : /* empty */
+ | document line
+ ;
+
+line
+ : statement eol
+ ;
+
+statement
+ :
+ | SPACE statement
+ | directive
+ ;
+
+
+directive
+ : openDirective typeDirective closeDirective
+ | openDirective typeDirective ':' argDirective closeDirective
+ ;
+
+eol
+ : NEWLINE
+ | SEMI
+ | EOF
+ ;
+
+openDirective
+ : open_directive { yy.parseDirective('%%{', 'open_directive'); }
+ ;
+
+typeDirective
+ : type_directive { yy.parseDirective($1, 'type_directive'); }
+ ;
+
+argDirective
+ : arg_directive { $1 = $1.trim().replace(/'/g, '"'); yy.parseDirective($1, 'arg_directive'); }
+ ;
+
+closeDirective
+ : close_directive { yy.parseDirective('}%%', 'close_directive', 'quadrantChart'); }
+ ;
+
+text: alphaNumToken
+ { $$={text:$1, type: 'text'};}
+ | text textNoTagsToken
+ { $$={text:$1.text+''+$2, type: $1.type};}
+ | STR
+ { $$={text: $1, type: 'text'};}
+ | MD_STR
+ { $$={text: $1, type: 'markdown'};}
+ ;
+
+alphaNum
+ : alphaNumToken
+ {$$=$1;}
+ | alphaNum alphaNumToken
+ {$$=$1+''+$2;}
+ ;
+
+
+alphaNumToken : PUNCTUATION | AMP | NUM| ALPHA | COMMA | PLUS | EQUALS | MULT | DOT | BRKT| UNDERSCORE ;
+
+textNoTagsToken: alphaNumToken | SPACE | MINUS;
+
+%%
diff --git a/packages/mermaid/src/diagrams/xychart/xychartBuilder.ts b/packages/mermaid/src/diagrams/xychart/xychartBuilder.ts
new file mode 100644
index 000000000..b4bbdc31e
--- /dev/null
+++ b/packages/mermaid/src/diagrams/xychart/xychartBuilder.ts
@@ -0,0 +1,5 @@
+// @ts-ignore: TODO Fix ts errors
+import { scaleLinear } from 'd3';
+import { log } from '../../logger.js';
+
+export class XYChartBuilder {}
diff --git a/packages/mermaid/src/diagrams/xychart/xychartDb.ts b/packages/mermaid/src/diagrams/xychart/xychartDb.ts
new file mode 100644
index 000000000..968d1390d
--- /dev/null
+++ b/packages/mermaid/src/diagrams/xychart/xychartDb.ts
@@ -0,0 +1,39 @@
+import { log } from '../../logger.js';
+import mermaidAPI from '../../mermaidAPI.js';
+import * as configApi from '../../config.js';
+import { sanitizeText } from '../common/common.js';
+import {
+ setAccTitle,
+ getAccTitle,
+ setDiagramTitle,
+ getDiagramTitle,
+ getAccDescription,
+ setAccDescription,
+ clear as commonClear,
+} from '../../commonDb.js';
+
+const config = configApi.getConfig();
+
+function textSanitizer(text: string) {
+ return sanitizeText(text.trim(), config);
+}
+
+export const parseDirective = function (statement: string, context: string, type: string) {
+ // @ts-ignore: TODO Fix ts errors
+ mermaidAPI.parseDirective(this, statement, context, type);
+};
+
+const clear = function () {
+ commonClear();
+};
+
+export default {
+ parseDirective,
+ clear,
+ setAccTitle,
+ getAccTitle,
+ setDiagramTitle,
+ getDiagramTitle,
+ getAccDescription,
+ setAccDescription,
+};
diff --git a/packages/mermaid/src/diagrams/xychart/xychartDetector.ts b/packages/mermaid/src/diagrams/xychart/xychartDetector.ts
new file mode 100644
index 000000000..d200adc59
--- /dev/null
+++ b/packages/mermaid/src/diagrams/xychart/xychartDetector.ts
@@ -0,0 +1,20 @@
+import type { DiagramDetector, ExternalDiagramDefinition } from '../../diagram-api/types.js';
+
+const id = 'xychart';
+
+const detector: DiagramDetector = (txt) => {
+ return txt.match(/^\s*xychart/i) !== null;
+};
+
+const loader = async () => {
+ const { diagram } = await import('./xychartDiagram.js');
+ return { id, diagram };
+};
+
+const plugin: ExternalDiagramDefinition = {
+ id,
+ detector,
+ loader,
+};
+
+export default plugin;
diff --git a/packages/mermaid/src/diagrams/xychart/xychartDiagram.ts b/packages/mermaid/src/diagrams/xychart/xychartDiagram.ts
new file mode 100644
index 000000000..590ceed28
--- /dev/null
+++ b/packages/mermaid/src/diagrams/xychart/xychartDiagram.ts
@@ -0,0 +1,12 @@
+import { DiagramDefinition } from '../../diagram-api/types.js';
+// @ts-ignore: TODO Fix ts errors
+import parser from './parser/xychart.jison';
+import db from './xychartDb.js';
+import renderer from './xychartRenderer.js';
+
+export const diagram: DiagramDefinition = {
+ parser,
+ db,
+ renderer,
+ styles: () => '',
+};
diff --git a/packages/mermaid/src/diagrams/xychart/xychartRenderer.ts b/packages/mermaid/src/diagrams/xychart/xychartRenderer.ts
new file mode 100644
index 000000000..a4caacf52
--- /dev/null
+++ b/packages/mermaid/src/diagrams/xychart/xychartRenderer.ts
@@ -0,0 +1,38 @@
+// @ts-ignore: TODO Fix ts errors
+import { select } from 'd3';
+import * as configApi from '../../config.js';
+import { log } from '../../logger.js';
+import { configureSvgSize } from '../../setupGraphViewbox.js';
+import { Diagram } from '../../Diagram.js';
+
+export const draw = (txt: string, id: string, _version: string, diagObj: Diagram) => {
+ const conf = configApi.getConfig();
+
+ log.debug('Rendering xychart chart\n' + txt);
+
+ const securityLevel = conf.securityLevel;
+ // Handle root and Document for when rendering in sandbox mode
+ let sandboxElement;
+ if (securityLevel === 'sandbox') {
+ sandboxElement = select('#i' + id);
+ }
+ const root =
+ securityLevel === 'sandbox'
+ ? select(sandboxElement.nodes()[0].contentDocument.body)
+ : select('body');
+
+ const svg = root.select(`[id="${id}"]`);
+
+ const group = svg.append('g').attr('class', 'main');
+
+ const width = 500;
+ const height = 500;
+
+ configureSvgSize(svg, height, width, true);
+
+ svg.attr('viewBox', '0 0 ' + width + ' ' + height);
+};
+
+export default {
+ draw,
+};