Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/sankey max link height and align parent #594

Merged
merged 3 commits into from
Dec 4, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"changes": [
{
"comment": "feat: support `maxNodeHeight` and `maxLinkHeight`, `crossNodeAlign` value `parent` in sankey\n\n",
"type": "none",
"packageName": "@visactor/vgrammar-core"
}
],
"packageName": "@visactor/vgrammar-core",
"email": "[email protected]"
}
11 changes: 10 additions & 1 deletion packages/vgrammar-sankey/src/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export interface SankeyOptions {
* the align type of y position of nodes is differnt layer when the direction is `hotizontal`
* the align type of x position of nodes is differnt layer when the direction is `hotizontal`
*/
crossNodeAlign?: 'start' | 'end' | 'middle';
crossNodeAlign?: 'start' | 'end' | 'middle' | 'parent';
/**
* The align type of all the nodes
*/
Expand Down Expand Up @@ -71,13 +71,22 @@ export interface SankeyOptions {
* It's recommended to be smaller than 5px
*/
minNodeHeight?: number;
/**
* the maximal size of node when data is not zero or null
* this configuration can be used to avoid too large node to be seen when data is too big
*/
maxNodeHeight?: number;
/**
* The minimal size of link when data is not zero or null
* This configuration can be used to avoid too thin link to be seen when data is too small
* It's recommended to be smaller than 5px
* This option should be smaller than `minNodeHeight` when both options are specified
*/
minLinkHeight?: number;
/**
* the maximal size of link when data is not zero or null
*/
maxLinkHeight?: number;
xiaoluoHe marked this conversation as resolved.
Show resolved Hide resolved
/** the iteration count of layout */
iterations?: number;
/** parse the key of node, the defaultValue */
Expand Down
44 changes: 39 additions & 5 deletions packages/vgrammar-sankey/src/layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -642,10 +642,17 @@ export class SankeyLayout {
initializeNodeBreadths(columns: SankeyNodeElement[][]) {
const minLinkHeight = this.options.minLinkHeight ?? 0;
let minNodeHeight = this.options.minNodeHeight ?? 0;
const maxNodeHeight = this.options.maxNodeHeight ?? Infinity;
let maxLinkHeight = this.options.maxLinkHeight;

if (isNil(minNodeHeight) || minNodeHeight < minLinkHeight) {
minNodeHeight = minLinkHeight;
}

if (isNil(maxLinkHeight) || maxLinkHeight > maxNodeHeight) {
maxLinkHeight = maxNodeHeight;
}

let ky = 0;
let getGapY: (node: SankeyNodeElement) => number = null;
let forceNodeHeight: number = null;
Expand Down Expand Up @@ -711,10 +718,13 @@ export class SankeyLayout {
sourceNodeHeight: number
) => number)
: (link: SankeyLinkElement, sourceNode: SankeyNodeElement, sourceNodeHeight: number) => {
return Math.max(
sourceNode.value ? sourceNodeHeight * linkClampe(link.value / sourceNode.value) : 0,
minLinkHeight,
0
return Math.min(
Math.max(
sourceNode.value ? sourceNodeHeight * linkClampe(link.value / sourceNode.value) : 0,
minLinkHeight,
0
),
maxLinkHeight
);
};

Expand All @@ -738,7 +748,7 @@ export class SankeyLayout {
}

calculatedNodeHeight = getNodeHeight(node);
nodeHeight = Math.max(calculatedNodeHeight, minNodeHeight);
nodeHeight = Math.min(Math.max(calculatedNodeHeight, minNodeHeight), maxNodeHeight);

node.y0 = y;
node.y1 = y + nodeHeight;
Expand All @@ -762,6 +772,30 @@ export class SankeyLayout {
node.y0 += deltaY;
node.y1 += deltaY;
}
} else if (this.options.crossNodeAlign === 'parent') {
const sourceNodes = nodes.reduce((res: Record<string, boolean>, node) => {
if (node.targetLinks && node.targetLinks.length) {
node.targetLinks.forEach(link => {
res[link.source] = true;
});
}
return res;
}, {});

if (Object.keys(sourceNodes).length && columns[i - 1] && columns[i - 1].length) {
const prevSourceNodes = columns[i - 1].filter(node => sourceNodes[node.key]);

if (prevSourceNodes && prevSourceNodes.length && prevSourceNodes[0].y0 !== nodes[0].y0) {
const startY = prevSourceNodes[0].y0;
const newDeltaY = startY - nodes[0].y0;

for (let j = 0, len = nodes.length; j < len; ++j) {
const node = nodes[j];
node.y0 += newDeltaY;
node.y1 += newDeltaY;
}
}
}
} else {
deltaY = deltaY / (nodes.length + 1);
for (let j = 0, len = nodes.length; j < len; ++j) {
Expand Down
Loading