'use strict';
var d3 = require('@plotly/d3');
var Lib = require('../../lib');
var numberFormat = Lib.numberFormat;
var render = require('./render');
var Fx = require('../../components/fx');
var Color = require('../../components/color');
var cn = require('./constants').cn;
var _ = Lib._;
function renderableValuePresent(d) {return d !== '';}
function ownTrace(selection, d) {
return selection.filter(function(s) {return s.key === d.traceId;});
}
function makeTranslucent(element, alpha) {
d3.select(element)
.select('path')
.style('fill-opacity', alpha);
d3.select(element)
.select('rect')
.style('fill-opacity', alpha);
}
function makeTextContrasty(element) {
d3.select(element)
.select('text.name')
.style('fill', 'black');
}
function relatedLinks(d) {
return function(l) {
return d.node.sourceLinks.indexOf(l.link) !== -1 || d.node.targetLinks.indexOf(l.link) !== -1;
};
}
function relatedNodes(l) {
return function(d) {
return d.node.sourceLinks.indexOf(l.link) !== -1 || d.node.targetLinks.indexOf(l.link) !== -1;
};
}
function nodeHoveredStyle(sankeyNode, d, sankey) {
if(d && sankey) {
ownTrace(sankey, d)
.selectAll('.' + cn.sankeyLink)
.filter(relatedLinks(d))
.call(linkHoveredStyle.bind(0, d, sankey, false));
}
}
function nodeNonHoveredStyle(sankeyNode, d, sankey) {
if(d && sankey) {
ownTrace(sankey, d)
.selectAll('.' + cn.sankeyLink)
.filter(relatedLinks(d))
.call(linkNonHoveredStyle.bind(0, d, sankey, false));
}
}
function linkHoveredStyle(d, sankey, visitNodes, sankeyLink) {
sankeyLink.style('fill', function(l) {
if(!l.link.concentrationscale) {
return l.tinyColorHoverHue;
}
}).style('fill-opacity', function(l) {
if(!l.link.concentrationscale) {
return l.tinyColorHoverAlpha;
}
});
sankeyLink.each(function(curLink) {
var label = curLink.link.label;
if(label !== '') {
ownTrace(sankey, d)
.selectAll('.' + cn.sankeyLink)
.filter(function(l) {return l.link.label === label;})
.style('fill', function(l) {
if(!l.link.concentrationscale) {
return l.tinyColorHoverHue;
}
}).style('fill-opacity', function(l) {
if(!l.link.concentrationscale) {
return l.tinyColorHoverAlpha;
}
});
}
});
if(visitNodes) {
ownTrace(sankey, d)
.selectAll('.' + cn.sankeyNode)
.filter(relatedNodes(d))
.call(nodeHoveredStyle);
}
}
function linkNonHoveredStyle(d, sankey, visitNodes, sankeyLink) {
sankeyLink.style('fill', function(l) {
return l.tinyColorHue;
}).style('fill-opacity', function(l) {
return l.tinyColorAlpha;
});
sankeyLink.each(function(curLink) {
var label = curLink.link.label;
if(label !== '') {
ownTrace(sankey, d)
.selectAll('.' + cn.sankeyLink)
.filter(function(l) {return l.link.label === label;})
.style('fill', function(l) {return l.tinyColorHue;})
.style('fill-opacity', function(l) {return l.tinyColorAlpha;});
}
});
if(visitNodes) {
ownTrace(sankey, d)
.selectAll(cn.sankeyNode)
.filter(relatedNodes(d))
.call(nodeNonHoveredStyle);
}
}
// does not support array values for now
function castHoverOption(trace, attr) {
var labelOpts = trace.hoverlabel || {};
var val = Lib.nestedProperty(labelOpts, attr).get();
return Array.isArray(val) ? false : val;
}
module.exports = function plot(gd, calcData) {
var fullLayout = gd._fullLayout;
var svg = fullLayout._paper;
var size = fullLayout._size;
// stash initial view
for(var i = 0; i < gd._fullData.length; i++) {
if(!gd._fullData[i].visible) continue;
if(gd._fullData[i].type !== cn.sankey) continue;
if(!gd._fullData[i]._viewInitial) {
var node = gd._fullData[i].node;
gd._fullData[i]._viewInitial = {
node: {
groups: node.groups.slice(),
x: node.x.slice(),
y: node.y.slice()
}
};
}
}
var linkSelect = function(element, d) {
var evt = d.link;
evt.originalEvent = d3.event;
gd._hoverdata = [evt];
Fx.click(gd, { target: true });
};
var linkHover = function(element, d, sankey) {
if(gd._fullLayout.hovermode === false) return;
d3.select(element).call(linkHoveredStyle.bind(0, d, sankey, true));
if(d.link.trace.link.hoverinfo !== 'skip') {
d.link.fullData = d.link.trace;
gd.emit('plotly_hover', {
event: d3.event,
points: [d.link]
});
}
};
var sourceLabel = _(gd, 'source:') + ' ';
var targetLabel = _(gd, 'target:') + ' ';
var concentrationLabel = _(gd, 'concentration:') + ' ';
var incomingLabel = _(gd, 'incoming flow count:') + ' ';
var outgoingLabel = _(gd, 'outgoing flow count:') + ' ';
var linkHoverFollow = function(element, d) {
if(gd._fullLayout.hovermode === false) return;
var obj = d.link.trace.link;
if(obj.hoverinfo === 'none' || obj.hoverinfo === 'skip') return;
var hoverItems = [];
function hoverCenterPosition(link) {
var hoverCenterX, hoverCenterY;
if(link.circular) {
hoverCenterX = (link.circularPathData.leftInnerExtent + link.circularPathData.rightInnerExtent) / 2;
hoverCenterY = link.circularPathData.verticalFullExtent;
} else {
hoverCenterX = (link.source.x1 + link.target.x0) / 2;
hoverCenterY = (link.y0 + link.y1) / 2;
}
var center = [hoverCenterX, hoverCenterY];
if(link.trace.orientation === 'v') center.reverse();
center[0] += d.parent.translateX;
center[1] += d.parent.translateY;
return center;
}
// For each related links, create a hoverItem
var anchorIndex = 0;
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;
if(d.link.pointNumber === link.pointNumber) anchorIndex = i;
link.fullData = link.trace;
obj = d.link.trace.link;
var hoverCenter = hoverCenterPosition(link);
var hovertemplateLabels = {valueLabel: numberFormat(d.valueFormat)(link.value) + d.valueSuffix};
hoverItems.push({
x: hoverCenter[0],
y: hoverCenter[1],
name: hovertemplateLabels.valueLabel,
text: [
link.label || '',
sourceLabel + link.source.label,
targetLabel + link.target.label,
link.concentrationscale ? concentrationLabel + numberFormat('%0.2f')(link.flow.labelConcentration) : ''
].filter(renderableValuePresent).join('
'),
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'),
fontWeight: castHoverOption(obj, 'font.weight'),
fontStyle: castHoverOption(obj, 'font.style'),
fontVariant: castHoverOption(obj, 'font.variant'),
fontTextcase: castHoverOption(obj, 'font.textcase'),
fontLineposition: castHoverOption(obj, 'font.lineposition'),
fontShadow: castHoverOption(obj, 'font.shadow'),
nameLength: castHoverOption(obj, 'namelength'),
textAlign: castHoverOption(obj, 'align'),
idealAlign: d3.event.x < hoverCenter[0] ? 'right' : 'left',
hovertemplate: obj.hovertemplate,
hovertemplateLabels: hovertemplateLabels,
eventData: [link]
});
}
var tooltips = Fx.loneHover(hoverItems, {
container: fullLayout._hoverlayer.node(),
outerContainer: fullLayout._paper.node(),
gd: gd,
anchorIndex: anchorIndex
});
tooltips.each(function() {
var tooltip = this;
if(!d.link.concentrationscale) {
makeTranslucent(tooltip, 0.65);
}
makeTextContrasty(tooltip);
});
};
var linkUnhover = function(element, d, sankey) {
if(gd._fullLayout.hovermode === false) return;
d3.select(element).call(linkNonHoveredStyle.bind(0, d, sankey, true));
if(d.link.trace.link.hoverinfo !== 'skip') {
d.link.fullData = d.link.trace;
gd.emit('plotly_unhover', {
event: d3.event,
points: [d.link]
});
}
Fx.loneUnhover(fullLayout._hoverlayer.node());
};
var nodeSelect = function(element, d, sankey) {
var evt = d.node;
evt.originalEvent = d3.event;
gd._hoverdata = [evt];
d3.select(element).call(nodeNonHoveredStyle, d, sankey);
Fx.click(gd, { target: true });
};
var nodeHover = function(element, d, sankey) {
if(gd._fullLayout.hovermode === false) return;
d3.select(element).call(nodeHoveredStyle, d, sankey);
if(d.node.trace.node.hoverinfo !== 'skip') {
d.node.fullData = d.node.trace;
gd.emit('plotly_hover', {
event: d3.event,
points: [d.node]
});
}
};
var nodeHoverFollow = function(element, d) {
if(gd._fullLayout.hovermode === false) return;
var obj = d.node.trace.node;
if(obj.hoverinfo === 'none' || obj.hoverinfo === 'skip') return;
var nodeRect = d3.select(element).select('.' + cn.nodeRect);
var rootBBox = gd._fullLayout._paperdiv.node().getBoundingClientRect();
var boundingBox = nodeRect.node().getBoundingClientRect();
var hoverCenterX0 = boundingBox.left - 2 - rootBBox.left;
var hoverCenterX1 = boundingBox.right + 2 - rootBBox.left;
var hoverCenterY = boundingBox.top + boundingBox.height / 4 - rootBBox.top;
var hovertemplateLabels = {valueLabel: numberFormat(d.valueFormat)(d.node.value) + d.valueSuffix};
d.node.fullData = d.node.trace;
gd._fullLayout._calcInverseTransform(gd);
var scaleX = gd._fullLayout._invScaleX;
var scaleY = gd._fullLayout._invScaleY;
var tooltip = Fx.loneHover({
x0: scaleX * hoverCenterX0,
x1: scaleX * hoverCenterX1,
y: scaleY * hoverCenterY,
name: numberFormat(d.valueFormat)(d.node.value) + d.valueSuffix,
text: [
d.node.label,
incomingLabel + d.node.targetLinks.length,
outgoingLabel + d.node.sourceLinks.length
].filter(renderableValuePresent).join('
'),
color: castHoverOption(obj, 'bgcolor') || d.tinyColorHue,
borderColor: castHoverOption(obj, 'bordercolor'),
fontFamily: castHoverOption(obj, 'font.family'),
fontSize: castHoverOption(obj, 'font.size'),
fontColor: castHoverOption(obj, 'font.color'),
fontWeight: castHoverOption(obj, 'font.weight'),
fontStyle: castHoverOption(obj, 'font.style'),
fontVariant: castHoverOption(obj, 'font.variant'),
fontTextcase: castHoverOption(obj, 'font.textcase'),
fontLineposition: castHoverOption(obj, 'font.lineposition'),
fontShadow: castHoverOption(obj, 'font.shadow'),
nameLength: castHoverOption(obj, 'namelength'),
textAlign: castHoverOption(obj, 'align'),
idealAlign: 'left',
hovertemplate: obj.hovertemplate,
hovertemplateLabels: hovertemplateLabels,
eventData: [d.node]
}, {
container: fullLayout._hoverlayer.node(),
outerContainer: fullLayout._paper.node(),
gd: gd
});
makeTranslucent(tooltip, 0.85);
makeTextContrasty(tooltip);
};
var nodeUnhover = function(element, d, sankey) {
if(gd._fullLayout.hovermode === false) return;
d3.select(element).call(nodeNonHoveredStyle, d, sankey);
if(d.node.trace.node.hoverinfo !== 'skip') {
d.node.fullData = d.node.trace;
gd.emit('plotly_unhover', {
event: d3.event,
points: [d.node]
});
}
Fx.loneUnhover(fullLayout._hoverlayer.node());
};
render(
gd,
svg,
calcData,
{
width: size.w,
height: size.h,
margin: {
t: size.t,
r: size.r,
b: size.b,
l: size.l
}
},
{
linkEvents: {
hover: linkHover,
follow: linkHoverFollow,
unhover: linkUnhover,
select: linkSelect
},
nodeEvents: {
hover: nodeHover,
follow: nodeHoverFollow,
unhover: nodeUnhover,
select: nodeSelect
}
}
);
};