At last something is working

This commit is contained in:
Nikolay Rozhkov 2023-06-18 01:32:45 +03:00
parent 1782f69c8f
commit afaf87e414
9 changed files with 278 additions and 72 deletions

View File

@ -17,22 +17,20 @@
<h2>Simple flow</h2>
<pre class="mermaid">
sankey
a--10->b
c--20->b
b--15->d
a-- 7->d
node_a->10->node_b;
node_c->20->node_b;
node_b->15->node_d;
node_a-> 7->node_d;
</pre>
<script type="module">
import mermaid from './mermaid.esm.mjs';
mermaid.initialize({
theme: 'default',
// themeCSS: '.node rect { fill: red; }',
logLevel: 3,
securityLevel: 'loose',
flowchart: { curve: 'basis' },
gantt: { axisFormat: '%m/%d/%Y' },
sequence: { actorMargin: 50 },
// sequenceDiagram: { actorMargin: 300 } // deprecated
});
</script>
</body>

View File

@ -13,8 +13,10 @@ services:
- ./:/mermaid
- root_cache:/root/.cache
- root_local:/root/.local
- root_local:/root/.npm
ports:
- 9000:9000
volumes:
root_cache:
root_local:
root_npm:

View File

@ -85,7 +85,12 @@
"coveralls": "^3.1.1",
"cypress": "^12.10.0",
"cypress-image-snapshot": "^4.0.1",
<<<<<<< HEAD
"esbuild": "^0.18.0",
=======
"d3-sankey": "^0.12.3",
"esbuild": "^0.17.18",
>>>>>>> 84c278d0 (At last something is working)
"eslint": "^8.39.0",
"eslint-config-prettier": "^8.8.0",
"eslint-plugin-cypress": "^2.13.2",

View File

@ -32,6 +32,7 @@ export interface MermaidConfig {
mindmap?: MindmapDiagramConfig;
gitGraph?: GitGraphDiagramConfig;
c4?: C4DiagramConfig;
sankey?: SankeyDiagramConfig;
dompurifyConfig?: DOMPurify.Config;
wrap?: boolean;
fontSize?: number;
@ -411,6 +412,8 @@ export interface FlowchartDiagramConfig extends BaseDiagramConfig {
wrappingWidth?: number;
}
export interface SankeyDiagramConfig extends BaseDiagramConfig {}
export interface FontConfig {
fontSize?: string | number;
fontFamily?: string;

View File

@ -40,6 +40,7 @@
/lex
%start start
%left ARROW
%% // language grammar
@ -66,10 +67,19 @@ attribute: ATTRIBUTE EQUAL value | ATTRIBUTE;
value: VALUE | OPEN_STRING STRING CLOSE_STRING;
stream: node ARROW AMOUNT ARROW tail { yy.addNode($1); yy.addLink(); };
tail: stream | node;
stream: node[source] ARROW amount ARROW tail[target] {
$$=$source;
yy.addLink($source, $target, $amount);
};
node: NODE { yy.addNode($1) };
amount: AMOUNT { $$=parseFloat($AMOUNT); };
tail
: stream { $$ = $stream }
| node { $$ = $node; }
;
node: NODE { $$ = yy.addNode($NODE); };
// : NODE exhaust intake exhaust_chain optional_attributes EOS
// exhaust_chain: ARROW AMOUNT intake_chain | ;

View File

@ -21,7 +21,7 @@ describe('Sankey diagram', function () {
it('recognizes one flow', () => {
const str = `
sankey
a -> 30 -> b -> 20 -> c
node_a -> 30 -> node_b -> 20 -> node_c
`;
parser.parse(str);
@ -30,9 +30,9 @@ describe('Sankey diagram', function () {
it('recognizes multiple flows', () => {
const str = `
sankey
a -> 30 -> b -> 12 -> e
c -> 30 -> d -> 12 -> e
c -> 40 -> e -> 12 -> q
node_a -> 30 -> node_b -> 12 -> node_e
node_c -> 30 -> node_d -> 12 -> node_e
node_c -> 40 -> node_e -> 12 -> node_q
`;
parser.parse(str);
@ -44,7 +44,7 @@ describe('Sankey diagram', function () {
sankey
node[]
node[attr=1]
a -> 30 -> b
node_a -> 30 -> node_b
node[attrWithoutValue]
node[attr = 3]
node[attr1 = 23413 attr2=1234]

View File

@ -1,12 +1,12 @@
import { log } from '../../logger.js';
import mermaidAPI from '../../mermaidAPI.js';
// import { log } from '../../logger.js';
// import mermaidAPI from '../../mermaidAPI.js';
import * as configApi from '../../config.js';
import common from '../common/common.js';
import {
// setAccTitle,
// getAccTitle,
// getAccDescription,
// setAccDescription,
setAccTitle,
getAccTitle,
getAccDescription,
setAccDescription,
setDiagramTitle,
getDiagramTitle,
clear as commonClear,
@ -20,46 +20,54 @@ import {
// return text.trimStart().replace(/^\s*%%(?!{)[^\n]+\n?/gm, '');
// };
let links: Array<Link> = [];
let nodes: { [id: string]: Node } = {};
let nodes: Array<Node> = [];
let nodesHash: Record<string, Node> = {};
const clear = function () {
const clear = () => {
links = [];
nodes = {};
nodes = [];
nodesHash = {};
commonClear();
};
type Nullable<T> = T | null;
interface ILink {
source?: Node;
target?: Node;
amount?: number;
}
class Link {
sourceNode: Nullable<Node>;
targetNode: Nullable<Node>;
source: Nullable<Node>;
target: Nullable<Node>;
amount: Nullable<number>;
constructor() {
this.sourceNode = null;
this.targetNode = null;
this.source = null;
this.target = null;
this.amount = 0;
}
}
/**
* Adds a stream between two elements on the diagram
* Adds a link between two elements on the diagram
*
* @param sourceNodeID - The id Node where the link starts
* @param targetNodeID - The id Node where the link ends
* @param source - Node where the link starts
* @param target - Node where the link ends
* @param amount - number, float or integer, describes the amount to be passed
*/
interface IAddLink {
sourceNodeID?: string;
targetNodeID?: string;
// amount?: number;
}
const addLink = ({ sourceNodeID, targetNodeID }: IAddLink = {}): Link => {
// const addLink = ({ source, target, amount }: ILink = {}): Link => {
const addLink = (source?: Node, target?: Node, amount?: number): Link => {
const link: Link = new Link();
if (sourceNodeID !== undefined) {
link.sourceNode = addNode(sourceNodeID);
if (source !== undefined) {
link.source = source;
}
if (targetNodeID !== undefined) {
link.targetNode = addNode(targetNodeID);
if (target !== undefined) {
link.target = target;
}
if (amount !== undefined) {
link.amount = amount;
}
links.push(link);
@ -68,11 +76,11 @@ const addLink = ({ sourceNodeID, targetNodeID }: IAddLink = {}): Link => {
};
class Node {
id: string;
ID: string;
title: string;
constructor(id: string) {
this.id = id;
this.title = id;
constructor(ID: string) {
this.ID = ID;
this.title = ID;
}
}
@ -81,25 +89,28 @@ class Node {
*
* @param id - The id Node
*/
const addNode = (id: string): Node => {
id = common.sanitizeText(id, configApi.getConfig());
if (nodes[id] === undefined) {
nodes[id] = new Node(id);
const addNode = (ID: string): Node => {
ID = common.sanitizeText(ID, configApi.getConfig());
if (nodesHash[ID] === undefined) {
nodesHash[ID] = new Node(ID);
}
const node = nodes[id];
const node = nodesHash[ID];
nodes.push(node);
// debugger;
return node;
};
export default {
// sankey interface
nodesHash,
nodes,
links,
addLink,
addNode,
// common DB interface
// TODO: If this is a must this probably should be an interface
// getAccTitle,
// setAccTitle,
// getAccDescription,
// setAccDescription,
getAccTitle,
setAccTitle,
getAccDescription,
setAccDescription,
getDiagramTitle,
setDiagramTitle,
clear,

View File

@ -1,22 +1,202 @@
// @ts-nocheck TODO: fix file
import { Diagram } from '../../Diagram.js';
import { log } from '../../logger.js';
import * as configApi from '../../config.js';
import {
select as d3select,
scaleOrdinal as d3scaleOrdinal,
schemeTableau10 as d3schemeTableau10,
// rgb as d3rgb,
map as d3map,
} from 'd3';
import {
sankey as d3sankey,
sankeyLinkHorizontal
} from 'd3-sankey';
import { configureSvgSize } from '../../setupGraphViewbox.js';
import sankeyDB from './sankeyDB.js';
/**
* Draws a sequenceDiagram in the tag with id: id based on the graph definition in text.
*
* @param _text - The text of the diagram
* @param text - The text of the diagram
* @param id - The id of the diagram which will be used as a DOM element id¨
* @param _version - Mermaid version from package.json
* @param diagObj - A standard diagram containing the db and the text and type etc of the diagram
*/
export const draw = function (text: string, id: string, _version: string, diagObj: Diagram) {
// debugger;
// diagObj.db.clear();
export const draw = function (text: string, id: string, _version: string, diagObj: Diagram): void {
// First of all parse sankey language
// Everything that is parsed will be stored in diagObj.DB
// That is why we need to clear DB first
//
if (typeof (diagObj?.db?.clear) !== 'undefined') { // why do we need to check for undefined? typescript complains
diagObj?.db?.clear();
}
// Launch parsing
diagObj.parser.parse(text);
log.debug('Parsed sankey diagram');
// const elem = doc.getElementById(id);
// Figure out what is happening there
// The main thing is svg object that is a wrapper from d3 for svg operations
//
const { securityLevel, sequence: conf } = configApi.getConfig();
let sandboxElement;
if (securityLevel === 'sandbox') {
sandboxElement = select('#i' + id);
}
const root =
securityLevel === 'sandbox'
? d3select(sandboxElement.nodes()[0].contentDocument.body)
: d3select('body');
const doc = securityLevel === 'sandbox' ? sandboxElement.nodes()[0].contentDocument : document;
const svg = securityLevel === 'sandbox' ? root.select(`[id="${id}"]`) : d3select(`[id="${id}"]`);
// Establish svg dimensions
//
const elem = doc.getElementById(id);
const width = elem.parentElement.offsetWidth;
const height = 600;
configureSvgSize(svg, height, width, true);
// Prepare data for construction
// This must be a mutable object with 2 properties:
// `nodes` and `links`
//
// let graph = {
// "nodes": [
// { "id": "Alice" },
// { "id": "Bob" },
// { "id": "Carol" }
// ],
// "links": [
// { "source": "Alice", "target": "Bob", "value": 23 },
// { "source": "Bob", "target": "Carol", "value": 43 }
// ]
// };
//
let graph = {
"nodes": [
{ "id": "Alice" },
{ "id": "Bob" },
{ "id": "Carol" }
],
"links": [
{ "source": "Alice", "target": "Bob", "value": 23 },
{ "source": "Bob", "target": "Carol", "value": 43 }
]
};
// Construct and configure a Sankey generator
// That will be a functino that calculates nodes and links dimentions
//
const sankey = d3sankey()
.nodeId((d) => d.id) // we use 'id' property to identify node
.nodeWidth(36)
.nodePadding(290)
.size([width, height]);
// .nodeAlign(d3Sankey.sankeyLeft) // d3.sankeyLeft, etc.
// .nodeWidth(15)
// .nodePadding(10)
// .extent([[1, 5], [width - 1, height - 5]]);
// .nodeId(d => d['id'])
//
// Compute the Sankey layout
// Namely calalculate nodes and links positions
// Our `graph` object will be mutated by this
//
sankey(graph);
// debugger;
return 'TEST';
// const node = svg.append("g")
// .selectAll("rect")
// .data(graph.nodes)
// .join("rect")
// .attr("x", d => d.x0)
// .attr("y", d => d.y0)
// .attr("height", d => d.y1 - d.y0)
// .attr("width", d => d.x1 - d.x0);
// // .attr("stroke", nodeStroke)
// // .attr("stroke-width", nodeStrokeWidth)
// // .attr("stroke-opacity", nodeStrokeOpacity)
// // .attr("stroke-linejoin", nodeStrokeLinejoin)
var color = d3scaleOrdinal(d3schemeTableau10);
// Creates the rects that represent the nodes.
const rect = svg.append("g")
.attr("stroke", "#000")
.selectAll("rect")
.data(graph.nodes)
.join("rect")
.attr("x", d => d.x0)
.attr("y", d => d.y0)
.attr("height", d => d.y1 - d.y0)
.attr("width", d => d.x1 - d.x0)
.attr("fill", d => color(d.node));
// // add in the links
// var link = svg.append("g")
// .selectAll(".link")
// .data(graph.links)
// .enter()
// .append("path")
// .attr("class", "link")
// .attr("d", sankeyLinkHorizontal())
// .style("stroke-width", function (d) { return Math.max(1, d.dy); })
// .sort(function (a, b) { return b.dy - a.dy; });
// // add in the nodes
// var node = svg.append("g")
// .selectAll(".node")
// .data(graph.nodes)
// .enter().append("g")
// .attr("class", "node")
// .attr("transform", function (d) { return "translate(" + d.x + "," + d.y + ")"; })
// // .call(d3.drag()
// // .subject(function(d) { return d; })
// // .on("start", function() { this.parentNode.appendChild(this); })
// // .on("drag", dragmove))
// ;
// // add the rectangles for the nodes
// node
// .append("rect")
// .attr("height", function (d) { return d.dy; })
// .attr("width", generator.nodeWidth())
// .style("fill", function (d) { return d.color = color(d.name.replace(/ .*/, "")); })
// .style("stroke", function (d) { return d3rgb(d.color).darker(2); })
// // Add hover text
// .append("title")
// .text(function (d) { return d.name + "\n" + "There is " + d.value + " stuff in this node"; });
// // add in the title for the nodes
// node
// .append("text")
// .attr("x", -6)
// .attr("y", function (d) { return d.dy / 2; })
// .attr("dy", ".35em")
// .attr("text-anchor", "end")
// .attr("transform", null)
// .text(function (d) { return d.name; })
// .filter(function (d) { return d.x < width / 2; })
// .attr("x", 6 + generator.nodeWidth())
// .attr("text-anchor", "start");
// console.log();
// debugger;
// .layout(1);
// const { nodes, links } = generator({
// nodes: graph.nodes,
// links: graph.links,
// });
};
export default {

15
run
View File

@ -10,23 +10,19 @@ args=${@:2}
case $command in
sh)
$RUN mermaid sh $args
$RUN --service-ports mermaid sh $args
;;
i | install)
$RUN mermaid sh -c "npx pnpm install $args"
;;
test)
$RUN mermaid sh -c "npx pnpm test $args"
add)
$RUN mermaid sh -c "npx pnpm -w add $args"
;;
vitest)
$RUN mermaid sh -c "npx pnpm vitest $args"
;;
e2e)
$RUN mermaid sh -c "npx pnpm e2e $args"
test | vitest | e2e )
$RUN mermaid sh -c "npx pnpm $command $args"
;;
lint)
@ -46,6 +42,7 @@ Run commonly used commands within docker containers
\033[1m$name install\033[0m # Equvalent of pnpm install
\033[1m$name dev\033[0m # Run dev server with examples, open http://localhost:9000
$name add # Add package, 'run add d3-sankey'
$name lint # Equvalent of pnpm -w run lint:fix
$name test # Run unit tests
$name vitest # Run watcher for unit tests