D3 Sankey链接缩放问题

如何解决D3 Sankey链接缩放问题

我需要绘制一个sankey图表以显示关联。我已经使用d3.scaleLinear()缩放了节点,也使小节点具有了含义,但是链接并未按节点进行缩放。我也尝试过链接缩放,但是它不起作用。

//node scale
var maxVal_node = d3.max(nodes.map(m => m.value));
var minVal_node = d3.min(nodes.map(m => m.value));
var nodeScale = d3.scaleLinear()
    .domain([minVal_node,maxVal_node])
    .range([10,120]);
d3.sankey = function() {
  var sankey = {},nodeWidth = 24,nodePadding = 8,size = [1,1],nodes = [],links = [];

  sankey.nodeWidth = function(_) {
    if (!arguments.length) return nodeWidth;
    nodeWidth = +_;
    return sankey;
  };

  sankey.nodePadding = function(_) {
    if (!arguments.length) return nodePadding;
    nodePadding = +_;
    return sankey;
  };

  sankey.nodes = function(_) {
    if (!arguments.length) return nodes;
    nodes = _;
    return sankey;
  };

  sankey.links = function(_) {
    if (!arguments.length) return links;
    links = _;
    return sankey;
  };

  sankey.size = function(_) {
    if (!arguments.length) return size;
    size = _;
    return sankey;
  };

  sankey.layout = function(iterations) {
    computeNodeLinks();
    computeNodeValues();
    computeNodeBreadths();
    computeNodeDepths(iterations);
    computeLinkDepths();
    return sankey;
  };

  sankey.relayout = function() {
    computeLinkDepths();
    return sankey;
  };

  sankey.link = function() {
    var curvature = .5;

    function link(d) {
      var x0 = d.source.x + d.source.dx,x1 = d.target.x,xi = d3.interpolateNumber(x0,x1),x2 = xi(curvature),x3 = xi(1 - curvature),y0 = d.source.y + d.sy + d.dy / 2,y1 = d.target.y + d.ty + d.dy / 2;
      return "M" + x0 + "," + y0 +
        "C" + x2 + "," + y0 +
        " " + x3 + "," + y1 +
        " " + x1 + "," + y1;
    }

    link.curvature = function(_) {
      if (!arguments.length) return curvature;
      curvature = +_;
      return link;
    };

    return link;
  };


  // Populate the sourceLinks and targetLinks for each node.
  // Also,if the source and target are not objects,assume they are indices.
  function computeNodeLinks() {
    nodes.forEach(function(node) {
      node.sourceLinks = [];
      node.targetLinks = [];
    });
    links.forEach(function(link) {
      var source = link.source,target = link.target;
      if (typeof source === "number") source = link.source = nodes[link.source];
      if (typeof target === "number") target = link.target = nodes[link.target];
      source.sourceLinks.push(link);
      target.targetLinks.push(link);
    });
  }

  // Compute the value (size) of each node by summing the associated links.
  function computeNodeValues() {
    nodes.forEach(function(node) {
      node.value = Math.max(
        d3.sum(node.sourceLinks,value),d3.sum(node.targetLinks,value)
      );
    });
  }

  // Iteratively assign the breadth (x-position) for each node.
  // Nodes are assigned the maximum breadth of incoming neighbors plus one;
  // nodes with no incoming links are assigned breadth zero,while
  // nodes with no outgoing links are assigned the maximum breadth.
  function computeNodeBreadths() {
    var remainingNodes = nodes,nextNodes,x = 0;

    while (remainingNodes.length) {
      nextNodes = [];
      remainingNodes.forEach(function(node) {
        node.x = x;
        node.dx = nodeWidth;
        node.sourceLinks.forEach(function(link) {
          if (nextNodes.indexOf(link.target) < 0) {
            nextNodes.push(link.target);
          }
        });
      });
      remainingNodes = nextNodes;
      ++x;
    }

    //
    moveSinksRight(x);
    scaleNodeBreadths((size[0] - nodeWidth) / (x - 1));
  }

  function moveSourcesRight() {
    nodes.forEach(function(node) {
      if (!node.targetLinks.length) {
        node.x = d3.min(node.sourceLinks,function(d) {
          return d.target.x;
        }) - 1;
      }
    });
  }

  function moveSinksRight(x) {
    nodes.forEach(function(node) {
      if (!node.sourceLinks.length) {
        node.x = x - 1;
      }
    });
  }

  function scaleNodeBreadths(kx) {
    nodes.forEach(function(node) {
      node.x *= kx;
    });
  }

  function computeNodeDepths(iterations) {
    var nodesByBreadth = d3.nest()
      .key(function(d) {
        return d.x;
      })
      .sortKeys(d3.ascending)
      .entries(nodes)
      .map(function(d) {
        return d.values;
      });

    //
    initializeNodeDepth();
    resolveCollisions();
    for (var alpha = 1; iterations > 0; --iterations) {
      relaxRightToLeft(alpha *= .99);
      resolveCollisions();
      relaxLeftToRight(alpha);
      resolveCollisions();
    }

    function initializeNodeDepth() {
      var ky = d3.min(nodesByBreadth,function(nodes) {
        return (size[1] - (nodes.length - 1) * nodePadding) / d3.sum(nodes,value);
      });

      nodesByBreadth.forEach(function(nodes) {
        nodes.forEach(function(node,i) {
          node.y = i;
          node.dy = node.value * ky;
        });
      });

      links.forEach(function(link) {
        link.dy = link.value * ky;
      });
    }

    function relaxLeftToRight(alpha) {
      nodesByBreadth.forEach(function(nodes,breadth) {
        nodes.forEach(function(node) {
          if (node.targetLinks.length) {
            var y = d3.sum(node.targetLinks,weightedSource) / d3.sum(node.targetLinks,value);
            //node.y += (y - center(node)) * alpha;
          }
        });
      });

      function weightedSource(link) {
        return center(link.source) * link.value;
      }
    }

    function relaxRightToLeft(alpha) {
      nodesByBreadth.slice().reverse().forEach(function(nodes) {
        nodes.forEach(function(node) {
          if (node.sourceLinks.length) {
            var y = d3.sum(node.sourceLinks,weightedTarget) / d3.sum(node.sourceLinks,value);
            //node.y += (y - center(node)) * alpha;
          }
        });
      });

      function weightedTarget(link) {
        return center(link.target) * link.value;
      }
    }

    function resolveCollisions() {
      nodesByBreadth.forEach(function(nodes) {
        var node,dy,y0 = 0,n = nodes.length,i;

        // Push any overlapping nodes down.
        nodes.sort(function(a,b) {
          return a.value > b.value ? -1 : a.value < b.value ? 1 : 0;
        });
        for (i = 0; i < n; ++i) {
          node = nodes[i];
          dy = y0 - node.y;
          if (dy > 0) node.y += dy;
          y0 = node.y + node.dy + nodePadding;
        }

        // If the bottommost node goes outside the bounds,push it back up.
        dy = y0 - nodePadding - size[1];
        if (dy > 0) {
          y0 = node.y -= dy;

          // Push any overlapping nodes back up.
          for (i = n - 2; i >= 0; --i) {
            node = nodes[i];
            dy = node.y + node.dy + nodePadding - y0;
            if (dy > 0) node.y -= dy;
            y0 = node.y;
          }
        }
      });
    }

    function ascendingDepth(a,b) {
      return a.y - b.y;
    }
  }

  function computeLinkDepths() {
    nodes.forEach(function(node) {
      node.sourceLinks.sort(ascendingTargetDepth);
      node.targetLinks.sort(ascendingSourceDepth);
    });
    nodes.forEach(function(node) {
      var sy = 0,ty = 0;
      node.sourceLinks.forEach(function(link) {
        link.sy = sy;
        sy += link.dy;
      });
      node.targetLinks.forEach(function(link) {
        link.ty = ty;
        ty += link.dy;
      });
    });

    function ascendingSourceDepth(a,b) {
      return a.source.y - b.source.y;
    }

    function ascendingTargetDepth(a,b) {
      return a.target.y - b.target.y;
    }
  }

  function center(node) {
    return node.y + node.dy / 2;
  }

  function value(link) {
    return link.value;
  }

  return sankey;
};


/* ------------------- our code ------------------------ */

var units = "Widgets";

var linkData = [{
  "source": 7,"target": 6,"productIdx": "8350","qty": 27200,"mv": 3054016,"matrixCost": 300,"sourceNodeName": "G","targetNodeName": "L","value": 27200
},{
  "source": 7,"target": 11,"qty": 196599,"mv": 22074135.72,"matrixCost": 5000,"targetNodeName": "K","value": 196599
},{
  "source": 9,"qty": 113401,"mv": 12732664.28,"matrixCost": 5020,"sourceNodeName": "I","value": 113401
},{
  "source": 2,"target": 10,"qty": 0,"mv": 0,"matrixCost": 100015,"sourceNodeName": "C","targetNodeName": "J","value": 0
},{
  "source": 0,"qty": 2500000,"mv": 280700000,"matrixCost": 100033,"sourceNodeName": "A","value": 2500000
},"target": 5,"matrixCost": 240048,"targetNodeName": "F","target": 3,"qty": 1309599,"mv": 147041775.72,"targetNodeName": "D","value": 1309599
},"qty": 3250408,"mv": 364955810.24,"value": 3250408
},{
  "source": 8,"target": 1,"sourceNodeName": "H","targetNodeName": "B",{
  "source": 4,"qty": 10,"mv": 1122.8,"sourceNodeName": "E","value": 10
}]

var nodes = [{
  "nodeId": 0,"nodeName": "A","entityCd": "16G"
},{
  "nodeId": 1,"nodeName": "B","entityCd": "161"
},{
  "nodeId": 2,"nodeName": "C",{
  "nodeId": 3,"nodeName": "D","entityCd": "XX"
},{
  "nodeId": 4,"nodeName": "E",{
  "nodeId": 5,"nodeName": "F",{
  "nodeId": 6,"nodeName": "L","entityCd": "16G","bucketCd": "LoantoCoverDublinPBCustShort","bucketName": "Loan to Cover Dublin PB Cust Short","bucketType": "U"
},{
  "nodeId": 7,"nodeName": "G",{
  "nodeId": 8,"nodeName": "H",{
  "nodeId": 9,"nodeName": "I",{
  "nodeId": 10,"nodeName": "J",{
  "nodeId": 11,"nodeName": "K","entityCd": "16G"
}]
var formatNumber = d3.format(",.0f"),// zero decimal places
  format = function(d) {
    return formatNumber(d)
  };

// append the svg object to the body of the page
var svg = d3.select("#" + "chart"),width = +svg.attr("width"),height = +svg.attr("height");


var sankey = d3.sankey()
  .nodeWidth(10)
  .nodePadding(10)
  .size([width - 1,height - 6]);

var path = sankey.link();

d3.selectAll('.tooltip-sankey').style("visibility","hidden");
var tooltip = d3.select("body")
  .append("div")
  .attr("class","tooltip-sankey")
  .style("position","absolute")
  .style("z-index","10")
  .style("visibility","hidden");

// load the data

sankey
  .nodes(nodes)
  .links(linkData)
  .layout(32);

//node scale
var maxVal_node = d3.max(nodes.map(m => m.value));
var minVal_node = d3.min(nodes.map(m => m.value));
var nodeScale = d3.scaleLinear().domain([minVal_node,maxVal_node])
  .range([10,120]);

// add in the links
var link = svg.append("g").selectAll(".link")
  .data(linkData)
  .enter().append("path")
  .attr("class","link-sankey")
  .attr("d",path)
  .style('stroke',function(d) {
    return d.color;
  }).style("stroke-width",function(d) {
    return Math.max(1,d.dy)
  })
  .sort(function(a,b) {
    return b.dy - a.dy;
  })

  .on("mouseover",function(d) {
    var sourceId,targetId;
    for (var item in d.source.sourceLinks)
      if (d.value === d.source.sourceLinks[item].value) sourceId = item;
    for (var item in d.target.targetLinks)
      if (d.value === d.target.targetLinks[item].value) targetId = item;
    var source = d3.select("#inner-rect-" + d.source.id + "-" + sourceId);
    var target = d3.select("#inner-rect-" + d.target.id + "-" + targetId);
    source.style("stroke-opacity",1).style("fill","#1f77b4");
    target.style("stroke-opacity","#1f77b4");

    //update text - one line
    var sourceText = d3.select("#text-" + d.source.id);
    sourceText.text(d.source.nodeName + ' : ' + d.value);
    var targetText = d3.select("#text-" + d.target.id);
    targetText.text(d.target.nodeName + ' : ' + d.value);

    tooltip.html("Source: <b>" + d.source.nodeName + "</b><br/>Target: <b>" + d.target.nodeName + "</b><br/>Identifier: <b>" + +"</b>" + "</b><br/>Qty: <b>" + format(d.value) + "</b>" + "</b><br/>Mv($): <b>" + format(d.mv) + "</b>");
    return tooltip.style("visibility","visible");
  })
  .on("mousemove",function() {
    var tooltipWidth = parseInt(tooltip.style("width"));
    if (window.innerWidth - d3.event.pageX < tooltipWidth) return tooltip.style("top",(d3.event.pageY - 10) + "px").style("left",(d3.event.pageX - tooltipWidth) + "px");
    return tooltip.style("top",(d3.event.pageX + 10) + "px");
  })
  .on("mouseout",targetId;
    for (var item in d.source.sourceLinks)
      if (d.value === d.source.sourceLinks[item].value) sourceId = item;
    for (var item in d.target.targetLinks)
      if (d.value === d.target.targetLinks[item].value) targetId = item;

    //update text - one line
    var sourceText = d3.select("#text-" + d.source.id);
    sourceText.text(d.source.nodeName + ' : ' + d.source.value);
    var targetText = d3.select("#text-" + d.target.id);
    targetText.text(d.target.nodeName + ' : ' + d.target.value);

    return tooltip.style("visibility","hidden");
  });

// add in the nodes
var node = svg.append("g").selectAll(".node")
  .data(nodes)
  .enter().append("g")
  .on("click",showConnections)
  .on("mouseover",function(d) {
    if (d.totalPledge == 0 || d.totalBorrow == 0) {
      tooltip.html("Name: <b>" + d.nodeName + "</b><br/>Total Qty: <b>" + format(d.value) + "</b>");
    } else {
      tooltip.html("Name: <b>" + d.nodeName + "</b><br/>Total Qty: <b>" + format(d.value) + "</b>");
    }
    return tooltip.style("visibility",function() {
    return tooltip.style("visibility","hidden");
  })
  .attr("class","node")
  .attr("transform",function(d) {
    return "translate(" + d.x + "," + d.y + ")";
  })
  .on("click",showConnections);

// add the rectangles for the nodes
node.append("rect")
  .attr("height",function(d) {
    return nodeScale(d.value);
  })
  .attr("width",sankey.nodeWidth())
  .style("fill",function() {
    return "#605f66"
  })
  .style("stroke",function(d) {
    return d3.rgb(d.color).darker(2);
  })
  .on("click",showConnections)


// add in the title for the nodes
node.append("text")
  .attr("x",-6)
  .attr("y",function(d) {
    return nodeScale(d.value) / 2
  })
  .attr("dy",".15em")
  .attr("text-anchor","end")
  //.style("font-size","10px")
  .attr("transform",null)
  .attr("class","nodeText")
  .text(function(d) {
    return d.nodeName;
  })
  .filter(function(d) {
    return d.x < width / 2;
  })
  .attr("x",6 + sankey.nodeWidth())
  .attr("text-anchor","start");

function showConnections(d) {
  if (!d3.select(this).classed("selected")) {
    d3.select(this).classed("selected",true);
    d3.select(this).style("stroke","black")
      .style("stroke-opacity",0.2);
    //If this link is connected to the node
    link.style("stroke-opacity",function(l) {
      if (l.source.nodeName == d.nodeName || l.target.nodeName == d.nodeName) {
        return 0.6;
      } else
        return 0.2;
    });
  } else {
    d3.select(this).classed("selected",false);
    d3.select(this).style("stroke",0.2)
    link.style("stroke-opacity",function() {
      return 0.2;
    });
  }
}
.node rect {
  cursor: move;
}

.link {
  fill: none;
  stroke: #000;
  stroke-opacity: .2;
}

.link:hover {
  stroke-opacity: .5;
}

* {
  font: 11px sans-serif;
}

.linkLabel {
  z-index: 10;
}

svg line.links {
  stroke: #999999;
  stroke-opacity: 0.6;
  stroke-width: 1px;
}

.link-sankey {
  fill: none;
  stroke: #1f77b4;
  stroke-opacity: .2;
}

.link-sankey:hover {
  stroke-opacity: .5 !important;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
<svg width="550" height="400" id="chart"></svg>

此外,小节点有点重叠,之间没有任何间隔。

谢谢。

解决方法

您似乎一直在困惑d3的不同部分,而没有足够仔细地查看d3-sankey的文档。它已经内置了一切,可以根据节点的值缩放节点。 d3.scaleLinear用于沿轴的图表,而Sankey是完全不同的野兽。

根据您提供的代码(在--- our code ---之前),我发现每个节点都有一个属性dx表示宽度,dy表示高度。使用它们可以彻底解决问题。

编辑:我已经通过转换每个链接的值来更新答案。通过使用Math.sqrt()并添加baseWidth,我给了较小的数量比较大的数量更多的功率。这意味着链接的宽度将不再对其值进行直接读取,但是由于它仍然大于比例,因此没关系。请注意,现在即使最小的节点也具有一定的宽度。

d3.sankey = function() {
  var sankey = {},nodeWidth = 24,nodePadding = 8,size = [1,1],nodes = [],links = [];

  sankey.nodeWidth = function(_) {
    if (!arguments.length) return nodeWidth;
    nodeWidth = +_;
    return sankey;
  };

  sankey.nodePadding = function(_) {
    if (!arguments.length) return nodePadding;
    nodePadding = +_;
    return sankey;
  };

  sankey.nodes = function(_) {
    if (!arguments.length) return nodes;
    nodes = _;
    return sankey;
  };

  sankey.links = function(_) {
    if (!arguments.length) return links;
    links = _;
    return sankey;
  };

  sankey.size = function(_) {
    if (!arguments.length) return size;
    size = _;
    return sankey;
  };

  sankey.layout = function(iterations) {
    computeNodeLinks();
    computeNodeValues();
    computeNodeBreadths();
    computeNodeDepths(iterations);
    computeLinkDepths();
    return sankey;
  };

  sankey.relayout = function() {
    computeLinkDepths();
    return sankey;
  };

  sankey.link = function() {
    var curvature = .5;

    function link(d) {
      var x0 = d.source.x + d.source.dx,x1 = d.target.x,xi = d3.interpolateNumber(x0,x1),x2 = xi(curvature),x3 = xi(1 - curvature),y0 = d.source.y + d.sy + d.dy / 2,y1 = d.target.y + d.ty + d.dy / 2;
      return "M" + x0 + "," + y0 +
        "C" + x2 + "," + y0 +
        " " + x3 + "," + y1 +
        " " + x1 + "," + y1;
    }

    link.curvature = function(_) {
      if (!arguments.length) return curvature;
      curvature = +_;
      return link;
    };

    return link;
  };


  // Populate the sourceLinks and targetLinks for each node.
  // Also,if the source and target are not objects,assume they are indices.
  function computeNodeLinks() {
    nodes.forEach(function(node) {
      node.sourceLinks = [];
      node.targetLinks = [];
    });
    links.forEach(function(link) {
      var source = link.source,target = link.target;
      if (typeof source === "number") source = link.source = nodes[link.source];
      if (typeof target === "number") target = link.target = nodes[link.target];
      source.sourceLinks.push(link);
      target.targetLinks.push(link);
    });
  }

  // Compute the value (size) of each node by summing the associated links.
  function computeNodeValues() {
    nodes.forEach(function(node) {
      node.value = Math.max(
        d3.sum(node.sourceLinks,value),d3.sum(node.targetLinks,value)
      );
    });
  }

  // Iteratively assign the breadth (x-position) for each node.
  // Nodes are assigned the maximum breadth of incoming neighbors plus one;
  // nodes with no incoming links are assigned breadth zero,while
  // nodes with no outgoing links are assigned the maximum breadth.
  function computeNodeBreadths() {
    var remainingNodes = nodes,nextNodes,x = 0;

    while (remainingNodes.length) {
      nextNodes = [];
      remainingNodes.forEach(function(node) {
        node.x = x;
        node.dx = nodeWidth;
        node.sourceLinks.forEach(function(link) {
          if (nextNodes.indexOf(link.target) < 0) {
            nextNodes.push(link.target);
          }
        });
      });
      remainingNodes = nextNodes;
      ++x;
    }

    //
    moveSinksRight(x);
    scaleNodeBreadths((size[0] - nodeWidth) / (x - 1));
  }

  function moveSourcesRight() {
    nodes.forEach(function(node) {
      if (!node.targetLinks.length) {
        node.x = d3.min(node.sourceLinks,function(d) {
          return d.target.x;
        }) - 1;
      }
    });
  }

  function moveSinksRight(x) {
    nodes.forEach(function(node) {
      if (!node.sourceLinks.length) {
        node.x = x - 1;
      }
    });
  }

  function scaleNodeBreadths(kx) {
    nodes.forEach(function(node) {
      node.x *= kx;
    });
  }

  function computeNodeDepths(iterations) {
    var nodesByBreadth = d3.nest()
      .key(function(d) {
        return d.x;
      })
      .sortKeys(d3.ascending)
      .entries(nodes)
      .map(function(d) {
        return d.values;
      });

    //
    initializeNodeDepth();
    resolveCollisions();
    for (var alpha = 1; iterations > 0; --iterations) {
      relaxRightToLeft(alpha *= .99);
      resolveCollisions();
      relaxLeftToRight(alpha);
      resolveCollisions();
    }

    function initializeNodeDepth() {
      var ky = d3.min(nodesByBreadth,function(nodes) {
        return (size[1] - (nodes.length - 1) * nodePadding) / d3.sum(nodes,value);
      });

      nodesByBreadth.forEach(function(nodes) {
        nodes.forEach(function(node,i) {
          node.y = i;
          node.dy = node.value * ky;
        });
      });

      links.forEach(function(link) {
        link.dy = link.value * ky;
      });
    }

    function relaxLeftToRight(alpha) {
      nodesByBreadth.forEach(function(nodes,breadth) {
        nodes.forEach(function(node) {
          if (node.targetLinks.length) {
            var y = d3.sum(node.targetLinks,weightedSource) / d3.sum(node.targetLinks,value);
            //node.y += (y - center(node)) * alpha;
          }
        });
      });

      function weightedSource(link) {
        return center(link.source) * link.value;
      }
    }

    function relaxRightToLeft(alpha) {
      nodesByBreadth.slice().reverse().forEach(function(nodes) {
        nodes.forEach(function(node) {
          if (node.sourceLinks.length) {
            var y = d3.sum(node.sourceLinks,weightedTarget) / d3.sum(node.sourceLinks,value);
            //node.y += (y - center(node)) * alpha;
          }
        });
      });

      function weightedTarget(link) {
        return center(link.target) * link.value;
      }
    }

    function resolveCollisions() {
      nodesByBreadth.forEach(function(nodes) {
        var node,dy,y0 = 0,n = nodes.length,i;

        // Push any overlapping nodes down.
        nodes.sort(function(a,b) {
          return a.value > b.value ? -1 : a.value < b.value ? 1 : 0;
        });
        for (i = 0; i < n; ++i) {
          node = nodes[i];
          dy = y0 - node.y;
          if (dy > 0) node.y += dy;
          y0 = node.y + node.dy + nodePadding;
        }

        // If the bottommost node goes outside the bounds,push it back up.
        dy = y0 - nodePadding - size[1];
        if (dy > 0) {
          y0 = node.y -= dy;

          // Push any overlapping nodes back up.
          for (i = n - 2; i >= 0; --i) {
            node = nodes[i];
            dy = node.y + node.dy + nodePadding - y0;
            if (dy > 0) node.y -= dy;
            y0 = node.y;
          }
        }
      });
    }

    function ascendingDepth(a,b) {
      return a.y - b.y;
    }
  }

  function computeLinkDepths() {
    nodes.forEach(function(node) {
      node.sourceLinks.sort(ascendingTargetDepth);
      node.targetLinks.sort(ascendingSourceDepth);
    });
    nodes.forEach(function(node) {
      var sy = 0,ty = 0;
      node.sourceLinks.forEach(function(link) {
        link.sy = sy;
        sy += link.dy;
      });
      node.targetLinks.forEach(function(link) {
        link.ty = ty;
        ty += link.dy;
      });
    });

    function ascendingSourceDepth(a,b) {
      return a.source.y - b.source.y;
    }

    function ascendingTargetDepth(a,b) {
      return a.target.y - b.target.y;
    }
  }

  function center(node) {
    return node.y + node.dy / 2;
  }

  function value(link) {
    return link.value;
  }

  return sankey;
};


/* ------------------- our code ------------------------ */

var units = "Widgets";

var linkData = [{
  "source": 7,"target": 6,"productIdx": "8350","qty": 27200,"mv": 3054016,"matrixCost": 300,"sourceNodeName": "G","targetNodeName": "L","value": 27200
},{
  "source": 7,"target": 11,"qty": 196599,"mv": 22074135.72,"matrixCost": 5000,"targetNodeName": "K","value": 196599
},{
  "source": 9,"qty": 113401,"mv": 12732664.28,"matrixCost": 5020,"sourceNodeName": "I","value": 113401
},{
  "source": 2,"target": 10,"qty": 0,"mv": 0,"matrixCost": 100015,"sourceNodeName": "C","targetNodeName": "J","value": 0
},{
  "source": 0,"qty": 2500000,"mv": 280700000,"matrixCost": 100033,"sourceNodeName": "A","value": 2500000
},"target": 5,"matrixCost": 240048,"targetNodeName": "F","target": 3,"qty": 1309599,"mv": 147041775.72,"targetNodeName": "D","value": 1309599
},"qty": 3250408,"mv": 364955810.24,"value": 3250408
},{
  "source": 8,"target": 1,"sourceNodeName": "H","targetNodeName": "B",{
  "source": 4,"qty": 10,"mv": 1122.8,"sourceNodeName": "E","value": 10
}]

var nodes = [{
  "nodeId": 0,"nodeName": "A","entityCd": "16G"
},{
  "nodeId": 1,"nodeName": "B","entityCd": "161"
},{
  "nodeId": 2,"nodeName": "C",{
  "nodeId": 3,"nodeName": "D","entityCd": "XX"
},{
  "nodeId": 4,"nodeName": "E",{
  "nodeId": 5,"nodeName": "F",{
  "nodeId": 6,"nodeName": "L","entityCd": "16G","bucketCd": "LoantoCoverDublinPBCustShort","bucketName": "Loan to Cover Dublin PB Cust Short","bucketType": "U"
},{
  "nodeId": 7,"nodeName": "G",{
  "nodeId": 8,"nodeName": "H",{
  "nodeId": 9,"nodeName": "I",{
  "nodeId": 10,"nodeName": "J",{
  "nodeId": 11,"nodeName": "K","entityCd": "16G"
}]
var formatNumber = d3.format(",.0f"),// zero decimal places
  format = function(d) {
    return formatNumber(d)
  };

// append the svg object to the body of the page
var svg = d3.select("#" + "chart"),width = +svg.attr("width"),height = +svg.attr("height");

var sankey = d3.sankey()
  .nodeWidth(10)
  .nodePadding(10)
  .size([width - 1,height - 6]);

var path = sankey.link();

d3.selectAll('.tooltip-sankey').style("visibility","hidden");
var tooltip = d3.select("body")
  .append("div")
  .attr("class","tooltip-sankey")
  .style("position","absolute")
  .style("z-index","10")
  .style("visibility","hidden");

// Transform the data so even nodes with small links always have something to show for it.
var baseWeight = 50
linkData.forEach(function(link) {
  link.value = baseWeight + Math.sqrt(link.qty);
});


// load the data

sankey
  .nodes(nodes)
  .links(linkData)
  .layout(32);

// add in the links
var link = svg.append("g").selectAll(".link")
  .data(linkData)
  .enter().append("path")
  .attr("class","link-sankey")
  .attr("d",path)
  .style('stroke',function(d) {
    return d.color;
  }).style("stroke-width",function(d) {
    return Math.max(1,d.dy)
  })
  .sort(function(a,b) {
    return b.dy - a.dy;
  })

  .on("mouseover",function(d) {
    var sourceId,targetId;
    for (var item in d.source.sourceLinks)
      if (d.value === d.source.sourceLinks[item].value) sourceId = item;
    for (var item in d.target.targetLinks)
      if (d.value === d.target.targetLinks[item].value) targetId = item;
    var source = d3.select("#inner-rect-" + d.source.id + "-" + sourceId);
    var target = d3.select("#inner-rect-" + d.target.id + "-" + targetId);
    source.style("stroke-opacity",1).style("fill","#1f77b4");
    target.style("stroke-opacity","#1f77b4");

    //update text - one line
    var sourceText = d3.select("#text-" + d.source.id);
    sourceText.text(d.source.nodeName + ' : ' + d.value);
    var targetText = d3.select("#text-" + d.target.id);
    targetText.text(d.target.nodeName + ' : ' + d.value);

    tooltip.html("Source: <b>" + d.source.nodeName + "</b><br/>Target: <b>" + d.target.nodeName + "</b><br/>Identifier: <b>" + +"</b>" + "</b><br/>Qty: <b>" + format(d.value) + "</b>" + "</b><br/>Mv($): <b>" + format(d.mv) + "</b>");
    return tooltip.style("visibility","visible");
  })
  .on("mousemove",function() {
    var tooltipWidth = parseInt(tooltip.style("width"));
    if (window.innerWidth - d3.event.pageX < tooltipWidth) return tooltip.style("top",(d3.event.pageY - 10) + "px").style("left",(d3.event.pageX - tooltipWidth) + "px");
    return tooltip.style("top",(d3.event.pageX + 10) + "px");
  })
  .on("mouseout",targetId;
    for (var item in d.source.sourceLinks)
      if (d.value === d.source.sourceLinks[item].value) sourceId = item;
    for (var item in d.target.targetLinks)
      if (d.value === d.target.targetLinks[item].value) targetId = item;

    //update text - one line
    var sourceText = d3.select("#text-" + d.source.id);
    sourceText.text(d.source.nodeName + ' : ' + d.source.value);
    var targetText = d3.select("#text-" + d.target.id);
    targetText.text(d.target.nodeName + ' : ' + d.target.value);

    return tooltip.style("visibility","hidden");
  });

// add in the nodes
var node = svg.append("g").selectAll(".node")
  .data(nodes)
  .enter().append("g")
  .on("click",showConnections)
  .on("mouseover",function(d) {
    if (d.totalPledge == 0 || d.totalBorrow == 0) {
      tooltip.html("Name: <b>" + d.nodeName + "</b><br/>Total Qty: <b>" + format(d.value) + "</b>");
    } else {
      tooltip.html("Name: <b>" + d.nodeName + "</b><br/>Total Qty: <b>" + format(d.value) + "</b>");
    }
    return tooltip.style("visibility",function() {
    return tooltip.style("visibility","hidden");
  })
  .attr("class","node")
  .attr("transform",function(d) {
    return "translate(" + d.x + "," + d.y + ")";
  })
  .on("click",showConnections);

// add the rectangles for the nodes
node.append("rect")
  .attr("height",function(d) { return d.dy; })
  .attr("width",function(d) { return d.dx; })
  .style("fill","#605f66")
  .style("stroke",function(d) {
    return d3.rgb(d.color).darker(2);
  })
  .on("click",showConnections)


// add in the title for the nodes
node.append("text")
  .attr("x",-6)
  .attr("y",function(d) { return d.dy / 2; })
  .attr("dy",".15em")
  .attr("text-anchor","end")
  //.style("font-size","10px")
  .attr("transform",null)
  .attr("class","nodeText")
  .text(function(d) {
    return d.nodeName;
  })
  .filter(function(d) {
    return d.x < width / 2;
  })
  .attr("x",6 + sankey.nodeWidth())
  .attr("text-anchor","start");

function showConnections(d) {
  if (!d3.select(this).classed("selected")) {
    d3.select(this).classed("selected",true);
    d3.select(this).style("stroke","black")
      .style("stroke-opacity",0.2);
    //If this link is connected to the node
    link.style("stroke-opacity",function(l) {
      if (l.source.nodeName == d.nodeName || l.target.nodeName == d.nodeName) {
        return 0.6;
      } else
        return 0.2;
    });
  } else {
    d3.select(this).classed("selected",false);
    d3.select(this).style("stroke",0.2)
    link.style("stroke-opacity",function() {
      return 0.2;
    });
  }
}
.node rect {
  cursor: move;
}

.link {
  fill: none;
  stroke: #000;
  stroke-opacity: .2;
}

.link:hover {
  stroke-opacity: .5;
}

* {
  font: 11px sans-serif;
}

.linkLabel {
  z-index: 10;
}

svg line.links {
  stroke: #999999;
  stroke-opacity: 0.6;
  stroke-width: 1px;
}

.link-sankey {
  fill: none;
  stroke: #1f77b4;
  stroke-opacity: .2;
}

.link-sankey:hover {
  stroke-opacity: .5 !important;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
<svg width="550" height="400" id="chart"></svg>

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 &lt;select id=&quot;xxx&quot;&gt; SELECT di.id, di.name, di.work_type, di.updated... &lt;where&gt; &lt;if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 &lt;property name=&quot;dynamic.classpath&quot; value=&quot;tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-