123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624 |
- #GPL 3 or later
- import os
- import math
- import json
- import time
- import random
- import colorsys
- import threading
- from gi.repository import Gtk
- from gi.repository import Gdk
- from gi.repository import GLib
- from gi.repository import cairo
- def box_of_color(color):
- def on_draw(widget, cr, data):
-
- width = widget.get_allocated_width()
- height = widget.get_allocated_height()
-
- cr.set_source_rgb(*color)
- cr.rectangle(0, 0, width, height)
- cr.fill()
-
- area = Gtk.DrawingArea()
- area.set_size_request(40, 40)
- area.connect("draw", on_draw, color)
- return area
-
- def pie_chart_draw(d, main_layer, win, data, colors):
-
- # Need to know how big is our chart
- w = d.get_allocated_width()
- h = d.get_allocated_height()
- # We want our circle to fit, so we find which one is
- # smaller.
- smaller = min(w, h)
-
- last = 0
- whole = float(math.pi*2)
- sum_of_data = 0
-
- for i in data:
- sum_of_data += float(data[i])
-
- for n, i in enumerate(data):
-
- this = whole* ( float(data[i]) / sum_of_data )
-
- main_layer.move_to(w/2, h/2)
- main_layer.arc(w/2, h/2, smaller/2, last, last+this)
- main_layer.close_path()
- main_layer.set_source_rgb(*colors[n%len(colors)])
- main_layer.fill()
-
- last = last+this
- def pie_chart(win, data, title="", converter=False):
- ret = Gtk.HBox(True)
- colors = make_colors(len(data))
-
- da = Gtk.DrawingArea()
- da.connect("draw", pie_chart_draw, win, data, colors)
- ret.pack_start(da, 1,1,5)
- lbox = Gtk.VBox()
- ret.pack_start(lbox, 1,1,5)
- sum_of_data = 0
-
- for i in data:
- sum_of_data += float(data[i])
- if converter:
- sum_of_data = converter(sum_of_data)
-
- lbox.pack_start(Gtk.Label(" Total : "+str(sum_of_data)+" "), 0,0,1)
-
- for n, i in enumerate(data):
- ibox = Gtk.HBox()
- lbox.pack_start(ibox, 0,0,3)
- ibox.pack_start(box_of_color(colors[n%len(colors)]), 0,0,0)
- show_size = data[i]
- if converter:
- show_size = converter(show_size)
- ibox.pack_start(Gtk.Label(" "+i+": "+str(show_size)+" "), 0,0,1)
-
-
-
- return ret
- def graph_draw(d, main_layer, win, data, currancy, graph_addition):
-
- data["items"] = sorted(data["items"], key=lambda k: k["timestamp"])
-
- # Need to know how big is our graph
- w = d.get_allocated_width()
- h = d.get_allocated_height()
- if data.get("allow_negative", False):
- zero_at = h / 2
- else:
- zero_at = h
- # The mouse position of a given frame
- mx = d.get_pointer()[0]
- my = d.get_pointer()[1]
- # Test of the mouse position
- # main_layer.move_to(0,0)
- # main_layer.line_to(mx, my)
- # main_layer.stroke()
-
- len_day = 60*60*24 # Seconds in a day
- len_hour = 60*60 # Seconds in an hour
- len_minute = 60 # Seconds in a minute
-
- # Here we are getting the latest and the earliest
- # timestamp, so we could calculate the step of the
- # graph. ( So the data will be readable )
- latest = 0
- try:
- earliest = data["items"][0]["timestamp"]
- except:
- earliest = 0
- for i in data["items"]:
- if i.get("timestamp", 0) > latest:
- latest = i.get("timestamp", 0)
- if i.get("timestamp", 0) < earliest:
- earliest = i.get("timestamp", 0)
- # Now let's look at our zoom value
- to_zoom = data["zoom"][0] != earliest or data["zoom"][1] != latest
- if data["zoom"][0] < earliest or data["zoom"][0] == 0:
- data["zoom"][0] = earliest
- if data["zoom"][1] > latest or data["zoom"][1] == 0:
- data["zoom"][1] = latest
-
- earliest, latest = data["zoom"]
- # Now I want to make a scale of dates from left
- # to right.
- main_layer.select_font_face("Monospace")
- main_layer.set_font_size(10)
-
- full_date = "%Y-%m-%d %H:%M:%S"
- only_date = "%Y-%m-%d"
- if latest - earliest > 10 * len_day:
- show_format = only_date
- count = int( w / (len("xxxx-xx-xx")*6+12) )
- else:
- show_format = full_date
- count = int( w / (len("xxxx-xx-xx xx:xx:xx")*6+12) )
- # Now I want to show the current date / time for
- # the area where the user is hovering.
- suglen = len("xxxx-xx-xx xx:xx:xx")*6+12
-
- thexm = mx-suglen/2
- if thexm < 2:
- thexm = 2
- elif thexm > w - suglen - 2:
- thexm = w - suglen - 2
- try:
- res_date = int( ( latest - earliest ) / w * mx + earliest )
- show_date = time.strftime(full_date, time.gmtime(res_date))
- except:
- show_date = "0000-00-00"
-
- main_layer.set_source_rgba(0.1,0.1,0.1,0.5)
- main_layer.rectangle(2+thexm,2,len(show_date)*6+2, 14)
- main_layer.fill()
- main_layer.move_to(3+thexm,12)
- main_layer.set_source_rgba(1,1,1,1)
- main_layer.show_text(str(show_date))
-
- # main_layer.set_source_rgba(0.7,0.7,0.7,1)
- # main_layer.move_to( mx, 20 )
- # main_layer.line_to( mx, h )
- # main_layer.stroke()
- # main_layer.set_dash([10,10])
- # main_layer.set_source_rgba(0.2,0.2,0.2,1)
- # main_layer.move_to( mx, 20 )
- # main_layer.line_to( mx, h )
- # main_layer.stroke()
- # main_layer.set_dash([1])
- # And the rest of the dates
-
- for date in range(count):
- try:
- res_date = int( ( latest - earliest ) / count * date + earliest )
- show_date = time.strftime(show_format, time.gmtime(res_date))
- except:
- show_date = "0000-00-00"
- thex = w / count * date
-
- # If not in range of the mouse ( so I could show the current day
- # for that specific area ).
- if int(thex) not in range(int(thexm-suglen/2), int(thexm+suglen)):
-
- main_layer.set_source_rgba(0.1,0.1,0.1,0.5)
- main_layer.rectangle(2+thex,2,len(show_date)*6+2, 14)
- main_layer.fill()
- main_layer.move_to(3+thex,12)
- main_layer.set_source_rgba(1,1,1,1)
- main_layer.show_text(str(show_date))
-
-
- # A step is how often will there be a data point
- # of the graph. Step of one minute, means every
- # point on the graph will consist all the data
- # happened in this minute.
-
-
- step = (latest - earliest) / (w / 2) # A second
-
-
- # Now we need the smallest and biggest value in a
- # given step
- values = []
- times = []
-
- pstep = earliest
- s = 0
- av = []
-
- for n, i in enumerate(data["items"]):
- if i.get("timestamp", 0) < earliest:
- continue
- if graph_addition == "add":
- s += float(i.get("amount", i.get("value", 0)))
- elif graph_addition == "average":
- av.append( float(i.get("amount", i.get("value", 0))) )
- elif graph_addition == "last":
- s = float(i.get("amount", i.get("value", 0)))
- if i.get("timestamp", 0) > pstep + step-1:
- pstep = i.get("timestamp", n)
-
- if graph_addition == "average":
- try:
- values.append(sum(av)/len(av))
- except:
- values.append(0)
- else:
- values.append(s)
- times.append(pstep)
- s = 0
- av = []
- if i.get("timestamp", 0) > latest:
- break
-
-
-
- # Finding the farthest point from the center
- # center being the 0 (zero)
- try:
- biggest = max(values)
- if min(values) * -1 > biggest:
- biggest = min(values) * -1 # Multuply by -1 reverses the - to a +
- except Exception as e:
- biggest = 1
-
-
- # Now let's draw it
- main_layer.set_line_cap(cairo.LineCap.ROUND)
-
- # POSITIVE VALUE
- try:
- toy = ( zero_at ) - ( ( zero_at ) / biggest * values[0] ) *0.9
- except:
- toy = zero_at
- #toy = min(toy, zero_at)
- main_layer.rectangle(0,0,w,zero_at)
- main_layer.clip()
-
- main_layer.move_to(0, toy)
-
- prex = 0
- prey = toy
- toxes = []
- toyes = []
-
- for n, i in enumerate(values):
-
- tox = w / (latest - earliest) * (times[n]-earliest)
- try:
- toy = ( zero_at ) - ( ( zero_at ) / biggest * i ) *0.9
- except:
- toy = zero_at
- toxes.append(tox)
- toyes.append(toy)
-
- #toy = min(toy, zero_at)
-
-
- main_layer.curve_to(
- tox - (tox - prex)/2,
- prey,
-
- prex + (tox - prex)/2,
- toy,
- tox,
- toy)
- prex = tox
- prey = toy
- main_layer.line_to( w, zero_at)
- main_layer.line_to( 0, zero_at)
- main_layer.set_source_rgba(0.2,0.8,0.2,0.5)
- main_layer.fill_preserve()
- main_layer.set_source_rgba(0.2,0.8,0.2,1)
- main_layer.stroke()
- # NEGATIVE VALUE
- try:
- toy = ( zero_at ) - ( ( zero_at ) / biggest * values[0] ) *0.9
- except:
- toy = zero_at
- #toy = max(toy, zero_at)
-
- main_layer.reset_clip()
- main_layer.rectangle(0,zero_at,w,h)
- main_layer.clip()
-
- main_layer.move_to(0, toy)
- prex = 0
- prey = toy
-
- for n, i in enumerate(values):
-
- tox = w / (latest - earliest) * (times[n]-earliest)
- try:
- toy = ( zero_at ) - ( ( zero_at ) / biggest * i ) *0.9
- except:
- toy = zero_at
- #toy = max(toy, zero_at)
-
- main_layer.curve_to(
-
- tox - (tox - prex)/2,
- prey,
-
- prex + (tox - prex)/2,
- toy,
-
- tox,
- toy)
- prex = tox
- prey = toy
- main_layer.line_to( w, zero_at)
- main_layer.line_to( 0, zero_at)
- main_layer.set_source_rgba(0.8,0.2,0.2,0.5)
- main_layer.fill_preserve()
- main_layer.set_source_rgba(0.8,0.2,0.2,1)
- main_layer.stroke()
- main_layer.reset_clip()
-
-
- # Reference line
- main_layer.set_source_rgba(0.7,0.7,0.7,1)
- main_layer.move_to( 0, zero_at )
- main_layer.line_to( w, zero_at )
- main_layer.stroke()
- main_layer.set_dash([10,10])
- main_layer.set_source_rgba(0.2,0.2,0.2,1)
- main_layer.move_to( 0, zero_at )
- main_layer.line_to( w, zero_at )
- main_layer.stroke()
- main_layer.set_dash([1])
- # MOUSE OVER SELECTOR
- def closest(l, v):
- distances = []
- for i in l:
- distances.append(max(i-v, v-i))
- try:
- return l[distances.index(min(distances))]
- except:
- return 0
- selectx = closest(toxes, mx)
- if selectx:
- selecty = toyes[toxes.index(selectx)]
- # Litte circle
-
- main_layer.arc(selectx, selecty, 8, 0, math.pi*2)
-
- main_layer.set_source_rgba(0.2,0.8,0.2,1)
- if selecty > zero_at:
- main_layer.set_source_rgba(0.8,0.2,0.2,1)
- main_layer.fill()
- # Line from that circle downwards
-
- main_layer.move_to(selectx, selecty)
- main_layer.line_to(selectx, zero_at)
- main_layer.stroke()
- # Data about this time frame
- to_data = times[toxes.index(selectx)]
- from_data = to_data - step
- try:
- from_data = time.strftime(show_format, time.gmtime(from_data))
- except:
- from_data = "0000-00-00"
- try:
- to_data = time.strftime(show_format, time.gmtime(to_data))
- except:
- to_data = "0000-00-00"
-
-
- # Counting the largest thing
- plist = ["From: "+from_data,
- "To: "+to_data,
- "Total: "+currancy+" "+str(round(values[toxes.index(selectx)], 2)) ]
-
- leng = 0
- for thing in plist:
- if len(str(thing))*6+2 > leng:
- leng = len(str(thing))*6+2
- if selectx > w/2:
- recx = selectx - leng - 10
- else:
- recx = selectx + 10
- if selecty + len(plist)*15 > h:
- recy = selecty - len(plist)*15
- else:
- recy = selecty
-
- main_layer.set_source_rgba(0.1,0.1,0.1,0.7)
- main_layer.rectangle(recx, recy, leng, len(plist)*15)
- main_layer.fill()
- for n, thing in enumerate(plist):
- main_layer.move_to(recx+2, recy+12+(15*n))
- main_layer.set_source_rgba(1,1,1,1)
- main_layer.show_text(thing)
-
- # Now let's get the values ( to the side of the graph )
- for i in range(int(h/20)):
- # TODO: This has to be tuned a bit. It's not perfect. But it's
- # very close.
-
- they = i*20+20
- try:
- value_is = round( biggest / zero_at * (zero_at - they), 2)
- except Exception as e:
- print("what", e)
- value_is = 0
-
- show_value = currancy + " " + str(value_is)
- if mx > w / 2:
- main_layer.set_source_rgba(0.1,0.1,0.1,0.5)
- main_layer.rectangle(2, 2+they,len(show_value)*6+4, 14)
- main_layer.fill()
- main_layer.move_to(3,12+they)
- main_layer.set_source_rgba(1,1,1,1)
- main_layer.show_text(show_value)
- else:
- main_layer.set_source_rgba(0.1,0.1,0.1,0.5)
- main_layer.rectangle(w-len(show_value)*6-4, 2+they,len(show_value)*6+4, 14)
- main_layer.fill()
- main_layer.move_to(w-len(show_value)*6-3,12+they)
- main_layer.set_source_rgba(1,1,1,1)
- main_layer.show_text(show_value)
- # Render a little pressed selector
- if "pressed" in data:
- for i in [data["pressed"], mx]:
- main_layer.set_source_rgba(0.7,0.7,0.7,1)
- main_layer.move_to( i, 0 )
- main_layer.line_to( i, h )
- main_layer.stroke()
- main_layer.set_dash([10,10])
- main_layer.set_source_rgba(0.2,0.2,0.2,1)
- main_layer.move_to( i, 0 )
- main_layer.line_to( i, h )
- main_layer.stroke()
- main_layer.set_dash([1])
-
- # Keep redrawing the graph
- d.queue_draw()
-
- def graph_button_press(w, e, data, da):
- data["pressed"] = e.x
-
- print(data["zoom"])
- print(e.x, e.y)
- def graph_button_release(w, e, data, da):
- if "pressed" in data:
- x = data["pressed"]
- # If there was no motion
- if x-2 < e.x < x+2:
- data["zoom"] = [0,0]
- else:
- w = da.get_allocated_width()
- zoom0 = data["zoom"][0] + ((data["zoom"][1] - data["zoom"][0]) / w * min(x, e.x))
- zoom1 = data["zoom"][0] + ((data["zoom"][1] - data["zoom"][0]) / w * max(x, e.x))
- data["zoom"] = [zoom0, zoom1]
- print(data["zoom"])
- del data["pressed"]
- def graph(win, data, title="", currancy="$", add_now=True, add_value="Same", graph_addition="add"):
- # adding one more data point for "now"
- if add_now:
- try:
- data["items"] = sorted(data["items"], key=lambda k: k["timestamp"])
- last = data["items"][-1].copy()
- last["timestamp"] = int(time.time())
- if not add_value == "Same":
- last["amount"] = add_value
- data["items"].append(last)
- except:
- pass
-
- event_box = Gtk.EventBox()
- da = Gtk.DrawingArea()
- da.set_size_request(200,200)
- da.connect("draw", graph_draw, win, data, currancy, graph_addition)
- event_box.connect("button-press-event", graph_button_press, data, da)
- event_box.connect("button-release-event", graph_button_release, data, da)
- event_box.add(da)
- return event_box
|