-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
d3-sankey-circular with grouping #3426
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
Changes from all commits
aea2f8e
69089bc
8a31460
4223f7d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -46,7 +46,7 @@ function sankeyModel(layout, d, traceIndex) { | |
.sankeyCircular() | ||
.circularLinkGap(2) | ||
.nodeId(function(d) { | ||
return d.index; | ||
return d.pointNumber; | ||
}); | ||
} else { | ||
sankey = d3Sankey.sankey(); | ||
|
@@ -60,12 +60,29 @@ function sankeyModel(layout, d, traceIndex) { | |
.nodes(nodes) | ||
.links(links); | ||
|
||
var graph = sankey(); | ||
|
||
if(sankey.nodePadding() < nodePad) { | ||
Lib.warn('node.pad was reduced to ', sankey.nodePadding(), ' to fit within the figure.'); | ||
} | ||
|
||
var graph = sankey(); | ||
Object.keys(calcData._groupLookup).forEach(function(nodePointNumber) { | ||
var groupIndex = parseInt(calcData._groupLookup[nodePointNumber]); | ||
var groupingNode = graph.nodes.find(function(node) { | ||
return node.pointNumber === groupIndex; | ||
}); | ||
groupingNode.key = groupingNode.key + ',' + nodePointNumber; | ||
graph.nodes.push({ | ||
pointNumber: parseInt(nodePointNumber), | ||
x0: groupingNode.x0, | ||
x1: groupingNode.x1, | ||
y0: groupingNode.y0, | ||
y1: groupingNode.y1, | ||
partOfGroup: true, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
sourceLinks: [], | ||
targetLinks: [] | ||
}); | ||
}); | ||
|
||
return { | ||
circular: circular, | ||
key: traceIndex, | ||
|
@@ -97,6 +114,14 @@ function sankeyModel(layout, d, traceIndex) { | |
}; | ||
} | ||
|
||
function columnModel(d, n) { | ||
var column = { | ||
key: 'column-' + n.column, | ||
coords: [[ (n.x0 + n.x1) / 2, 0], [ (n.x0 + n.x1) / 2, d.height]] | ||
}; | ||
return column; | ||
} | ||
|
||
function linkModel(d, l, i) { | ||
var tc = tinycolor(l.color); | ||
var basicKey = l.source.label + '|' + l.target.label; | ||
|
@@ -159,8 +184,10 @@ function nodeModel(d, n, i) { | |
var visibleThickness = n.x1 - n.x0; | ||
var visibleLength = Math.max(0.5, (n.y1 - n.y0)); | ||
|
||
var basicKey = n.label; | ||
var key = basicKey + '__' + i; | ||
var partOfGroup = false; | ||
if(n.partOfGroup) partOfGroup = n.partOfGroup; | ||
|
||
var key = n.key || n.pointNumber; | ||
|
||
// for event data | ||
n.trace = d.trace; | ||
|
@@ -173,6 +200,7 @@ function nodeModel(d, n, i) { | |
return { | ||
index: n.pointNumber, | ||
key: key, | ||
partOfGroup: partOfGroup, | ||
traceId: d.key, | ||
node: n, | ||
nodePad: d.nodePad, | ||
|
@@ -256,19 +284,19 @@ function attachPointerEvents(selection, sankey, eventSet) { | |
selection | ||
.on('.basic', null) // remove any preexisting handlers | ||
.on('mouseover.basic', function(d) { | ||
if(!d.interactionState.dragInProgress) { | ||
if(!d.interactionState.dragInProgress && !d.partOfGroup) { | ||
eventSet.hover(this, d, sankey); | ||
d.interactionState.hovered = [this, d]; | ||
} | ||
}) | ||
.on('mousemove.basic', function(d) { | ||
if(!d.interactionState.dragInProgress) { | ||
if(!d.interactionState.dragInProgress && !d.partOfGroup) { | ||
eventSet.follow(this, d); | ||
d.interactionState.hovered = [this, d]; | ||
} | ||
}) | ||
.on('mouseout.basic', function(d) { | ||
if(!d.interactionState.dragInProgress) { | ||
if(!d.interactionState.dragInProgress && !d.partOfGroup) { | ||
eventSet.unhover(this, d, sankey); | ||
d.interactionState.hovered = false; | ||
} | ||
|
@@ -278,7 +306,7 @@ function attachPointerEvents(selection, sankey, eventSet) { | |
eventSet.unhover(this, d, sankey); | ||
d.interactionState.hovered = false; | ||
} | ||
if(!d.interactionState.dragInProgress) { | ||
if(!d.interactionState.dragInProgress && !d.partOfGroup) { | ||
eventSet.select(this, d, sankey); | ||
} | ||
}); | ||
|
@@ -353,7 +381,9 @@ function attachDragHandler(sankeyNode, sankeyLink, callbacks) { | |
function attachForce(sankeyNode, forceKey, d) { | ||
// Attach force to nodes in the same column (same x coordinate) | ||
switchToForceFormat(d.graph.nodes); | ||
var nodes = d.graph.nodes.filter(function(n) {return n.originalX === d.node.originalX;}); | ||
var nodes = d.graph.nodes | ||
.filter(function(n) {return n.originalX === d.node.originalX;}) | ||
.filter(function(n) {return !n.partOfGroup;}); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Take the children nodes out of the force simulation |
||
d.forceLayouts[forceKey] = d3Force.forceSimulation(nodes) | ||
.alphaDecay(0) | ||
.force('collide', d3Force.forceCollide() | ||
|
@@ -497,9 +527,13 @@ module.exports = function(svg, calcData, layout, callbacks) { | |
.enter().append('path') | ||
.classed(c.cn.sankeyLink, true) | ||
.attr('d', linkPath()) | ||
.call(attachPointerEvents, sankey, callbacks.linkEvents); | ||
.call(attachPointerEvents, sankey, callbacks.linkEvents) | ||
.style('opacity', 0); | ||
|
||
sankeyLink | ||
.transition() | ||
.ease(c.ease).duration(c.duration) | ||
.style('opacity', 1) | ||
.style('stroke', function(d) { | ||
if(!d.circular) return salientEnough(d) ? Color.tinyRGB(tinycolor(d.linkLineColor)) : d.tinyColorHue; | ||
return d.tinyColorHue; | ||
|
@@ -517,17 +551,43 @@ module.exports = function(svg, calcData, layout, callbacks) { | |
.style('stroke-width', function(d) { | ||
if(d.circular) return d.link.width; | ||
return salientEnough(d) ? d.linkLineWidth : 1; | ||
}); | ||
|
||
sankeyLink.transition() | ||
.ease(c.ease).duration(c.duration) | ||
.attr('d', linkPath()); | ||
}) | ||
.attr('d', linkPath()); | ||
|
||
sankeyLink.exit().transition() | ||
.ease(c.ease).duration(c.duration) | ||
.style('opacity', 0) | ||
.remove(); | ||
|
||
var sankeyColumnSet = sankey.selectAll('.sankeyColumnSet') | ||
.data(repeat, keyFun); | ||
|
||
sankeyColumnSet.enter() | ||
.append('g') | ||
.classed('sankeyColumnSet', true); | ||
|
||
var sankeyColumn = sankeyColumnSet.selectAll('.sankeyColumn') | ||
.data(function(d) { | ||
var nodes = d.graph.nodes; | ||
return nodes | ||
.map(columnModel.bind(null, d)); | ||
}, keyFun); | ||
|
||
sankeyColumn.enter() | ||
.append('g') | ||
.classed('sankeyColumn', true); | ||
|
||
sankeyColumn | ||
.append('path') | ||
.attr('d', function(column) { | ||
var d = column.coords; | ||
return 'M' + d[0][0] + ',' + d[0][1] + 'V' + d[1][0] + ',' + d[1][1]; | ||
}) | ||
.style('stroke-width', function() {return '0px';}) | ||
.style('stroke-dasharray', function() { return '1 1';}) | ||
.style('stroke', function() {return 'black';}) | ||
.style('stroke-opacity', function() { return 0.4;}); | ||
|
||
var sankeyNodeSet = sankey.selectAll('.' + c.cn.sankeyNodeSet) | ||
.data(repeat, keyFun); | ||
|
||
|
@@ -549,22 +609,26 @@ module.exports = function(svg, calcData, layout, callbacks) { | |
var nodes = d.graph.nodes; | ||
persistOriginalPlace(nodes); | ||
return nodes | ||
.filter(function(n) {return n.value;}) | ||
// .filter(function(n) {return n.value;}) | ||
.map(nodeModel.bind(null, d)); | ||
}, keyFun); | ||
|
||
sankeyNode.enter() | ||
.append('g') | ||
// .style('opacity', 0) | ||
.classed(c.cn.sankeyNode, true) | ||
.call(updateNodePositions) | ||
.call(attachPointerEvents, sankey, callbacks.nodeEvents); | ||
.style('opacity', 0) | ||
|
||
|
||
sankeyNode | ||
.call(attachPointerEvents, sankey, callbacks.nodeEvents) | ||
.call(attachDragHandler, sankeyLink, callbacks); // has to be here as it binds sankeyLink | ||
|
||
sankeyNode.transition() | ||
.ease(c.ease).duration(c.duration) | ||
.call(updateNodePositions); | ||
.call(updateNodePositions) | ||
.style('opacity', function(n) { return n.partOfGroup ? 0 : 1;}); | ||
|
||
sankeyNode.exit().transition() | ||
.ease(c.ease).duration(c.duration) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
d3-sankey
already does that downstream (ie. removing unlinked nodes) and it's useful to keep the unlinked nodes if you want to render something associated to them. For example, I use it with d3 to render and animate ghost nodes on grouping.