analytics.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831
  1. #####################################################################
  2. # #
  3. # THIS IS A SOURCE CODE FILE FROM A PROGRAM TO INTERACT WITH THE #
  4. # LBRY PROTOCOL ( lbry.com ). IT WILL USE THE LBRY SDK ( lbrynet ) #
  5. # FROM THEIR REPOSITORY ( https://github.com/lbryio/lbry-sdk ) #
  6. # WHICH I GONNA PRESENT TO YOU AS A BINARY. SINCE I DID NOT DEVELOP #
  7. # IT AND I'M LAZY TO INTEGRATE IN A MORE SMART WAY. THE SOURCE CODE #
  8. # OF THE SDK IS AVAILABLE IN THE REPOSITORY MENTIONED ABOVE. #
  9. # #
  10. # ALL THE CODE IN THIS REPOSITORY INCLUDING THIS FILE IS #
  11. # (C) J.Y.Amihud and Other Contributors 2021. EXCEPT THE LBRY SDK. #
  12. # YOU CAN USE THIS FILE AND ANY OTHER FILE IN THIS REPOSITORY UNDER #
  13. # THE TERMS OF GNU GENERAL PUBLIC LICENSE VERSION 3 OR ANY LATER #
  14. # VERSION. TO FIND THE FULL TEXT OF THE LICENSE GO TO THE GNU.ORG #
  15. # WEBSITE AT ( https://www.gnu.org/licenses/gpl-3.0.html ). #
  16. # #
  17. # THE LBRY SDK IS UNFORTUNATELY UNDER THE MIT LICENSE. IF YOU ARE #
  18. # NOT INTENDING TO USE MY CODE AND JUST THE SDK. YOU CAN FIND IT ON #
  19. # THEIR OFFICIAL REPOSITORY ABOVE. THEIR LICENSE CHOICE DOES NOT #
  20. # SPREAD ONTO THIS PROJECT. DON'T GET A FALSE ASSUMPTION THAT SINCE #
  21. # THEY USE A PUSH-OVER LICENSE, I GONNA DO THE SAME. I'M NOT. #
  22. # #
  23. # THE LICENSE CHOSEN FOR THIS PROJECT WILL PROTECT THE 4 ESSENTIAL #
  24. # FREEDOMS OF THE USER FURTHER, BY NOT ALLOWING ANY WHO TO CHANGE #
  25. # THE LICENSE AT WILL. SO NO PROPRIETARY SOFTWARE DEVELOPER COULD #
  26. # TAKE THIS CODE AND MAKE THEIR USER-SUBJUGATING SOFTWARE FROM IT. #
  27. # #
  28. #####################################################################
  29. # Since LBRY wallet is just a long stream of increasing and decreasing
  30. # numbers. I call this the analytics.
  31. import os
  32. import math
  33. import json
  34. import time
  35. import random
  36. import colorsys
  37. import threading
  38. from gi.repository import Gtk
  39. from gi.repository import Gdk
  40. from gi.repository import GLib
  41. from gi.repository import cairo
  42. from flbry import fetch
  43. from flbry import settings
  44. def make_colors(amount=4, color=(0.1,0.5,0.8)):
  45. # TODO: Figure out how to got the themes prefered active color
  46. # and use it here.
  47. colors = []
  48. for i in range(amount):
  49. nc = colorsys.rgb_to_hsv(*color)
  50. nc = list(nc)
  51. if i != 0:
  52. nc[1] = 0
  53. nc[2] = (i)*(1/amount)
  54. colors.append(colorsys.hsv_to_rgb(*nc))
  55. return colors
  56. def window(win):
  57. awin = Gtk.Window()
  58. awin.set_title("FastLBRY GTK: Wallet / Analytics")
  59. awin.set_size_request(500, 500)
  60. notebook = Gtk.Notebook()
  61. notebook.set_scrollable(True)
  62. awin.add(notebook)
  63. ############################ ANALYTICS SECTION #############################
  64. box = Gtk.VBox()
  65. notebook.append_page(box, Gtk.Label("Totals / Analytics"))
  66. # Let's get...
  67. raw_balance = fetch.lbrynet("wallet_balance")
  68. # ... and format the balance information.
  69. balance = {
  70. "Spendable":raw_balance["available"],
  71. "Claim Bids":raw_balance["reserved_subtotals"]["claims"],
  72. "Supports":raw_balance["reserved_subtotals"]["supports"],
  73. "Tips":raw_balance["reserved_subtotals"]["tips"],
  74. }
  75. # Pie chart for balance
  76. def conv(t):
  77. return str(t) + " LBC"
  78. box.pack_start(pie_chart(win, balance, "Totals", converter=conv), 0, 0 , 0)
  79. minus_30_days = 60*60*24*30*-1
  80. transactions = {"items":[], # The list of items
  81. "zoom":[0,0], #minus_30_days,0], # The range of the selection ( by timestamp )
  82. "allow_negative":True
  83. }
  84. def getting_all_transactions(data, win):
  85. # This is a small function funnin in a separate thread, loading
  86. # all transactions so the graph could draw them.
  87. # TODO: this commented code works perfectly fine to get the kind of
  88. # data that I want. But it's hell'a heavy on my computer. Last time
  89. # I tried, it froze. So I need to figure out something about this.
  90. # cash_file = settings.get_settings_folder()+"GTK-transactions-graph.json"
  91. # try:
  92. # with open(cash_file) as json_file:
  93. # data["items"] = json.load(json_file)
  94. # except Exception as e:
  95. # print(e)
  96. # def add(out, addpage):
  97. # ret = False
  98. # added = 0
  99. # count = 0
  100. # for i in out["items"]:
  101. # # Removing unnesesary data
  102. # a = i.copy()
  103. # # a["amount"] = i["amount"]
  104. # # a["timestamp"] = i["timestamp"]
  105. # if a["timestamp"] == None: # Too recent
  106. # a["timestamp"] = int(time.time())
  107. # # a["txid"] = i["txid"]
  108. # # a["name"] = i.get("name", "")
  109. # if a not in data["items"]:
  110. # added += 1
  111. # data["items"].append(a)
  112. # elif not addpage:
  113. # count += 1
  114. # ret = True
  115. # return ret
  116. # Fetching
  117. # out = fetch.lbrynet("transaction_list", { "page_size":50})
  118. # all_of_them = out["total_items"]
  119. # addpage = 0
  120. # add(out, 0)
  121. # for i in range(out["total_pages"]):
  122. # out = fetch.lbrynet("transaction_list", {"page":addpage+i+2,
  123. # #"exclude_internal_transfers": True,
  124. # #"no_totals":True,
  125. # "page_size":50})
  126. # if add(out, addpage):
  127. # addpage = int((len(data["items"]))/50) - 2
  128. # # Save to cash
  129. # with open(cash_file, 'w') as fp:
  130. # json.dump(data["items"], fp)
  131. # if not win.keep_loading_the_wallet_graph or all_of_them == len(data["items"]):
  132. # return
  133. # THE MEANWHILE SOLUTION
  134. out = fetch.lbrynet("txo_plot", { "days_back":1000, # Fetch 100 days of txo
  135. "exclude_internal_transfers":True, # Without crap
  136. "is_not_my_input":True, # Not from me ( as in support only )
  137. })
  138. for i in out:
  139. a = {}
  140. a["amount"] = i["total"]
  141. a["timestamp"] = int(time.mktime(time.strptime(i["day"],"%Y-%m-%d")))
  142. data["items"].append(a)
  143. win.keep_loading_the_wallet_graph = True
  144. t = threading.Thread(target=getting_all_transactions, args=(transactions, win))
  145. t.setDaemon(True)
  146. t.start()
  147. def kill_graph(w):
  148. win.keep_loading_the_wallet_graph = False
  149. awin.connect("destroy", kill_graph)
  150. # Graph with the history of all transactions
  151. the_graph = graph(win, transactions, "Totals")
  152. box.pack_start(the_graph, 1, 1 , 0)
  153. awin.show_all()
  154. def box_of_color(color):
  155. def on_draw(widget, cr, data):
  156. width = widget.get_allocated_width()
  157. height = widget.get_allocated_height()
  158. cr.set_source_rgb(*color)
  159. cr.rectangle(0, 0, width, height)
  160. cr.fill()
  161. area = Gtk.DrawingArea()
  162. area.set_size_request(40, 40)
  163. area.connect("draw", on_draw, color)
  164. return area
  165. def pie_chart_draw(d, main_layer, win, data, colors):
  166. # Need to know how big is our chart
  167. w = d.get_allocated_width()
  168. h = d.get_allocated_height()
  169. # We want our circle to fit, so we find which one is
  170. # smaller.
  171. smaller = min(w, h)
  172. last = 0
  173. whole = float(math.pi*2)
  174. sum_of_data = 0
  175. for i in data:
  176. sum_of_data += float(data[i])
  177. for n, i in enumerate(data):
  178. this = whole* ( float(data[i]) / sum_of_data )
  179. main_layer.move_to(w/2, h/2)
  180. main_layer.arc(w/2, h/2, smaller/2, last, last+this)
  181. main_layer.close_path()
  182. main_layer.set_source_rgb(*colors[n%len(colors)])
  183. main_layer.fill()
  184. last = last+this
  185. def pie_chart(win, data, title="", converter=False):
  186. ret = Gtk.HBox(True)
  187. colors = make_colors(len(data))
  188. da = Gtk.DrawingArea()
  189. da.connect("draw", pie_chart_draw, win, data, colors)
  190. ret.pack_start(da, 1,1,5)
  191. lbox = Gtk.VBox()
  192. ret.pack_start(lbox, 1,1,5)
  193. sum_of_data = 0
  194. for i in data:
  195. sum_of_data += float(data[i])
  196. if converter:
  197. sum_of_data = converter(sum_of_data)
  198. lbox.pack_start(Gtk.Label(" Total : "+str(sum_of_data)+" "), 0,0,1)
  199. for n, i in enumerate(data):
  200. ibox = Gtk.HBox()
  201. lbox.pack_start(ibox, 0,0,3)
  202. ibox.pack_start(box_of_color(colors[n%len(colors)]), 0,0,0)
  203. show_size = data[i]
  204. if converter:
  205. show_size = converter(show_size)
  206. ibox.pack_start(Gtk.Label(" "+i+": "+str(show_size)+" "), 0,0,1)
  207. return ret
  208. def graph_draw(d, main_layer, win, data, currancy, graph_addition):
  209. data["items"] = sorted(data["items"], key=lambda k: k["timestamp"])
  210. # Need to know how big is our graph
  211. w = d.get_allocated_width()
  212. h = d.get_allocated_height()
  213. if data.get("allow_negative", False):
  214. zero_at = h / 2
  215. else:
  216. zero_at = h
  217. # The mouse position of a given frame
  218. mx = d.get_pointer()[0]
  219. my = d.get_pointer()[1]
  220. # Test of the mouse position
  221. # main_layer.move_to(0,0)
  222. # main_layer.line_to(mx, my)
  223. # main_layer.stroke()
  224. len_day = 60*60*24 # Seconds in a day
  225. len_hour = 60*60 # Seconds in an hour
  226. len_minute = 60 # Seconds in a minute
  227. # Here we are getting the latest and the earliest
  228. # timestamp, so we could calculate the step of the
  229. # graph. ( So the data will be readable )
  230. latest = 0
  231. try:
  232. earliest = data["items"][0]["timestamp"]
  233. except:
  234. earliest = 0
  235. for i in data["items"]:
  236. if i.get("timestamp", 0) > latest:
  237. latest = i.get("timestamp", 0)
  238. if i.get("timestamp", 0) < earliest:
  239. earliest = i.get("timestamp", 0)
  240. # Now let's look at our zoom value
  241. to_zoom = data["zoom"][0] != earliest or data["zoom"][1] != latest
  242. if data["zoom"][0] < earliest or data["zoom"][0] == 0:
  243. data["zoom"][0] = earliest
  244. if data["zoom"][1] > latest or data["zoom"][1] == 0:
  245. data["zoom"][1] = latest
  246. earliest, latest = data["zoom"]
  247. # Now I want to make a scale of dates from left
  248. # to right.
  249. main_layer.select_font_face("Monospace")
  250. main_layer.set_font_size(10)
  251. full_date = "%Y-%m-%d %H:%M:%S"
  252. only_date = "%Y-%m-%d"
  253. if latest - earliest > 10 * len_day:
  254. show_format = only_date
  255. count = int( w / (len("xxxx-xx-xx")*6+12) )
  256. else:
  257. show_format = full_date
  258. count = int( w / (len("xxxx-xx-xx xx:xx:xx")*6+12) )
  259. # Now I want to show the current date / time for
  260. # the area where the user is hovering.
  261. suglen = len("xxxx-xx-xx xx:xx:xx")*6+12
  262. thexm = mx-suglen/2
  263. if thexm < 2:
  264. thexm = 2
  265. elif thexm > w - suglen - 2:
  266. thexm = w - suglen - 2
  267. try:
  268. res_date = int( ( latest - earliest ) / w * mx + earliest )
  269. show_date = time.strftime(full_date, time.gmtime(res_date))
  270. except:
  271. show_date = "0000-00-00"
  272. main_layer.set_source_rgba(0.1,0.1,0.1,0.5)
  273. main_layer.rectangle(2+thexm,2,len(show_date)*6+2, 14)
  274. main_layer.fill()
  275. main_layer.move_to(3+thexm,12)
  276. main_layer.set_source_rgba(1,1,1,1)
  277. main_layer.show_text(str(show_date))
  278. # main_layer.set_source_rgba(0.7,0.7,0.7,1)
  279. # main_layer.move_to( mx, 20 )
  280. # main_layer.line_to( mx, h )
  281. # main_layer.stroke()
  282. # main_layer.set_dash([10,10])
  283. # main_layer.set_source_rgba(0.2,0.2,0.2,1)
  284. # main_layer.move_to( mx, 20 )
  285. # main_layer.line_to( mx, h )
  286. # main_layer.stroke()
  287. # main_layer.set_dash([1])
  288. # And the rest of the dates
  289. for date in range(count):
  290. try:
  291. res_date = int( ( latest - earliest ) / count * date + earliest )
  292. show_date = time.strftime(show_format, time.gmtime(res_date))
  293. except:
  294. show_date = "0000-00-00"
  295. thex = w / count * date
  296. # If not in range of the mouse ( so I could show the current day
  297. # for that specific area ).
  298. if int(thex) not in range(int(thexm-suglen/2), int(thexm+suglen)):
  299. main_layer.set_source_rgba(0.1,0.1,0.1,0.5)
  300. main_layer.rectangle(2+thex,2,len(show_date)*6+2, 14)
  301. main_layer.fill()
  302. main_layer.move_to(3+thex,12)
  303. main_layer.set_source_rgba(1,1,1,1)
  304. main_layer.show_text(str(show_date))
  305. # A step is how often will there be a data point
  306. # of the graph. Step of one minute, means every
  307. # point on the graph will consist all the data
  308. # happened in this minute.
  309. step = (latest - earliest) / (w / 2) # A second
  310. # Now we need the smallest and biggest value in a
  311. # given step
  312. values = []
  313. times = []
  314. pstep = earliest
  315. s = 0
  316. av = []
  317. for n, i in enumerate(data["items"]):
  318. if i.get("timestamp", 0) < earliest:
  319. continue
  320. if graph_addition == "add":
  321. s += float(i.get("amount", i.get("value", 0)))
  322. elif graph_addition == "average":
  323. av.append( float(i.get("amount", i.get("value", 0))) )
  324. elif graph_addition == "last":
  325. s = float(i.get("amount", i.get("value", 0)))
  326. if i.get("timestamp", 0) > pstep + step-1:
  327. pstep = i.get("timestamp", n)
  328. if graph_addition == "average":
  329. try:
  330. values.append(sum(av)/len(av))
  331. except:
  332. values.append(0)
  333. else:
  334. values.append(s)
  335. times.append(pstep)
  336. s = 0
  337. av = []
  338. if i.get("timestamp", 0) > latest:
  339. break
  340. # Finding the farthest point from the center
  341. # center being the 0 (zero)
  342. try:
  343. biggest = max(values)
  344. if min(values) * -1 > biggest:
  345. biggest = min(values) * -1 # Multuply by -1 reverses the - to a +
  346. except Exception as e:
  347. biggest = 1
  348. # Now let's draw it
  349. main_layer.set_line_cap(cairo.LineCap.ROUND)
  350. # POSITIVE VALUE
  351. try:
  352. toy = ( zero_at ) - ( ( zero_at ) / biggest * values[0] ) *0.9
  353. except:
  354. toy = zero_at
  355. #toy = min(toy, zero_at)
  356. main_layer.rectangle(0,0,w,zero_at)
  357. main_layer.clip()
  358. main_layer.move_to(0, toy)
  359. prex = 0
  360. prey = toy
  361. toxes = []
  362. toyes = []
  363. for n, i in enumerate(values):
  364. tox = w / (latest - earliest) * (times[n]-earliest)
  365. try:
  366. toy = ( zero_at ) - ( ( zero_at ) / biggest * i ) *0.9
  367. except:
  368. toy = zero_at
  369. toxes.append(tox)
  370. toyes.append(toy)
  371. #toy = min(toy, zero_at)
  372. main_layer.curve_to(
  373. tox - (tox - prex)/2,
  374. prey,
  375. prex + (tox - prex)/2,
  376. toy,
  377. tox,
  378. toy)
  379. prex = tox
  380. prey = toy
  381. main_layer.line_to( w, zero_at)
  382. main_layer.line_to( 0, zero_at)
  383. main_layer.set_source_rgba(0.2,0.8,0.2,0.5)
  384. main_layer.fill_preserve()
  385. main_layer.set_source_rgba(0.2,0.8,0.2,1)
  386. main_layer.stroke()
  387. # NEGATIVE VALUE
  388. try:
  389. toy = ( zero_at ) - ( ( zero_at ) / biggest * values[0] ) *0.9
  390. except:
  391. toy = zero_at
  392. #toy = max(toy, zero_at)
  393. main_layer.reset_clip()
  394. main_layer.rectangle(0,zero_at,w,h)
  395. main_layer.clip()
  396. main_layer.move_to(0, toy)
  397. prex = 0
  398. prey = toy
  399. for n, i in enumerate(values):
  400. tox = w / (latest - earliest) * (times[n]-earliest)
  401. try:
  402. toy = ( zero_at ) - ( ( zero_at ) / biggest * i ) *0.9
  403. except:
  404. toy = zero_at
  405. #toy = max(toy, zero_at)
  406. main_layer.curve_to(
  407. tox - (tox - prex)/2,
  408. prey,
  409. prex + (tox - prex)/2,
  410. toy,
  411. tox,
  412. toy)
  413. prex = tox
  414. prey = toy
  415. main_layer.line_to( w, zero_at)
  416. main_layer.line_to( 0, zero_at)
  417. main_layer.set_source_rgba(0.8,0.2,0.2,0.5)
  418. main_layer.fill_preserve()
  419. main_layer.set_source_rgba(0.8,0.2,0.2,1)
  420. main_layer.stroke()
  421. main_layer.reset_clip()
  422. # Reference line
  423. main_layer.set_source_rgba(0.7,0.7,0.7,1)
  424. main_layer.move_to( 0, zero_at )
  425. main_layer.line_to( w, zero_at )
  426. main_layer.stroke()
  427. main_layer.set_dash([10,10])
  428. main_layer.set_source_rgba(0.2,0.2,0.2,1)
  429. main_layer.move_to( 0, zero_at )
  430. main_layer.line_to( w, zero_at )
  431. main_layer.stroke()
  432. main_layer.set_dash([1])
  433. # MOUSE OVER SELECTOR
  434. def closest(l, v):
  435. distances = []
  436. for i in l:
  437. distances.append(max(i-v, v-i))
  438. try:
  439. return l[distances.index(min(distances))]
  440. except:
  441. return 0
  442. selectx = closest(toxes, mx)
  443. if selectx:
  444. selecty = toyes[toxes.index(selectx)]
  445. # Litte circle
  446. main_layer.arc(selectx, selecty, 8, 0, math.pi*2)
  447. main_layer.set_source_rgba(0.2,0.8,0.2,1)
  448. if selecty > zero_at:
  449. main_layer.set_source_rgba(0.8,0.2,0.2,1)
  450. main_layer.fill()
  451. # Line from that circle downwards
  452. main_layer.move_to(selectx, selecty)
  453. main_layer.line_to(selectx, zero_at)
  454. main_layer.stroke()
  455. # Data about this time frame
  456. to_data = times[toxes.index(selectx)]
  457. from_data = to_data - step
  458. try:
  459. from_data = time.strftime(show_format, time.gmtime(from_data))
  460. except:
  461. from_data = "0000-00-00"
  462. try:
  463. to_data = time.strftime(show_format, time.gmtime(to_data))
  464. except:
  465. to_data = "0000-00-00"
  466. # Counting the largest thing
  467. plist = ["From: "+from_data,
  468. "To: "+to_data,
  469. "Total: "+currancy+" "+str(round(values[toxes.index(selectx)], 2)) ]
  470. leng = 0
  471. for thing in plist:
  472. if len(str(thing))*6+2 > leng:
  473. leng = len(str(thing))*6+2
  474. if selectx > w/2:
  475. recx = selectx - leng - 10
  476. else:
  477. recx = selectx + 10
  478. if selecty + len(plist)*15 > h:
  479. recy = selecty - len(plist)*15
  480. else:
  481. recy = selecty
  482. main_layer.set_source_rgba(0.1,0.1,0.1,0.7)
  483. main_layer.rectangle(recx, recy, leng, len(plist)*15)
  484. main_layer.fill()
  485. for n, thing in enumerate(plist):
  486. main_layer.move_to(recx+2, recy+12+(15*n))
  487. main_layer.set_source_rgba(1,1,1,1)
  488. main_layer.show_text(thing)
  489. # Now let's get the values ( to the side of the graph )
  490. for i in range(int(h/20)):
  491. # TODO: This has to be tuned a bit. It's not perfect. But it's
  492. # very close.
  493. they = i*20+20
  494. try:
  495. value_is = round( biggest / zero_at * (zero_at - they), 2)
  496. except Exception as e:
  497. print("what", e)
  498. value_is = 0
  499. show_value = currancy + " " + str(value_is)
  500. if mx > w / 2:
  501. main_layer.set_source_rgba(0.1,0.1,0.1,0.5)
  502. main_layer.rectangle(2, 2+they,len(show_value)*6+4, 14)
  503. main_layer.fill()
  504. main_layer.move_to(3,12+they)
  505. main_layer.set_source_rgba(1,1,1,1)
  506. main_layer.show_text(show_value)
  507. else:
  508. main_layer.set_source_rgba(0.1,0.1,0.1,0.5)
  509. main_layer.rectangle(w-len(show_value)*6-4, 2+they,len(show_value)*6+4, 14)
  510. main_layer.fill()
  511. main_layer.move_to(w-len(show_value)*6-3,12+they)
  512. main_layer.set_source_rgba(1,1,1,1)
  513. main_layer.show_text(show_value)
  514. # Render a little pressed selector
  515. if "pressed" in data:
  516. for i in [data["pressed"], mx]:
  517. main_layer.set_source_rgba(0.7,0.7,0.7,1)
  518. main_layer.move_to( i, 0 )
  519. main_layer.line_to( i, h )
  520. main_layer.stroke()
  521. main_layer.set_dash([10,10])
  522. main_layer.set_source_rgba(0.2,0.2,0.2,1)
  523. main_layer.move_to( i, 0 )
  524. main_layer.line_to( i, h )
  525. main_layer.stroke()
  526. main_layer.set_dash([1])
  527. # Keep redrawing the graph
  528. d.queue_draw()
  529. def graph_button_press(w, e, data, da):
  530. data["pressed"] = e.x
  531. print(data["zoom"])
  532. print(e.x, e.y)
  533. def graph_button_release(w, e, data, da):
  534. if "pressed" in data:
  535. x = data["pressed"]
  536. # If there was no motion
  537. if x-2 < e.x < x+2:
  538. data["zoom"] = [0,0]
  539. else:
  540. w = da.get_allocated_width()
  541. zoom0 = data["zoom"][0] + ((data["zoom"][1] - data["zoom"][0]) / w * min(x, e.x))
  542. zoom1 = data["zoom"][0] + ((data["zoom"][1] - data["zoom"][0]) / w * max(x, e.x))
  543. data["zoom"] = [zoom0, zoom1]
  544. print(data["zoom"])
  545. del data["pressed"]
  546. def graph(win, data, title="", currancy="$", add_now=True, add_value="Same", graph_addition="add"):
  547. # adding one more data point for "now"
  548. if add_now:
  549. try:
  550. data["items"] = sorted(data["items"], key=lambda k: k["timestamp"])
  551. last = data["items"][-1].copy()
  552. last["timestamp"] = int(time.time())
  553. if not add_value == "Same":
  554. last["amount"] = add_value
  555. data["items"].append(last)
  556. except:
  557. pass
  558. event_box = Gtk.EventBox()
  559. da = Gtk.DrawingArea()
  560. da.set_size_request(100,100)
  561. da.connect("draw", graph_draw, win, data, currancy, graph_addition)
  562. event_box.connect("button-press-event", graph_button_press, data, da)
  563. event_box.connect("button-release-event", graph_button_release, data, da)
  564. event_box.add(da)
  565. return event_box