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

Unwanted circle on link path #111

Open
Spenhouet opened this issue Jun 10, 2022 · 1 comment
Open

Unwanted circle on link path #111

Spenhouet opened this issue Jun 10, 2022 · 1 comment

Comments

@Spenhouet
Copy link

Spenhouet commented Jun 10, 2022

I made a simple first sankey diagram but on some link paths I'm getting unwanted paths. When I play around with the path size it looks like there is a circle in the middle of the path which is as big as the stroke width. So if the path is shorter than the stroke width, then you can see that circle.

image

Here a jsfiddle with the shown sankey diagram: https://jsfiddle.net/a2oc3nr1/9/

Interestingly, if I put the exact path which I marked in the image above alone into a jsfiddle, the issue is not reproducible.

image
https://jsfiddle.net/8k5xog3z/1/

It seems that the example provided here also suffers the same issue:

https://observablehq.com/@d3/parallel-sets?collection=@d3/d3-sankey

image

I could not figure out how to avoid / fix that.

@Spenhouet
Copy link
Author

Spenhouet commented Jun 12, 2022

It turned out that this is in fact an issue with the current implementation which uses the stroke width to draw the links - which breaks down if the link is shorter than wide.
You can find more details on this issue here: https://observablehq.com/@enjalot/weird-sankey-links

The solution is to actually draw the outline of the link.
image

You can find my full solution here: https://stackoverflow.com/a/72593081/2230045

Feel free to use and adapt this code:

/** @type {Function} [linkWidth=d => '0.9'] - A function to return a float to scale the link width. */
export let linkWidth = (d) => 0.9;

/**
 * This function is a drop in replacement for d3.sankeyLinkHorizontal().
 * Except any accessors/options.
 * @param {Object} link - Link object.
 * @param {Number} link.y0 - y coordinate for the start of the link.
 * @param {Number} link.y1 - y coordinate for the end of the link.
 * @param {Number} link.width - Width of the link.
 * @param {Object} link.source - Source node object.
 * @param {Number} link.source.x1 - x coordinate for the start of the link.
 * @param {Object} link.target - Target node object.
 * @param {Number} link.target.x0 - x coordinate for the end of the link.
 **/
function sankeyLinkPathHorizontal(link) {
    // Start and end of the link
    let sx1 = link.source.x1;
    let tx0 = link.target.x0 + 1;

    // All four outer corners of the link
    // where e.g. lsy0 is the upper corner of the link on the source side
    let lsy0 = link.y0 - (link.width / 2) * linkWidth(link);
    let lsy1 = link.y0 + (link.width / 2) * linkWidth(link);
    let lty0 = link.y1 - (link.width / 2) * linkWidth(link);
    let lty1 = link.y1 + (link.width / 2) * linkWidth(link);

    // Center (x) of the link
    let lcx = sx1 + (tx0 - sx1) / 2;

    // Define outline of link as path
    let path = d3.path();
    path.moveTo(sx1, lsy0);
    path.bezierCurveTo(lcx, lsy0, lcx, lty0, tx0, lty0);
    path.lineTo(tx0, lty1);
    path.bezierCurveTo(lcx, lty1, lcx, lsy1, sx1, lsy1);
    path.lineTo(sx1, lsy0);
    return path.toString();
}

/**
 * This function is a drop in replacement for d3.sankeyLinkVertical().
 * Except any accessors/options.
 * @param {Object} link - Link object.
 * @param {Number} link.y0 - y coordinate for the start of the link.
 * @param {Number} link.y1 - y coordinate for the end of the link.
 * @param {Number} link.width - Width of the link.
 * @param {Object} link.source - Source node object.
 * @param {Number} link.source.x1 - x coordinate for the start of the link.
 * @param {Object} link.target - Target node object.
 * @param {Number} link.target.x0 - x coordinate for the end of the link.
 **/
function sankeyLinkPathVertical(link) {
	// Start and end of the link
	let sy1 = link.source.x1;
	let ty0 = link.target.x0 + 1;

	// All four outer corners of the link
	// where e.g. lsx0 is the right corner of the link on the source side
	let lsx0 = link.y0 - (link.width / 2) * linkWidth(link);
	let lsx1 = link.y0 + (link.width / 2) * linkWidth(link);
	let ltx0 = link.y1 - (link.width / 2) * linkWidth(link);
	let ltx1 = link.y1 + (link.width / 2) * linkWidth(link);

	// Center (y) of the link
	let lcy = sy1 + (ty0 - sy1) / 2;

	// Define outline of link as path
	let path = d3.path();
	path.moveTo(lsx0, sy1);
	path.bezierCurveTo(lsx0, lcy, ltx0, lcy, ltx0, ty0);
	path.lineTo(ltx1, ty0);
	path.bezierCurveTo(ltx1, lcy, lsx1, lcy, lsx1, sy1);
	path.lineTo(lsx0, sy1);
	return path.toString();
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

1 participant