You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
498 lines
17 KiB
498 lines
17 KiB
import contextlib, glfw, skia |
|
import sys |
|
import time |
|
from datetime import datetime, timedelta, timezone |
|
from pprint import pprint |
|
from OpenGL import GL |
|
from read_db import get_from_db, transfer_weight_from_db, transfer_author_weight_from_db, \ |
|
transfer_author_weight_from_db_days, transfer_source_analysis_from_db |
|
import pickle |
|
from treeplot import * |
|
import magic |
|
from colourpal import * |
|
import math |
|
import os |
|
|
|
DATE_SPACE = 0 |
|
WIDTH, HEIGHT = 1500, 1000 |
|
RECT_HEIGHT = HEIGHT - DATE_SPACE |
|
|
|
# https://gist.github.com/archeoid/6f4df73886730d09faabfe6d02e79fb2 |
|
|
|
DATA_CACHE = "treedata.b" |
|
FILES_LIST_CACHE = "filesystemdata.b" |
|
|
|
|
|
@contextlib.contextmanager |
|
def glfw_window(): |
|
if not glfw.init(): |
|
raise RuntimeError("glfw.init() failed") |
|
glfw.window_hint(glfw.STENCIL_BITS, 8) |
|
window = glfw.create_window(WIDTH, HEIGHT, "", None, None) # glfw.get_primary_monitor() |
|
glfw.make_context_current(window) |
|
yield window |
|
glfw.terminate() |
|
|
|
|
|
@contextlib.contextmanager |
|
def skia_surface(window): |
|
context = skia.GrDirectContext.MakeGL() |
|
(fb_width, fb_height) = glfw.get_framebuffer_size(window) |
|
backend_render_target = skia.GrBackendRenderTarget( |
|
fb_width, |
|
fb_height, |
|
0, # sampleCnt |
|
0, # stencilBits |
|
skia.GrGLFramebufferInfo(0, GL.GL_RGBA8), |
|
) |
|
surface = skia.Surface.MakeFromBackendRenderTarget( |
|
context, |
|
backend_render_target, |
|
skia.kBottomLeft_GrSurfaceOrigin, |
|
skia.kRGBA_8888_ColorType, |
|
skia.ColorSpace.MakeSRGB(), |
|
) |
|
assert surface is not None |
|
yield surface |
|
context.abandonContext() |
|
|
|
|
|
print("Loading") |
|
|
|
from pathlib import Path |
|
import os |
|
|
|
if not os.path.exists(FILES_LIST_CACHE): |
|
data = {} |
|
# path = "/home/alistair/Documents/UQ/" |
|
# path = "/home/alistair/Documents/UQ/2023/COSC3000" |
|
path = "/run/media/alistair/storj/linux/" |
|
|
|
for p in Path(path).glob("**/*"): |
|
size = 1 # os.path.getsize(p) |
|
if os.path.isfile(p) and "text" in magic.from_file(p): |
|
try: |
|
with open(p, "r") as f: |
|
size = len(f.readlines()) |
|
except: |
|
print("LDSALDjLKADJLAJDA") |
|
elif os.path.isdir(p): |
|
continue |
|
|
|
pp = tuple(x for x in str(p).split("/") if len(x) > 0) |
|
if len(pp) == 0: |
|
continue |
|
data[pp] = size |
|
|
|
# for i in range(10000): |
|
# if len({p[i+1:]:None}) == len(data): |
|
# end = i |
|
# break |
|
i = 0 |
|
while len({k[i + 2:]: None for k in data}) == len(data): |
|
i += 1 |
|
|
|
data = {k[i:]: v for k, v in data.items()} |
|
clean = {} |
|
for p, size in data.items(): |
|
p = p[1:] |
|
if len(p) > 0: |
|
clean[p] = DataFrame(p, size, size) |
|
|
|
with open(FILES_LIST_CACHE, "wb") as f: |
|
pickle.dump(clean, f) |
|
else: |
|
with open(FILES_LIST_CACHE, "rb") as f: |
|
clean = pickle.load(f) |
|
|
|
clearcolour = skia.ColorBLACK |
|
padding = 0 |
|
leaves_only=True |
|
tm = None |
|
if not os.path.exists(DATA_CACHE): |
|
# clean = get_from_db() |
|
|
|
print("Building Tree") |
|
|
|
map = TreeMap(clean) |
|
print("MAP:", list(map.levels.values())[0:10]) |
|
|
|
tm = map.to_tree() |
|
tm = tm.pack(Rect(0, 0, WIDTH, RECT_HEIGHT), padding=padding) |
|
|
|
print("Setting labels") |
|
|
|
for t in tm.items(): |
|
t.attribs["label"] = "/".join(t.path) |
|
tm.attribs["label"] = "root" |
|
|
|
print("Done.") |
|
|
|
# transfer_weight_from_db(tm) |
|
#dates = transfer_author_weight_from_db_days(tm) |
|
transfer_author_weight_from_db(tm) |
|
#tm.attribs["all_dates"] = dates |
|
|
|
print("Saving to file", DATA_CACHE) |
|
with open(DATA_CACHE, "wb") as f: |
|
pickle.dump(tm, f) |
|
else: |
|
print("Loading from file", DATA_CACHE) |
|
with open(DATA_CACHE, "rb") as f: |
|
tm = pickle.load(f) |
|
|
|
print("Packing tree") |
|
tm: TreeMap = tm.pack(Rect(0, 0, WIDTH, RECT_HEIGHT), padding=padding) |
|
|
|
print("Shrinking tree. Size:", tm.count_children()) |
|
# tm = tm.trim(threshold=0.05) |
|
print("New size:", tm.count_children()) |
|
tm: TreeMap = tm.pack(Rect(0, 0, WIDTH, RECT_HEIGHT), padding=padding) |
|
|
|
#transfer_source_analysis_from_db(tm) |
|
|
|
rerooted = tm |
|
parent = [rerooted] |
|
child_frame = rerooted |
|
|
|
anim_speed = 1.1 |
|
levels = 1000 |
|
# |
|
# for node in tm.items(): |
|
# # With transfer_author_weight_from_db(tm) |
|
# # Num deltas |
|
# node.weight = 1.1 * math.log(1 + node.attribs["additions"] + node.attribs["deletions"]) |
|
# |
|
def authors_heatmap(): |
|
global tm |
|
max_authors = max(len(n.attribs["this_authors"]) for n in tm.items()) |
|
for node in tm.items(): |
|
node.weight = 5 * len(node.attribs["this_authors"]) / max_authors |
|
# With transfer_author_weight_from_db(tm) |
|
# num authors |
|
set_colours_new(tm, hsv(0, 1, 1), hsv(364, 1, 1)) |
|
|
|
def additions_to_deletions_heatmap(): |
|
global tm |
|
max_d = math.log(max(t.attribs["additions"] + t.attribs["deletions"] for t in tm.leaves())) |
|
max_d = max(t.attribs["additions"] + t.attribs["deletions"] for t in tm.leaves()) |
|
print(max_d) |
|
for node in tm.items(): |
|
# With transfer_author_weight_from_db(tm) |
|
# Amount added |
|
if (node.attribs["additions"] + node.attribs["deletions"]) > 0: |
|
node.weight = math.log(node.attribs["additions"] + node.attribs["deletions"]) / math.log(max_d) |
|
node.attribs["gradient"] = node.attribs["additions"] / (node.attribs["additions"] + node.attribs["deletions"]) |
|
else: |
|
node.attribs["gradient"] = 0.5 |
|
node.weight = 0 |
|
grad = lerp_gradient(rgb(255, 90, 0 ), rgb(0, 90, 255), 100) |
|
set_colours_gradient(tm, grad) |
|
# |
|
|
|
additions_to_deletions_heatmap() |
|
#authors_heatmap() |
|
|
|
#for node in tm.items(): |
|
# node.weight = 1.0 |
|
|
|
# Scaling for weight |
|
# max_weight = max(node.weight for node in tm.items()) |
|
# scaling = 1/(max_weight) |
|
# for node in tm.items(): |
|
# node.weight *= scaling |
|
# print(max_weight) |
|
|
|
print("Setting Colours") |
|
#set_colours(tm, Color("red"), Color("blue")) |
|
# set_colours_bw(tm) |
|
#set_colours_new(tm, hsv(0, 1, 1.0), hsv(364, 1, 1.0)) |
|
|
|
tm.attribs["colour"] = rgb(255,255,255) |
|
|
|
#for node in tm.items(): |
|
# # # With |
|
# # # Amount added |
|
# # # transfer_source_analysis_from_db(tm) |
|
# node.weight = node.attribs["comment_lines"] / node.attribs["lines"] if node.attribs["lines"] > 0 else 0 |
|
# if not (node.attribs["is_source"] or node.attribs["is_header"]) and node.is_leaf(): |
|
# node.attribs["colour"] = hsv(0, 0, 0.2) |
|
# node.weight = 1 |
|
## print(node.weight) |
|
# |
|
|
|
resize = False |
|
finished_animation = True |
|
start_animation = True |
|
animation_is_going_up = False |
|
dragging = False |
|
translation = (0, 0) |
|
zoom = 1 |
|
|
|
|
|
def handle_resize(window, width, height): |
|
global resize |
|
global rerooted |
|
global WIDTH |
|
global HEIGHT |
|
WIDTH = width |
|
HEIGHT = height |
|
RECT_HEIGHT = height - DATE_SPACE |
|
resize = True |
|
rerooted = child_frame |
|
rerooted = rerooted.pack( |
|
Rect(0, 0, WIDTH, RECT_HEIGHT), padding=padding, rattrib="rect_target" |
|
) |
|
|
|
|
|
def handle_mouse_click(window, button, action, mods): |
|
x, y = glfw.get_cursor_pos(window) |
|
global rerooted |
|
global update |
|
global parent |
|
global child_frame |
|
global start_animation |
|
global animating_up |
|
global dragging |
|
global translation |
|
|
|
if action == glfw.PRESS and button == glfw.MOUSE_BUTTON_1: |
|
if glfw.get_key(window, glfw.KEY_LEFT_SHIFT) == glfw.PRESS: |
|
dragging = glfw.get_cursor_pos(window) |
|
print("drag") |
|
return |
|
if action == glfw.RELEASE: |
|
if dragging: |
|
x, y = glfw.get_cursor_pos(window) |
|
translation = ( |
|
translation[0] + x - dragging[0], |
|
translation[1] + y - dragging[1], |
|
) |
|
|
|
dragging = None |
|
return |
|
|
|
if action == glfw.RELEASE and button == glfw.MOUSE_BUTTON_2: |
|
rerooted = child_frame # if cancelling animation |
|
new = parent.pop(-1) |
|
new.pack(Rect(0, 0, WIDTH, RECT_HEIGHT), padding=padding, rattrib="rect_target") |
|
child_frame = new |
|
if (len(parent)) == 0: |
|
parent = [new] |
|
start_animation = True |
|
animation_is_going_up = True |
|
return |
|
|
|
if not finished_animation: |
|
return |
|
|
|
if action == glfw.RELEASE and button == glfw.MOUSE_BUTTON_1: |
|
new = rerooted.locate_at_position(x, y, levels=0) |
|
if new is None: |
|
return |
|
parent.append(rerooted) |
|
new.pack(Rect(0, 0, WIDTH, RECT_HEIGHT), padding=padding, rattrib="rect_target") |
|
# rerooted = new |
|
child_frame = new |
|
start_animation = True |
|
animation_is_going_up = False |
|
|
|
|
|
def handle_scroll(window, x, y): |
|
global zoom |
|
zoom += 0.1 * y |
|
zoom = max(min(zoom, 5), 1) |
|
|
|
need_redraw = True |
|
|
|
def main(): |
|
frame = 0 |
|
global resize |
|
global levels |
|
global rerooted |
|
global finished_animation |
|
global start_animation |
|
animation_background = None |
|
current_date = 0 |
|
|
|
with glfw_window() as window: |
|
GL.glClear(GL.GL_COLOR_BUFFER_BIT) |
|
|
|
glfw.set_window_size_callback(window, handle_resize) |
|
glfw.set_mouse_button_callback(window, handle_mouse_click) |
|
glfw.set_scroll_callback(window, handle_scroll) |
|
|
|
while True: |
|
with skia_surface(window) as surface: |
|
|
|
def redraw(canvas, force=False): |
|
global need_redraw |
|
if not (force or need_redraw or resize or not finished_animation): |
|
return |
|
draw( |
|
canvas, |
|
rerooted, |
|
max_level=levels, |
|
label_level=2, |
|
text_scale=2 / zoom, |
|
leaves_only=leaves_only |
|
) |
|
canvas.scale(1 / zoom, 1 / zoom) |
|
canvas.translate(-tx, -ty) |
|
draw_labels(canvas, rerooted, max_level=levels, label_level=4, text_scale=1 / zoom) |
|
draw_labels(canvas, rerooted, max_level=levels, label_level=3, text_scale=1 / zoom) |
|
draw_labels(canvas, rerooted, max_level=levels, label_level=2, text_scale=2 / zoom) |
|
draw_labels(canvas, rerooted, max_level=levels, label_level=1, text_scale=3 / zoom, autoscale=True) |
|
need_redraw = False |
|
|
|
while not resize: |
|
if ( |
|
glfw.get_key(window, glfw.KEY_ESCAPE) == glfw.PRESS |
|
) or glfw.window_should_close(window): |
|
print("done") |
|
return |
|
|
|
if glfw.get_key(window, glfw.KEY_EQUAL) == glfw.PRESS: |
|
levels += 1 |
|
if glfw.get_key(window, glfw.KEY_MINUS) == glfw.PRESS: |
|
levels -= 1 |
|
if glfw.get_key(window, glfw.KEY_S) == glfw.PRESS: |
|
outf = "out/" + str(time.time()) + "-" "_".join(rerooted.path) |
|
print("Saving") |
|
image = surface.makeImageSnapshot() |
|
if image is not None: |
|
image.save(outf + ".png", skia.kPNG) |
|
else: |
|
print("Error saving") |
|
|
|
# https://kyamagu.github.io/skia-python/tutorial/canvas.html#pdf |
|
stream = skia.FILEWStream(outf + ".pdf") |
|
with skia.PDF.MakeDocument(stream) as document: |
|
with document.page(WIDTH, HEIGHT) as canvas: |
|
redraw(canvas, force=True) |
|
stream.flush() |
|
print("Saved PDF") |
|
|
|
# Next date |
|
# if "all_dates" in tm.attribs: |
|
# this_date = tm.attribs["all_dates"][current_date] |
|
# print("date: ", this_date) |
|
# for k in tm.items(): |
|
# if this_date in k.attribs["additions_date"]: |
|
# k.weight = math.log(1 + k.attribs["additions_date"][this_date] + k.attribs["deletions_date"][this_date]) |
|
# k.weight *= 0.1 |
|
# else: |
|
# k.weight = 0 |
|
# current_date = (current_date + 1) % len(tm.attribs["all_dates"]) |
|
# #set_colours_bw(tm) |
|
# #set_colours(tm, Color("red"), Color("blue")) |
|
# tm.attribs["colour"] = Color("white") |
|
|
|
tx, ty = translation |
|
if dragging: |
|
tx, ty = ( |
|
translation[0] + x - dragging[0], |
|
translation[1] + y - dragging[1], |
|
) |
|
|
|
# Mouse transformed to canvas |
|
x, y = glfw.get_cursor_pos(window) |
|
adj_x, adj_y = ( |
|
(x + translation[0]) / zoom, |
|
(y + translation[1]) / zoom, |
|
) |
|
# highlight |
|
#highlighted = rerooted.locate_at_position(adj_x, adj_y, levels=0) |
|
#highlighted.attribs["highlight"] = False |
|
|
|
# Interpolate animation |
|
|
|
if start_animation: |
|
start_animation = False |
|
if not animation_is_going_up: |
|
animation_background = surface.makeImageSnapshot() |
|
|
|
finished_animation = True |
|
|
|
for t in child_frame.items(): |
|
if "rect_target" in t.attribs: |
|
t.attribs["rect"], finished = t.attribs[ |
|
"rect" |
|
].step_towards(t.attribs["rect_target"], amount=anim_speed) |
|
if finished: |
|
t.attribs.pop("rect_target") |
|
else: |
|
finished_animation = False |
|
|
|
if not finished_animation: |
|
with surface as canvas: |
|
canvas.translate(tx, ty) |
|
canvas.scale(zoom, zoom) |
|
canvas.clear(clearcolour) |
|
canvas.drawImage(animation_background, 0, 0) |
|
draw( |
|
canvas, |
|
child_frame, |
|
max_level=levels, |
|
label_level=-1, |
|
leaves_only=leaves_only |
|
) |
|
canvas.scale(1 / zoom, 1 / zoom) |
|
canvas.translate(-tx, -ty) |
|
else: |
|
rerooted = child_frame |
|
with surface as canvas: |
|
redraw(canvas, force=True) |
|
# ddd = datetime.strptime(time.ctime(this_date * 60 * 60 * 24), "%a %b %d %H:%M:%S %Y") |
|
# draw_text(canvas, ddd.strftime("Week from %d %b %Y"), 10, 1080 - 10, size=30) |
|
|
|
# Draw label tooltip |
|
treeframe = rerooted.locate_at_position( |
|
adj_x, adj_y, levels=levels - 1 |
|
) |
|
|
|
if glfw.get_key(window, glfw.KEY_SPACE) == glfw.PRESS: |
|
pprint(treeframe) |
|
pprint(treeframe.attribs) |
|
|
|
treeframe = None |
|
if treeframe is not None: |
|
text = skia.TextBlob( |
|
treeframe.attribs["label"], |
|
# + " value: " |
|
# + str(treeframe.size), |
|
skia.Font(None, 12), |
|
) |
|
textb = text.bounds() |
|
tw, th = textb.width(), textb.height() |
|
|
|
paint1 = skia.Paint(AntiAlias=True, Color=skia.ColorWHITE) |
|
paint2 = skia.Paint(AntiAlias=True, Color=skia.ColorBLACK) |
|
rx = min(x, WIDTH - tw - 10) |
|
ry = y + th + 15 |
|
bg = skia.Rect(rx, ry, rx + tw, ry + th) |
|
with surface as canvas: |
|
# canvas.drawRect(bg, paint1) |
|
# canvas.scale(zoom, zoom) |
|
# canvas.translate(tx, ty) |
|
canvas.drawTextBlob(text, rx, ry, paint2) |
|
canvas.drawTextBlob(text, rx, ry, paint2) |
|
# canvas.translate(-tx, -ty) |
|
# canvas.scale(1 / zoom, 1 / zoom) |
|
|
|
surface.flushAndSubmit() |
|
glfw.swap_buffers(window) |
|
#glfw.poll_events() |
|
glfw.wait_events() |
|
#highlighted.attribs.pop("highlight") |
|
|
|
# image = surface.makeImageSnapshot() |
|
# image.save(f"anim/{current_date:04}-out.png", skia.kPNG) |
|
|
|
resize = False |
|
|
|
|
|
# import cProfile |
|
# cProfile.run('main()') |
|
main() |
|
sys.exit(0)
|
|
|