diff --git a/CICD/ChordDiagramm/Chord_Diagramm - Sheet1.csv b/CICD/ChordDiagramm/Chord_Diagramm - Sheet1.csv new file mode 100644 index 0000000000000000000000000000000000000000..ffed806fb3b659db9b40714983a4a3af881520ac --- /dev/null +++ b/CICD/ChordDiagramm/Chord_Diagramm - Sheet1.csv @@ -0,0 +1,10 @@ +0,0,0,1,1,1,1,1,1,0 +0,0,0,0,0,0,1,1,0,0 +0,0,0,0,1,1,1,1,0,0 +1,0,0,0,0,0,0,1,0,0 +1,0,1,0,0,0,0,1,0,0 +1,0,1,0,0,0,0,1,0,0 +1,1,1,0,0,0,0,1,1,0 +1,1,1,1,1,1,1,0,0,0 +1,0,0,0,0,0,1,0,0,0 +0,0,0,0,0,0,0,0,0,0 \ No newline at end of file diff --git a/CICD/ChordDiagramm/Chord_Diagramm.png b/CICD/ChordDiagramm/Chord_Diagramm.png new file mode 100644 index 0000000000000000000000000000000000000000..c53a11844083e852d45d2b824c3bae9bd4fc8876 Binary files /dev/null and b/CICD/ChordDiagramm/Chord_Diagramm.png differ diff --git a/CICD/ChordDiagramm/genChordDiagramm.py b/CICD/ChordDiagramm/genChordDiagramm.py new file mode 100644 index 0000000000000000000000000000000000000000..c898d55fea7a943d76636359a807e484c0170d1d --- /dev/null +++ b/CICD/ChordDiagramm/genChordDiagramm.py @@ -0,0 +1,240 @@ +#!/usr/bin/env python3 +# script copied from https://github.com/fengwangPhysics/matplotlib-chord-diagram/blob/master/README.md +# source data manually edited via https://docs.google.com/spreadsheets/d/1JN9S_A5ICPQOvgyVbWJSFJiw-5gO2vF-4AeYuWl-lbs/edit#gid=0 +# chord diagram +import matplotlib.pyplot as plt +from matplotlib.path import Path +import matplotlib.patches as patches + +import numpy as np + +LW = 0.3 + +def polar2xy(r, theta): + return np.array([r*np.cos(theta), r*np.sin(theta)]) + +def hex2rgb(c): + return tuple(int(c[i:i+2], 16)/256.0 for i in (1, 3 ,5)) + +def IdeogramArc(start=0, end=60, radius=1.0, width=0.2, ax=None, color=(1,0,0)): + # start, end should be in [0, 360) + if start > end: + start, end = end, start + start *= np.pi/180. + end *= np.pi/180. + # optimal distance to the control points + # https://stackoverflow.com/questions/1734745/how-to-create-circle-with-b%C3%A9zier-curves + opt = 4./3. * np.tan((end-start)/ 4.) * radius + inner = radius*(1-width) + verts = [ + polar2xy(radius, start), + polar2xy(radius, start) + polar2xy(opt, start+0.5*np.pi), + polar2xy(radius, end) + polar2xy(opt, end-0.5*np.pi), + polar2xy(radius, end), + polar2xy(inner, end), + polar2xy(inner, end) + polar2xy(opt*(1-width), end-0.5*np.pi), + polar2xy(inner, start) + polar2xy(opt*(1-width), start+0.5*np.pi), + polar2xy(inner, start), + polar2xy(radius, start), + ] + + codes = [Path.MOVETO, + Path.CURVE4, + Path.CURVE4, + Path.CURVE4, + Path.LINETO, + Path.CURVE4, + Path.CURVE4, + Path.CURVE4, + Path.CLOSEPOLY, + ] + + if ax == None: + return verts, codes + else: + path = Path(verts, codes) + patch = patches.PathPatch(path, facecolor=color+(0.5,), edgecolor=color+(0.4,), lw=LW) + ax.add_patch(patch) + + +def ChordArc(start1=0, end1=60, start2=180, end2=240, radius=1.0, chordwidth=0.7, ax=None, color=(1,0,0)): + # start, end should be in [0, 360) + if start1 > end1: + start1, end1 = end1, start1 + if start2 > end2: + start2, end2 = end2, start2 + start1 *= np.pi/180. + end1 *= np.pi/180. + start2 *= np.pi/180. + end2 *= np.pi/180. + opt1 = 4./3. * np.tan((end1-start1)/ 4.) * radius + opt2 = 4./3. * np.tan((end2-start2)/ 4.) * radius + rchord = radius * (1-chordwidth) + verts = [ + polar2xy(radius, start1), + polar2xy(radius, start1) + polar2xy(opt1, start1+0.5*np.pi), + polar2xy(radius, end1) + polar2xy(opt1, end1-0.5*np.pi), + polar2xy(radius, end1), + polar2xy(rchord, end1), + polar2xy(rchord, start2), + polar2xy(radius, start2), + polar2xy(radius, start2) + polar2xy(opt2, start2+0.5*np.pi), + polar2xy(radius, end2) + polar2xy(opt2, end2-0.5*np.pi), + polar2xy(radius, end2), + polar2xy(rchord, end2), + polar2xy(rchord, start1), + polar2xy(radius, start1), + ] + + codes = [Path.MOVETO, + Path.CURVE4, + Path.CURVE4, + Path.CURVE4, + Path.CURVE4, + Path.CURVE4, + Path.CURVE4, + Path.CURVE4, + Path.CURVE4, + Path.CURVE4, + Path.CURVE4, + Path.CURVE4, + Path.CURVE4, + ] + + if ax == None: + return verts, codes + else: + path = Path(verts, codes) + patch = patches.PathPatch(path, facecolor=color+(0.5,), edgecolor=color+(0.4,), lw=LW) + ax.add_patch(patch) + +def selfChordArc(start=0, end=60, radius=1.0, chordwidth=0.7, ax=None, color=(1,0,0)): + # start, end should be in [0, 360) + if start > end: + start, end = end, start + start *= np.pi/180. + end *= np.pi/180. + opt = 4./3. * np.tan((end-start)/ 4.) * radius + rchord = radius * (1-chordwidth) + verts = [ + polar2xy(radius, start), + polar2xy(radius, start) + polar2xy(opt, start+0.5*np.pi), + polar2xy(radius, end) + polar2xy(opt, end-0.5*np.pi), + polar2xy(radius, end), + polar2xy(rchord, end), + polar2xy(rchord, start), + polar2xy(radius, start), + ] + + codes = [Path.MOVETO, + Path.CURVE4, + Path.CURVE4, + Path.CURVE4, + Path.CURVE4, + Path.CURVE4, + Path.CURVE4, + ] + + if ax == None: + return verts, codes + else: + path = Path(verts, codes) + patch = patches.PathPatch(path, facecolor=color+(0.5,), edgecolor=color+(0.4,), lw=LW) + ax.add_patch(patch) + +def chordDiagram(X, ax, colors=None, width=0.1, pad=2, chordwidth=0.7): + """Plot a chord diagram + Parameters + ---------- + X : + flux data, X[i, j] is the flux from i to j + ax : + matplotlib `axes` to show the plot + colors : optional + user defined colors in rgb format. Use function hex2rgb() to convert hex color to rgb color. Default: d3.js category10 + width : optional + width/thickness of the ideogram arc + pad : optional + gap pad between two neighboring ideogram arcs, unit: degree, default: 2 degree + chordwidth : optional + position of the control points for the chords, controlling the shape of the chords + """ + # X[i, j]: i -> j + x = X.sum(axis = 1) # sum over rows + ax.set_xlim(-1.1, 1.1) + ax.set_ylim(-1.1, 1.1) + + if colors is None: + # use d3.js category10 https://github.com/d3/d3-3.x-api-reference/blob/master/Ordinal-Scales.md#category10 + colors = ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#9467bd', + '#8c564b', '#e377c2', '#7f7f7f', '#bcbd22', '#17becf', '#c49c94'] + if len(x) > len(colors): + print('x is too large! Use x smaller than 11') + colors = [hex2rgb(colors[i]) for i in range(len(x))] + + # find position for each start and end + y = x/np.sum(x).astype(float) * (360 - pad*len(x)) + + pos = {} + arc = [] + nodePos = [] + start = 0 + for i in range(len(x)): + end = start + y[i] + arc.append((start, end)) + angle = 0.5*(start+end) + #print(start, end, angle) + if -30 <= angle <= 210: + angle -= 90 + else: + angle -= 270 + nodePos.append(tuple(polar2xy(1.1, 0.5*(start+end)*np.pi/180.)) + (angle,)) + z = (X[i, :]/x[i].astype(float)) * (end - start) + ids = np.argsort(z) + z0 = start + for j in ids: + pos[(i, j)] = (z0, z0+z[j]) + z0 += z[j] + start = end + pad + + for i in range(len(x)): + start, end = arc[i] + IdeogramArc(start=start, end=end, radius=1.0, ax=ax, color=colors[i], width=width) + start, end = pos[(i,i)] + selfChordArc(start, end, radius=1.-width, color=colors[i], chordwidth=chordwidth*0.7, ax=ax) + for j in range(i): + color = colors[i] + if X[i, j] > X[j, i]: + color = colors[j] + start1, end1 = pos[(i,j)] + start2, end2 = pos[(j,i)] + ChordArc(start1, end1, start2, end2, + radius=1.-width, color=colors[i], chordwidth=chordwidth, ax=ax) + + #print(nodePos) + return nodePos + +################################## +if __name__ == "__main__": + fig = plt.figure(figsize=(6,6)) + flux = np.array([ + [ 0, 1, 0, 0], #OS Sum:2 ; Centos, Ubuntu + [ 0, 0, 0, 0], #Plays + [ 0, 0, 0, 1], # Cluster: Sum5; Generic, M3, Monarch, SHPC, ACCS + [ 0, 0, 1, 2] #Cloud Sum3: AWS,Nimbus,Nectar + ]) + from numpy import genfromtxt + + flux = genfromtxt('Chord_Diagramm - Sheet1.csv', delimiter=',') + ax = plt.axes([0,0,1,1]) + + #nodePos = chordDiagram(flux, ax, colors=[hex2rgb(x) for x in ['#666666', '#66ff66', '#ff6666', '#6666ff']]) + nodePos = chordDiagram(flux, ax) + ax.axis('off') + prop = dict(fontsize=16*0.8, ha='center', va='center') + nodes = ['OS_Centos76','OS_Centos8','OS_Ubuntu1804','PLY_NFSSQL','PLY_MGMT','PLY_Login','PLY_Compute','C_Generic','C_M3','C_Monarch'] + for i in range(len(nodes)): + ax.text(nodePos[i][0], nodePos[i][1], nodes[i], rotation=nodePos[i][2], **prop) + + plt.savefig("Chord_Diagramm.png", dpi=600,transparent=False,bbox_inches='tight', pad_inches=0.02) + plt.show(pad_inches=0.02)