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.