diff --git a/graph.py b/graph.py new file mode 100644 index 0000000..bba5cd0 --- /dev/null +++ b/graph.py @@ -0,0 +1,169 @@ +#!/usr/bin/env python3 + +from graphviz import Digraph +from re import compile as regex +from collections import defaultdict +from subprocess import check_output +from os import listdir +from itertools import combinations_with_replacement as comb +from colorsys import hsv_to_rgb +from math import inf, log2 + +# The magic offtopia-specific variables. +eval(compile(check_output("""cat /home/zgrep/irctimes/munge.py | sed -n '/^rename\s*=\s*/,/)$/p'""", shell=True), __file__, 'exec')) +bots = 'MartesZibellina program minicat Eldis4 happybot bslsk05 WOPO oonbotti2 j-bot PeNGu1N_oF_d00m'.split() +common = [''.join(chr(int(n)) for n in x.split('_')).lower() for x in listdir('/home/zgrep/irctimes/numpydata')] + +colors = ['#f0a3ff', '#0075dc', '#993f00', '#4c005c', '#191919', '#005c31', '#2bce48', + '#ffcc99', '#808080', '#94ffb5', '#8f7c00', '#9dcc00', '#c20088', '#003380', + '#ffa405', '#ffa8bb', '#426600', '#ff0010', '#5ef1f2', '#00998f', '#e0ff66', + '#740aff', '#990000', '#ffff80', '#ffff00', '#ff5005'] + +talkto = regex(r'[0-9-]+ [0-9:]+ <([^>]+)> (?:(\S+?):\s+|\s*<([^>]+)>)?') + +def readlogs(): + with open('/home/zgrep/offtopiabday/irc.freenode.net/#offtopia/out', 'rb') as fh: + prev, replyto = None, None + for line in fh: + line = line.decode('utf-8', errors='ignore') + m = talkto.match(line) + if m: + nick, to, to2 = m.groups() + nick = rename.get(nick, nick) + if to2: to = to2 + to = rename.get(to, to) + if to and to not in ('nolog', 'D', ':D'): + replyto = to + elif nick != prev: + replyto = prev + if replyto\ + and replyto not in bots\ + and replyto.lower() in common\ + and nick not in bots\ + and nick.lower() in common: + yield nick, replyto + prev = nick + +def accum(it): + numlines = defaultdict(int) + graph = defaultdict(int) + for nick, to in it: + numlines[nick] += 1 + graph[(nick, to)] += 1 + return numlines, graph + +def top(numlines, n=10): + global colors + assert isinstance(n, int) and 0 < n <= len(colors) + people = list(sorted(numlines, key=numlines.get, reverse=True)) + return people[:n] + +def minmax(people, data): + minimum, maximum = inf, 0 + for a in people: + for b in people: + d = data[(a, b)] + if d < minimum: + minimum = d + if d > maximum: + maximum = d + return minimum, maximum + +def matrix(people, data): + matrix = [ [0] * (1 + len(people)) for _ in range(1 + len(people)) ] + matrix[0][0] = None + for i, a in enumerate(people): + matrix[0][1+i] = a + matrix[1+i][0] = a + for j, b in enumerate(people): + matrix[1+i][1+j] = data[(a, b)] + return '# Row ---sends-to---> Column\n\n' + repr(matrix) + '\n' + +outdir = '/home/zgrep/offtopiabday/cloud/output/graph/' + +def main(): + global colors + print('Reading logs...') + numlines, data = accum(readlogs()) + + print('Creating matrix...') + with open(outdir + 'matrix.py', 'w') as fh: + fh.write(matrix(list(numlines), data)) + + for a in numlines: + print('Crunching', a, 'data...') + graph = Digraph(format='png', engine='circo') + graph.attr('node', shape='plaintext', fontsize='20', margin='0.2') + + minimum, maximum = inf, 0 + for b in numlines: + for d in (data[(a, b)], data[(b, a)]): + if d < minimum: + minimum = d + if d > maximum: + maximum = d + + for b in numlines: + fn, rn = data[(a, b)], data[(b, a)] + f = 10 * (fn - minimum) / max(1, maximum - minimum) + r = 10 * (rn - minimum) / max(1, maximum - minimum) + w = f + r + if w < 0.05: + continue + i = log2(1+log2(1+abs(f - r)/max(f, r))) + if r > f: + color = hsv_to_rgb(0, 1, i) + else: + color = hsv_to_rgb(1/3, 1, i) + color = '#' + ''.join(hex(int(255*c))[2:].zfill(2) for c in color) + if f < r: + direction='back' + elif f > r: + direction = '' + else: + direction = 'both' + graph.edge(a, b, dir=direction, penwidth=str(w), color=color) + + print('Drawing', a, 'graph...') + graph.render(outdir + a.replace('/', '*')) + + print('Crunching collective data...') + people = top(numlines, len(colors)) + minimum, maximum = minmax(people, data) + colormap = { p: c for p, c in zip(sorted(people), colors) } + + graph = Digraph(format='png', engine='circo') + graph.attr('node', shape='plaintext', fontsize='25', margin='0.2') + #graph.attr('graph', bgcolor='#000000') + + for p in people: + graph.node(p, fontcolor=colormap[p]) + + for a, b in comb(people, 2): + f, r = data[(a, b)], data[(b, a)] + f = 15 * (f - minimum) / max(1, maximum - minimum) + r = 15 * (r - minimum) / max(1, maximum - minimum) + w = f + r + if w < 0.05: + continue + graph.edge(a, b, dir='both', color=colormap[b] + ';0.5:' + colormap[a], penwidth=str(w)) + + print('Drawing graph...') + graph.render(outdir + 'c/offtopia') + + print('Done.') + + return list(numlines) + +if __name__ == '__main__': + people = main() + people.sort() + print('Making index.') + with open(outdir + 'index.html', 'w') as fh: + fh.write('#offtopia graphs\n')