Mit Python ein Sunburst-Diagramm erzeugen
Sunburst-Diagramme eignen sich gut für eine Uebersicht von hierarchischen Daten, beispielsweise für eine Darstellung des belegten Diskplatzes.
Das nachfolgend beschriebene Script liest die Ausgabe von du und erzeugt daraus ein Diagramm in SVG. du ist eine Linux-Utility die bestimmt wieviel Platz die Directories belegen.
Das Root-Directory von Knoppix 8.6.1 sieht zum Beispiel so aus:
Für Linux-Kenner sieht das Bild recht seltsam aus, aber Knoppix ist ein Life-System, was bedeutet dass mehrere Directories öfter vorkommen.
Ein Nachteil der Sunburst-Darstellung: es ist schwierig die Segmente anzuschreiben. Deshalb haben alle Segmente ein title-Element mit dem Namen des Directories und diese Titel werden beim Mouseover angezeigt.
Einen Graph erzeugen
du nimmt zwar einen Pfad als Argument an, aber die Ausgabe sah nicht so aus wie ich es erwartete. Die besten Resultate bekam ich mit folgender Kommandozeile:
> (cd /; du) | python3 sunburst.py > sunburst.svg
segment.py
Segment Ist eine Klasse die Daten und Methoden für ein Segment bereithält. Der komplizierteste Teil ist die Methode write_path. Um sie etwas übersichtlicher zu halten sind manche Variablen extra kurz benannt. p1, p2, p3 und p4 sind die Eckpunkte des Segments. Sie wurden als lokale Variablen definiert um das print-Statement nicht allzu lang werden zu lassen.
import math
class Segment:
"""Class for a segment in the diagram
Contains the data for one segment and
the methods to generate the SVG for
the segment"""
def __init__( self, lst ):
"""Constructs a segment from a list of strings"""
self.offset = 0.0
self.size = lst[0]
self.name = lst[-1]
self.parent = "/".join( lst[1:-1] )
self.angle = 0
self.cx = 0
self.cy = 0
self.color = None
def set_color( self, color ):
self.color = color
def write_path( self, parents, center, radius, ringwidth ):
"""Writes the path element for the segment to STDOUT"""
if self.color == None:
self.color = parents[self.parent].color
start = parents[self.parent].offset
end = start + self.angle
parents[self.parent].offset = end
self.offset = start
parents[f"{self.parent}/{self.name}"] = self
flag = 1 if (end - start) > math.pi else 0
# Shorten the names for better readability of the path string
rw = ringwidth
r = radius
p1 = self.point( center, r + rw, start )
p2 = self.point( center, r + rw, end )
p3 = self.point( center, r, end )
p4 = self.point( center, r, start )
self.cx = center + (r+rw/2) * math.sin(start+self.angle/2)
self.cy = center - (r+rw/2) * math.cos(start+self.angle/2)
print( f" <path d=\"M {p1} A {r+rw} {r+rw} 0 {flag} 1 {p2} L {p3} A {r} {r} 0 {flag} 0 {p4} Z\" fill=\"{self.color}\" stroke=\"white\">" )
print( f" <title>{self.name}</title>" )
print( " </path>" )
def write_text( self ):
if self.angle > 0.35:
print( f"<text x=\"{self.cx}\" y=\"{self.cy}\">{self.name}</text>" )
def point( self, center, radius, angle ):
return f"{center+radius*math.sin(angle)} {center-radius*math.cos(angle)}"
sunburst.py
Dieses Script liest die Ausgabe von du vom Standard-Input und zerlegt die Zeilen in Listen variabler Länge. Diese Listen werden in der Liste data abgelegt, und zwar geordnet nach der Länge. In data[0] befindet sich das Root-Element, in data[1] alle Directories direkt darunter, in der nächsten Liste alle SubSub-Directories.
Die andere wichtige Struktur ist parents. Das ist ein Dictionary mit dem die Segmente ihre Parents finden können.
from segment import Segment
import math
import sys
RINGWIDTH = 60
def read_data():
"""Read the output of du"""
# One list for each path length
data = [ [], [], [], [], [] ]
for line in sys.stdin:
if line.count("/") < len(data):
line = line.strip()
line = line.replace("\t","/")
lst = line.split("/")
data[len(lst)-2].append( Segment(lst) )
return data
def init( data ):
"""Initialize factor and parents"""
root = data[0][0]
return { root.name : root }, (2 * math.pi) / float(root.size)
def prepare_lists( data, factor, limit ):
"""Sorts the ring segments, computes the angles and eliminates tiny segments"""
for lst in data[1:]:
lst.sort( key = lambda s: int(s.size), reverse = True )
for i in range( len(lst)-1, -1, -1 ):
angle = float(lst[i].size) * factor
if angle >= limit:
lst[i].angle = angle
else:
del lst[i]
def assign_colors( data ):
"""Assigns the colors to the segments in the first ring"""
ring = data[1]
length = len(ring)
for i in range(length):
ring[i].set_color( f"hsl({int(360.0/length*(length-i))},70%,50%)" )
def main():
data = read_data()
parents, factor = init( data )
prepare_lists( data, factor, 0.017453293 )
assign_colors( data )
print( "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" )
print( "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 560 560\">" )
radius = 25
for ring in data[1:]:
for segment in ring:
segment.write_path( parents, 280, radius, RINGWIDTH )
radius = radius + RINGWIDTH
print( " <g pointer-events=\"none\" text-anchor=\"middle\" font-size=\"10\" font-family=\"sans-serif\" font-weight=\"bold\">" )
for ring in data[1:]:
for segment in ring:
segment.write_text()
print( " </g>" )
print( "</svg>" )
if( __name__ == "__main__" ):
main()
Selber probieren?
Kein Problem: sunburst.zip enthält die Sourcefiles.