Skip to content

sankey: compare links in a flow on hover #3730

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

Merged
merged 5 commits into from
Apr 8, 2019
Merged
Show file tree
Hide file tree
Changes from 3 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
2 changes: 1 addition & 1 deletion src/components/fx/hover.js
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ exports.multiHovers = function multiHovers(hoverItems, opts) {

alignHoverText(hoverLabel, fullOpts.rotateLabels);

return hoverLabel.node();
return hoverLabel;
};

// The actual implementation is here:
Expand Down
4 changes: 4 additions & 0 deletions src/components/modebar/manage.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ function getButtonGroups(gd, buttonsToRemove, buttonsToAdd, showSendToCloud) {
var hasTernary = fullLayout._has('ternary');
var hasMapbox = fullLayout._has('mapbox');
var hasPolar = fullLayout._has('polar');
var hasSankey = fullLayout._has('sankey');
var allAxesFixed = areAllAxesFixed(fullLayout);

var groups = [];
Expand Down Expand Up @@ -139,6 +140,9 @@ function getButtonGroups(gd, buttonsToRemove, buttonsToAdd, showSendToCloud) {
else if(hasPie) {
hoverGroup = ['hoverClosestPie'];
}
else if(hasSankey) {
hoverGroup = ['hoverClosestCartesian', 'hoverCompareCartesian'];
}
else { // hasPolar, hasTernary
// always show at least one hover icon.
hoverGroup = ['toggleHover'];
Expand Down
86 changes: 49 additions & 37 deletions src/traces/sankey/plot.js
Original file line number Diff line number Diff line change
Expand Up @@ -156,51 +156,63 @@ module.exports = function plot(gd, calcData) {
if(gd._fullLayout.hovermode === false) return;
var obj = d.link.trace.link;
if(obj.hoverinfo === 'none' || obj.hoverinfo === 'skip') return;
var rootBBox = gd._fullLayout._paperdiv.node().getBoundingClientRect();
var hoverCenterX;
var hoverCenterY;
if(d.link.circular) {
hoverCenterX = (d.link.circularPathData.leftInnerExtent + d.link.circularPathData.rightInnerExtent) / 2 + d.parent.translateX;
hoverCenterY = d.link.circularPathData.verticalFullExtent + d.parent.translateY;
} else {
var boundingBox = element.getBoundingClientRect();
hoverCenterX = boundingBox.left + boundingBox.width / 2 - rootBBox.left;
hoverCenterY = boundingBox.top + boundingBox.height / 2 - rootBBox.top;
}

var hovertemplateLabels = {valueLabel: d3.format(d.valueFormat)(d.link.value) + d.valueSuffix};
d.link.fullData = d.link.trace;
var hoverItems = [];

// For each related links, create a hoverItem
for(var i = 0; i < d.flow.links.length; i++) {
var link = d.flow.links[i];
if(gd._fullLayout.hovermode === 'closest' && d.link.pointNumber !== link.pointNumber) continue;
Copy link
Contributor

@etpinard etpinard Apr 4, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, hovermode: 'x' or 'y' enables the "compare" mode? That's a little unfortunate because sankey don't have x nor y axes, but I guess it's not worth adding a hovermode: 'compare' just for sankey.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

... so I'm ok with this 👌

link.fullData = link.trace;
obj = d.link.trace.link;
var hoverCenterX;
var hoverCenterY;
if(link.circular) {
hoverCenterX = (link.circularPathData.leftInnerExtent + link.circularPathData.rightInnerExtent) / 2 + d.parent.translateX;
hoverCenterY = link.circularPathData.verticalFullExtent + d.parent.translateY;
} else {
hoverCenterX = (link.source.x1 + link.target.x0) / 2 + d.parent.translateX;
hoverCenterY = (link.y0 + link.y1) / 2 + d.parent.translateY;
}

var tooltip = Fx.loneHover({
x: hoverCenterX,
y: hoverCenterY,
name: hovertemplateLabels.valueLabel,
text: [
d.link.label || '',
sourceLabel + d.link.source.label,
targetLabel + d.link.target.label,
d.link.concentrationscale ? concentrationLabel + d3.format('%0.2f')(d.link.flow.labelConcentration) : ''
].filter(renderableValuePresent).join('<br>'),
color: castHoverOption(obj, 'bgcolor') || Color.addOpacity(d.tinyColorHue, 1),
borderColor: castHoverOption(obj, 'bordercolor'),
fontFamily: castHoverOption(obj, 'font.family'),
fontSize: castHoverOption(obj, 'font.size'),
fontColor: castHoverOption(obj, 'font.color'),
idealAlign: d3.event.x < hoverCenterX ? 'right' : 'left',
var hovertemplateLabels = {valueLabel: d3.format(d.valueFormat)(link.value) + d.valueSuffix};

hoverItems.push({
x: hoverCenterX,
y: hoverCenterY,
name: hovertemplateLabels.valueLabel,
text: [
link.label || '',
sourceLabel + link.source.label,
targetLabel + link.target.label,
link.concentrationscale ? concentrationLabel + d3.format('%0.2f')(link.flow.labelConcentration) : ''
].filter(renderableValuePresent).join('<br>'),
color: castHoverOption(obj, 'bgcolor') || Color.addOpacity(link.color, 1),
borderColor: castHoverOption(obj, 'bordercolor'),
fontFamily: castHoverOption(obj, 'font.family'),
fontSize: castHoverOption(obj, 'font.size'),
fontColor: castHoverOption(obj, 'font.color'),
idealAlign: d3.event.x < hoverCenterX ? 'right' : 'left',

hovertemplate: obj.hovertemplate,
hovertemplateLabels: hovertemplateLabels,
eventData: [link]
});
}

hovertemplate: obj.hovertemplate,
hovertemplateLabels: hovertemplateLabels,
eventData: [d.link]
}, {
var tooltips = Fx.multiHovers(hoverItems, {
container: fullLayout._hoverlayer.node(),
outerContainer: fullLayout._paper.node(),
gd: gd
});

if(!d.link.concentrationscale) {
makeTranslucent(tooltip, 0.65);
}
makeTextContrasty(tooltip);
tooltips.each(function() {
var tooltip = this;
if(!d.link.concentrationscale) {
makeTranslucent(tooltip, 0.65);
}
makeTextContrasty(tooltip);
});
};

var linkUnhover = function(element, d, sankey) {
Expand Down
6 changes: 3 additions & 3 deletions src/traces/sankey/render.js
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,9 @@ function sankeyModel(layout, d, traceIndex) {
concentration: link.value / total,
links: flowLinks
};
if(link.concentrationscale) {
link.color = tinycolor(link.concentrationscale(link.flow.labelConcentration));
}
}
}

Expand Down Expand Up @@ -287,9 +290,6 @@ function sankeyModel(layout, d, traceIndex) {

function linkModel(d, l, i) {
var tc = tinycolor(l.color);
if(l.concentrationscale) {
tc = tinycolor(l.concentrationscale(l.flow.labelConcentration));
}
var basicKey = l.source.label + '|' + l.target.label;
var key = basicKey + '__' + i;

Expand Down
3 changes: 2 additions & 1 deletion test/image/mocks/sankey_link_concentration.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
"layout": {
"title": "Sankey diagram with links colored based on their concentration within a flow",
"width": 800,
"height": 800
"height": 800,
"hovermode": "x"
}
}