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 @@
  • Quadrant charts

  • +
  • +

    XY charts

    +
  • Requirements

  • 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'; } +<acc_title>(?!\n|;|#)*[^\n]* { this.popState(); return "acc_title_value"; } +accDescr\s*":"\s* { this.begin("acc_descr");return 'acc_descr'; } +<acc_descr>(?!\n|;|#)*[^\n]* { this.popState(); return "acc_descr_value"; } +accDescr\s*"{"\s* { this.begin("acc_descr_multiline");} +<acc_descr_multiline>[\}] { this.popState(); } +<acc_descr_multiline>[^\}]* return "acc_descr_multiline_value"; + + +["][`] { this.begin("md_string");} +<md_string>[^`"]+ { return "MD_STR";} +<md_string>[`]["] { this.popState();} +["] this.begin("string"); +<string>["] this.popState(); +<string>[^"]* 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'; +<<EOF>> 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, +};