Reputation: 22208
I have a highcharts sankey diagram with two sides:
There are situations where some of my nodes have empty links (=with 0 weight). I would like the node to being displayed despite having no link from or to it.
Any chance I can achieve this?
I read on this thread that I have to fake it with weight=1 connexions, I could make the link transparent, and twitch the tooltip to hide those, but that's very painful for something that feels pretty basic.
Maybe a custom call of the generateNode
call or something?
Thanks for the help
Upvotes: 1
Views: 586
Reputation: 3695
You can use the following wrap to show a node when the weight
is 0
.
const isObject = Highcharts.isObject,
merge = Highcharts.merge
function getDLOptions(
params
) {
const optionsPoint = (
isObject(params.optionsPoint) ?
params.optionsPoint.dataLabels : {}
),
optionsLevel = (
isObject(params.level) ?
params.level.dataLabels : {}
),
options = merge({
style: {}
}, optionsLevel, optionsPoint);
return options;
}
Highcharts.wrap(
Highcharts.seriesTypes.sankey.prototype,
'translateNode',
function(proceed, node, column) {
var translationFactor = this.translationFactor,
series = this,
chart = this.chart,
options = this.options,
sum = node.getSum(),
nodeHeight = Math.max(Math.round(sum * translationFactor),
this.options.minLinkWidth),
nodeWidth = Math.round(this.nodeWidth),
crisp = Math.round(options.borderWidth) % 2 / 2,
nodeOffset = column.sankeyColumn.offset(node,
translationFactor),
fromNodeTop = Math.floor(Highcharts.pick(nodeOffset.absoluteTop, (column.sankeyColumn.top(translationFactor) +
nodeOffset.relativeTop))) + crisp,
left = Math.floor(this.colDistance * node.column +
options.borderWidth / 2) + Highcharts.relativeLength(node.options.offsetHorizontal || 0,
nodeWidth) +
crisp,
nodeLeft = chart.inverted ?
chart.plotSizeX - left :
left;
node.sum = sum;
proceed.apply(this, Array.prototype.slice.call(arguments, 1));
if (1) {
// Draw the node
node.shapeType = 'rect';
node.nodeX = nodeLeft;
node.nodeY = fromNodeTop;
let x = nodeLeft,
y = fromNodeTop,
width = node.options.width || options.width || nodeWidth,
height = node.options.height || options.height || nodeHeight;
if (chart.inverted) {
x = nodeLeft - nodeWidth;
y = chart.plotSizeY - fromNodeTop - nodeHeight;
width = node.options.height || options.height || nodeWidth;
height = node.options.width || options.width || nodeHeight;
}
// Calculate data label options for the point
node.dlOptions = getDLOptions({
level: (this.mapOptionsToLevel)[node.level],
optionsPoint: node.options
});
// Pass test in drawPoints
node.plotX = 1;
node.plotY = 1;
// Set the anchor position for tooltips
node.tooltipPos = chart.inverted ? [
(chart.plotSizeY) - y - height / 2,
(chart.plotSizeX) - x - width / 2
] : [
x + width / 2,
y + height / 2
];
node.shapeArgs = {
x,
y,
width,
height,
display: node.hasShape() ? '' : 'none'
};
} else {
node.dlOptions = {
enabled: false
};
}
}
);
Demo: http://jsfiddle.net/BlackLabel/uh6fp89j/
In the above solution, another node arrangement would be difficult to achieve and may require a lot of modifications beyond our scope of support.
You can consider using mentioned "tricky solution", since might return a better positioning result. This solution is based on changing 0 weight nodes on the chart.load()
event and converting the tooltip as well, so it may require adjustment to your project.
chart: {
events: {
load() {
this.series[0].points.forEach(point => {
if (point.weight === 0) {
point.update({
weight: 0.1,
color: 'transparent'
})
}
})
}
}
},
tooltip: {
nodeFormatter: function() {
return `${this.name}: <b>${Math.floor(this.sum)}</b><br/>`
},
pointFormatter: function() {
return `${this.fromNode.name} → ${this.toNode.name}: <b>${Math.floor(this.weight)}</b><br/>`
}
},
Demo: http://jsfiddle.net/BlackLabel/0dqpabku/
Upvotes: 0