1

I'm working on a Sankey diagram and I'm experiencing some issues with the labeling process. Is there a way to put the labels and the values of each flow on the same line? I would like them to look like this: "Label: value".

Here are the code and the resulting diagram.

from matplotlib import pyplot as plt
from matplotlib.sankey import Sankey

fig = plt.figure(figsize=(8.3, 11.7))
ax = fig.add_subplot(1, 1, 1)
plt.axis('off')

# these will be provided soon
# Input = ...
# L0 = ...
# L1, L2, L3, L4, L5, L6, L7, L8, L9 = ...
# F9 = ...

sankey = Sankey(ax=ax,
                scale=1 / Input,
                offset=0.6,
                head_angle=135,
                shoulder=0,
                gap=0.2,
                radius=0.1,
                format='%.1f',
                unit='%')
s0 = sankey.add(flows=[Input, -L0, -(Input - L0)],
                labels=['Input 1', 'Loss 0', ''],
                orientations=[0, 1, 0],
                trunklength=1,
                rotation=-90,
                fc='crimson', alpha=0.8)
s1 = sankey.add(flows=[Input - L0, -L1, -L2, -L3, -L4, -L5, -L6, -L7, -L8, -L9, -F9],
                labels=['Input 2', 'Loss 1', 'Loss 2', 'Loss 3', 'Loss 4', 'Loss 5', 'Loss 6', 'Loss 7', 'Loss 8',
                        'Loss 9', 'Output'],
                orientations=[0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
                trunklength=1,
                rotation=-90,
                prior=0, connect=(2, 0),
                fc='crimson', alpha=0.6)
diagrams = sankey.finish()
for d in diagrams:
    for t in d.texts:
        t.set_fontsize(10)
        t.set_horizontalalignment('center')
        t.set_verticalalignment('center')

diagrams[0].texts[0].set_position(xy=[0, 0.4])
diagrams[0].texts[2].set_position(xy=[10, 10])

plt.show()

Sankey diagram

the resulting output

JohanC
  • 71,591
  • 8
  • 33
  • 66
Niccolo
  • 13
  • 4
  • Please add the import of the exact libraries you're using and provide some meaningful values for `Input, colors_2, F9, L0, L1, ...` – JohanC Jan 17 '20 at 09:12

1 Answers1

1

Supposing your input values are the shown percentages, your goal could be reached by manually adding that information to each label. Use '\n' when you need a new line, use a dash ' – ' or a colon when writing the percentages on one line. You'll need to reset the format and unit parameter of the sankey to prevent that the percentage will be added a second time.

As the sankey still is writing the empty format and unit, the text can be updated to remove the empty line and get correct vertical centering.

from matplotlib import pyplot as plt
from matplotlib.sankey import Sankey
from random import randint

Input = 240.1
L0 = 140.1
F9 = 21.1
L = [randint(1,300) for _ in range(9)]
norm_factor = (Input - L0 - F9) / sum(L)

flows_s2 = [Input - L0] + [-l * norm_factor for l in L] + [-F9]
labels_s2 = ['Input 2', 'Loss 1', 'Loss 2', 'Loss 3', 'Loss 4', 'Loss 5', 'Loss 6', 'Loss 7', 'Loss 8', 'Loss 9',
             'Output']
labels_s2_long = [f'{label}\n{flow} %' for label, flow in zip(labels_s2[:1], flows_s2)]
labels_s2_long += [f'{label} – {-flow:.1f} %' for label, flow in zip(labels_s2[1:], flows_s2[1:])]

fig = plt.figure(figsize=(8.3, 11.7))
ax = fig.add_subplot(1, 1, 1)
plt.axis('off')
sankey = Sankey(ax=ax,
                scale=2 / Input,
                offset=0.6,
                head_angle=135,
                shoulder=0,
                gap=0.2,
                radius=0.1,
                format='%.1f',
                unit='%')
s0 = sankey.add(flows=[Input, -L0, -(Input - L0)],
                labels=['Input 1', 'Loss 0', ''],
                orientations=[0, 1, 0],
                trunklength=1,
                rotation=-90,
                fc='crimson', alpha=0.8)
sankey.format = ''
sankey.unit = ''
s1 = sankey.add(flows=flows_s2,
                labels=labels_s2_long,
                orientations=[0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
                trunklength=1,
                rotation=-90,
                prior=0, connect=(2, 0),
                fc='tomato', alpha=0.6)
diagrams = sankey.finish()
for d in diagrams:
    for t in d.texts:
        text = t.get_text()
        if text[-1] == '\n': # remove empty line at the end, needed for centering
            t.set_text(text[:-1])
        t.set_fontsize(10)
        t.set_verticalalignment('center')
        if text[:4] == 'Loss' and text[:6] != 'Loss 0': # align all loss labels except loss 0
            t.set_horizontalalignment('left')
            xy = t.get_position()
            t.set_position(xy=(0.18, xy[1]))
        else:
            t.set_horizontalalignment('center')
        #t.set_bbox(dict(facecolor='red', alpha=0.5, edgecolor='blue'))

diagrams[0].texts[0].set_position(xy=(0, 0.42)) # adjust position of input 1
diagrams[0].texts[1].set_position(xy=(1.75, diagrams[0].texts[1].get_position()[1])) # adjust pos. of loss 0
diagrams[0].texts[2].set_text('')  # remove output 1 as it coincides with input 2
diagrams[1].texts[-1].set_position(xy=(diagrams[1].texts[-1].get_position()[0], -5)) # adjust pos. of output


plt.tight_layout()
plt.show()

output

JohanC
  • 71,591
  • 8
  • 33
  • 66
  • Hi! Thanks a lot! it was exactly what I was looking for! – Niccolo Jan 20 '20 at 07:48
  • I just added some code to correct the vertical centering of the 'loss' lines. – JohanC Jan 20 '20 at 08:44
  • hi @JohanC , I just figured out, if you set unit=None, sankey will not plot any value. Like this the label get vertically nicer places automatically. Unfortunately, horizontally it is still 'center'. – bue Jan 04 '21 at 21:44
  • Thanks for this. This is the best doco for my use-case that I've found anywhere and I now have a working conceptual model. Cheers! – CreekGeek Jan 12 '23 at 20:23