ChartBig.coffee 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  1. class ChartBig extends Class
  2. constructor: () ->
  3. @need_update = false
  4. @data = {}
  5. @data_max = {}
  6. @data_total = {}
  7. types = {}
  8. update: (cb) =>
  9. if Page.params.interval == "1w"
  10. interval = 60 * 60 * 24 * 7
  11. step = 60 * 60
  12. query_select = "MAX(date_added) AS date_added, type_id, SUM(value) AS value"
  13. query_group = "GROUP BY type_id, strftime('%Y-%m-%d %H', date_added, 'unixepoch', 'localtime')"
  14. else
  15. interval = 60 * 60 * 24
  16. step = 60 * 5
  17. query_select = "*"
  18. query_group = ""
  19. if Page.params.date_added_to
  20. date_added_to = (new Date(Page.params.date_added_to + " 23:59")).getTime() / 1000
  21. date_added_from = date_added_to - interval
  22. else
  23. date_added_to = Time.timestamp()
  24. date_added_from = Time.timestamp() - interval
  25. query = """
  26. SELECT #{query_select} FROM data
  27. WHERE type_id IN :type_ids AND date_added >= :date_added_from AND date_added <= :date_added_to
  28. #{query_group}
  29. ORDER BY date_added
  30. """
  31. type_ids = (Page.page_stats.type_id_db[type.name] for type in @types)
  32. Page.cmd "chartDbQuery", [query, {type_ids: type_ids, date_added_from: date_added_from, date_added_to: date_added_to}], (res) =>
  33. @logStart "Parse result"
  34. @data = {labels: []}
  35. @data_max = {}
  36. @data_total = {}
  37. for type_id in type_ids
  38. @data[type_id] = {}
  39. @data_max[Page.page_stats.type_name_db[type_id]] = 0
  40. @data_total[Page.page_stats.type_name_db[type_id]] = 0
  41. # Index data by closest step date
  42. for row in res
  43. type_name = Page.page_stats.type_name_db[row.type_id]
  44. @data[row.type_id][Math.ceil(row.date_added / step) * step] = row.value
  45. @data_max[type_name] = Math.max(row.value, @data_max[type_name])
  46. @data_total[type_name] += row.value
  47. # Make the graph symmetric
  48. @configuration.options.scales.yAxes[0].ticks.suggestedMax = Math.max(@data_max["file_bytes_sent"], @data_max["file_bytes_recv"])
  49. @configuration.options.scales.yAxes[0].ticks.suggestedMin = 0 - @configuration.options.scales.yAxes[0].ticks.suggestedMax
  50. @configuration.options.scales.yAxes[1].ticks.suggestedMax = Math.max(@data_max["request_num_sent"], @data_max["request_num_recv"])
  51. @configuration.options.scales.yAxes[1].ticks.suggestedMin = 0 - @configuration.options.scales.yAxes[1].ticks.suggestedMax
  52. # Reset data values
  53. for type_id, i in type_ids
  54. dataset = @configuration.data.datasets[@types[i].dataset_id]
  55. dataset.data.length = 0
  56. dataset.data_i = 0
  57. @configuration.data.labels.length = 0
  58. @configuration.data.labels_i = 0
  59. # Update data values
  60. data_date_added = Math.ceil(date_added_from / step) * step
  61. while data_date_added <= date_added_to
  62. # Skip empty data from chart beginning
  63. if not data_found
  64. for type_id, i in type_ids
  65. if @data[type_id][data_date_added]
  66. data_found = true
  67. break
  68. if not data_found
  69. data_date_added += step
  70. continue
  71. for type_id, i in type_ids
  72. data_value = @data[type_id][data_date_added]
  73. dataset = @configuration.data.datasets[@types[i].dataset_id]
  74. # scale = @chart.scales[dataset.yAxisID]
  75. type = @types[i]
  76. if type.negative
  77. data_value = 0 - data_value
  78. dataset.data[dataset.data_i] = data_value
  79. dataset.data_i += 1
  80. @configuration.data.labels.push(data_date_added * 1000)
  81. @configuration.data.labels_i += 1
  82. data_date_added += step
  83. @logEnd "Parse result", "labels: #{@configuration.data.labels.length}"
  84. if @chart
  85. @chart.update()
  86. else
  87. @initChart()
  88. cb?()
  89. Page.projector.scheduleRender()
  90. storeCanvasNode: (node) =>
  91. if @chart
  92. @chart.clear()
  93. @chart.destroy()
  94. @chart = null
  95. node.parentNode.style.height = node.getBoundingClientRect().height + "px"
  96. # node.parentNode.style.overflow = "hidden"
  97. @ctx = node.getContext("2d")
  98. @chart_node = node
  99. @configuration ?= @getChartConfiguration()
  100. initChart: =>
  101. @log "initChart"
  102. @chart = new Chart(@ctx, @configuration)
  103. setTimeout ( =>
  104. @chart_node.parentNode.style.height = ""
  105. ), 100
  106. timer_resize = null
  107. window.addEventListener "resize", =>
  108. clearInterval(timer_resize)
  109. setTimeout ( => @chart.resize()), 300
  110. testDataAddition: ->
  111. timer_i = 0
  112. setInterval ( =>
  113. # Reset data values
  114. new_labels = @configuration.data.labels.slice()
  115. new_data = @configuration.data.datasets[@types[0].dataset_id].data.slice()
  116. #for type_id, i in type_ids
  117. # @configuration.data.datasets[@types[i].dataset_id].data = []
  118. @configuration.data.labels = []
  119. timer_i += 1
  120. for type_id, i in type_ids
  121. dataset = @configuration.data.datasets[@types[i].dataset_id]
  122. dataset.data.push(Math.round(Math.random() * 10))
  123. dataset.data.shift()
  124. #new_data.push(Math.random() * 10)
  125. #new_data.shift()
  126. #@configuration.data.datasets[@types[0].dataset_id].data.splice(0, @configuration.data.datasets[@types[0].dataset_id].data.length)
  127. for data in new_data
  128. @configuration.data.datasets[@types[0].dataset_id].data.push(data)
  129. @configuration.data.labels = new_labels
  130. @configuration.data.labels.push(1000 * (Time.timestamp() + (timer_i * 60 * 5)))
  131. @configuration.data.labels.shift()
  132. @chart.update()
  133. ), 5000
  134. createGradientStroke: (stops) ->
  135. gradient = @ctx.createLinearGradient(0, 0, 900, 0)
  136. for color, i in stops
  137. gradient.addColorStop(i * (1 / (stops.length - 1)), color)
  138. return gradient
  139. createGradientFill: (stops, mode="normal") ->
  140. if mode == "lower"
  141. gradient = @ctx.createLinearGradient(0, 0, 0, 300)
  142. else
  143. gradient = @ctx.createLinearGradient(0, 50, 0, 200)
  144. for color, i in stops
  145. gradient.addColorStop(i * (1 / (stops.length - 1)), color)
  146. return gradient
  147. getChartConfiguration: ->
  148. gradient_stroke = @createGradientStroke(["#5A46DF", "#8F49AA", "#D64C57"])
  149. gradient_stroke_bgline_up = @createGradientStroke(["#EEAAFF11", "#EEAAFF33", "#2da3b366"])
  150. gradient_stroke_bgline_down = @createGradientStroke(["#EEAAFF11", "#EEAAFF33", "#80623f88"])
  151. gradient_stroke_up = @createGradientStroke(["#2b68d9", "#2f99be", "#1dfc59"])
  152. gradient_stroke_down = @createGradientStroke(["#bac735", "#c2a548", "#f1294b"])
  153. gradient_fill = @createGradientFill(["#50455DEE", "#26262C33"])
  154. gradient_fill_up = @createGradientFill(["#1dfc5922", "#2f373333"])
  155. gradient_fill_down = @createGradientFill(["#45353533", "#f1294b22"], "lower")
  156. configuration = {
  157. type: 'line',
  158. data: {
  159. labels: [],
  160. datasets: [
  161. {
  162. type: 'line',
  163. label: "Upload",
  164. borderColor: gradient_stroke_up,
  165. pointBorderColor: gradient_stroke_up,
  166. pointBackgroundColor: gradient_stroke_up,
  167. pointHoverBackgroundColor: gradient_stroke_up,
  168. pointHoverBorderColor: gradient_stroke_up,
  169. pointHoverRadius: 2,
  170. pointRadius: 0,
  171. steppedLine: true,
  172. fill: true,
  173. backgroundColor: gradient_fill_up,
  174. borderWidth: 1,
  175. lineTension: 0,
  176. data: []
  177. }
  178. {
  179. type: 'line',
  180. label: "Download",
  181. borderColor: gradient_stroke_down,
  182. pointBorderColor: gradient_stroke_down,
  183. pointBackgroundColor: gradient_stroke_down,
  184. pointHoverBackgroundColor: gradient_stroke_down,
  185. pointHoverBorderColor: gradient_stroke_down,
  186. pointHoverRadius: 2,
  187. pointRadius: 0,
  188. steppedLine: true,
  189. fill: true,
  190. backgroundColor: gradient_fill_down,
  191. borderWidth: 1,
  192. lineTension: 0,
  193. data: []
  194. },
  195. {
  196. type: 'line',
  197. label: "Sent",
  198. borderColor: gradient_stroke_bgline_up,
  199. backgroundColor: "rgba(255,255,255,0.0)",
  200. pointRadius: 0,
  201. borderWidth: 1,
  202. pointHoverRadius: 2,
  203. pointHoverBackgroundColor: gradient_stroke_bgline_up,
  204. pointHoverBorderColor: gradient_stroke_bgline_up,
  205. fill: true,
  206. yAxisID: 'Request',
  207. steppedLine: true,
  208. lineTension: 0,
  209. data: []
  210. },
  211. {
  212. type: 'line',
  213. label: "Received",
  214. borderColor: gradient_stroke_bgline_down,
  215. backgroundColor: "rgba(255,255,255,0.0)",
  216. pointRadius: 0,
  217. borderWidth: 1,
  218. pointHoverRadius: 2,
  219. pointHoverBackgroundColor: gradient_stroke_bgline_down,
  220. pointHoverBorderColor: gradient_stroke_bgline_down,
  221. fill: true,
  222. yAxisID: 'Request',
  223. steppedLine: true,
  224. lineTension: 0,
  225. data: []
  226. }
  227. ]
  228. },
  229. options: {
  230. animation: {
  231. easing: "easeOutExpo",
  232. duration: 2000
  233. },
  234. legend: {
  235. display: false,
  236. position: "top",
  237. labels: {
  238. fontColor: 'white'
  239. }
  240. },
  241. title: {
  242. display: false
  243. },
  244. tooltips: {
  245. mode: "index",
  246. intersect: false,
  247. displayColors: false,
  248. xPadding: 10,
  249. yPadding: 10,
  250. cornerRadius: 0,
  251. caretPadding: 10,
  252. bodyFontColor: "rgba(255,255,255,0.6)",
  253. callbacks: {
  254. title: (tootlip_items, data) ->
  255. Time.date(tootlip_items[0].xLabel, "long").replace(/:00$/, "")
  256. label: (tootlip_items, data) ->
  257. if data.datasets[tootlip_items.datasetIndex].yAxisID == "Request"
  258. return data.datasets[tootlip_items.datasetIndex].label+": " + Math.abs(tootlip_items.yLabel) + " requests"
  259. else
  260. return data.datasets[tootlip_items.datasetIndex].label+": " + Text.formatSize(Math.abs(tootlip_items.yLabel))
  261. }
  262. },
  263. hover: { mode: "index", intersect: false },
  264. scales: {
  265. yAxes: [{
  266. id: 'Transfer',
  267. ticks: {
  268. fontColor: "rgba(100,110,132,1)",
  269. fontStyle: "bold",
  270. beginAtZero: true,
  271. suggestedMax: 30000000,
  272. suggestedMin: -30000000,
  273. display: true,
  274. padding: 30,
  275. callback: (value) ->
  276. return Text.formatSize(Math.abs(value))
  277. },
  278. gridLines: {
  279. drawTicks: true,
  280. drawBorder: false,
  281. display: true,
  282. zeroLineColor: "rgba(255,255,255,0.1)",
  283. tickMarkLength: 20,
  284. zeroLineBorderDashOffset: 100,
  285. color: "rgba(255,255,255,0.05)"
  286. }
  287. },
  288. {
  289. id: 'Request',
  290. position: "right",
  291. ticks: {
  292. beginAtZero: true,
  293. maxTicksLimit: 5,
  294. suggestedMax: 180,
  295. suggestedMin: -180,
  296. display: false
  297. },
  298. gridLines: {
  299. display: false,
  300. zeroLineColor: "rgba(255,255,255,0)",
  301. drawBorder: false
  302. }
  303. }],
  304. xAxes: [{
  305. type: "time",
  306. gridLines: {
  307. color: "rgba(255,255,255,0.1)",
  308. display: false,
  309. offsetGridLines: true,
  310. drawBorder: false
  311. },
  312. ticks: {
  313. padding: 15,
  314. fontColor: "rgba(100,110,132,1)",
  315. fontStyle: "bold",
  316. callback: (data_label, index) =>
  317. @last_data_label ?= "None 00 00:00"
  318. if @last_data_label.match(/.* /)[0] == data_label.match(/.* /)[0]
  319. back = ["", data_label.replace(/.* /, "")]
  320. else
  321. parts = data_label.split(" ")
  322. if parts.length != 3
  323. return data_label
  324. back = [parts[0] + " " + parts[1], parts[2]]
  325. @last_data_label = data_label
  326. return back
  327. },
  328. time: {
  329. displayFormats: {
  330. 'second': 'MMM DD HH:mm',
  331. 'minute': 'MMM DD HH:mm',
  332. 'hour': 'MMM DD HH:mm',
  333. 'day': 'MMM DD HH:mm',
  334. 'week': 'MMM DD HH:mm',
  335. 'month': 'MMM DD HH:mm',
  336. 'quarter': 'MMM DD HH:mm',
  337. 'year': 'MMM DD HH:mm'
  338. }
  339. }
  340. }]
  341. }
  342. }
  343. }
  344. return configuration
  345. render: =>
  346. if @need_update
  347. @update()
  348. @need_update = false
  349. h("div.ChartBig", [
  350. h("canvas.#{Page.params.interval}", {width: 1350, height: 450, afterCreate: @storeCanvasNode, updateAnimation: Animation.show, mode: Page.params.interval})
  351. ])
  352. window.ChartBig = ChartBig