Added gradient for sankey and cleaned code

This commit is contained in:
Nikolay Rozhkov 2023-06-22 21:26:39 +03:00
parent 518da3236f
commit d9036c7af1
8 changed files with 104 additions and 39 deletions

View File

@ -66,6 +66,8 @@
"@cypress/code-coverage": "^3.10.7",
"@rollup/plugin-typescript": "^11.1.1",
"@types/cors": "^2.8.13",
"@types/d3": "^7.4.0",
"@types/d3-sankey": "^0.12.1",
"@types/eslint": "^8.37.0",
"@types/express": "^4.17.17",
"@types/js-yaml": "^4.0.5",

View File

@ -73,7 +73,6 @@ const findOrCreateNode = function (ID: string): SankeyNode {
return node;
};
// TODO: this will be better using getters in typescript
const getNodes = () => nodes;
const getLinks = () => links;

View File

@ -2,7 +2,6 @@ import { DiagramDefinition } from '../../diagram-api/types.js';
// @ts-ignore: jison doesn't export types
import parser from './parser/sankey.jison';
import db from './sankeyDB.js';
import styles from './styles.js';
import renderer from './sankeyRenderer.js';
import { prepareTextForParsing } from './sankeyUtils.js';
@ -13,5 +12,5 @@ export const diagram: DiagramDefinition = {
parser,
db,
renderer,
styles,
styles: () => {},
};

View File

@ -4,7 +4,9 @@ import * as configApi from '../../config.js';
import {
select as d3select,
// @ts-ignore TODO: make proper import
scaleOrdinal as d3scaleOrdinal,
// @ts-ignore TODO: make proper import
schemeTableau10 as d3schemeTableau10,
} from 'd3';
@ -17,6 +19,7 @@ import {
sankeyJustify as d3SankeyJustify,
} from 'd3-sankey';
import { configureSvgSize } from '../../setupGraphViewbox.js';
import { Uid } from './sankeyUtils.js';
/**
* Draws a sequenceDiagram in the tag with id: id based on the graph definition in text.
@ -31,14 +34,15 @@ export const draw = function (text: string, id: string, _version: string, diagOb
// The main thing is svg object that is a d3 wrapper for svg operations
//
const { securityLevel, sequence: conf } = configApi.getConfig();
let sandboxElement;
let sandboxElement: any;
if (securityLevel === 'sandbox') {
sandboxElement = d3select('#i' + id);
}
const root =
securityLevel === 'sandbox'
? d3select(sandboxElement.nodes()[0].contentDocument.body)
: d3select('body');
let root = d3select('body');
if (securityLevel === 'sandbox' && sandboxElement) {
d3select(sandboxElement.nodes()[0].contentDocument.body)
}
const doc = securityLevel === 'sandbox' ? sandboxElement.nodes()[0].contentDocument : document;
const svg = securityLevel === 'sandbox' ? root.select(`[id="${id}"]`) : d3select(`[id="${id}"]`);
@ -46,11 +50,11 @@ export const draw = function (text: string, id: string, _version: string, diagOb
//
const elem = doc.getElementById(id);
const width = elem.parentElement.offsetWidth;
const height = 600; // TODO calculate height?
const height = 600;
// FIX: using max width prevents height from being set
configureSvgSize(svg, height, width, true);
svg.attr('height', height); // that's why we need this line
configureSvgSize(svg, height, width, false);
// svg.attr('height', height); // that's why we need this line
// Prepare data for construction based on diagObj.db
// This must be a mutable object with 2 properties:
@ -68,7 +72,6 @@ export const draw = function (text: string, id: string, _version: string, diagOb
// ]
// };
//
const graph = diagObj.db.getGraph();
const nodeAligns = {
left: d3SankeyLeft,
@ -83,13 +86,13 @@ export const draw = function (text: string, id: string, _version: string, diagOb
//
const nodeWidth = 10;
const sankey = d3Sankey()
.nodeId((d) => d.id) // we use 'id' property to identify node
.nodeId((d: any) => d.id) // we use 'id' property to identify node
.nodeWidth(nodeWidth)
.nodePadding(10)
.nodeAlign(nodeAlign) // d3.sankeyLeft, etc.
.nodeAlign(nodeAlign)
.extent([
[0, 0],
[width - nodeWidth, height],
[width, height],
]);
// Compute the Sankey layout: calculate nodes and links positions
@ -98,9 +101,9 @@ export const draw = function (text: string, id: string, _version: string, diagOb
sankey(graph);
// Get color scheme for the graph
const color = d3scaleOrdinal(d3schemeTableau10);
const colorScheme = d3scaleOrdinal(d3schemeTableau10);
// Create groups for nodes
// Create rectangles for nodes
svg
.append('g')
.attr('class', 'nodes')
@ -108,17 +111,17 @@ export const draw = function (text: string, id: string, _version: string, diagOb
.data(graph.nodes)
.join('g')
.attr('class', 'node')
.attr('transform', function (d) {
.attr('transform', function (d: any) {
return 'translate(' + d.x0 + ',' + d.y0 + ')';
})
.attr('x', (d) => d.x0)
.attr('y', (d) => d.y0)
.attr('x', (d: any) => d.x0)
.attr('y', (d: any) => d.y0)
.append('rect')
.attr('height', (d) => {
.attr('height', (d: any) => {
return d.y1 - d.y0;
})
.attr('width', (d) => d.x1 - d.x0)
.attr('fill', (d) => color(d.id));
.attr('width', (d: any) => d.x1 - d.x0)
.attr('fill', (d: any) => colorScheme(d.id));
// Create labels for nodes
svg
@ -129,14 +132,14 @@ export const draw = function (text: string, id: string, _version: string, diagOb
.selectAll('text')
.data(graph.nodes)
.join('text')
.attr('x', (d) => (d.x0 < width / 2 ? d.x1 + 6 : d.x0 - 6))
.attr('y', (d) => (d.y1 + d.y0) / 2)
.attr('x', (d: any) => (d.x0 < width / 2 ? d.x1 + 6 : d.x0 - 6))
.attr('y', (d: any) => (d.y1 + d.y0) / 2)
.attr('dy', '0.35em')
.attr('text-anchor', (d) => (d.x0 < width / 2 ? 'start' : 'end'))
.text((d) => d.label);
.attr('text-anchor', (d: any) => (d.x0 < width / 2 ? 'start' : 'end'))
.text((d: any) => d.label);
// Creates the paths that represent the links.
const link_g = svg
const link = svg
.append('g')
.attr('class', 'links')
.attr('fill', 'none')
@ -147,11 +150,27 @@ export const draw = function (text: string, id: string, _version: string, diagOb
.attr('class', 'link')
.style('mix-blend-mode', 'multiply');
link_g
const gradient = link.append("linearGradient")
.attr("id", d => (d.uid = Uid.next("linearGradient-")).id)
.attr("gradientUnits", "userSpaceOnUse")
.attr("x1", d => d.source.x1)
.attr("x2", d => d.target.x0);
gradient
.append("stop")
.attr("offset", "0%")
.attr("stop-color", d => colorScheme(d.source.id));
gradient
.append("stop")
.attr("offset", "100%")
.attr("stop-color", d => colorScheme(d.target.id));
link
.append('path')
.attr('d', d3SankeyLinkHorizontal())
.attr('stroke', (d) => color(d.source.id))
.attr('stroke-width', (d) => Math.max(1, d.width));
.attr('stroke', (d: any) => d.uid)
.attr('stroke-width', (d: any) => Math.max(1, d.width));
};
export default {

View File

@ -1,4 +1,4 @@
export const prepareTextForParsing = (text: string): string => {
const prepareTextForParsing = (text: string): string => {
const textToParse = text
.replaceAll(/^[^\S\n\r]+|[^\S\n\r]+$/g, '') // remove all trailing spaces for each row
.replaceAll(/([\n\r])+/g, '\n') // remove empty lines duplicated
@ -6,3 +6,33 @@ export const prepareTextForParsing = (text: string): string => {
return textToParse;
};
class Uid {
private static count: number = 0;
id: string;
href: string;
public static next(name: string): Uid {
return new Uid(name + ++Uid.count);
}
constructor(id: string) {
this.id = id;
this.href = `#${id}`;
}
toString(): string {
return "url(" + this.href + ")";
}
}
export {
Uid,
prepareTextForParsing
};

View File

@ -1,5 +0,0 @@
const getStyles = (options) =>
`
`;
export default getStyles;

View File

@ -29,7 +29,6 @@ import state from './diagrams/state/styles.js';
import journey from './diagrams/user-journey/styles.js';
import timeline from './diagrams/timeline/styles.js';
import mindmap from './diagrams/mindmap/styles.js';
import sankey from './diagrams/sankey/styles.js';
import themes from './themes/index.js';
async function checkValidStylisCSSStyleSheet(stylisString: string) {
@ -99,7 +98,7 @@ describe('styles', () => {
sequence,
state,
timeline,
sankey,
sankey: () => {},
})) {
test(`should return a valid style for diagram ${diagramId} and theme ${themeId}`, async () => {
const { default: getStyles, addStylesForDiagram } = await import('./styles.js');

View File

@ -25,6 +25,12 @@ importers:
'@types/cors':
specifier: ^2.8.13
version: 2.8.13
'@types/d3':
specifier: ^7.4.0
version: 7.4.0
'@types/d3-sankey':
specifier: ^0.12.1
version: 0.12.1
'@types/eslint':
specifier: ^8.37.0
version: 8.37.0
@ -3983,6 +3989,10 @@ packages:
'@types/d3-color': 3.1.0
dev: true
/@types/d3-path@1.0.9:
resolution: {integrity: sha512-NaIeSIBiFgSC6IGUBjZWcscUJEq7vpVu7KthHN8eieTV9d9MqkSOZLH4chq1PmcKy06PNe3axLeKmRIyxJ+PZQ==}
dev: true
/@types/d3-path@3.0.0:
resolution: {integrity: sha512-0g/A+mZXgFkQxN3HniRDbXMN79K3CdTpLsevj+PXiTcb2hVyvkZUBg37StmgCQkaD84cUJ4uaDAWq7UJOQy2Tg==}
dev: true
@ -3999,6 +4009,12 @@ packages:
resolution: {integrity: sha512-IIE6YTekGczpLYo/HehAy3JGF1ty7+usI97LqraNa8IiDur+L44d0VOjAvFQWJVdZOJHukUJw+ZdZBlgeUsHOQ==}
dev: true
/@types/d3-sankey@0.12.1:
resolution: {integrity: sha512-10X6l6lXB42udBNX9/fDN+kJuooifSMk7+x4U9815eobavldqis4wDdFQUQjMazh+qlzsUZsGzXKxfWFUVt+3w==}
dependencies:
'@types/d3-shape': 1.3.8
dev: true
/@types/d3-scale-chromatic@3.0.0:
resolution: {integrity: sha512-dsoJGEIShosKVRBZB0Vo3C8nqSDqVGujJU6tPznsBJxNJNwMF8utmS83nvCBKQYPpjCzaaHcrf66iTRpZosLPw==}
dev: true
@ -4013,6 +4029,12 @@ packages:
resolution: {integrity: sha512-xCB0z3Hi8eFIqyja3vW8iV01+OHGYR2di/+e+AiOcXIOrY82lcvWW8Ke1DYE/EUVMsBl4Db9RppSBS3X1U6J0w==}
dev: true
/@types/d3-shape@1.3.8:
resolution: {integrity: sha512-gqfnMz6Fd5H6GOLYixOZP/xlrMtJms9BaS+6oWxTKHNqPGZ93BkWWupQSCYm6YHqx6h9wjRupuJb90bun6ZaYg==}
dependencies:
'@types/d3-path': 1.0.9
dev: true
/@types/d3-shape@3.1.0:
resolution: {integrity: sha512-jYIYxFFA9vrJ8Hd4Se83YI6XF+gzDL1aC5DCsldai4XYYiVNdhtpGbA/GM6iyQ8ayhSp3a148LY34hy7A4TxZA==}
dependencies: