Place Text Values to Right of Sankey Diagram

Place text values to right of sankey diagram

Sorry, I just now ran across this. This would be a great use for the new onRender function in htmlwidgets. I tried to comment inline to explain the couple of lines of added JavaScript to move the node text. networkD3 filters in these lines to change the placement to right or left based on width. We will just apply this to all of the text so it will be to the right of our node rectangles.

library(networkD3)
library(data.table)
set.seed(1999)
links <- data.table(
src = rep(0:4, times=c(1,1,2,3,5)),
target = sample(1:11, 12, TRUE),
value = sample(100, 12)
)[src < target, ] # no loops
nodes <- data.table(name=LETTERS[1:12])

## Need to hover to get counts
sankeyNetwork(Links=links, Nodes=nodes, Source='src', Target='target',
Value='value', NodeID='name', fontSize=16)

## Add text to label
txt <- links[, .(total = sum(value)), by=c('target')]
nodes[txt$target+1L, name := paste0(name, ' (', txt$total, ')')]

## Displays the counts as part of the labels
sankeyNetwork(Links=links, Nodes=nodes, Source='src', Target='target',
Value='value', NodeID='name', fontSize=16, width=600, height=300)

#################### move leaf node text right ################
# for this to work
# install the newest htmlwidgets
# devtools::install_github("ramnathv/htmlwidgets")

library(htmlwidgets)
# add margin left since we'll need extra room
# if you are wondering why margin left,
# I think we just discovered a bug
sn <- sankeyNetwork(
Links=links, Nodes=nodes, Source='src', Target='target',
Value='value', NodeID='name', fontSize=16,
width=600, height=300,
# give us so room for our newly aligned labels
margin = list("left"=100)
)
# see how it looks
sn

# now let's use the new htmlwidget function
# onRender
onRender(
sn,
'
function(el,x){
// select all our node text
var node_text = d3.select(el)
.selectAll(".node text")
//and make them match
//https://github.com/christophergandrud/networkD3/blob/master/inst/htmlwidgets/sankeyNetwork.js#L180-L181
.attr("x", 6 + x.options.nodeWidth)
.attr("text-anchor", "start");
}
'
)

Plotly: how to write a text over my Sankey diagram columns?

You can use annotations to add text, using x-values of 0, 1, 2 with the xref set to "x", and using y-values of 1.05 with the yref set to "paper" as in paper coordinates. This will ensure the annotations are above the plot.

import plotly.graph_objects as go

fig = go.Figure(data=[go.Sankey(
node = dict(
pad = 15,
thickness = 20,
line = dict(color = "black", width = 0.5),
label = ["A1", "A2", "B1", "B2", "C1", "C2"],
color = "blue"
),
link = dict(
source = [0, 1, 0, 2, 3, 3], # indices correspond to labels, eg A1, A2, A1, B1, ...
target = [2, 3, 3, 4, 4, 5],
value = [8, 4, 2, 8, 4, 2]
))])

layout={

}

for x_coordinate, column_name in enumerate(["column 1","column 2","column 3"]):
fig.add_annotation(
x=x_coordinate,
y=1.05,
xref="x",
yref="paper",
text=column_name,
showarrow=False,
font=dict(
family="Courier New, monospace",
size=16,
color="tomato"
),
align="center",
)

fig.update_layout(
title_text="Basic Sankey Diagram",
xaxis={
'showgrid': False, # thin lines in the background
'zeroline': False, # thick line at x=0
'visible': False, # numbers below
},
yaxis={
'showgrid': False, # thin lines in the background
'zeroline': False, # thick line at x=0
'visible': False, # numbers below
}, plot_bgcolor='rgba(0,0,0,0)', font_size=10)

fig.show()

Sankey with titles

In R, how to display value on the links/paths of Sankey Graph?

This can be achieved by injecting custom JavaScript code when it's rendered using htmlwidgets::onRender(). The example below will initially position the link labels appropriately, but if the nodes are manually moved, the link labels will not automatically update accordingly. To achieve that, you would probably have to also override the default dragmove behaviour.

library(htmlwidgets)
library(networkD3)

nodes <-
data.frame(
name = c("Node A", "Node B", "Node C", "Node D")
)

links <-
data.frame(
source = c(0, 0, 1, 2),
target = c(1, 2, 3, 3),
value = c(10, 20, 30, 40)
)

p <- sankeyNetwork(Links = links, Nodes = nodes,
Source = "source", Target = "target",
Value = "value", NodeID = "name",
fontSize= 20, nodeWidth = 30)

htmlwidgets::onRender(p, '
function(el) {
var nodeWidth = this.sankey.nodeWidth();
var links = this.sankey.links();

links.forEach((d, i) => {
var startX = d.source.x + nodeWidth;
var endX = d.target.x;

var startY = d.source.y + d.sy + d.dy / 2;
var endY = d.target.y + d.ty + d.dy / 2;

d3.select(el).select("svg g")
.append("text")
.attr("text-anchor", "middle")
.attr("alignment-baseline", "middle")
.attr("x", startX + ((endX - startX) / 2))
.attr("y", startY + ((endY - startY) / 2))
.text(d.value);
})
}
')

Sample Image

How to align the labels of sankey diagram?

You can return every label as HTML element:

plotOptions: {
sankey: {
dataLabels: {
useHTML: true,
nodeFormatter: function() {
return "<span class='labelPosition label-" + this.colorIndex + "'>" + this.key + "</span>"
}
}
}
},

set position: relative; and position every label manually in CSS:

.labelPosition {
color: black;
position: relative;
}

.label-0 {
top: 0px;
}

.label-5 {
top: 6px;
}

.label-6 {
top: -1px;
}

.label-7 {
top: 2px;
}

.label-8 {
top: 12px;
}

Online demo: jsFiddle

NetworkD3 Sankey Plot in R: How to switch text labels from the right to the left of the inner nodes only

Starting from @timelyportfolio's answer, you can modify it to filter to any subset of nodes before applying the formatting...

library(networkD3)
library(htmlwidgets)

links <- data.frame(
src = c(0,0,1,1,2,2,3,3,4,4,5,5,6),
target = c(3,6,4,5,5,6,7,9,8,9,7,10,7),
value = 1
)

nodes <- data.frame(name = c(1, 2, 3,
"Middle 1", "Middle 2", "Middle 3", "Middle 4",
4,5,6,7))

sn <- sankeyNetwork(
Links=links, Nodes=nodes, Source='src', Target='target',
Value='value', NodeID='name', fontSize=16,
width=600, height=300,
margin = list("left"=100)
)

sn

sn <- onRender(
sn,
'
function(el,x){
// select all our node text
d3.select(el)
.selectAll(".node text")
.filter(function(d) { return d.name.startsWith("Middle"); })
.attr("x", x.options.nodeWidth - 16)
.attr("text-anchor", "end");
}
'
)

sn$jsHooks$render[[1]]
# $code
# [1] "\n function(el,x){\n // select all our node text\n d3.select(el)\n .selectAll(\".node text\")\n .filter(function(d) { return d.name.startsWith(\"Middle\"); })\n .attr(\"x\", x.options.nodeWidth - 16)\n .attr(\"text-anchor\", \"end\");\n }\n "
#
# $data
# NULL

sn

Sample Image

R networkD3 sankey - add value to node label

The jsCode argument of htmlwidgets::onRender() is a string/character vector that contains valid JavaScript.

library(networkD3)

a = read.csv(header = TRUE, text = "
date,Data Center,Customer,companyID,source,target,value
6/1/2021,dcA,customer1,companyID1,open_list_view_1,open_card_2,1
6/1/2021,dcA,customer1,companyID1,open_card_2,edit_card_3,1
6/1/2021,dcA,customer1,companyID1,edit_card_3,save_card_4,1
6/1/2021,dcA,customer1,companyID1,save_card_4,back_to_card_list_5,2
6/1/2021,dcA,customer1,companyID1,back_to_card_list_5,show_more_6,1
6/1/2021,dcA,customer1,companyID1,show_more_6,view_introduction_7,1
6/1/2021,dcA,customer1,companyID1,view_introduction_7,scroll_down_8,2
6/2/2021,dcA,customer2,companyID2,open_list_view_1,open_card_2,3
6/2/2021,dcA,customer2,companyID2,open_card_2,edit_card_3,1
6/2/2021,dcA,customer2,companyID2,edit_card_3,save_card_4,4
6/2/2021,dcA,customer2,companyID2,save_card_4,back_to_card_list_5,2
6/2/2021,dcA,customer2,companyID2,back_to_card_list_5,show_more_6,1
6/2/2021,dcA,customer2,companyID2,show_more_6,view_introduction_7,1
6/2/2021,dcA,customer2,companyID2,view_introduction_7,scroll_down_8,5
")

node_names <- unique(c(as.character(a$source), as.character(a$target)))
nodes <- data.frame(name = node_names)
links <- data.frame(source = match(a$source, node_names) - 1,
target = match(a$target, node_names) - 1,
value = a$value)

p <- sankeyNetwork(Links = links, Nodes = nodes, Source = "source",
Target = "target", Value = "value", NodeID = "name",
sinksRight = FALSE)

javascript_string <-
'function(el, x){
d3.select(el).selectAll(".node text")
.text(d => d.name + " (" + d.value + ")");
}'

htmlwidgets::onRender(x = p, jsCode = javascript_string)

Sample Image

Add text within links in d3.js sankey diagram

As was said in the comments you can't append text to SVG elements. So say at the moment you append your link to a container. For example :

var link = container.append('path')....//and so on

You have to do the same for text. So, for example :

var linkText = container.append('text')

You say you can't keep track of the x and y positions ?

Basically as your link is in between two points, you need to find the centre. Here is how I would do it :

.attr("x", function(d) { console.log(d); return d.source.x + (d.target.x - d.source.x) / 2; })
.attr("y", function(d) { return d.source.y + (d.target.y - d.source.y) / 2; })

So you get the source' x position then add half the difference of targetX - sourceX and do the same for y.

I have put this in a fiddle for you to see : https://jsfiddle.net/thatOneGuy/8ayq5nwa/2/

You can hover over the links to see the title that way you can check it's all correct :)

How to align text vertically on a node within a Sankey diagram (D3.JS)?

I am going to walk you through several experiments (all with accompanied jsfiddles) that will guide you to the solution of your problem.

Starting Point

(click for jsfiddle)

This is slightly modified example from your question:

Sample Image

It has been modified so that it contains data in JavaScript instead of in JSON file, and also it contains code from Sankey plugin. This is done just in order to have working example in JsFiddle. You can of course adjust it to suit your needs with respect to data etc...

The key code is here, as you already mentioned:

  node.selectAll("text.nodeValue")
.attr("x", sankey.nodeWidth() / 2)
.attr("y", function (d) { return (d.dy / 2) })
.text(function (d) { return formatNumber(d.value); })
.attr("text-anchor", "middle");

Experiment 1

(click for jsfiddle)

I was really bothered that values and names of nodes are not aligned, so I added this code that slightly moves values downwards:

      .attr("dy", 5)

The result is here:

Sample Image


Experiment 2

(click for jsfiddle)

Now lets try to add this code (I used 45 degrees on purpose to easier spot how rotation works):

      .attr("transform", "rotate(-45)")

Surprisingly, we get this:

Sample Image

And if we examine the html/svg in firebug or similar tool, we'll notice that the reason for such rotation behavior is that value labels are actually part of other container: (and center of rotation is not center of label, but origin of that container)

Sample Image


Experiment 3

(click for jsfiddle)

In this experiment, I wanted to avoid using rotation at all. I wanted to use svg text property writing-mode, like this:

      .style("writing-mode", "tb")

And I got this:

Sample Image

However, I couldn't get value labels to be oriented with its top to the left. Also, I heard this method has some Firefox compatibility problems, so I gave it up.


Solution

(click for jsfiddle)

After experiments above, my conclusion was that rotation should be done first, and that translation should be applied so that already rotated value labels are moved to the center of nodes. Since this translation is data-dependent, the code should look like this:

  node.selectAll("text.nodeValue")
.text(function (d) { return formatNumber(d.value); })
.attr("text-anchor", "middle")
.attr("transform", function (d) {
return "rotate(-90) translate(" +
(-d.dy / 2) +
", " +
(sankey.nodeWidth() / 2 + 5) +
")";});

The result is:

Sample Image



Related Topics



Leave a reply



Submit