line_graph.lua 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  1. -- @author cedlemo
  2. local setmetatable = setmetatable
  3. local ipairs = ipairs
  4. local math = math
  5. local table = table
  6. local type = type
  7. local string = string
  8. local color = require("gears.color")
  9. local base = require("wibox.widget.base")
  10. local helpers = require("blingbling.helpers")
  11. local superproperties = require('blingbling.superproperties')
  12. ---Graph widget.
  13. --@module blingbling.linegraph
  14. local linegraph = { mt = {} }
  15. local data = setmetatable({}, { __mode = "k" })
  16. ---Fill all the widget (width * height) with this color (default is none ).
  17. --@usage mygraph:set_background_color(string) -->"#rrggbbaa"
  18. --@name set_background_color
  19. --@class function
  20. --@param color a string "#rrggbbaa" or "#rrggbb"
  21. ---Fill the graph area background with this color (default is none).
  22. --@usage mygraph:set_graph_background_color(string) -->"#rrggbbaa"
  23. --@name set_graph_background_color
  24. --@class function
  25. --@param color a string "#rrggbbaa" or "#rrggbb"
  26. ---Set rounded corners for background and graph background.
  27. --@usage mygraph:set_rounded_size(a) -> a in [0,1]
  28. --@name set_rounded_size
  29. --@class function
  30. --@param rounded_size float in [0,1]
  31. ---Define the top and bottom margin for the graph area.
  32. --@usage mygraph:set_v_margin(integer)
  33. --@name set_v_margin
  34. --@class function
  35. --@param margin an integer for top and bottom margin
  36. ---Define the left and right margin for the graph area.
  37. --@usage mygraph:set_h_margin(integer)
  38. --@name set_h_margin
  39. --@class function
  40. --@param margin an integer for left and right margin
  41. ---Define the graph color.
  42. --@usage mygraph:set_graph_color(string) -->"#rrggbbaa"
  43. --@name set_graph_color
  44. --@class function
  45. --@param color a string "#rrggbbaa" or "#rrggbb"
  46. ---Define the graph outline.
  47. --@usage mygraph:set_graph_line_color(string) -->"#rrggbbaa"
  48. --@name set_graph_line_color
  49. --@class function
  50. --@param color a string "#rrggbbaa" or "#rrggbb"
  51. ---Display text on the graph or not.
  52. --@usage mygraph:set_show_text(boolean) --> true or false
  53. --@name set_show_text
  54. --@class function
  55. --@param boolean true or false (default is false)
  56. ---Define displayed text value format string
  57. --@usage mygraph:set_value_format(string) --> "%2.f"
  58. --@name set_value_format
  59. --@class function
  60. --@param graph the graph
  61. --@param printf format string for display text
  62. ---Define the color of the text.
  63. --@usage mygraph:set_text_color(string) -->"#rrggbbaa"
  64. --@name set_text_color
  65. --@class function
  66. --@param color a string "#rrggbbaa" or "#rrggbb" defaul is white
  67. ---Define the background color of the text.
  68. --@usage mygraph:set_text_background_color(string) -->"#rrggbbaa"
  69. --@name set_text_background_color
  70. --@class function
  71. --@param color a string "#rrggbbaa" or "#rrggbb"
  72. ---Define the text's font .
  73. --@usage mygraph:set_font(string)
  74. --@name set_font
  75. --@class function
  76. --@param font a string that contains the font name family and weight
  77. ---Define the text font size.
  78. --@usage mygraph:set_font_size(integer)
  79. --@name set_font_size
  80. --@class function
  81. --@param size the font size
  82. ---Define the template of the text to display.
  83. --@usage mygraph:set_label(string)
  84. --By default the text is : (value_send_to_the_widget *100) .. "%"
  85. --static string: example set_label("CPU usage:") will display "CUP usage:" on the graph
  86. --dynamic string: use $percent in the string example set_label("Load $percent %") will display "Load 10%"
  87. --@name set_label
  88. --@class function
  89. --@param text the text to display
  90. local properties = { "width", "height", "h_margin", "v_margin",
  91. "background_color", "graph_background_color",
  92. "rounded_size", "graph_color", "graph_line_color",
  93. "show_text", "text_color", "font_size", "font",
  94. "text_background_color", "label", "value_format"
  95. }
  96. function linegraph.draw(graph, wibox, cr, width, height)
  97. -- Set the values we need
  98. local props = helpers.load_properties(properties, data, graph, superproperties)
  99. local line_width = 1
  100. cr:set_line_width(line_width)
  101. cr:set_antialias("subpixel")
  102. -- Draw the widget background
  103. helpers.draw_rounded_corners_rectangle( cr,
  104. 0,
  105. 0,
  106. width,
  107. height,
  108. props.background_color,
  109. props.rounded_size
  110. )
  111. -- Draw the graph background
  112. helpers.draw_rounded_corners_rectangle(cr,
  113. props.h_margin,
  114. props.v_margin,
  115. width - props.h_margin,
  116. height - props.v_margin ,
  117. props.graph_background_color,
  118. props.rounded_size,
  119. props.graph_background_border)
  120. helpers.clip_rounded_corners_rectangle(cr,
  121. props.h_margin,
  122. props.v_margin,
  123. width - props.h_margin,
  124. height - (props.v_margin),
  125. props.rounded_size
  126. )
  127. --Drawn the graph
  128. --if graph_background_border is set, graph must not be drawn on it
  129. --find nb values we can draw every column_length px
  130. --if rounded, make sure that graph don't begin or end outside background
  131. --check for the less value between hight and height to calculate the space for rounded size:
  132. local column_length = 6
  133. local less_value = height > width and width / 2 or height / 2
  134. local used_width = width - (2 * props.h_margin + 2 * (props.rounded_size * less_value))
  135. max_column = math.ceil( used_width / column_length)
  136. --Check if the table graph values is empty / not initialized
  137. --if next(data[graph].values) == nil then
  138. if #data[graph].values == 0 or #data[graph].values ~= max_column then
  139. -- initialize graph_values with empty values:
  140. data[graph].values={}
  141. for i=1,max_column do
  142. --the following line feed the graph with random value if you uncomment it and comment the line after it
  143. --data[graph].values[i]=math.random(0,100) / 100
  144. data[graph].values[i] = 0
  145. end
  146. end
  147. local values = data[graph].values
  148. --Fill the graph
  149. x = width -(props.h_margin + props.rounded_size * less_value)
  150. y = height-(props.v_margin)
  151. cr:new_path()
  152. cr:move_to(x,y)
  153. cr:line_to(x,y)
  154. for i=1,max_column do
  155. y_range = height - (2 * props.v_margin)
  156. y = height - (props.v_margin + ((values[i]) * y_range))
  157. cr:line_to(x,y)
  158. x = x-column_length
  159. end
  160. y = height - (props.v_margin )
  161. cr:line_to(x + column_length ,y)
  162. cr:line_to(width - props.h_margin,height - (props.v_margin ))
  163. cr:close_path()
  164. local r,g,b,a = helpers.hexadecimal_to_rgba_percent(props.graph_color)
  165. cr:set_source_rgba(r, g, b, a)
  166. cr:fill()
  167. --Draw the graph line
  168. x = width - (props.h_margin + props.rounded_size * less_value)
  169. y = height - (props.v_margin)
  170. cr:new_path()
  171. cr:move_to(x,y)
  172. cr:line_to(x,y)
  173. for i=1,max_column do
  174. y_range = height - (2 * props.v_margin)
  175. y = height - (props.v_margin + ((values[i]) * y_range))
  176. -- this is a trick:
  177. -- when value a equal to zero, I remove add 1 pixel to the y point in order
  178. -- to put the point outside the clip area done with helpers.clip_rounded_corners_rectangle
  179. -- so when a value is == 0 the point is almost not visible whereas the line of the graph
  180. -- is not broken.
  181. if values[i] == 0 then
  182. y = y + 1
  183. end
  184. cr:line_to(x,y)
  185. x = x - column_length
  186. end
  187. y = height - (props.v_margin - 1) -- the y point here is set outside the clip rectangle
  188. cr:line_to(x + column_length ,y)
  189. r,g,b,a = helpers.hexadecimal_to_rgba_percent(props.graph_line_color)
  190. cr:set_source_rgba(r, g, b, a)
  191. cr:stroke()
  192. if props.show_text == true then
  193. --Draw Text and it's background
  194. local font
  195. if type(props.font) == "string" then
  196. font = props.font .. " " .. props.font_size
  197. elseif type(props.font) == "table" then
  198. font = (props.font.family or "Sans") .. " " .. (props.font.slang or "normal") .. " " .. (props.font.weight or "normal") .. " " .. props.font_size
  199. end
  200. local value = string.format(props.value_format, values[1] * 100)
  201. if props.label then
  202. text=string.gsub(data[graph].label,"$percent", value)
  203. else
  204. text=value .. "%"
  205. end
  206. helpers.draw_layout_and_background(cr,
  207. text,
  208. props.h_margin + props.rounded_size * less_value,
  209. height/2 ,
  210. font,
  211. props.text_background_color,
  212. props.text_color,
  213. "start",
  214. "middle")
  215. end
  216. end
  217. function linegraph.fit(graph, width, height)
  218. return data[graph].width, data[graph].height
  219. end
  220. ---Add a value to the graph.
  221. --For compatibility between old and new awesome widget, add_value can be replaced by set_value
  222. --@usage mygraph:add_value(a) or mygraph:set_value(a)
  223. --@param graph the graph
  224. --@param value The value between 0 and 1.
  225. --@param group The stack color group index.
  226. local function add_value(graph, value, group)
  227. if not graph then return end
  228. local value = value or 0
  229. local values = data[graph].values
  230. if string.find(value, "nan") then
  231. value=0
  232. end
  233. local values = data[graph].values
  234. table.remove(values, #values)
  235. table.insert(values,1,value)
  236. graph:emit_signal("widget::updated")
  237. return graph
  238. end
  239. ---Set the graph height.
  240. --@param height The height to set.
  241. function linegraph:set_height( height)
  242. if height >= 5 then
  243. data[self].height = height
  244. self:emit_signal("widget::updated")
  245. end
  246. return self
  247. end
  248. ---Set the graph width.
  249. --@param width The width to set.
  250. function linegraph:set_width( width)
  251. if width >= 5 then
  252. data[self].width = width
  253. self:emit_signal("widget::updated")
  254. end
  255. return self
  256. end
  257. -- Build properties function
  258. for _, prop in ipairs(properties) do
  259. if not linegraph["set_" .. prop] then
  260. linegraph["set_" .. prop] = function(graph, value)
  261. data[graph][prop] = value
  262. graph:emit_signal("widget::updated")
  263. return graph
  264. end
  265. end
  266. end
  267. ---Create a graph widget.
  268. --@param args Standard widget() arguments. You should add width and height
  269. --key to set graph geometry.
  270. --@return A graph widget.
  271. function linegraph.new(args)
  272. local args = args or {}
  273. args.width = args.width or 100
  274. args.height = args.height or 20
  275. if args.width < 5 or args.height < 5 then return end
  276. local graph = base.make_widget()
  277. data[graph] = {}
  278. for _, v in ipairs(properties) do
  279. data[graph][v] = args[v]
  280. end
  281. data[graph].values = {}
  282. -- Set methods
  283. graph.set_value = add_value
  284. graph.add_value = add_value
  285. graph.draw = linegraph.draw
  286. graph.fit = linegraph.fit
  287. for _, prop in ipairs(properties) do
  288. graph["set_" .. prop] = linegraph["set_" .. prop]
  289. end
  290. return graph
  291. end
  292. function linegraph.mt:__call(...)
  293. return linegraph.new(...)
  294. end
  295. return setmetatable(linegraph, linegraph.mt)