From 7aefbc3e3238a5ee5e39699fb2d5a429c628216d Mon Sep 17 00:00:00 2001 From: exoego Date: Thu, 27 Jun 2024 19:09:22 +0900 Subject: [PATCH] init venn diagram --- .build/jsonSchema.ts | 1 + demos/index.html | 3 + demos/venn.html | 65 +++++++ .../setup/interfaces/mermaid.MermaidConfig.md | 20 ++- docs/config/setup/modules/defaultConfig.md | 2 +- package.json | 6 + packages/mermaid/package.json | 1 + packages/mermaid/src/config.type.ts | 10 ++ packages/mermaid/src/defaultConfig.ts | 3 + .../src/diagram-api/diagram-orchestration.ts | 4 +- .../src/diagrams/venn/parser/venn.jison | 72 ++++++++ .../src/diagrams/venn/parser/venn.spec.ts | 66 +++++++ packages/mermaid/src/diagrams/venn/styles.ts | 162 ++++++++++++++++++ packages/mermaid/src/diagrams/venn/vennDB.ts | 58 +++++++ .../mermaid/src/diagrams/venn/vennDetector.ts | 24 +++ .../mermaid/src/diagrams/venn/vennDiagram.ts | 13 ++ .../mermaid/src/diagrams/venn/vennRenderer.ts | 90 ++++++++++ .../mermaid/src/diagrams/venn/vennTypes.ts | 15 ++ .../mermaid/src/docs/.vitepress/config.ts | 1 + .../mermaid/src/schemas/config.schema.yaml | 15 ++ packages/mermaid/src/styles.spec.ts | 2 + pnpm-lock.yaml | 40 +++++ 22 files changed, 666 insertions(+), 7 deletions(-) create mode 100644 demos/venn.html create mode 100644 packages/mermaid/src/diagrams/venn/parser/venn.jison create mode 100644 packages/mermaid/src/diagrams/venn/parser/venn.spec.ts create mode 100644 packages/mermaid/src/diagrams/venn/styles.ts create mode 100644 packages/mermaid/src/diagrams/venn/vennDB.ts create mode 100644 packages/mermaid/src/diagrams/venn/vennDetector.ts create mode 100644 packages/mermaid/src/diagrams/venn/vennDiagram.ts create mode 100644 packages/mermaid/src/diagrams/venn/vennRenderer.ts create mode 100644 packages/mermaid/src/diagrams/venn/vennTypes.ts diff --git a/.build/jsonSchema.ts b/.build/jsonSchema.ts index 50b9ff097b..832c374571 100644 --- a/.build/jsonSchema.ts +++ b/.build/jsonSchema.ts @@ -26,6 +26,7 @@ const MERMAID_CONFIG_DIAGRAM_KEYS = [ 'block', 'packet', 'architecture', + 'venn', ] as const; /** diff --git a/demos/index.html b/demos/index.html index 07b51a3136..ecd44d3df3 100644 --- a/demos/index.html +++ b/demos/index.html @@ -91,6 +91,9 @@

Layered Blocks

  • Architecture

  • +
  • +

    Venn diagram

    +
  • diff --git a/demos/venn.html b/demos/venn.html new file mode 100644 index 0000000000..0446cddbb0 --- /dev/null +++ b/demos/venn.html @@ -0,0 +1,65 @@ + + + + + + Mermaid Quick Test Page + + + + + +

    Venn diagram demo

    + +
    +
    +      venn-beta
    +        title Very Basic Venn Diagram
    +        sets A
    +        sets B
    +        sets A,B     label: AB
    +      
    +
    +
    +      venn-beta
    +        title Three overlapping circles
    +        sets A
    +        sets B
    +        sets C
    +        sets A,B   label: AB, background: skyblue
    +        sets B,C   label: BC, background: orange
    +        sets A,C   label: AC, background: lightgreen
    +        sets A,B,C   label: ABC, color: red,  background: white
    +      
    +
    +
    +      venn-beta
    +        title Three concatenated circles and one isolated circle
    +        sets A       size: 15, label: ALPHA
    +        sets B       size: 5,  label: BETA
    +        sets C       size: 10
    +        sets D       size: 10, label: DELTA
    +        sets A,B     size: 1,  label: AB, background: gold
    +        sets B,D     size: 1
    +      
    +
    + + + + + diff --git a/docs/config/setup/interfaces/mermaid.MermaidConfig.md b/docs/config/setup/interfaces/mermaid.MermaidConfig.md index ad078653a6..147fa62790 100644 --- a/docs/config/setup/interfaces/mermaid.MermaidConfig.md +++ b/docs/config/setup/interfaces/mermaid.MermaidConfig.md @@ -121,7 +121,7 @@ should not change unless content is changed. #### Defined in -[packages/mermaid/src/config.type.ts:201](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L201) +[packages/mermaid/src/config.type.ts:202](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L202) --- @@ -183,7 +183,7 @@ See #### Defined in -[packages/mermaid/src/config.type.ts:203](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L203) +[packages/mermaid/src/config.type.ts:204](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L204) --- @@ -310,7 +310,7 @@ Defines which main look to use for the diagram. #### Defined in -[packages/mermaid/src/config.type.ts:204](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L204) +[packages/mermaid/src/config.type.ts:205](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L205) --- @@ -465,7 +465,7 @@ This is useful when you want to control how to handle syntax errors in your appl #### Defined in -[packages/mermaid/src/config.type.ts:210](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L210) +[packages/mermaid/src/config.type.ts:211](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L211) --- @@ -512,13 +512,23 @@ You may also use `themeCSS` to override this value. --- +### venn + +• `Optional` **venn**: `VennDiagramConfig` + +#### Defined in + +[packages/mermaid/src/config.type.ts:201](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L201) + +--- + ### wrap • `Optional` **wrap**: `boolean` #### Defined in -[packages/mermaid/src/config.type.ts:202](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L202) +[packages/mermaid/src/config.type.ts:203](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L203) --- diff --git a/docs/config/setup/modules/defaultConfig.md b/docs/config/setup/modules/defaultConfig.md index 68486467c2..b4cf55dd1d 100644 --- a/docs/config/setup/modules/defaultConfig.md +++ b/docs/config/setup/modules/defaultConfig.md @@ -14,7 +14,7 @@ #### Defined in -[packages/mermaid/src/defaultConfig.ts:267](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L267) +[packages/mermaid/src/defaultConfig.ts:270](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L270) --- diff --git a/package.json b/package.json index c4c692d859..d956eff45c 100644 --- a/package.json +++ b/package.json @@ -136,6 +136,12 @@ "pnpm": { "patchedDependencies": { "roughjs": "patches/roughjs.patch" + }, + "overrides": { + "fmin>json2module": "npm:empty-npm-package@1.0.0", + "fmin>rollup": "npm:empty-npm-package@1.0.0", + "fmin>tape": "npm:empty-npm-package@1.0.0", + "fmin>uglify-js": "npm:empty-npm-package@1.0.0" } } } diff --git a/packages/mermaid/package.json b/packages/mermaid/package.json index afee6aab5f..b6a41a4bed 100644 --- a/packages/mermaid/package.json +++ b/packages/mermaid/package.json @@ -72,6 +72,7 @@ "@mermaid-js/parser": "workspace:^", "@types/d3": "^7.4.3", "@types/dompurify": "^3.0.5", + "@upsetjs/venn.js": "^1.4.2", "cytoscape": "^3.29.2", "cytoscape-cose-bilkent": "^4.1.0", "cytoscape-fcose": "^2.2.0", diff --git a/packages/mermaid/src/config.type.ts b/packages/mermaid/src/config.type.ts index 035a158e0d..d73c24d36a 100644 --- a/packages/mermaid/src/config.type.ts +++ b/packages/mermaid/src/config.type.ts @@ -198,6 +198,7 @@ export interface MermaidConfig { sankey?: SankeyDiagramConfig; packet?: PacketDiagramConfig; block?: BlockDiagramConfig; + venn?: VennDiagramConfig; dompurifyConfig?: DOMPurifyConfiguration; wrap?: boolean; fontSize?: number; @@ -1511,6 +1512,15 @@ export interface PacketDiagramConfig extends BaseDiagramConfig { export interface BlockDiagramConfig extends BaseDiagramConfig { padding?: number; } +/** + * The object containing configurations specific for Venn diagrams. + * + * This interface was referenced by `MermaidConfig`'s JSON-Schema + * via the `definition` "VennDiagramConfig". + */ +export interface VennDiagramConfig extends BaseDiagramConfig { + padding?: number; +} /** * This interface was referenced by `MermaidConfig`'s JSON-Schema * via the `definition` "FontConfig". diff --git a/packages/mermaid/src/defaultConfig.ts b/packages/mermaid/src/defaultConfig.ts index feae37f52d..12e189a8ee 100644 --- a/packages/mermaid/src/defaultConfig.ts +++ b/packages/mermaid/src/defaultConfig.ts @@ -252,6 +252,9 @@ const config: RequiredDeep = { packet: { ...defaultConfigJson.packet, }, + venn: { + ...defaultConfigJson.venn, + }, }; const keyify = (obj: any, prefix = ''): string[] => diff --git a/packages/mermaid/src/diagram-api/diagram-orchestration.ts b/packages/mermaid/src/diagram-api/diagram-orchestration.ts index d68a1c4982..2870578b4f 100644 --- a/packages/mermaid/src/diagram-api/diagram-orchestration.ts +++ b/packages/mermaid/src/diagram-api/diagram-orchestration.ts @@ -23,6 +23,7 @@ import sankey from '../diagrams/sankey/sankeyDetector.js'; import { packet } from '../diagrams/packet/detector.js'; import block from '../diagrams/block/blockDetector.js'; import architecture from '../diagrams/architecture/architectureDetector.js'; +import venn from '../diagrams/venn/vennDetector.js'; import { registerLazyLoadedDiagrams } from './detectType.js'; import { registerDiagram } from './diagramAPI.js'; @@ -92,6 +93,7 @@ export const addDiagrams = () => { packet, xychart, block, - architecture + architecture, + venn ); }; diff --git a/packages/mermaid/src/diagrams/venn/parser/venn.jison b/packages/mermaid/src/diagrams/venn/parser/venn.jison new file mode 100644 index 0000000000..1ce1b6efea --- /dev/null +++ b/packages/mermaid/src/diagrams/venn/parser/venn.jison @@ -0,0 +1,72 @@ +%lex +%options case-insensitive + +%x string +%x title +%% +\%\%(?!\{)[^\n]* /* skip comments */ +[^\}]\%\%[^\n]* /* skip comments */ +[\n\r]+ return 'NEWLINE'; +\%\%[^\n]* /* do nothing */ +\s+ /* skip */ +<> return 'EOF'; + +"title"\s[^#\n;]+ {return 'TITLE';} +"venn-beta" {return 'VENN';} +"sets" { return 'SETS'; } + +[+-]?(?:\d+(?:\.\d+)?|\.\d+) { return 'NUMERIC'; } +[A-Za-z_][A-Za-z0-9\-_]* { return 'IDENTIFIER'; } +[^":,]+ { return "OPT_VALUE"; } + +"," { return 'COMMA'; } +":" { return 'COLON'; } + +/lex + +%start start + +%% /* language grammar */ + +start + : VENN document 'EOF' { return $2; } + ; + +document + : /* empty */ { $$ = [] } + | document line {$1.push($2);$$ = $1} + ; + +line + : SPACE statement { $$ = $2 } + | statement { $$ = $1 } + | NEWLINE { $$=[];} + | EOF { $$=[];} + ; + +statement + : TITLE {yy.setDiagramTitle( $1.substr(6));$$=$1.substr(6);} + | SETS identifierList { yy.addSubsetData($identifierList, undefined); } + | SETS identifierList stylesOpt { yy.addSubsetData($identifierList, $stylesOpt); } + ; + +stylesOpt + : styleField { $$ = [$styleField] } + | stylesOpt COMMA styleField { $$ = [...$stylesOpt, $styleField] } + ; + +styleField + : IDENTIFIER COLON IDENTIFIER { $$ = [$1, $3] } + | IDENTIFIER COLON OPT_VALUE { $$ = [$IDENTIFIER, $OPT_VALUE] } + | IDENTIFIER COLON NUMERIC { $$ = [$IDENTIFIER, $NUMERIC] } + ; + +text: STR + ; + +identifierList + : IDENTIFIER { $$ = [$IDENTIFIER] } + | identifierList COMMA IDENTIFIER { $$ = [...$identifierList, $IDENTIFIER] } + ; + +%% diff --git a/packages/mermaid/src/diagrams/venn/parser/venn.spec.ts b/packages/mermaid/src/diagrams/venn/parser/venn.spec.ts new file mode 100644 index 0000000000..b0aad9d1e3 --- /dev/null +++ b/packages/mermaid/src/diagrams/venn/parser/venn.spec.ts @@ -0,0 +1,66 @@ +// @ts-ignore: jison doesn't export types +import venn from './venn.jison'; +import { db } from '../vennDB.js'; +import { expect } from 'vitest'; + +describe('Venn diagram', function () { + beforeEach(function () { + venn.parser.yy = db; + venn.parser.yy.clear(); + venn.parser.yy.getLogger = () => console; + }); + + test('simple', () => { + const str = `venn-beta + title foo bar + sets A + sets B + sets A,B + `; + venn.parse(str); + expect(db.getDiagramTitle()).toBe('foo bar'); + expect(db.getSubsetData()).toEqual([ + expect.objectContaining({ sets: ['A'], size: 10 }), + expect.objectContaining({ sets: ['B'], size: 10 }), + expect.objectContaining({ sets: ['A', 'B'], size: 2.5 }), + ]); + }); + + test('with options', () => { + const str = `venn-beta + title foo bar + sets A + sets B size : 20 + sets C size : 30, label : bar + sets A,D size : 5.3, label : foo + sets C, A,B + `; + venn.parse(str); + expect(db.getSubsetData()).toEqual([ + expect.objectContaining({ sets: ['A'], size: 10 }), + expect.objectContaining({ sets: ['B'], size: 20 }), + expect.objectContaining({ sets: ['C'], size: 30, label: 'bar' }), + expect.objectContaining({ sets: ['A', 'D'], size: 5.3, label: 'foo' }), + expect.objectContaining({ sets: ['A', 'B', 'C'], size: 1.1111111111111112 }), + ]); + }); + + test('with elements', () => { + const str = `venn-beta + title foo bar + sets A + sets B size : 20 + sets C size : 30, label : bar + sets A,D size : 5.3, label : foo + sets C, A,B + `; + venn.parse(str); + expect(db.getSubsetData()).toEqual([ + expect.objectContaining({ sets: ['A'], size: 10 }), + expect.objectContaining({ sets: ['B'], size: 20 }), + expect.objectContaining({ sets: ['C'], size: 30, label: 'bar' }), + expect.objectContaining({ sets: ['A', 'D'], size: 5.3, label: 'foo' }), + expect.objectContaining({ sets: ['A', 'B', 'C'], size: 1.1111111111111112 }), + ]); + }); +}); diff --git a/packages/mermaid/src/diagrams/venn/styles.ts b/packages/mermaid/src/diagrams/venn/styles.ts new file mode 100644 index 0000000000..8ba0f7459c --- /dev/null +++ b/packages/mermaid/src/diagrams/venn/styles.ts @@ -0,0 +1,162 @@ +import * as khroma from 'khroma'; + +/** Returns the styles given options */ +export interface VennChartStyleOptions { + arrowheadColor: string; + border2: string; + clusterBkg: string; + clusterBorder: string; + edgeLabelBackground: string; + fontFamily: string; + lineColor: string; + mainBkg: string; + nodeBorder: string; + nodeTextColor: string; + tertiaryColor: string; + textColor: string; + titleColor: string; +} + +const fade = (color: string, opacity: number) => { + // @ts-ignore TODO: incorrect types from khroma + const channel = khroma.channel; + + const r = channel(color, 'r'); + const g = channel(color, 'g'); + const b = channel(color, 'b'); + + // @ts-ignore incorrect types from khroma + return khroma.rgba(r, g, b, opacity); +}; + +const getStyles = (options: VennChartStyleOptions) => + ` + text.font-size-0 { + font-size: 100%; + } + text.font-size-1 { + font-size: 150%; + } + text.font-size-2 { + font-size: 200%; + } + text.font-size-3 { + font-size: 250%; + } + + + .label { + font-family: ${options.fontFamily}; + color: ${options.nodeTextColor || options.textColor}; + } + .cluster-label text { + fill: ${options.titleColor}; + } + .cluster-label span,p { + color: ${options.titleColor}; + } + + + + .label text,span,p { + fill: ${options.nodeTextColor || options.textColor}; + color: ${options.nodeTextColor || options.textColor}; + } + + .node rect, + .node circle, + .node ellipse, + .node polygon, + .node path { + fill: ${options.mainBkg}; + stroke: ${options.nodeBorder}; + stroke-width: 1px; + } + .flowchart-label text { + text-anchor: middle; + } + // .flowchart-label .text-outer-tspan { + // text-anchor: middle; + // } + // .flowchart-label .text-inner-tspan { + // text-anchor: start; + // } + + .node .label { + text-align: center; + } + .node.clickable { + cursor: pointer; + } + + .arrowheadPath { + fill: ${options.arrowheadColor}; + } + + .edgePath .path { + stroke: ${options.lineColor}; + stroke-width: 2.0px; + } + + .flowchart-link { + stroke: ${options.lineColor}; + fill: none; + } + + .edgeLabel { + background-color: ${options.edgeLabelBackground}; + rect { + opacity: 0.5; + background-color: ${options.edgeLabelBackground}; + fill: ${options.edgeLabelBackground}; + } + text-align: center; + } + + /* For html labels only */ + .labelBkg { + background-color: ${fade(options.edgeLabelBackground, 0.5)}; + // background-color: + } + + .node .cluster { + // fill: ${fade(options.mainBkg, 0.5)}; + fill: ${fade(options.clusterBkg, 0.5)}; + stroke: ${fade(options.clusterBorder, 0.2)}; + box-shadow: rgba(50, 50, 93, 0.25) 0px 13px 27px -5px, rgba(0, 0, 0, 0.3) 0px 8px 16px -8px; + stroke-width: 1px; + } + + .cluster text { + fill: ${options.titleColor}; + } + + .cluster span,p { + color: ${options.titleColor}; + } + /* .cluster div { + color: ${options.titleColor}; + } */ + + div.mermaidTooltip { + position: absolute; + text-align: center; + max-width: 200px; + padding: 2px; + font-family: ${options.fontFamily}; + font-size: 12px; + background: ${options.tertiaryColor}; + border: 1px solid ${options.border2}; + border-radius: 2px; + pointer-events: none; + z-index: 100; + } + + .flowchartTitleText { + text-anchor: middle; + font-size: 18px; + fill: ${options.textColor}; + } +`; + +export default getStyles; diff --git a/packages/mermaid/src/diagrams/venn/vennDB.ts b/packages/mermaid/src/diagrams/venn/vennDB.ts new file mode 100644 index 0000000000..9c7c30cabb --- /dev/null +++ b/packages/mermaid/src/diagrams/venn/vennDB.ts @@ -0,0 +1,58 @@ +import type { VennDB, VennData } from './vennTypes.js'; +import type { VennDiagramConfig } from '../../config.type.js'; +import { cleanAndMerge } from '../../utils.js'; +import { getConfig as commonGetConfig } from '../../config.js'; +import { + clear, + getAccDescription, + getAccTitle, + getDiagramTitle, + setAccDescription, + setAccTitle, + setDiagramTitle, +} from '../common/commonDb.js'; +import DEFAULT_CONFIG from '../../defaultConfig.js'; + +const subsets = new Array(); +export const addSubsetData: VennDB['addSubsetData'] = (identifierList, data) => { + const { size: rawSize, label, color, background } = Object.fromEntries(data ?? []); + + const size = rawSize ? parseFloat(rawSize) : 10 / Math.pow(identifierList.length, 2); + + subsets.push({ + sets: identifierList.sort(), + size, + label, + color, + background, + }); +}; +export const getSubsetData = () => { + return subsets; +}; + +const DEFAULT_PACKET_CONFIG: Required = DEFAULT_CONFIG.venn; +const getConfig = (): Required => { + return cleanAndMerge({ + ...DEFAULT_PACKET_CONFIG, + ...commonGetConfig().packet, + }); +}; + +const customClear = () => { + clear(); + subsets.length = 0; +}; + +export const db: VennDB = { + getConfig, + clear: customClear, + setAccTitle, + getAccTitle, + setDiagramTitle, + getDiagramTitle, + getAccDescription, + setAccDescription, + addSubsetData, + getSubsetData, +}; diff --git a/packages/mermaid/src/diagrams/venn/vennDetector.ts b/packages/mermaid/src/diagrams/venn/vennDetector.ts new file mode 100644 index 0000000000..57cf0ff0c4 --- /dev/null +++ b/packages/mermaid/src/diagrams/venn/vennDetector.ts @@ -0,0 +1,24 @@ +import type { + DiagramDetector, + DiagramLoader, + ExternalDiagramDefinition, +} from '../../diagram-api/types.js'; + +const id = 'venn'; + +const detector: DiagramDetector = (txt) => { + return /^\s*venn-beta/.test(txt); +}; + +const loader: DiagramLoader = async () => { + const { diagram } = await import('./vennDiagram.js'); + return { id, diagram }; +}; + +const plugin: ExternalDiagramDefinition = { + id, + detector, + loader, +}; + +export default plugin; diff --git a/packages/mermaid/src/diagrams/venn/vennDiagram.ts b/packages/mermaid/src/diagrams/venn/vennDiagram.ts new file mode 100644 index 0000000000..74b8af9543 --- /dev/null +++ b/packages/mermaid/src/diagrams/venn/vennDiagram.ts @@ -0,0 +1,13 @@ +import type { DiagramDefinition } from '../../diagram-api/types.js'; +// @ts-ignore: jison doesn't export types +import parser from './parser/venn.jison'; +import { db } from './vennDB.js'; +import flowStyles from './styles.js'; +import { renderer } from './vennRenderer.js'; + +export const diagram: DiagramDefinition = { + parser, + db, + renderer, + styles: flowStyles, +}; diff --git a/packages/mermaid/src/diagrams/venn/vennRenderer.ts b/packages/mermaid/src/diagrams/venn/vennRenderer.ts new file mode 100644 index 0000000000..5ec37de6cf --- /dev/null +++ b/packages/mermaid/src/diagrams/venn/vennRenderer.ts @@ -0,0 +1,90 @@ +import type { Diagram } from '../../Diagram.js'; +import type { VennDB, VennData } from './vennTypes.js'; +import type { DiagramRenderer, DrawDefinition } from '../../diagram-api/types.js'; +import { selectSvgElement } from '../../rendering-util/selectSvgElement.js'; +import * as venn from '@upsetjs/venn.js'; +import { schemeCategory10 as colors, select as d3select } from 'd3'; +// import { configureSvgSize } from '../../setupGraphViewbox.js'; + +export const draw: DrawDefinition = function ( + _text: string, + id: string, + _version: string, + diagObj: Diagram +): void { + const db = diagObj.db as VennDB; + const title = db.getDiagramTitle?.(); + const titleHeight = title ? 48 : 0; + const sets = db.getSubsetData(); + const customFontColorMap = new Map(); + const customBackgroundColorMap = new Map(); + for (const set of sets) { + if (set.color) { + customFontColorMap.set(set.sets, set.color); + } + if (set.background) { + customBackgroundColorMap.set(set.sets, set.background); + } + } + + const svg = selectSvgElement(id); + const svgWidth = 1600; + const svgHeight = 900; + svg.attr('viewbox', `0 0 ${svgWidth} ${svgHeight}`); + + if (title) { + svg + .append('text') + .text(title) + .attr('font-size', '32px') + .attr('text-anchor', 'middle') + .attr('dominant-baseline', 'middle') + .attr('x', '50%') + .attr('y', 32); + } + + // Create a dummy root to render the Venn diagram in + const dummyD3root = d3select(document.createElement('div')); + const vennDiagram = venn + .VennDiagram() + .width(svgWidth) + .height(svgHeight - titleHeight); + dummyD3root.datum(sets).call(vennDiagram as never); + + // Styling + dummyD3root + .selectAll('.venn-circle path') + .style('fill-opacity', 0) + .style('stroke-width', 5) + .style('stroke-opacity', 0.3) + .style('stroke', (_, i) => colors[i]); + dummyD3root.selectAll('.venn-circle text').style('font-size', '48px'); //.style('fill', 'white'); + + dummyD3root + .selectAll('.venn-intersection text') + .style('font-size', '48px') + .style('fill', (e) => { + const d = e as VennData; + return customFontColorMap.get(d.sets) || 'b;acl'; + }); + + dummyD3root + .selectAll('.venn-intersection path') + .style('fill-opacity', (e) => { + const d = e as VennData; + return customBackgroundColorMap.get(d.sets) ? 1 : 0; + }) + .style('fill', (e) => { + const d = e as VennData; + return customBackgroundColorMap.get(d.sets) || 'white'; + }); + const vennBox = svg.append('svg').attr('y', titleHeight); + + // Transfer the Venn diagram to the real SVG + vennBox.append(() => dummyD3root.select('svg').node()); + + // Not needed? + // configureSvgSize(svg, svgHeight, svgWidth, true); +}; + +export const renderer: DiagramRenderer = { draw }; diff --git a/packages/mermaid/src/diagrams/venn/vennTypes.ts b/packages/mermaid/src/diagrams/venn/vennTypes.ts new file mode 100644 index 0000000000..1dd2ce3a9c --- /dev/null +++ b/packages/mermaid/src/diagrams/venn/vennTypes.ts @@ -0,0 +1,15 @@ +import type { DiagramDBBase } from '../../diagram-api/types.js'; +import type { VennDiagramConfig } from '../../config.type.js'; + +export interface VennData { + sets: string[]; + size: number; + label: string | undefined; + color: string | undefined; + background: string | undefined; +} + +export interface VennDB extends DiagramDBBase { + addSubsetData: (identifierList: string[], data: [string, string][] | undefined) => void; + getSubsetData: () => VennData[]; +} diff --git a/packages/mermaid/src/docs/.vitepress/config.ts b/packages/mermaid/src/docs/.vitepress/config.ts index 4dec8231f7..c74cc11980 100644 --- a/packages/mermaid/src/docs/.vitepress/config.ts +++ b/packages/mermaid/src/docs/.vitepress/config.ts @@ -158,6 +158,7 @@ function sidebarSyntax() { { text: 'Block Diagram 🔥', link: '/syntax/block' }, { text: 'Packet 🔥', link: '/syntax/packet' }, { text: 'Architecture 🔥', link: '/syntax/architecture' }, + { text: 'Venn 🔥', link: '/syntax/venn' }, { text: 'Other Examples', link: '/syntax/examples' }, ], }, diff --git a/packages/mermaid/src/schemas/config.schema.yaml b/packages/mermaid/src/schemas/config.schema.yaml index a7b3549ebf..653c810ff9 100644 --- a/packages/mermaid/src/schemas/config.schema.yaml +++ b/packages/mermaid/src/schemas/config.schema.yaml @@ -54,6 +54,7 @@ required: - packet - block - look + - venn properties: theme: description: | @@ -289,6 +290,8 @@ properties: $ref: '#/$defs/PacketDiagramConfig' block: $ref: '#/$defs/BlockDiagramConfig' + venn: + $ref: '#/$defs/VennDiagramConfig' dompurifyConfig: title: DOM Purify Configuration description: Configuration options to pass to the `dompurify` library. @@ -2177,6 +2180,18 @@ $defs: # JSON Schema definition (maybe we should move these to a separate file) minimum: 0 default: 8 + VennDiagramConfig: + title: Venn Diagram Config + allOf: [{ $ref: '#/$defs/BaseDiagramConfig' }] + description: The object containing configurations specific for Venn diagrams. + type: object + unevaluatedProperties: false + properties: + padding: + type: number + minimum: 0 + default: 8 + FontCalculator: title: Font Calculator description: | diff --git a/packages/mermaid/src/styles.spec.ts b/packages/mermaid/src/styles.spec.ts index 70e9e7ec54..b53fa88e4f 100644 --- a/packages/mermaid/src/styles.spec.ts +++ b/packages/mermaid/src/styles.spec.ts @@ -29,6 +29,7 @@ import timeline from './diagrams/timeline/styles.js'; import mindmap from './diagrams/mindmap/styles.js'; import packet from './diagrams/packet/styles.js'; import block from './diagrams/block/styles.js'; +import venn from './diagrams/venn/styles.js'; import themes from './themes/index.js'; function checkValidStylisCSSStyleSheet(stylisString: string) { @@ -99,6 +100,7 @@ describe('styles', () => { block, timeline, packet, + venn, })) { test(`should return a valid style for diagram ${diagramId} and theme ${themeId}`, async () => { const { default: getStyles, addStylesForDiagram } = await import('./styles.js'); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0a9bc7d758..62596a11aa 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,6 +4,12 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false +overrides: + fmin>json2module: npm:empty-npm-package@1.0.0 + fmin>rollup: npm:empty-npm-package@1.0.0 + fmin>tape: npm:empty-npm-package@1.0.0 + fmin>uglify-js: npm:empty-npm-package@1.0.0 + patchedDependencies: roughjs: hash: vxb6t6fqvzyhwhtjiliqr25jyq @@ -229,6 +235,9 @@ importers: '@types/dompurify': specifier: ^3.0.5 version: 3.0.5 + '@upsetjs/venn.js': + specifier: ^1.4.2 + version: 1.4.2 cytoscape: specifier: ^3.29.2 version: 3.30.2 @@ -3136,6 +3145,9 @@ packages: peerDependencies: vite: ^2.9.0 || ^3.0.0-0 || ^4.0.0 || ^5.0.0-0 + '@upsetjs/venn.js@1.4.2': + resolution: {integrity: sha512-sFyczoc4T0FonsMiHo/7AXqTpuBOOlIGTIMli5tFdfTXUECogGsnHY4+BQfHX9EaYk4zxBno/9PKsxEfY3CZLA==} + '@vite-pwa/vitepress@0.4.0': resolution: {integrity: sha512-MrsSCK5EBCzQAQgq5/3XHaFIjkypda58Wzy6PkDwZoRHnWexik0C2GUxMOe+RA+qdpGxB0mEkhqajeOmuYMvhw==} peerDependencies: @@ -4130,6 +4142,9 @@ packages: resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} engines: {node: '>= 0.6'} + contour_plot@0.0.1: + resolution: {integrity: sha512-Nil2HI76Xux6sVGORvhSS8v66m+/h5CwFkBJDO+U5vWaMdNC0yXNCsGDPbzPhvqOEU5koebhdEvD372LI+IyLw==} + convert-source-map@1.9.0: resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} @@ -4776,6 +4791,9 @@ packages: emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + empty-npm-package@1.0.0: + resolution: {integrity: sha512-q4Mq/+XO7UNDdMiPpR/LIBIW1Zl4V0Z6UT9aKGqIAnBCtCb3lvZJM1KbDbdzdC8fKflwflModfjR29Nt0EpcwA==} + encodeurl@1.0.2: resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} engines: {node: '>= 0.8'} @@ -5324,6 +5342,9 @@ packages: flexsearch@0.7.43: resolution: {integrity: sha512-c5o/+Um8aqCSOXGcZoqZOm+NqtVwNsvVpWv6lfmSclU954O3wvQKxxK8zj74fPaSJbXpSLTs4PRhh+wnoCXnKg==} + fmin@0.0.2: + resolution: {integrity: sha512-sSi6DzInhl9d8yqssDfGZejChO8d2bAGIpysPsvYsxFe898z89XhCZg6CPNV3nhUhFefeC/AXZK2bAJxlBjN6A==} + focus-trap@7.6.0: resolution: {integrity: sha512-1td0l3pMkWJLFipobUcGaf+5DTY4PLDDrcqoSaKP8ediO/CoWCCYk/fT/Y2A4e6TNB+Sh6clRJCjOPPnKoNHnQ==} @@ -12606,6 +12627,13 @@ snapshots: transitivePeerDependencies: - rollup + '@upsetjs/venn.js@1.4.2': + dependencies: + fmin: 0.0.2 + optionalDependencies: + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + '@vite-pwa/vitepress@0.4.0(vite-plugin-pwa@0.19.8(vite@5.4.8(@types/node@20.16.11)(terser@5.34.1))(workbox-build@7.1.1(@types/babel__core@7.20.5))(workbox-window@7.1.0))': dependencies: vite-plugin-pwa: 0.19.8(vite@5.4.8(@types/node@20.16.11)(terser@5.34.1))(workbox-build@7.1.1(@types/babel__core@7.20.5))(workbox-window@7.1.0) @@ -13778,6 +13806,8 @@ snapshots: content-type@1.0.5: {} + contour_plot@0.0.1: {} + convert-source-map@1.9.0: {} convert-source-map@2.0.0: {} @@ -14554,6 +14584,8 @@ snapshots: emoji-regex@9.2.2: {} + empty-npm-package@1.0.0: {} + encodeurl@1.0.2: {} encodeurl@2.0.0: {} @@ -15364,6 +15396,14 @@ snapshots: flexsearch@0.7.43: {} + fmin@0.0.2: + dependencies: + contour_plot: 0.0.1 + json2module: empty-npm-package@1.0.0 + rollup: empty-npm-package@1.0.0 + tape: empty-npm-package@1.0.0 + uglify-js: empty-npm-package@1.0.0 + focus-trap@7.6.0: dependencies: tabbable: 6.2.0