├── DataviewMermaid.md └── README.md /DataviewMermaid.md: -------------------------------------------------------------------------------- 1 | --- 2 | Direction: "LR" # LR | TD 3 | ShowCode: false # Code or Diagram 4 | SubGroupNames: ["Authors", "Fiction", "Non-Fiction"] # Subgroup names ("" or " " allowed) 5 | RemoveOrphans: false #Only keep nodes that link to something 6 | KeepLinksWithoutSource: true #Only draw links that start from an imported node 7 | KeepLinksWithoutDest: true #Only draw links that end at an imported node 8 | 9 | # Array of DQL queries to pull in nodes - see ReadMe 10 | # | Link | DisplayName | OpenBracket | CloseBracket | Style | 11 | Nodes: 12 | - 'TABLE WITHOUT ID Sources, "", "{{", "}}", "red" FROM #T/📚_Book WHERE Sources FLATTEN Sources SORT Kind, Topics, Sources' 13 | - 'TABLE file.name, "([", "])", "yellow" FROM #T/📚_Book WHERE Kind = "Fiction" SORT Topics, Sources' 14 | - 'TABLE file.name, "([", "])", "aqua" FROM #T/📚_Book WHERE Kind = "Non-Fiction" SORT Topics, Sources' 15 | 16 | # Array of DQL queries to pull in relationships between nodes - see ReadMe 17 | # | Source | Destination | Arrow | 18 | Links: 19 | - 'TABLE WITHOUT ID Sources, file.name, "-- " + dateformat(Published, "dd MMM yyyy") + " -->" FROM #T/📚_Book WHERE Sources FLATTEN Sources' 20 | 21 | # Custom node styles if you don't like the built in colours (like red, purple, etc) - see ReadMe 22 | Styles: 23 | - 'classDef Custom1 fill:#DDEEFF,color:#000,stroke:#000,stroke-width:1px' 24 | --- 25 | # Mermaid Output 26 | ``` dataviewjs 27 | //YAML Settings 28 | let c = dv.current(); 29 | let direction = c.Direction ?? "LR"; 30 | let showAsCode = c.ShowCode ?? false; 31 | let subGroups = c.SubGroupNames ?? []; 32 | let removeOrphans = c.RemoveOrphans ?? false; 33 | let keepLinksWithoutSource = c.KeepLinksWithoutSource ?? true; 34 | let keepLinksWithoutDest = c.KeepLinksWithoutDes ?? true; 35 | 36 | var nodeQueries = c.Nodes ?? ['TABLE WITHOUT ID "[[No Nodes]]", "No Nodes", "[", "]", "red" FROM "" LIMIT 1']; 37 | var linkQueries = c.Links ?? []; 38 | var styles = c.Styles ?? []; 39 | 40 | //DEFAULTS 41 | let def = {open: "[", close: "]", style: "default"}; 42 | styles.push("classDef red fill:#f2b5bd88,color:#c94f60,stroke:#c94f60,stroke-width:1px"); 43 | styles.push("classDef orange fill:#efc5b388,color:#c76a43,stroke:#c76a43,stroke-width:1px"); 44 | styles.push("classDef yellow fill:#f2d9b588,color:#b68035,stroke:#b68035,stroke-width:1px"); 45 | styles.push("classDef green fill:#bde0bd88,color:#3c9f3e,stroke:#3c9f3e,stroke-width:1px"); 46 | styles.push("classDef mint fill:#c9e4d688,color:#24a864,stroke:#24a864,stroke-width:1px"); 47 | styles.push("classDef aqua fill:#c0dede88,color:#339999,stroke:#339999,stroke-width:1px"); 48 | styles.push("classDef blue fill:#cbcde188,color:#6d73b0,stroke:#6d73b0,stroke-width:1px"); 49 | styles.push("classDef purple fill:#d4c0e388,color:#8c59b1,stroke:#8c59b1,stroke-width:1px"); 50 | styles.push("classDef pink fill:#e6c1d788,color:#b54a88,stroke:#b54a88,stroke-width:1px"); 51 | styles.push("classDef grey fill:#d3cfcf88,color:#7e7273,stroke:#7e7273,stroke-width:1px"); 52 | styles.push("classDef default fill:#88888888,color:#000,stroke:#000,stroke-width:1px"); 53 | 54 | //-------------- 55 | //SHOULDN'T NEED TO CHANGE CODE BELOW HERE 56 | //-------------- 57 | 58 | //FRONTMATTER STRING 59 | let mFront = (showAsCode ? "```\n" : "```mermaid\n" ); 60 | mFront += "graph " + direction + "\n\n"; 61 | 62 | //CREATE ARRAY OF ALL NODES 63 | var nodesArray = []; 64 | for (var q = 0; q < nodeQueries.length; q++) { 65 | let DQLResults = await dv.tryQuery(nodeQueries[q]); 66 | DQLResults.values.forEach(node => { 67 | let ref = getNodeRefName(node[0]); 68 | let ID = getLegalCharacters(ref); 69 | let display = cleanLabel(node[0], node[1]); 70 | if (!node[4]) {node[4] = "default"} 71 | let nodeObj = {qID: q, ID:ID, link:ref, name, display:display, 72 | open:node[2], close:node[3], style:node[4], linked: false} 73 | nodesArray.push(nodeObj); 74 | }); 75 | }; 76 | 77 | //CREATE MERMAID STRING FOR LINKS (CHECK FOR ORPHANS) 78 | nodesArray = dv.array(nodesArray); 79 | var mLinks = "%%---LINKS---\n"; 80 | for (var q = 0; q < linkQueries.length; q++) { 81 | let DQLResults = await dv.tryQuery(linkQueries[q]); 82 | DQLResults.values.forEach(link => { 83 | let sRef = getNodeRefName(link[0]); 84 | let sID = getLegalCharacters(sRef); 85 | let dRef = getNodeRefName(link[1]); 86 | let dID = getLegalCharacters(dRef); 87 | let arrow = (link[2] == "" ? " --> " : " " + link[2] + " "); 88 | 89 | //Find nodes if they were imported explicitly 90 | let sIndex = nodesArray.ID.indexOf(sID); 91 | let dIndex = nodesArray.ID.indexOf(dID); 92 | 93 | //Mark those nodes as linked if they were found 94 | if (sIndex >= 0) {nodesArray[sIndex].linked = true;} 95 | if (dIndex >= 0) {nodesArray[dIndex].linked = true;} 96 | 97 | //if both nodes exist OR we have permission to create them 98 | if ((sIndex >= 0 || keepLinksWithoutSource) && 99 | (dIndex >= 0 || keepLinksWithoutDest)) { 100 | 101 | var sString = " "; 102 | if (sIndex >= 0) { sString += sID; } 103 | else { sString += getNodeDef(sID, def.open, "", 104 | cleanLabel(sRef), def.close, def.style); } 105 | var dString = ""; 106 | if (dIndex >= 0) { dString += dID; } 107 | else { dString += getNodeDef(dID, def.open, "", 108 | cleanLabel(dRef), def.close, def.style); } 109 | 110 | mLinks += sString + arrow + dString + "\n"; 111 | } 112 | }); 113 | mLinks = mLinks + "\n"; 114 | }; 115 | 116 | //CREATE MERMAID STRING FOR THE NODES 117 | var mNodes = "%%---NODES---\n"; 118 | var qID = -1; 119 | var subGroup = ""; 120 | nodesArray.forEach(n => { 121 | //if node is linked, or I don't care about orphans 122 | if (n.linked || !removeOrphans) { 123 | //new subgroup 124 | if (qID != n.qID) { 125 | if (subGroup != "") {mNodes += "end\n\n"} //close previous 126 | qID = n.qID; 127 | subGroup = subGroups[qID] ?? ""; 128 | if (subGroup != "") { 129 | mNodes += "subgraph SG" + qID + " [" + subGroup + "]\n"; 130 | } 131 | } 132 | mNodes += " " 133 | mNodes += getNodeDef(n.ID, n.open, n.link, n.display, n.close, n.style); 134 | } 135 | }); 136 | if (subGroup != "") {mNodes += "end\n\n"} 137 | 138 | //STYLE CLASSES 139 | var styleClasses = "%%---STYLES---\n"; 140 | for (var s = 0; s < styles.length; s++) { 141 | styleClasses += styles[s] + "\n\n" 142 | } 143 | 144 | //OUTPUT 145 | dv.span(mFront + mNodes + mLinks + styleClasses + "```"); 146 | 147 | 148 | //-------------- 149 | //HELPER FUNCTIONS 150 | //-------------- 151 | function getNodeDef(ID, open, href, display, close, style) { 152 | var def = ""; 153 | def += ID + open + "\"
"; 154 | def += (href != "" ? "" : ""); 155 | def += display; 156 | def += (href != "" ? "" : ""); 157 | def += "
\"" + close + ":::" + style + "\n"; 158 | return def 159 | } 160 | 161 | function getNodeRefName(node) { 162 | // return file name if valid page, otherwise return a string representation 163 | if (dv.page(node)) { 164 | return dv.page(node).file.name; 165 | } else { 166 | return cleanLinkyString(node); 167 | } 168 | } 169 | 170 | function getLegalCharacters(name) { 171 | //Remove characters not allowed in Mermaid IDs 172 | return name.replace(/[^a-zA-Z0-9]/g, ''); 173 | } 174 | 175 | function cleanLinkyString(link) { 176 | //Remove [[ | ]] from linky strings 177 | return String(link).split("|")[0].replace(/[\[\]]/g, ''); 178 | } 179 | 180 | function cleanLabel(node, display) { 181 | //Try to clean up the display name, but if empty - infer from node 182 | var wrapped = ""; 183 | if (!display || display == "") { 184 | wrapped = getNodeRefName(node); 185 | } else { 186 | wrapped = cleanLinkyString(String(display)); 187 | } 188 | wrapped = wrapped.replace(/(?![^\n]{1,20}$)([^\n]{1,20})\s/g, '$1\
'); 189 | wrapped = wrapped.replace(/[❝❞]/g,'\\"'); 190 | return wrapped 191 | } 192 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ReadMe 2 | This dataviewjs script **dynamically renders DQL queries into a Mermaid flow diagram** based on queries and and formatting instructions contained in the file's YAML. 3 | 4 | The code **can show either the diagram OR the raw Mermaid code**, which could then be copied/embedded/shared without needing dataview or the underlying source data. 5 | 6 | It has been designed to **require no JS expertise** - with all standard functions available through DQL and the YAML parameters. 7 | ## TLDR 8 | - All setup is in YAML dataview fields, so no JS expertise required. 9 | - Handles internal obsidian links - **diagrams are clickable**. 10 | - Fully customisable names / colours / node shapes / link style / link labels 11 | ### Basic Process 12 | - Save the md file to your vault. 13 | - Modify it with DQL queries that list the nodes you want drawn. 14 | - Modify it with DQL queries that list the relationships between nodes. 15 | - The underlying dataviewJS will run your queries, process the results, and convert everything into mermaid syntax. 16 | 17 | **Your options are mostly limited by your DQL creativity.** 18 | 19 | ## Example Output 20 | ![Image](https://user-images.githubusercontent.com/110291999/183233644-ee6a6318-f085-4077-a735-d8be135d9462.png) 21 | 22 | ### Note about Sorting 23 | Nodes and links are added to the Mermaid code in the order that they are imported - nodes first, then links. 24 | 25 | Ordering the DQL queries themselves (and SORTing the results within) can be very important for getting a clean layout in your final diagram. 26 | 27 | I suggest playing with these orderings until you are happy with the results - and then moving the raw Mermaid code to a separate file to massage the order of key nodes and links if required. 28 | 29 | ## Example YAML Settings 30 | The following parameters can be included in the YAML of the charting page. Alternatively, these could be hard coded into the JS code itself - and the corresponding YAML variables have been listed at the top of the JS code to make this easier. 31 | 32 | However, it may be easier to leave them in the YAML while you're still playing with the DQL queries and their ordering. 33 | 34 | ``` 35 | --- 36 | Direction: "LR" 37 | ShowCode: false 38 | SubGroupNames: ["Authors", "Fiction", "Non-Fiction"] 39 | RemoveOrphans: false 40 | KeepLinksWithoutSource: true 41 | KeepLinksWithoutDest: true 42 | 43 | Nodes: 44 | - 'TABLE WITHOUT ID Sources, "", "{{", "}}", "red" FROM #T/📚_Book WHERE Sources FLATTEN Sources SORT Kind, Topics, Sources' 45 | - 'TABLE file.name, "([", "])", "yellow" FROM #T/📚_Book WHERE Kind = "Fiction" SORT Topics, Sources' 46 | - 'TABLE file.name, "([", "])", "aqua" FROM #T/📚_Book WHERE Kind = "Non-Fiction" SORT Topics, Sources' 47 | 48 | Links: 49 | - 'TABLE WITHOUT ID Sources, file.name, "-- " + dateformat(Published, "dd MMM yyyy") + " -->" FROM #T/📚_Book WHERE Sources FLATTEN Sources' 50 | 51 | Styles: 52 | - 'classDef Custom1 fill:#DDEEFF,color:#000,stroke:#000,stroke-width:1px' 53 | --- 54 | ``` 55 | 56 | ## YAML Basics 57 | ``` 58 | Direction: "LR" 59 | ShowCode: false 60 | SubGroupNames: ["Authors", "Fiction", "Non-Fiction"] 61 | RemoveOrphans: false 62 | KeepLinksWithoutSource: true 63 | KeepLinksWithoutDest: true 64 | ``` 65 | 66 | | Field | Default | Description | 67 | | - | - | - | 68 | | **Direction** | *"LR"* | The Layout direction for the chart - either "LR" or "TD".| 69 | | **ShowCode** | *false* | Whether to display results as a chart or as raw mermaid code.| 70 | | **SubGroupNames** | *[ ]* | An optional array of subgroup name for each node query. Use "" to hide, or " " for blank.| 71 | | **RemoveOrphans** | *false* | If true, only nodes that participate in relationships (i.e. nodes that ultimately show up in the [[#Link Queries]] will be drawn. If false, all nodes imported by the [[#Node Queries]] will be drawn, regardless of whether they connect to anything else.| 72 | | **KeepLinksWithoutSource** | *true* | If true, unformatted nodes will be created to represent the source of a link when there isn't a real node imported explicitly by [[#Node Queries]]. If false, such 'sourceless' links will be discarded. Mostly useful for cleaning up when you don't want to add too many filter criteria to your link queries.| 73 | | **KeepLinksWithoutDest** | *true* | If true, unformatted nodes will be created to represent the destination of a link when there isn't a real node imported explicitly by [[#Node Queries]]. If false, such links will be discarded instead. Mostly useful for cleaning up when you don't want to add too many filter criteria to your link queries.| 74 | 75 | 76 | ## YAML Node Queries 77 | ``` 78 | Nodes: 79 | - 'TABLE WITHOUT ID Sources, "", "{{", "}}", "red" FROM #T/📚_Book WHERE Sources FLATTEN Sources SORT Kind, Topics, Sources' 80 | - 'TABLE file.name, "([", "])", "yellow" FROM #T/📚_Book WHERE Kind = "Fiction" SORT Topics, Sources' 81 | - 'TABLE file.name, "([", "])", "aqua" FROM #T/📚_Book WHERE Kind = "Non-Fiction" SORT Topics, Sources' 82 | ``` 83 | 84 | Defined in the YAML by a **Nodes:** field, and structured as an array of DQL query strings that each return a set of nodes and formatting information to draw them on the Mermaid diagram. 85 | 86 | Queries should return at least 5 columns as follows (column names, and any extra columns will be ignored): 87 | | Page | DisplayName | OpenBracket | CloseBracket | StyleClass | 88 | |-|-|-|-|-| 89 | | [[Test]] | "😀 Test" | "([" | "])" | "red" | 90 | 91 | - *Column 1* should be a *page object* (though an unlinked reference or string will also work). 92 | - *Column 2* should be a *display name* string. An empty string can be provided and the code will infer a display name from object in Column 1. 93 | - *Column 3* should be a string containing Mermaid open [bracket syntax](https://mermaid-js.github.io/mermaid/#/flowchart?id=node-shapes). 94 | - *Column 4* should be a string containing the corresponding closing bracket syntax. 95 | - *Column 5* should be the string name of a Style class. The code includes a set of standard colour options to choose from: *red, orange, yellow, green, mint, aqua, blue, purple, pink, grey*. However [[#Custom Styles]] can also be created. This column is not required. 96 | 97 | ## YAML Link Queries 98 | ``` 99 | Links: 100 | - 'TABLE WITHOUT ID Sources, file.name, "-- " + dateformat(Published, "dd MMM yyyy") + " -->" FROM #T/📚_Book WHERE Sources FLATTEN Sources' 101 | ``` 102 | 103 | Defined in the YAML by a **Links:** field, and structured as an array of DQL query strings that each return a set of relationships between two nodes to represent the arrows/links on a Mermaid diagram. 104 | 105 | Queries should return at least 3 columns as follows (column names, and any extra columns will be ignored): 106 | | Source | Dest | link | 107 | |-|-|-| 108 | | [[Test]] | [[Test2]] | "-- By -->" | 109 | 110 | - *Column 1* should be a *page object* representing the source of the relationship (however an unlinked reference or string will also work). 111 | - *Column 2* should be a *page object* representing the destination of the relationship (however an unlinked reference or string will also work). 112 | - *Column 3* should be a string representation of Mermaid [link syntax](https://mermaid-js.github.io/mermaid/#/flowchart?id=links-between-nodes). 113 | 114 | ## YAML Subgraph 115 | You can assign all nodes from a node query into a subgraph. For example if you have 116 | ``` 117 | Nodes: 118 | - 'TABLE "", "([", "])" from "folder A"' 119 | - 'TABLE "", "([", "])" from "folder B"' 120 | - 'TABLE "", "([", "])" from "folder C"' 121 | ``` 122 | Then the nodes of each query will be assigned into a subgroup by using this `SubGroupNames: ["A", "B", "C"]`. 123 | 124 | Note: placeholder nodes (nodes that exist in [[#Link Queries]], but don't exists explicitly in [[#Node Queries]]) are not included in the subgraphs. 125 | 126 | ## YAML Custom Styles 127 | ``` 128 | Styles: 129 | - 'classDef Custom1 fill:#DDEEFF,color:#000,stroke:#000,stroke-width:1px' 130 | ``` 131 | 132 | Defined in the YAML by a **Styles:** field, and structured as an array of Mermaid Class format strings. These should include a unique identifier (Custom1 in the example below) that can then be used in any [[#Node Queries]] to style the node. 133 | 134 | - *e.g:* "classDef Custom1 fill:#DDEEFF,color:#000,stroke:#000,stroke-width:1px" 135 | - Note that the included dataviewJS code also defines a set of standard colour options to choose from: *red, orange, yellow, green, mint, aqua, blue, purple, pink, grey*; so it is not necessary to define any custom styles unless the defaults aren't suitable. 136 | 137 | ## Other Defaults (defined in the dataviewJS itself) 138 | - **def** - *Default = [ open: "[", close: "]", style: "default"]* - an object containing the default open, close, and style parameters used when creating placeholder nodes (Nodes that exist in [[#Link Queries]], but don't exists explicitly in [[#Node Queries]]). 139 | - **styles.push** - A collection of default style settings for the standard colours: *red, orange, yellow, green, mint, aqua, blue, purple, pink, grey*. 140 | 141 | ## Tip 142 | As you can see that the YALM and the actual code are quite long, so they together can clutter your note when you want to jump to the real data you want to edit. To avoid this, you can create a supplement note that contains the code, then in your main note you can [embed](https://help.obsidian.md/How+to/Embed+files "Embed files - Obsidian Help") it with `![[supplement note^graph code]]` 143 | --------------------------------------------------------------------------------