awesome/lib/wibox/widget/graph.lua

948 lines
32 KiB
Lua
Raw Normal View History

2016-05-23 07:30:44 +02:00
---------------------------------------------------------------------------
--- Display multiple values as a stream of bars.
2016-05-23 07:30:44 +02:00
--
2016-08-16 07:43:42 +02:00
-- The graph goes from left to right. To change this to right to left, use
-- a `wibox.container.mirror` widget. This can also be used to have data
-- shown from top to bottom.
--
-- To add text on top of the graph, use a `wibox.layout.stack` and a
-- `wibox.container.align` widgets.
--
-- To display the graph vertically, use a `wibox.container.rotate` widget.
--
--@DOC_wibox_widget_defaults_graph_EXAMPLE@
2016-05-23 07:30:44 +02:00
-- @author Julien Danjou <julien@danjou.info>
-- @copyright 2009 Julien Danjou
-- @widgetmod wibox.widget.graph
-- @supermodule wibox.widget.base
2016-05-23 07:30:44 +02:00
---------------------------------------------------------------------------
local setmetatable = setmetatable
local ipairs = ipairs
local math = math
graph: complete widget overhaul and bug fixes Squashed commit of the following: commit 69821f51fe1e8652715658543e50631ece495090 Author: Alex Belykh <albel727@ngs.ru> Date: Mon May 10 19:17:55 2021 +0700 Refactor property handling in wibox.widget.graph In addition, baseline_value, step_width and step_spacing properties are all set to their default non-nil numeric values during instance creation now. commit 842eb429bf5df4b2ba46b2e6f7646afe89b1a3c3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 9 06:18:45 2021 +0700 Make graph:draw_values() private There's no documentation nor confidence that it's a good API, so I'm hiding it for now. commit 720746780574e4bad14a71fc4174955139c2eb50 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 04:04:13 2021 +0700 Document graph_should_draw_data_group() for posterity commit 7c73f3754ce6ba86b118f1170a6f9e406ab4841d Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:59:21 2021 +0700 Make graph:should_draw_data_group() private There's no confidence whether it's a good API, so I'm hiding it for now. commit b3539e20adb6423118af726ae4f8751ef864b5d2 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:36:44 2021 +0700 Make graph:preprocess_values() private There's no confidence whether it's a good API, so I'm hiding it for now. commit 2df4400e780a6260f8d0ab1c60734fbb11bf3c4f Author: Alex Belykh <albel727@ngs.ru> Date: Thu May 6 10:05:23 2021 +0700 Add spec/wibox/widget/graph_spec.lua commit ce01a9771ab439b8df841f1565aa4aa8a44a5e0d Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 08:16:18 2021 +0700 Improve graph.capacity documentation commit e28fade8f655802f5bf89154169f68d256b8e332 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 07:36:01 2021 +0700 Document graph:compute_drawn_values_num() commit dd68332292465422d08b48e9f0229f143beccbc6 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 05:24:23 2021 +0700 Document graph:pick_data_group_color() method commit ca6dc55f77aca88a6e9cc90bb6fd0045739f41f5 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 04:13:58 2021 +0700 Guard against setting data groups sparsely in graph:add_value() commit cdea5f126f7984e72924f6dffbd312c4fe3b032f Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 02:36:12 2021 +0700 Refactor scaling and baseline choice out of graph:draw() commit 10135deafe8a9390ba66ab657ebb1fb41d549e8c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 21:34:48 2021 +0700 Document graph.nan_color and graph.stack properties commit 0d73bb84ac66b87e16aee472bd1f299549f66a1c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 20:49:24 2021 +0700 Use a better example in docs for the graph.stack property commit c27d160a0826d033d49d6141608c40aa8a0642fa Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 16:05:38 2021 +0700 Document graph.baseline_value property commit 459bc8176c13d4ea32af0dcdc2399d3b929b1c6a Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:53:47 2021 +0700 Document graph.clamp_bars property commit e732ee0b3b007d213e689b98cc00ae7cb141ae08 Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:34 2021 +0700 Improve graph:add_value() and :clear() documentation commit ae73e3aa9f8d60409cbe4cb4da9612f31af4f38e Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:11 2021 +0700 Improve graph min_value/max_value/scale documentation commit a7350996a9def773b91b1631e6c0507e33d8f93b Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:27:36 2021 +0700 Cleanup graph examples to show only relevant things in docs commit 30740c26f4f41c16f4c8a3c339bcb7bd299e14cd Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 14:04:36 2021 +0700 Rename graph.stack_colors property to `group_colors` The colors are used for stacked and non-stacked graphs alike, so the name didn't make sense. Also document and clarify some color stuff. commit 1ea01bfd410971cf0d0c25fd74cd764d5eb7279d Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 21:25:41 2021 +0700 Add an example file for code coverage of some fringe graph cases commit eae68e39553f88a6275a0c65bf040b5d4c262b54 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 20:23:32 2021 +0700 Add step_shape to the graph/nan_color example for code coverage commit 8027147bc2eab34a97b49a428fb8cef27f122bed Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:55:18 2021 +0700 Add example files for graph.nan_color/nan_indication properties commit 89a25a165c52b2c15b6c4d95ecb777a9067e66f9 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:33:07 2021 +0700 Make widget graph:add_value(v) default v to NaN 0 as a default makes no sense and silences the error that is failing to specify a value, to which the user should rather be alerted. The graph widget has sane NaN handling now and will do just that. commit 2835d552440be48d356a7f7b1d64963696dd550b Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:20:48 2021 +0700 Implement graph.nan_color and nan_indication properties Now, whenever there's a NaN among values and graph.nan_indication is set, the corresponding area, where the value bar should have been drawn, is filled with the nan_color from top to bottom. The painting is done after all data is rendered, to make sure that it won't be overpainted and go unnoticed. NaNs among graph's values are inevitably a sign of some error and it is arguably not a sane default behavior to simply not render them at all. The user should take immediate note of any errors by default, instead of wondering at a mysteriously empty graph widget, which is why nan_indication is set to true by default. If the user wants to silence NaNs, they can always simply set it to false. nan_color if unset, defaults to a noticeable yellow-black pattern. As a direct and planned consequence, stacked graphs with negative values in them trigger the NaN handling too. But silencing NaN handling for those graphs is also possible and semi-sane, because then NaNs and negatives are ignored and the graph behaves (almost) like they were zeros. commit 9c1f8f08b400e01e19c534f3810c9c9ab485760c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 26 08:10:19 2021 +0700 Refactor out graph_map_value_to_widget_coordinates() function commit 1905991ddecabdae3e15ffb8ee5b18a1bee1a711 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 28 13:39:29 2021 +0700 Add an example file for graph.baseline_value property commit b31b39d66f4c037a9484b71a3f910703ae01fac0 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 07:03:49 2021 +0700 Add an example file for graph.clamp_bars=false/true comparison commit a1fc850e3047f9ee2c9ddeffacfbabf3ded34171 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 04:06:45 2021 +0700 Add an example file for graphs with negative values and shapes commit 2f012f8ff1bb5094c14e724a61e59a2a3a296bc8 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 01:06:05 2021 +0700 Add an example file for a graph displaying negative values commit 7b14fbd59a4c4c51962fd3110a1a900dd5f4eb98 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 11:18:26 2021 +0700 Add an example file for graphs with step_width < 1 commit dfd9a54544b637e2fbe3d1325f9cc24b3f4d2313 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 10:17:38 2021 +0700 Add an example file for graph.stack=false/true comparison Should also demonstrate that multiple data groups are possible for non-stacked graphs too. commit 5b532106c3fd255f9b66a66cfb88d977b489ab5d Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 09:34:14 2021 +0700 Add an example file for disabling graph data groups with nil colors commit ac6f1083c02fcb428f089a616f9b0ebaa5496b6c Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:57:25 2021 +0700 Add an example file for step_shape/width/spacing on stacked graphs commit b3703db6a5139e73e72c1cfd198ece2cd9be7df2 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:43:56 2021 +0700 Add an example file for scale=false/true on stacked graph commit b000aed4a12cb44fb501e1c8432a71a14b2f8f3f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 20 08:22:19 2021 +0700 Document deprecation of graph's width and height commit cb0ad617bd6c83a499896797318e3b0b588c3566 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 07:53:37 2021 +0700 Deprecate width and height properties of wibox.widget.graph commit 8c64e14ad91a074b784166a7054e53444cccb9d2 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 04:37:59 2021 +0700 Refactor graph:draw() to separate concerns Firstly, summation of group values for stacked graphs is extracted into graph:preprocess_values(). This makes graph:draw() smaller and exposes the fact, that stacked graphs are almost entirely just a form of a more general concept: pre-render data manipulation. It's something a user might want to customize, and now they can do it by monkey-patching the method. Secondly, the logic for deciding, if and with which color a data group should be drawn, is extracted into graph:should_draw_data_group() and get_data_group_color() methods respectively. This makes it customizable too and makes more obvious the fact, that it is a general thing that has nothing to do with stacked graphs in particular. With this there are only 5 lines remaining in graph:draw(), that could be said to be specific to stacked graphs, all pertaining to the `prev_y` variable, which merely preserves the bar coordinates from a previous data group. commit 2a32b6305d893f413303728a0a3da1dcd9a5591e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 22:52:37 2021 +0700 Pointlessly optimize graph:add_value() 1) Purge old values first, then insert the new one, if needed. Since Lua grows arrays in power of two steps, and our guess_capacity() also takes care to return even integers, it would be a shame to overstep the capacity by one by inserting first and thus getting the table size doubled only to never use the most of it. 2) Use a removal procedure that doesn't call the slow length operator at all. Previous code called it twice (once in table.remove) for every removed value. commit 680ea8f5d81e9040afb4662b3229e937e4b89e02 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:23:38 2021 +0700 Set graph data series color before the painting loop commit b970f0f221ae76250f2a94433ff79d653caa6e21 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:05:37 2021 +0700 Drop a large no longer necessary `if` in graph:draw_values() This commit is almost purely a whitespace change. commit 6b4f0541c70ec99d7f6e276bae64939f337120d6 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:00:50 2021 +0700 Extract values rendering code into graph:draw_values() method This is to avoid ungodly if nesting levels, which are needed to bail out of drawing values early and skip to drawing the border. The next commit will get rid of the if. commit e0950e4f99d1ea1c4b8cea2cc67629fa40030c2a Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 14:50:20 2021 +0700 Track graph draw() usage to guess necessary array capacity The last step of divorcing add_values() from width calculations, while ensuring that user doesn't observe a lack of data to draw. The widget now tracks an approximate number of values that it has been asked to draw() in _private.last_drawn_values_num, and truncates its values array based on that (if not overridden by the capacity property). commit 2e3d542282f35e7c0fed454da0f2eada0fcf3b78 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 13:35:12 2021 +0700 Add `capacity` property to graph widget This is the second step of divorcing add_values() from calculations involving widths. How many values are stored and how many values are drawn are distinct concepts. There isn't even a single definitive "how many values are drawn" because the widget could be rendered in several places with distinct widths simultaneously, and in every such place the data must be drawn appropriately, e.g. with auto-scaling, that takes only the visible data points into account. And the draw() method can do that now. The default capacity value of nil uses heuristics based on widget's _private.width to guess some capacity that would tolerate some dynamic widget resizing. If the user finds the heuristic failing them, they can set the capacity property to the desired maximum number of values that will be kept in the _private.values array (in each data group). commit 08d4a80686f7fe363551c35589a460839f60853e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 03:27:18 2021 +0700 Extract values size computation into compute_drawn_values_num() This is the first step of making add_values() not concern itself (directly) with widget dimensions. The user can monkey-patch the method to override how many values are (logically) drawn, because it affects graph auto-scaling and can be useful for rendering of step_shape-s that paint outside of their step_width. I didn't make it a full-blown function-valued property, because it seems a fringe functionality, which I'm too lazy to document. commit 3c5617c98ab1147618ed22e8414a77fc56b45f6e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 02:32:29 2021 +0700 Copy graph widget prototype methods over in bulk commit 169538837ece41a03d02ba8fbc0e4097062b67a3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun Apr 25 05:58:01 2021 +0700 Remove graph.baseline_y property After giving it some thought I've concluded, that it is unlikely to be useful as it is now. I'd better reserve the name for some more sensible functionality, like fixing the baseline_value defined axis at the given widget coordinate, adjusting the scaling accordingly, so min_value/max_value won't generally correspond to widget top and bottom anymore. commit 202ffaa9a0b820c7c471e1121d01b91f8f76cd38 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 12:06:55 2021 +0700 Add baseline_y and baseline_value properties to graph widget Now values in graph have a *baseline*, tunable with baseline_y and baseline_value properties. It is the (for now invisible) horizontal line from which all bars in the graph grow. I call it a "baseline" instead of "axis", because it doesn't actually have to correspond to the 0 value. But it can, and actually does now by default. baseline_y and baseline_value properties determine the vertical placement of the baseline in the widget. baseline_y is the position of the baseline in normalized widget coordinates, e.g. 0 corresponds to widget's top, 1 - to widget's bottom, and 0.5 - to widget's middle. So if e.g. one sets baseline_y = 0, then no matter how the widget is resized and how its values are scaled, bars in it will be drawn starting from the top. baseline_value is the position of the baseline in value coordinates. So if one sets baseline_value = 10, then no matter what happens, bars for values greater than 10 will be drawn as growing up from this line, and bars for values smaller as growing down. Where and if it will be seen in the widget, depends on the scaling. baseline_value = 0 is the familiar zero axis. baseline_y is unset by default and has priority, i.e. if it's set, then baseline_value is ignored. baseline_value defaults to 0. Prior to this commit, bars were always drawn from the bottom edge of the widget, or, in the new terms, the widget had baseline_y = 1. But it was indistinguishable from baseline_value = 0, because the widget clamped all negative values to 0, so the difference between the two could never be observed. baseline_value = 0 is clearly more useful and familiar to people, so now it is in force by default. commit df7e19abaebf0a871b5ca217b4cc3365533267a6 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 11:18:12 2021 +0700 Move value clamping from graph:add_value() into draw() Previously all values were forcibly clamped between 0 (not tunable at all) and max_value (1 by default). Thus graphs with negative values were not an option. NaN values were conflated with zeros, even though it may be useful to maintain the distinction and render them differently. And if user happened to dynamically change the max_value property, they discovered, that their data got truncated. Preserving user data as it was given is more flexible and makes things easier to debug. This commit does exactly that. So what happens now to the newly allowed values during draw()? There are 3 new cases to consider: 1) NaNs 2) Negatives 3) Values outside of min_value..max_value range NaNs are presently simply not drawn. An empty place is reserved where the value should've been, but nothing else is done. This is distinct from how a zero is handled, e.g. if step_shape is set, it is called with the height parameter = 0, which can still end up drawing something, depending on the shape. One could also consider addition of nan_color/nan_shape properties which could be drawn to alert user to a gap in their data. Negative values necessitate introduction of the concept of the zero axis, from which they will be drawn DOWN, in contrast to positive values. Where should this axis be drawn? How should step_shape-s with inherent direction, e.g. arrows, be drawn for negatives? This will be addressed in the next commit. Values outside of min/max_value range can imply shapes that go beyond widget dimensions. An arrow shape that goes beyond the roof is visibly different from an arrow that saturates and merely touches the widget top, and the user may reasonably desire either behavior. Therefore this commit introduces a boolean clamp_bars property, which defaults to true for reasons of robustness and backward compatibility with the behaviour prior to this commit. The property of course affects only the display of the values, the actual user data stays intact. commit c9c24fb1ab741cd6bb8074971d568e0646c51532 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:29:01 2021 +0700 Use rectangles for drawing graph bars of all sizes I have benchmarked various bar drawing strategies and graph sizes and found out that drawing graph bars with rectangles is as fast or even marginally faster than using fat lines. Thus the code that tries to special-case 1px bars as lines seems to be not worth the additional complexity it introduces, and this commit removes it. Incidentally, I've also established, that rectangles are more robust. If one draws graph bars and feeds cairo very large coordinates (~2^62), then corresponding rectangles fail to render, but all the other rectangles in the same series get rendered successfully, whereas a single large coordinate in a move_to/line_to() sequence is enough to cause all lines in the batch to fail to render. commit bc7f4be5e06405b5aa7fb443a5ace2b9b9575b1f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:01:42 2021 +0700 Cache cairo methods used in the inner loop of graph:draw() I have benchmarked the draw() method, both on cairo xcb and on argb32 image surfaces, and have discovered that cairo context method calls are unnaturally slow, to the point, that almost nothing else in the function matters. For example, drawing bars with vertical lines made the whole draw() function almost 2 times slower than doing the same with rectangles, merely because the former requires two method calls (move_to/line_to), and the latter only one. Simply caching hotpath cairo methods in local variables, reduced the difference between the drawing methods to a negligible one and made the whole function 4 times faster on a typical 100-bar graph, (1.4ms to 0.33ms on my laptop) thus bringing closer my secret dream of drawing live audio data with awesome graphs (kidding). I guess, LGI does something not very optimal there, like gobject-introspecting the method every time, or something. There might be some untapped optimization potential, possibly in upstream, like a method cache, from which everything in awesome could benefit, even though I imagine not many things are so call-intensive as the graph widget, to really feel the difference. commit 538df25bdf02b53d0bbe81809eef2e6fdb05ff6f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 07:32:37 2021 +0700 Use 1-based index in the inner graph draw loop It's used there only once for a calculation and then several times to index some tables, and index 0 is bad for lua tables performance-wise. commit 7412546916cd0a0a490130a120255ffb719398b8 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:55:12 2021 +0700 Leave graph.max_value uninitialized at construction This is a user-visible change, but the previous behavior was probably not what user expected anyway, namely with graph.scale set the graph mysteriously couldn't zoom in onto values that are smaller than 1. commit eb81d7929a161e79c13262cfa73ac2e6a820e8ed Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:48:29 2021 +0700 Allow graph.max_value be nil This makes its behave symmetrically to min_value. Now it defaults to 1, when graph.scale is not set, and to the maximum value in the graph data, when graph.scale is set. commit f622a8430cbfde7007b58d7f4c9c76b08c91e997 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 12:01:13 2021 +0700 Calculate max values num in graph:add_value() more precisely At most so many values were kept in the widget values array, as there are pixels of usable widget width (i.e. sans border). This is exactly right for the surely most common case of graphs with 1px-wide bars, but is more than needed for graphs with step_width+step_spacing>1, and not enough for graphs with subpixel bars. Moreover this made graph autoscaling behave weirdly in graphs with thicker bars, because old values survived longer in the array and kept affecting min_value/max_value calculations even after they were shifted off-screen. The maximum number of kept values is now calculated exactly as the number of values that can be displayed at once, taking step_width and step_spacing into account. It might be worth adding a max_values_num property to the graph to let user override this calculation, in case they draw shapes in step_shape() outside of its supposed rectangle, so our assumptions of visibility are wrong, or the widget often gets resized, resulting in the annoyance of visibly truncated series. commit 0aa63249a18641251a1bf77423739164f0eab39f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 02:30:13 2021 +0700 Fix border_color/border_width bugs in graph widget add_value() wrongly assumed that border_width is always set, when border_color is, so if user set only the latter, an error occurred. add_value() assumed that border_width is always < width which led to awesome hanging up in an infinite loop, should that fail to be the case. The graph border_width property was driven by the border_color property, instead of the other way round. If border_color wasn't set, the border wasn't drawn all, even if user sets non-zero border_width, which is a confusing behavior, in contrast to all other widgets, which simply use a plethora of fallback colors to draw the border, when needed. This also meant that, despite being documented, the beautiful.graph_border_color fallback color could not and was not ever used. If the border_color was set though, an unset border_width was immediately assumed to be 1 instead of 0, also contrary to what other widgets do. Theme colors shouldn't influence widget layout, so this commit fixes this behavior to: 1) border_width always defaults to 0, if unset. 2) border_color falls back to beautiful.graph_border_color or white. Maybe one should also add a beautiful.graph_border_width theme variable, like some widgets do. commit dad4a3253b8fef971fb82720d2a407fcc0a879ad Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 03:11:27 2021 +0700 Implement multiple data series drawing for non-stacked graph The widget can already hold and draw multiple data groups, so it makes little sense to limit drawing only to the first group when graph.stack = false, probably leading to user's confusion. Now user can call add_value(v, group) with group > 1 and the data series will be drawn (over groups with lesser id-s) with graph.stack_colors[group] color, if stack_colors is set, or with graph.color, if stack_colors isn't set. Thus nothing visibly changes for all current users of non-stacked graphs with only single data series, but the new functionality is now there for those who need it. One can't fully emulate this functionality, e.g. with multiple graphs widgets in a wibox.layout.stack, because data series in a single widget can enjoy a common auto-scaling. commit 3e645c976184ab5f50d625271c25062db9ab7ed9 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:59:12 2021 +0700 Don't draw a stacked graph data group if there's no color for it If stack_colors[idx] is nil for the group `idx`, don't draw it at all (as opposed, e.g. to the effect of drawing it with a transparent color, or as though it had all values == 0, which both produce different results). This code is still subtly different from prior behavior, because now user can have a non-contiguous stack_colors table and turn the display of any data group on and off at will, whereas the code two commits earlier would just stop rendering as soon as ipairs(stack_colors) does, i.e. no data series were drawn past the first nil hole. This commit only looks large, but is mostly a whitespace change due to nesting from a pair of wrapping ifs. commit 8c0273786de1ade66bdd977699f98b588da9f487 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:32:42 2021 +0700 Unify stacked and non-stacked graph rendering Now stacked graphs support everything the nonstacked do, like step_width ~= 1 and custom step_shape-s. Technical notes: This code no longer calls cr:stroke() for every single value in a stacked graph, but rather paints a whole series and then cr:stroke/fill()-s it at all once with its color, improving performance considerably. Additionally the graph no longer relies on the presence of the stack_colors property to render series. It draws all data that it has, and, if there's no corresponding color in stack_colors, falls back to graph.color. This will be changed in the next commit to the prior behavior of only drawing those series for which there's a color in the stack_colors table, but that would've made this commit's diff much larger due to required nesting ifs over large code chunks. This commit also takes care to properly handle NaNs and negative values. Those are currently (almost) guaranteed to not occur in the values array due to clipping in graph:add_value(), but I intend to eventually drop the clipping, because it does nothing that draw() really relies upon, and allowing unclipped and NaN values is more flexible and less surprising, e.g. when user dynamically toggles graph.scale. Eventually I intend to implement drawing negative values in non-stacked graphs and possibly even in the stacked ones. commit 38c6589c51b2aa057d7c2f40182ecf32b5b2ff5e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 10:04:15 2021 +0700 Convert graph:draw() to use `self` commit f21f1b2550426df1c4ea8abde079b1ccef84b394 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 12:27:26 2021 +0700 Fix scaling for stacked graphs Stacked graphs have cumulative heights, values from all groups that end up in the same graph bar are effectively summed. Previous code didn't take that into account when calculating min/max_value for the purposes of rescaling, but simply examined every value separately. This code adds up all values up front and finds min/max among those, to determine proper scaling that would fit the graph exactly. commit 1ce4c7bf63bdd6c99c195d668347bec49355e921 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 09:49:35 2021 +0700 Draw shaped graph bars only up to the baseline Prior to this commit, every step_shape bar was, for some reason, drawn with height = full widget's height, regardless of the value it represents. So for the most bars their lower end was far outside the box and therefore clipped off. So e.g. one couldn't see the lower round edges of rounded rectangles. This commit fixes that by appropriately varying the height that is passed into step_shape(), so that lower edges of the bars always exactly touch the lower edge of the graph. If the user wants to restore the prior behavior, they can simply ignore the passed-in height in step_shape() and just always draw their shape with height = full widget height. But they couldn't replicate the effect of this commit in their rc.lua without essentially writing their own graph widget. commit e25185b08da23745b5815f85f6e9e43222ed2294 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 08:42:54 2021 +0700 Avoid incremental transform shifting during graph bar rendering Instead of letting addition errors and cognitive load accumulate simply use the same offset calculation that is used for lines and reset the transform back to the top-left corner after every drawn bar. It's conceptually simpler, and will be useful later for stacked graphs and more. It also fixes a discrepancy between plain and step_shape rendering. Prior to this commit, bar coloring occurred with a disturbed transform, which was centered at the last drawn bar, wherever it happened to be. This meant that nontrivial colors, like gradients and images, were arbitrarily offset when step_shape was used. commit f3e98866d8583c35d0b557c09f529299edc9ba2c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:45:29 2021 +0700 Refactor out graph bar height expression into a variable commit f54528d6892f5aaa4595917853c9e2130f743fdb Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:20:25 2021 +0700 Draw graph bars with lines only when step_width == 1 Before this commit graph bars of step_width < 1 were drawn with lines of width 1, but that's a bit unfortunate, because it led to much overpainting, while still ending up with a graphically incorrect result. Let's draw such bars with rectangles and let cairo do things with subpixels, getting more graphically accurate result, while still keeping the common case of step_width == 1 (supposedly) fast-drawn with lines. commit b6515dca67c18b78eb5657378131a62ab43d4cfc Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:54:07 2021 +0700 Fix unsharp edges when drawing graph bars with rectangles The 0.5 offset is only helpful for 1-width strokes, not for fills. commit 7f3db8f07e82f821bcd1dc418d66564f537609d6 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:21:01 2021 +0700 Do coordinate transformations even if the value isn't drawn The graph presently doesn't draw values < 0, but there was a discrepancy between drawing with and without step_shape. The default line/rectangle drawing reserves an empty place where the skipped value should've been, but no such empty place is reserved, when step_shape is used. This commit fixes that. Skipped values always have their empty space now. commit 7b731ebfe96af7a9dba9c57371da6761cf22c00c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:03:30 2021 +0700 Fix graph bar scaling when min_value is not 0 Previous calculation lead to underscaling of values so that even value = max_value wasn't drawn to full height. commit dca59fd8b7fff0e7e646f2dfc6bd92047028e5a8 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:18:40 2021 +0700 Calculate the horizontal graph bar offset only when needed commit 940ddf4b9825e9c3a91d276208c089d8c74e49db Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:17:23 2021 +0700 Call cr:move_to() only when drawing the graph with lines It's redundant otherwise and might even interfere with user's drawing in step_shape(). commit b5d5cd10ca29fb29cd751faaa95137c2fe89ff63 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:04:06 2021 +0700 Fix wrong horizontal offset calculations in widget.graph Indices in the loop are zero-based, so things like i-1 and i>1 aren't necessary and actually wouldn't have been correct even in a 1-based loop. step_shape = gears.shape.rectangle and step_shape = nil, which is supposed to default to rectangle according to docs, were drawn differently due to mismatched offset calculations, which led to problems especially noticeable when step_spacing is not zero, e.g: 1) whole graph shifted left too much, drawn partially off-screen. 2) whole graph shifted right too much, and the first bar placed closer to the rest than others. commit dc369fa198281e000afc1bd17326f3a264adf7d1 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 02:09:21 2021 +0700 Fix the "border_width affects graph bar width" bug Graph bars were drawn with lines of border_width thickness instead of 1, i.e. possibly too fat to be readable. commit 1ba73378cf29ed27a6ecd18716394779ceb543d9 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 00:13:03 2021 +0700 Simplify internal values representation in graph even more New values in graph._private.values were pushed to the end, and old values were removed from the beginning of the table. In draw() however newer values are drawn first, at the left edge of the graph, necessitating annoying idx = #values - idx conversions everywhere. In particular in stacked graph that means that values that are drawn together may have different indices. One has to shift the values array in add_value() either way, so one might as well insert new values at the beginning of the array and simplify the rest of the code. This doesn't seem like much yet, but it will prove to be much more convenient and less error-prone in what I'm going to do later. commit f70523ee68dbd6b67050ace4b82903a9b22440c7 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 13:35:10 2021 +0700 Simplify internal values representation in graph widget graph._private.values was either a table of values or a table of tables of values, depending on whether "stack" property is enabled, ... or a mix of both if user forgot to pass the second "group" parameter to graph:add_value(). This resulted in pretty fragile and confusing behavior, e.g. leaving user to wonder, why they see nothing more after setting stack=true for a graph in a previously working rc.lua, and demanding a very careful use of add_value() from the user, or else some values or even entire stacks would mysteriously vanish and errors in draw() get triggered, sometimes after a period of seemingly normal functioning. Now `values` is always a table of tables and the group parameter in add_value() defaults to 1, thus making the non-stacked graph a trivial variant of the stacked graph.
2021-05-20 17:14:03 +02:00
local math_max = math.max
local math_min = math.min
2016-05-23 07:30:44 +02:00
local table = table
local color = require("gears.color")
graph: complete widget overhaul and bug fixes Squashed commit of the following: commit 69821f51fe1e8652715658543e50631ece495090 Author: Alex Belykh <albel727@ngs.ru> Date: Mon May 10 19:17:55 2021 +0700 Refactor property handling in wibox.widget.graph In addition, baseline_value, step_width and step_spacing properties are all set to their default non-nil numeric values during instance creation now. commit 842eb429bf5df4b2ba46b2e6f7646afe89b1a3c3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 9 06:18:45 2021 +0700 Make graph:draw_values() private There's no documentation nor confidence that it's a good API, so I'm hiding it for now. commit 720746780574e4bad14a71fc4174955139c2eb50 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 04:04:13 2021 +0700 Document graph_should_draw_data_group() for posterity commit 7c73f3754ce6ba86b118f1170a6f9e406ab4841d Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:59:21 2021 +0700 Make graph:should_draw_data_group() private There's no confidence whether it's a good API, so I'm hiding it for now. commit b3539e20adb6423118af726ae4f8751ef864b5d2 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:36:44 2021 +0700 Make graph:preprocess_values() private There's no confidence whether it's a good API, so I'm hiding it for now. commit 2df4400e780a6260f8d0ab1c60734fbb11bf3c4f Author: Alex Belykh <albel727@ngs.ru> Date: Thu May 6 10:05:23 2021 +0700 Add spec/wibox/widget/graph_spec.lua commit ce01a9771ab439b8df841f1565aa4aa8a44a5e0d Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 08:16:18 2021 +0700 Improve graph.capacity documentation commit e28fade8f655802f5bf89154169f68d256b8e332 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 07:36:01 2021 +0700 Document graph:compute_drawn_values_num() commit dd68332292465422d08b48e9f0229f143beccbc6 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 05:24:23 2021 +0700 Document graph:pick_data_group_color() method commit ca6dc55f77aca88a6e9cc90bb6fd0045739f41f5 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 04:13:58 2021 +0700 Guard against setting data groups sparsely in graph:add_value() commit cdea5f126f7984e72924f6dffbd312c4fe3b032f Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 02:36:12 2021 +0700 Refactor scaling and baseline choice out of graph:draw() commit 10135deafe8a9390ba66ab657ebb1fb41d549e8c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 21:34:48 2021 +0700 Document graph.nan_color and graph.stack properties commit 0d73bb84ac66b87e16aee472bd1f299549f66a1c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 20:49:24 2021 +0700 Use a better example in docs for the graph.stack property commit c27d160a0826d033d49d6141608c40aa8a0642fa Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 16:05:38 2021 +0700 Document graph.baseline_value property commit 459bc8176c13d4ea32af0dcdc2399d3b929b1c6a Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:53:47 2021 +0700 Document graph.clamp_bars property commit e732ee0b3b007d213e689b98cc00ae7cb141ae08 Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:34 2021 +0700 Improve graph:add_value() and :clear() documentation commit ae73e3aa9f8d60409cbe4cb4da9612f31af4f38e Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:11 2021 +0700 Improve graph min_value/max_value/scale documentation commit a7350996a9def773b91b1631e6c0507e33d8f93b Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:27:36 2021 +0700 Cleanup graph examples to show only relevant things in docs commit 30740c26f4f41c16f4c8a3c339bcb7bd299e14cd Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 14:04:36 2021 +0700 Rename graph.stack_colors property to `group_colors` The colors are used for stacked and non-stacked graphs alike, so the name didn't make sense. Also document and clarify some color stuff. commit 1ea01bfd410971cf0d0c25fd74cd764d5eb7279d Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 21:25:41 2021 +0700 Add an example file for code coverage of some fringe graph cases commit eae68e39553f88a6275a0c65bf040b5d4c262b54 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 20:23:32 2021 +0700 Add step_shape to the graph/nan_color example for code coverage commit 8027147bc2eab34a97b49a428fb8cef27f122bed Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:55:18 2021 +0700 Add example files for graph.nan_color/nan_indication properties commit 89a25a165c52b2c15b6c4d95ecb777a9067e66f9 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:33:07 2021 +0700 Make widget graph:add_value(v) default v to NaN 0 as a default makes no sense and silences the error that is failing to specify a value, to which the user should rather be alerted. The graph widget has sane NaN handling now and will do just that. commit 2835d552440be48d356a7f7b1d64963696dd550b Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:20:48 2021 +0700 Implement graph.nan_color and nan_indication properties Now, whenever there's a NaN among values and graph.nan_indication is set, the corresponding area, where the value bar should have been drawn, is filled with the nan_color from top to bottom. The painting is done after all data is rendered, to make sure that it won't be overpainted and go unnoticed. NaNs among graph's values are inevitably a sign of some error and it is arguably not a sane default behavior to simply not render them at all. The user should take immediate note of any errors by default, instead of wondering at a mysteriously empty graph widget, which is why nan_indication is set to true by default. If the user wants to silence NaNs, they can always simply set it to false. nan_color if unset, defaults to a noticeable yellow-black pattern. As a direct and planned consequence, stacked graphs with negative values in them trigger the NaN handling too. But silencing NaN handling for those graphs is also possible and semi-sane, because then NaNs and negatives are ignored and the graph behaves (almost) like they were zeros. commit 9c1f8f08b400e01e19c534f3810c9c9ab485760c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 26 08:10:19 2021 +0700 Refactor out graph_map_value_to_widget_coordinates() function commit 1905991ddecabdae3e15ffb8ee5b18a1bee1a711 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 28 13:39:29 2021 +0700 Add an example file for graph.baseline_value property commit b31b39d66f4c037a9484b71a3f910703ae01fac0 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 07:03:49 2021 +0700 Add an example file for graph.clamp_bars=false/true comparison commit a1fc850e3047f9ee2c9ddeffacfbabf3ded34171 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 04:06:45 2021 +0700 Add an example file for graphs with negative values and shapes commit 2f012f8ff1bb5094c14e724a61e59a2a3a296bc8 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 01:06:05 2021 +0700 Add an example file for a graph displaying negative values commit 7b14fbd59a4c4c51962fd3110a1a900dd5f4eb98 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 11:18:26 2021 +0700 Add an example file for graphs with step_width < 1 commit dfd9a54544b637e2fbe3d1325f9cc24b3f4d2313 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 10:17:38 2021 +0700 Add an example file for graph.stack=false/true comparison Should also demonstrate that multiple data groups are possible for non-stacked graphs too. commit 5b532106c3fd255f9b66a66cfb88d977b489ab5d Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 09:34:14 2021 +0700 Add an example file for disabling graph data groups with nil colors commit ac6f1083c02fcb428f089a616f9b0ebaa5496b6c Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:57:25 2021 +0700 Add an example file for step_shape/width/spacing on stacked graphs commit b3703db6a5139e73e72c1cfd198ece2cd9be7df2 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:43:56 2021 +0700 Add an example file for scale=false/true on stacked graph commit b000aed4a12cb44fb501e1c8432a71a14b2f8f3f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 20 08:22:19 2021 +0700 Document deprecation of graph's width and height commit cb0ad617bd6c83a499896797318e3b0b588c3566 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 07:53:37 2021 +0700 Deprecate width and height properties of wibox.widget.graph commit 8c64e14ad91a074b784166a7054e53444cccb9d2 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 04:37:59 2021 +0700 Refactor graph:draw() to separate concerns Firstly, summation of group values for stacked graphs is extracted into graph:preprocess_values(). This makes graph:draw() smaller and exposes the fact, that stacked graphs are almost entirely just a form of a more general concept: pre-render data manipulation. It's something a user might want to customize, and now they can do it by monkey-patching the method. Secondly, the logic for deciding, if and with which color a data group should be drawn, is extracted into graph:should_draw_data_group() and get_data_group_color() methods respectively. This makes it customizable too and makes more obvious the fact, that it is a general thing that has nothing to do with stacked graphs in particular. With this there are only 5 lines remaining in graph:draw(), that could be said to be specific to stacked graphs, all pertaining to the `prev_y` variable, which merely preserves the bar coordinates from a previous data group. commit 2a32b6305d893f413303728a0a3da1dcd9a5591e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 22:52:37 2021 +0700 Pointlessly optimize graph:add_value() 1) Purge old values first, then insert the new one, if needed. Since Lua grows arrays in power of two steps, and our guess_capacity() also takes care to return even integers, it would be a shame to overstep the capacity by one by inserting first and thus getting the table size doubled only to never use the most of it. 2) Use a removal procedure that doesn't call the slow length operator at all. Previous code called it twice (once in table.remove) for every removed value. commit 680ea8f5d81e9040afb4662b3229e937e4b89e02 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:23:38 2021 +0700 Set graph data series color before the painting loop commit b970f0f221ae76250f2a94433ff79d653caa6e21 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:05:37 2021 +0700 Drop a large no longer necessary `if` in graph:draw_values() This commit is almost purely a whitespace change. commit 6b4f0541c70ec99d7f6e276bae64939f337120d6 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:00:50 2021 +0700 Extract values rendering code into graph:draw_values() method This is to avoid ungodly if nesting levels, which are needed to bail out of drawing values early and skip to drawing the border. The next commit will get rid of the if. commit e0950e4f99d1ea1c4b8cea2cc67629fa40030c2a Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 14:50:20 2021 +0700 Track graph draw() usage to guess necessary array capacity The last step of divorcing add_values() from width calculations, while ensuring that user doesn't observe a lack of data to draw. The widget now tracks an approximate number of values that it has been asked to draw() in _private.last_drawn_values_num, and truncates its values array based on that (if not overridden by the capacity property). commit 2e3d542282f35e7c0fed454da0f2eada0fcf3b78 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 13:35:12 2021 +0700 Add `capacity` property to graph widget This is the second step of divorcing add_values() from calculations involving widths. How many values are stored and how many values are drawn are distinct concepts. There isn't even a single definitive "how many values are drawn" because the widget could be rendered in several places with distinct widths simultaneously, and in every such place the data must be drawn appropriately, e.g. with auto-scaling, that takes only the visible data points into account. And the draw() method can do that now. The default capacity value of nil uses heuristics based on widget's _private.width to guess some capacity that would tolerate some dynamic widget resizing. If the user finds the heuristic failing them, they can set the capacity property to the desired maximum number of values that will be kept in the _private.values array (in each data group). commit 08d4a80686f7fe363551c35589a460839f60853e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 03:27:18 2021 +0700 Extract values size computation into compute_drawn_values_num() This is the first step of making add_values() not concern itself (directly) with widget dimensions. The user can monkey-patch the method to override how many values are (logically) drawn, because it affects graph auto-scaling and can be useful for rendering of step_shape-s that paint outside of their step_width. I didn't make it a full-blown function-valued property, because it seems a fringe functionality, which I'm too lazy to document. commit 3c5617c98ab1147618ed22e8414a77fc56b45f6e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 02:32:29 2021 +0700 Copy graph widget prototype methods over in bulk commit 169538837ece41a03d02ba8fbc0e4097062b67a3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun Apr 25 05:58:01 2021 +0700 Remove graph.baseline_y property After giving it some thought I've concluded, that it is unlikely to be useful as it is now. I'd better reserve the name for some more sensible functionality, like fixing the baseline_value defined axis at the given widget coordinate, adjusting the scaling accordingly, so min_value/max_value won't generally correspond to widget top and bottom anymore. commit 202ffaa9a0b820c7c471e1121d01b91f8f76cd38 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 12:06:55 2021 +0700 Add baseline_y and baseline_value properties to graph widget Now values in graph have a *baseline*, tunable with baseline_y and baseline_value properties. It is the (for now invisible) horizontal line from which all bars in the graph grow. I call it a "baseline" instead of "axis", because it doesn't actually have to correspond to the 0 value. But it can, and actually does now by default. baseline_y and baseline_value properties determine the vertical placement of the baseline in the widget. baseline_y is the position of the baseline in normalized widget coordinates, e.g. 0 corresponds to widget's top, 1 - to widget's bottom, and 0.5 - to widget's middle. So if e.g. one sets baseline_y = 0, then no matter how the widget is resized and how its values are scaled, bars in it will be drawn starting from the top. baseline_value is the position of the baseline in value coordinates. So if one sets baseline_value = 10, then no matter what happens, bars for values greater than 10 will be drawn as growing up from this line, and bars for values smaller as growing down. Where and if it will be seen in the widget, depends on the scaling. baseline_value = 0 is the familiar zero axis. baseline_y is unset by default and has priority, i.e. if it's set, then baseline_value is ignored. baseline_value defaults to 0. Prior to this commit, bars were always drawn from the bottom edge of the widget, or, in the new terms, the widget had baseline_y = 1. But it was indistinguishable from baseline_value = 0, because the widget clamped all negative values to 0, so the difference between the two could never be observed. baseline_value = 0 is clearly more useful and familiar to people, so now it is in force by default. commit df7e19abaebf0a871b5ca217b4cc3365533267a6 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 11:18:12 2021 +0700 Move value clamping from graph:add_value() into draw() Previously all values were forcibly clamped between 0 (not tunable at all) and max_value (1 by default). Thus graphs with negative values were not an option. NaN values were conflated with zeros, even though it may be useful to maintain the distinction and render them differently. And if user happened to dynamically change the max_value property, they discovered, that their data got truncated. Preserving user data as it was given is more flexible and makes things easier to debug. This commit does exactly that. So what happens now to the newly allowed values during draw()? There are 3 new cases to consider: 1) NaNs 2) Negatives 3) Values outside of min_value..max_value range NaNs are presently simply not drawn. An empty place is reserved where the value should've been, but nothing else is done. This is distinct from how a zero is handled, e.g. if step_shape is set, it is called with the height parameter = 0, which can still end up drawing something, depending on the shape. One could also consider addition of nan_color/nan_shape properties which could be drawn to alert user to a gap in their data. Negative values necessitate introduction of the concept of the zero axis, from which they will be drawn DOWN, in contrast to positive values. Where should this axis be drawn? How should step_shape-s with inherent direction, e.g. arrows, be drawn for negatives? This will be addressed in the next commit. Values outside of min/max_value range can imply shapes that go beyond widget dimensions. An arrow shape that goes beyond the roof is visibly different from an arrow that saturates and merely touches the widget top, and the user may reasonably desire either behavior. Therefore this commit introduces a boolean clamp_bars property, which defaults to true for reasons of robustness and backward compatibility with the behaviour prior to this commit. The property of course affects only the display of the values, the actual user data stays intact. commit c9c24fb1ab741cd6bb8074971d568e0646c51532 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:29:01 2021 +0700 Use rectangles for drawing graph bars of all sizes I have benchmarked various bar drawing strategies and graph sizes and found out that drawing graph bars with rectangles is as fast or even marginally faster than using fat lines. Thus the code that tries to special-case 1px bars as lines seems to be not worth the additional complexity it introduces, and this commit removes it. Incidentally, I've also established, that rectangles are more robust. If one draws graph bars and feeds cairo very large coordinates (~2^62), then corresponding rectangles fail to render, but all the other rectangles in the same series get rendered successfully, whereas a single large coordinate in a move_to/line_to() sequence is enough to cause all lines in the batch to fail to render. commit bc7f4be5e06405b5aa7fb443a5ace2b9b9575b1f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:01:42 2021 +0700 Cache cairo methods used in the inner loop of graph:draw() I have benchmarked the draw() method, both on cairo xcb and on argb32 image surfaces, and have discovered that cairo context method calls are unnaturally slow, to the point, that almost nothing else in the function matters. For example, drawing bars with vertical lines made the whole draw() function almost 2 times slower than doing the same with rectangles, merely because the former requires two method calls (move_to/line_to), and the latter only one. Simply caching hotpath cairo methods in local variables, reduced the difference between the drawing methods to a negligible one and made the whole function 4 times faster on a typical 100-bar graph, (1.4ms to 0.33ms on my laptop) thus bringing closer my secret dream of drawing live audio data with awesome graphs (kidding). I guess, LGI does something not very optimal there, like gobject-introspecting the method every time, or something. There might be some untapped optimization potential, possibly in upstream, like a method cache, from which everything in awesome could benefit, even though I imagine not many things are so call-intensive as the graph widget, to really feel the difference. commit 538df25bdf02b53d0bbe81809eef2e6fdb05ff6f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 07:32:37 2021 +0700 Use 1-based index in the inner graph draw loop It's used there only once for a calculation and then several times to index some tables, and index 0 is bad for lua tables performance-wise. commit 7412546916cd0a0a490130a120255ffb719398b8 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:55:12 2021 +0700 Leave graph.max_value uninitialized at construction This is a user-visible change, but the previous behavior was probably not what user expected anyway, namely with graph.scale set the graph mysteriously couldn't zoom in onto values that are smaller than 1. commit eb81d7929a161e79c13262cfa73ac2e6a820e8ed Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:48:29 2021 +0700 Allow graph.max_value be nil This makes its behave symmetrically to min_value. Now it defaults to 1, when graph.scale is not set, and to the maximum value in the graph data, when graph.scale is set. commit f622a8430cbfde7007b58d7f4c9c76b08c91e997 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 12:01:13 2021 +0700 Calculate max values num in graph:add_value() more precisely At most so many values were kept in the widget values array, as there are pixels of usable widget width (i.e. sans border). This is exactly right for the surely most common case of graphs with 1px-wide bars, but is more than needed for graphs with step_width+step_spacing>1, and not enough for graphs with subpixel bars. Moreover this made graph autoscaling behave weirdly in graphs with thicker bars, because old values survived longer in the array and kept affecting min_value/max_value calculations even after they were shifted off-screen. The maximum number of kept values is now calculated exactly as the number of values that can be displayed at once, taking step_width and step_spacing into account. It might be worth adding a max_values_num property to the graph to let user override this calculation, in case they draw shapes in step_shape() outside of its supposed rectangle, so our assumptions of visibility are wrong, or the widget often gets resized, resulting in the annoyance of visibly truncated series. commit 0aa63249a18641251a1bf77423739164f0eab39f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 02:30:13 2021 +0700 Fix border_color/border_width bugs in graph widget add_value() wrongly assumed that border_width is always set, when border_color is, so if user set only the latter, an error occurred. add_value() assumed that border_width is always < width which led to awesome hanging up in an infinite loop, should that fail to be the case. The graph border_width property was driven by the border_color property, instead of the other way round. If border_color wasn't set, the border wasn't drawn all, even if user sets non-zero border_width, which is a confusing behavior, in contrast to all other widgets, which simply use a plethora of fallback colors to draw the border, when needed. This also meant that, despite being documented, the beautiful.graph_border_color fallback color could not and was not ever used. If the border_color was set though, an unset border_width was immediately assumed to be 1 instead of 0, also contrary to what other widgets do. Theme colors shouldn't influence widget layout, so this commit fixes this behavior to: 1) border_width always defaults to 0, if unset. 2) border_color falls back to beautiful.graph_border_color or white. Maybe one should also add a beautiful.graph_border_width theme variable, like some widgets do. commit dad4a3253b8fef971fb82720d2a407fcc0a879ad Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 03:11:27 2021 +0700 Implement multiple data series drawing for non-stacked graph The widget can already hold and draw multiple data groups, so it makes little sense to limit drawing only to the first group when graph.stack = false, probably leading to user's confusion. Now user can call add_value(v, group) with group > 1 and the data series will be drawn (over groups with lesser id-s) with graph.stack_colors[group] color, if stack_colors is set, or with graph.color, if stack_colors isn't set. Thus nothing visibly changes for all current users of non-stacked graphs with only single data series, but the new functionality is now there for those who need it. One can't fully emulate this functionality, e.g. with multiple graphs widgets in a wibox.layout.stack, because data series in a single widget can enjoy a common auto-scaling. commit 3e645c976184ab5f50d625271c25062db9ab7ed9 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:59:12 2021 +0700 Don't draw a stacked graph data group if there's no color for it If stack_colors[idx] is nil for the group `idx`, don't draw it at all (as opposed, e.g. to the effect of drawing it with a transparent color, or as though it had all values == 0, which both produce different results). This code is still subtly different from prior behavior, because now user can have a non-contiguous stack_colors table and turn the display of any data group on and off at will, whereas the code two commits earlier would just stop rendering as soon as ipairs(stack_colors) does, i.e. no data series were drawn past the first nil hole. This commit only looks large, but is mostly a whitespace change due to nesting from a pair of wrapping ifs. commit 8c0273786de1ade66bdd977699f98b588da9f487 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:32:42 2021 +0700 Unify stacked and non-stacked graph rendering Now stacked graphs support everything the nonstacked do, like step_width ~= 1 and custom step_shape-s. Technical notes: This code no longer calls cr:stroke() for every single value in a stacked graph, but rather paints a whole series and then cr:stroke/fill()-s it at all once with its color, improving performance considerably. Additionally the graph no longer relies on the presence of the stack_colors property to render series. It draws all data that it has, and, if there's no corresponding color in stack_colors, falls back to graph.color. This will be changed in the next commit to the prior behavior of only drawing those series for which there's a color in the stack_colors table, but that would've made this commit's diff much larger due to required nesting ifs over large code chunks. This commit also takes care to properly handle NaNs and negative values. Those are currently (almost) guaranteed to not occur in the values array due to clipping in graph:add_value(), but I intend to eventually drop the clipping, because it does nothing that draw() really relies upon, and allowing unclipped and NaN values is more flexible and less surprising, e.g. when user dynamically toggles graph.scale. Eventually I intend to implement drawing negative values in non-stacked graphs and possibly even in the stacked ones. commit 38c6589c51b2aa057d7c2f40182ecf32b5b2ff5e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 10:04:15 2021 +0700 Convert graph:draw() to use `self` commit f21f1b2550426df1c4ea8abde079b1ccef84b394 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 12:27:26 2021 +0700 Fix scaling for stacked graphs Stacked graphs have cumulative heights, values from all groups that end up in the same graph bar are effectively summed. Previous code didn't take that into account when calculating min/max_value for the purposes of rescaling, but simply examined every value separately. This code adds up all values up front and finds min/max among those, to determine proper scaling that would fit the graph exactly. commit 1ce4c7bf63bdd6c99c195d668347bec49355e921 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 09:49:35 2021 +0700 Draw shaped graph bars only up to the baseline Prior to this commit, every step_shape bar was, for some reason, drawn with height = full widget's height, regardless of the value it represents. So for the most bars their lower end was far outside the box and therefore clipped off. So e.g. one couldn't see the lower round edges of rounded rectangles. This commit fixes that by appropriately varying the height that is passed into step_shape(), so that lower edges of the bars always exactly touch the lower edge of the graph. If the user wants to restore the prior behavior, they can simply ignore the passed-in height in step_shape() and just always draw their shape with height = full widget height. But they couldn't replicate the effect of this commit in their rc.lua without essentially writing their own graph widget. commit e25185b08da23745b5815f85f6e9e43222ed2294 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 08:42:54 2021 +0700 Avoid incremental transform shifting during graph bar rendering Instead of letting addition errors and cognitive load accumulate simply use the same offset calculation that is used for lines and reset the transform back to the top-left corner after every drawn bar. It's conceptually simpler, and will be useful later for stacked graphs and more. It also fixes a discrepancy between plain and step_shape rendering. Prior to this commit, bar coloring occurred with a disturbed transform, which was centered at the last drawn bar, wherever it happened to be. This meant that nontrivial colors, like gradients and images, were arbitrarily offset when step_shape was used. commit f3e98866d8583c35d0b557c09f529299edc9ba2c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:45:29 2021 +0700 Refactor out graph bar height expression into a variable commit f54528d6892f5aaa4595917853c9e2130f743fdb Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:20:25 2021 +0700 Draw graph bars with lines only when step_width == 1 Before this commit graph bars of step_width < 1 were drawn with lines of width 1, but that's a bit unfortunate, because it led to much overpainting, while still ending up with a graphically incorrect result. Let's draw such bars with rectangles and let cairo do things with subpixels, getting more graphically accurate result, while still keeping the common case of step_width == 1 (supposedly) fast-drawn with lines. commit b6515dca67c18b78eb5657378131a62ab43d4cfc Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:54:07 2021 +0700 Fix unsharp edges when drawing graph bars with rectangles The 0.5 offset is only helpful for 1-width strokes, not for fills. commit 7f3db8f07e82f821bcd1dc418d66564f537609d6 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:21:01 2021 +0700 Do coordinate transformations even if the value isn't drawn The graph presently doesn't draw values < 0, but there was a discrepancy between drawing with and without step_shape. The default line/rectangle drawing reserves an empty place where the skipped value should've been, but no such empty place is reserved, when step_shape is used. This commit fixes that. Skipped values always have their empty space now. commit 7b731ebfe96af7a9dba9c57371da6761cf22c00c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:03:30 2021 +0700 Fix graph bar scaling when min_value is not 0 Previous calculation lead to underscaling of values so that even value = max_value wasn't drawn to full height. commit dca59fd8b7fff0e7e646f2dfc6bd92047028e5a8 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:18:40 2021 +0700 Calculate the horizontal graph bar offset only when needed commit 940ddf4b9825e9c3a91d276208c089d8c74e49db Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:17:23 2021 +0700 Call cr:move_to() only when drawing the graph with lines It's redundant otherwise and might even interfere with user's drawing in step_shape(). commit b5d5cd10ca29fb29cd751faaa95137c2fe89ff63 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:04:06 2021 +0700 Fix wrong horizontal offset calculations in widget.graph Indices in the loop are zero-based, so things like i-1 and i>1 aren't necessary and actually wouldn't have been correct even in a 1-based loop. step_shape = gears.shape.rectangle and step_shape = nil, which is supposed to default to rectangle according to docs, were drawn differently due to mismatched offset calculations, which led to problems especially noticeable when step_spacing is not zero, e.g: 1) whole graph shifted left too much, drawn partially off-screen. 2) whole graph shifted right too much, and the first bar placed closer to the rest than others. commit dc369fa198281e000afc1bd17326f3a264adf7d1 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 02:09:21 2021 +0700 Fix the "border_width affects graph bar width" bug Graph bars were drawn with lines of border_width thickness instead of 1, i.e. possibly too fat to be readable. commit 1ba73378cf29ed27a6ecd18716394779ceb543d9 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 00:13:03 2021 +0700 Simplify internal values representation in graph even more New values in graph._private.values were pushed to the end, and old values were removed from the beginning of the table. In draw() however newer values are drawn first, at the left edge of the graph, necessitating annoying idx = #values - idx conversions everywhere. In particular in stacked graph that means that values that are drawn together may have different indices. One has to shift the values array in add_value() either way, so one might as well insert new values at the beginning of the array and simplify the rest of the code. This doesn't seem like much yet, but it will prove to be much more convenient and less error-prone in what I'm going to do later. commit f70523ee68dbd6b67050ace4b82903a9b22440c7 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 13:35:10 2021 +0700 Simplify internal values representation in graph widget graph._private.values was either a table of values or a table of tables of values, depending on whether "stack" property is enabled, ... or a mix of both if user forgot to pass the second "group" parameter to graph:add_value(). This resulted in pretty fragile and confusing behavior, e.g. leaving user to wonder, why they see nothing more after setting stack=true for a graph in a previously working rc.lua, and demanding a very careful use of add_value() from the user, or else some values or even entire stacks would mysteriously vanish and errors in draw() get triggered, sometimes after a period of seemingly normal functioning. Now `values` is always a table of tables and the group parameter in add_value() defaults to 1, thus making the non-stacked graph a trivial variant of the stacked graph.
2021-05-20 17:14:03 +02:00
local gdebug = require("gears.debug")
local gtable = require("gears.table")
2016-05-23 07:30:44 +02:00
local base = require("wibox.widget.base")
2016-05-27 07:23:12 +02:00
local beautiful = require("beautiful")
2016-05-23 07:30:44 +02:00
local graph = { mt = {} }
2021-03-28 11:33:38 +02:00
--- Set the graph border_width.
--
--@DOC_wibox_widget_graph_border_width_EXAMPLE@
--
-- @property border_width
-- @tparam[opt=0] number border_width
-- @propertyunit pixel
-- @negativeallowed false
2021-03-28 11:33:38 +02:00
-- @propemits true false
-- @see border_color
2016-05-23 07:30:44 +02:00
--- Set the graph border color.
--
2021-03-28 11:33:38 +02:00
--@DOC_wibox_widget_graph_border_color_EXAMPLE@
--
-- @property border_color
-- @tparam color|nil border_color The border color to set.
-- @propbeautiful
-- @propemits true false
-- @see gears.color
2016-05-23 07:30:44 +02:00
--- Set the graph foreground color.
--
graph: complete widget overhaul and bug fixes Squashed commit of the following: commit 69821f51fe1e8652715658543e50631ece495090 Author: Alex Belykh <albel727@ngs.ru> Date: Mon May 10 19:17:55 2021 +0700 Refactor property handling in wibox.widget.graph In addition, baseline_value, step_width and step_spacing properties are all set to their default non-nil numeric values during instance creation now. commit 842eb429bf5df4b2ba46b2e6f7646afe89b1a3c3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 9 06:18:45 2021 +0700 Make graph:draw_values() private There's no documentation nor confidence that it's a good API, so I'm hiding it for now. commit 720746780574e4bad14a71fc4174955139c2eb50 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 04:04:13 2021 +0700 Document graph_should_draw_data_group() for posterity commit 7c73f3754ce6ba86b118f1170a6f9e406ab4841d Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:59:21 2021 +0700 Make graph:should_draw_data_group() private There's no confidence whether it's a good API, so I'm hiding it for now. commit b3539e20adb6423118af726ae4f8751ef864b5d2 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:36:44 2021 +0700 Make graph:preprocess_values() private There's no confidence whether it's a good API, so I'm hiding it for now. commit 2df4400e780a6260f8d0ab1c60734fbb11bf3c4f Author: Alex Belykh <albel727@ngs.ru> Date: Thu May 6 10:05:23 2021 +0700 Add spec/wibox/widget/graph_spec.lua commit ce01a9771ab439b8df841f1565aa4aa8a44a5e0d Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 08:16:18 2021 +0700 Improve graph.capacity documentation commit e28fade8f655802f5bf89154169f68d256b8e332 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 07:36:01 2021 +0700 Document graph:compute_drawn_values_num() commit dd68332292465422d08b48e9f0229f143beccbc6 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 05:24:23 2021 +0700 Document graph:pick_data_group_color() method commit ca6dc55f77aca88a6e9cc90bb6fd0045739f41f5 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 04:13:58 2021 +0700 Guard against setting data groups sparsely in graph:add_value() commit cdea5f126f7984e72924f6dffbd312c4fe3b032f Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 02:36:12 2021 +0700 Refactor scaling and baseline choice out of graph:draw() commit 10135deafe8a9390ba66ab657ebb1fb41d549e8c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 21:34:48 2021 +0700 Document graph.nan_color and graph.stack properties commit 0d73bb84ac66b87e16aee472bd1f299549f66a1c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 20:49:24 2021 +0700 Use a better example in docs for the graph.stack property commit c27d160a0826d033d49d6141608c40aa8a0642fa Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 16:05:38 2021 +0700 Document graph.baseline_value property commit 459bc8176c13d4ea32af0dcdc2399d3b929b1c6a Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:53:47 2021 +0700 Document graph.clamp_bars property commit e732ee0b3b007d213e689b98cc00ae7cb141ae08 Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:34 2021 +0700 Improve graph:add_value() and :clear() documentation commit ae73e3aa9f8d60409cbe4cb4da9612f31af4f38e Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:11 2021 +0700 Improve graph min_value/max_value/scale documentation commit a7350996a9def773b91b1631e6c0507e33d8f93b Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:27:36 2021 +0700 Cleanup graph examples to show only relevant things in docs commit 30740c26f4f41c16f4c8a3c339bcb7bd299e14cd Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 14:04:36 2021 +0700 Rename graph.stack_colors property to `group_colors` The colors are used for stacked and non-stacked graphs alike, so the name didn't make sense. Also document and clarify some color stuff. commit 1ea01bfd410971cf0d0c25fd74cd764d5eb7279d Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 21:25:41 2021 +0700 Add an example file for code coverage of some fringe graph cases commit eae68e39553f88a6275a0c65bf040b5d4c262b54 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 20:23:32 2021 +0700 Add step_shape to the graph/nan_color example for code coverage commit 8027147bc2eab34a97b49a428fb8cef27f122bed Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:55:18 2021 +0700 Add example files for graph.nan_color/nan_indication properties commit 89a25a165c52b2c15b6c4d95ecb777a9067e66f9 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:33:07 2021 +0700 Make widget graph:add_value(v) default v to NaN 0 as a default makes no sense and silences the error that is failing to specify a value, to which the user should rather be alerted. The graph widget has sane NaN handling now and will do just that. commit 2835d552440be48d356a7f7b1d64963696dd550b Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:20:48 2021 +0700 Implement graph.nan_color and nan_indication properties Now, whenever there's a NaN among values and graph.nan_indication is set, the corresponding area, where the value bar should have been drawn, is filled with the nan_color from top to bottom. The painting is done after all data is rendered, to make sure that it won't be overpainted and go unnoticed. NaNs among graph's values are inevitably a sign of some error and it is arguably not a sane default behavior to simply not render them at all. The user should take immediate note of any errors by default, instead of wondering at a mysteriously empty graph widget, which is why nan_indication is set to true by default. If the user wants to silence NaNs, they can always simply set it to false. nan_color if unset, defaults to a noticeable yellow-black pattern. As a direct and planned consequence, stacked graphs with negative values in them trigger the NaN handling too. But silencing NaN handling for those graphs is also possible and semi-sane, because then NaNs and negatives are ignored and the graph behaves (almost) like they were zeros. commit 9c1f8f08b400e01e19c534f3810c9c9ab485760c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 26 08:10:19 2021 +0700 Refactor out graph_map_value_to_widget_coordinates() function commit 1905991ddecabdae3e15ffb8ee5b18a1bee1a711 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 28 13:39:29 2021 +0700 Add an example file for graph.baseline_value property commit b31b39d66f4c037a9484b71a3f910703ae01fac0 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 07:03:49 2021 +0700 Add an example file for graph.clamp_bars=false/true comparison commit a1fc850e3047f9ee2c9ddeffacfbabf3ded34171 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 04:06:45 2021 +0700 Add an example file for graphs with negative values and shapes commit 2f012f8ff1bb5094c14e724a61e59a2a3a296bc8 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 01:06:05 2021 +0700 Add an example file for a graph displaying negative values commit 7b14fbd59a4c4c51962fd3110a1a900dd5f4eb98 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 11:18:26 2021 +0700 Add an example file for graphs with step_width < 1 commit dfd9a54544b637e2fbe3d1325f9cc24b3f4d2313 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 10:17:38 2021 +0700 Add an example file for graph.stack=false/true comparison Should also demonstrate that multiple data groups are possible for non-stacked graphs too. commit 5b532106c3fd255f9b66a66cfb88d977b489ab5d Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 09:34:14 2021 +0700 Add an example file for disabling graph data groups with nil colors commit ac6f1083c02fcb428f089a616f9b0ebaa5496b6c Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:57:25 2021 +0700 Add an example file for step_shape/width/spacing on stacked graphs commit b3703db6a5139e73e72c1cfd198ece2cd9be7df2 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:43:56 2021 +0700 Add an example file for scale=false/true on stacked graph commit b000aed4a12cb44fb501e1c8432a71a14b2f8f3f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 20 08:22:19 2021 +0700 Document deprecation of graph's width and height commit cb0ad617bd6c83a499896797318e3b0b588c3566 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 07:53:37 2021 +0700 Deprecate width and height properties of wibox.widget.graph commit 8c64e14ad91a074b784166a7054e53444cccb9d2 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 04:37:59 2021 +0700 Refactor graph:draw() to separate concerns Firstly, summation of group values for stacked graphs is extracted into graph:preprocess_values(). This makes graph:draw() smaller and exposes the fact, that stacked graphs are almost entirely just a form of a more general concept: pre-render data manipulation. It's something a user might want to customize, and now they can do it by monkey-patching the method. Secondly, the logic for deciding, if and with which color a data group should be drawn, is extracted into graph:should_draw_data_group() and get_data_group_color() methods respectively. This makes it customizable too and makes more obvious the fact, that it is a general thing that has nothing to do with stacked graphs in particular. With this there are only 5 lines remaining in graph:draw(), that could be said to be specific to stacked graphs, all pertaining to the `prev_y` variable, which merely preserves the bar coordinates from a previous data group. commit 2a32b6305d893f413303728a0a3da1dcd9a5591e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 22:52:37 2021 +0700 Pointlessly optimize graph:add_value() 1) Purge old values first, then insert the new one, if needed. Since Lua grows arrays in power of two steps, and our guess_capacity() also takes care to return even integers, it would be a shame to overstep the capacity by one by inserting first and thus getting the table size doubled only to never use the most of it. 2) Use a removal procedure that doesn't call the slow length operator at all. Previous code called it twice (once in table.remove) for every removed value. commit 680ea8f5d81e9040afb4662b3229e937e4b89e02 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:23:38 2021 +0700 Set graph data series color before the painting loop commit b970f0f221ae76250f2a94433ff79d653caa6e21 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:05:37 2021 +0700 Drop a large no longer necessary `if` in graph:draw_values() This commit is almost purely a whitespace change. commit 6b4f0541c70ec99d7f6e276bae64939f337120d6 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:00:50 2021 +0700 Extract values rendering code into graph:draw_values() method This is to avoid ungodly if nesting levels, which are needed to bail out of drawing values early and skip to drawing the border. The next commit will get rid of the if. commit e0950e4f99d1ea1c4b8cea2cc67629fa40030c2a Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 14:50:20 2021 +0700 Track graph draw() usage to guess necessary array capacity The last step of divorcing add_values() from width calculations, while ensuring that user doesn't observe a lack of data to draw. The widget now tracks an approximate number of values that it has been asked to draw() in _private.last_drawn_values_num, and truncates its values array based on that (if not overridden by the capacity property). commit 2e3d542282f35e7c0fed454da0f2eada0fcf3b78 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 13:35:12 2021 +0700 Add `capacity` property to graph widget This is the second step of divorcing add_values() from calculations involving widths. How many values are stored and how many values are drawn are distinct concepts. There isn't even a single definitive "how many values are drawn" because the widget could be rendered in several places with distinct widths simultaneously, and in every such place the data must be drawn appropriately, e.g. with auto-scaling, that takes only the visible data points into account. And the draw() method can do that now. The default capacity value of nil uses heuristics based on widget's _private.width to guess some capacity that would tolerate some dynamic widget resizing. If the user finds the heuristic failing them, they can set the capacity property to the desired maximum number of values that will be kept in the _private.values array (in each data group). commit 08d4a80686f7fe363551c35589a460839f60853e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 03:27:18 2021 +0700 Extract values size computation into compute_drawn_values_num() This is the first step of making add_values() not concern itself (directly) with widget dimensions. The user can monkey-patch the method to override how many values are (logically) drawn, because it affects graph auto-scaling and can be useful for rendering of step_shape-s that paint outside of their step_width. I didn't make it a full-blown function-valued property, because it seems a fringe functionality, which I'm too lazy to document. commit 3c5617c98ab1147618ed22e8414a77fc56b45f6e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 02:32:29 2021 +0700 Copy graph widget prototype methods over in bulk commit 169538837ece41a03d02ba8fbc0e4097062b67a3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun Apr 25 05:58:01 2021 +0700 Remove graph.baseline_y property After giving it some thought I've concluded, that it is unlikely to be useful as it is now. I'd better reserve the name for some more sensible functionality, like fixing the baseline_value defined axis at the given widget coordinate, adjusting the scaling accordingly, so min_value/max_value won't generally correspond to widget top and bottom anymore. commit 202ffaa9a0b820c7c471e1121d01b91f8f76cd38 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 12:06:55 2021 +0700 Add baseline_y and baseline_value properties to graph widget Now values in graph have a *baseline*, tunable with baseline_y and baseline_value properties. It is the (for now invisible) horizontal line from which all bars in the graph grow. I call it a "baseline" instead of "axis", because it doesn't actually have to correspond to the 0 value. But it can, and actually does now by default. baseline_y and baseline_value properties determine the vertical placement of the baseline in the widget. baseline_y is the position of the baseline in normalized widget coordinates, e.g. 0 corresponds to widget's top, 1 - to widget's bottom, and 0.5 - to widget's middle. So if e.g. one sets baseline_y = 0, then no matter how the widget is resized and how its values are scaled, bars in it will be drawn starting from the top. baseline_value is the position of the baseline in value coordinates. So if one sets baseline_value = 10, then no matter what happens, bars for values greater than 10 will be drawn as growing up from this line, and bars for values smaller as growing down. Where and if it will be seen in the widget, depends on the scaling. baseline_value = 0 is the familiar zero axis. baseline_y is unset by default and has priority, i.e. if it's set, then baseline_value is ignored. baseline_value defaults to 0. Prior to this commit, bars were always drawn from the bottom edge of the widget, or, in the new terms, the widget had baseline_y = 1. But it was indistinguishable from baseline_value = 0, because the widget clamped all negative values to 0, so the difference between the two could never be observed. baseline_value = 0 is clearly more useful and familiar to people, so now it is in force by default. commit df7e19abaebf0a871b5ca217b4cc3365533267a6 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 11:18:12 2021 +0700 Move value clamping from graph:add_value() into draw() Previously all values were forcibly clamped between 0 (not tunable at all) and max_value (1 by default). Thus graphs with negative values were not an option. NaN values were conflated with zeros, even though it may be useful to maintain the distinction and render them differently. And if user happened to dynamically change the max_value property, they discovered, that their data got truncated. Preserving user data as it was given is more flexible and makes things easier to debug. This commit does exactly that. So what happens now to the newly allowed values during draw()? There are 3 new cases to consider: 1) NaNs 2) Negatives 3) Values outside of min_value..max_value range NaNs are presently simply not drawn. An empty place is reserved where the value should've been, but nothing else is done. This is distinct from how a zero is handled, e.g. if step_shape is set, it is called with the height parameter = 0, which can still end up drawing something, depending on the shape. One could also consider addition of nan_color/nan_shape properties which could be drawn to alert user to a gap in their data. Negative values necessitate introduction of the concept of the zero axis, from which they will be drawn DOWN, in contrast to positive values. Where should this axis be drawn? How should step_shape-s with inherent direction, e.g. arrows, be drawn for negatives? This will be addressed in the next commit. Values outside of min/max_value range can imply shapes that go beyond widget dimensions. An arrow shape that goes beyond the roof is visibly different from an arrow that saturates and merely touches the widget top, and the user may reasonably desire either behavior. Therefore this commit introduces a boolean clamp_bars property, which defaults to true for reasons of robustness and backward compatibility with the behaviour prior to this commit. The property of course affects only the display of the values, the actual user data stays intact. commit c9c24fb1ab741cd6bb8074971d568e0646c51532 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:29:01 2021 +0700 Use rectangles for drawing graph bars of all sizes I have benchmarked various bar drawing strategies and graph sizes and found out that drawing graph bars with rectangles is as fast or even marginally faster than using fat lines. Thus the code that tries to special-case 1px bars as lines seems to be not worth the additional complexity it introduces, and this commit removes it. Incidentally, I've also established, that rectangles are more robust. If one draws graph bars and feeds cairo very large coordinates (~2^62), then corresponding rectangles fail to render, but all the other rectangles in the same series get rendered successfully, whereas a single large coordinate in a move_to/line_to() sequence is enough to cause all lines in the batch to fail to render. commit bc7f4be5e06405b5aa7fb443a5ace2b9b9575b1f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:01:42 2021 +0700 Cache cairo methods used in the inner loop of graph:draw() I have benchmarked the draw() method, both on cairo xcb and on argb32 image surfaces, and have discovered that cairo context method calls are unnaturally slow, to the point, that almost nothing else in the function matters. For example, drawing bars with vertical lines made the whole draw() function almost 2 times slower than doing the same with rectangles, merely because the former requires two method calls (move_to/line_to), and the latter only one. Simply caching hotpath cairo methods in local variables, reduced the difference between the drawing methods to a negligible one and made the whole function 4 times faster on a typical 100-bar graph, (1.4ms to 0.33ms on my laptop) thus bringing closer my secret dream of drawing live audio data with awesome graphs (kidding). I guess, LGI does something not very optimal there, like gobject-introspecting the method every time, or something. There might be some untapped optimization potential, possibly in upstream, like a method cache, from which everything in awesome could benefit, even though I imagine not many things are so call-intensive as the graph widget, to really feel the difference. commit 538df25bdf02b53d0bbe81809eef2e6fdb05ff6f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 07:32:37 2021 +0700 Use 1-based index in the inner graph draw loop It's used there only once for a calculation and then several times to index some tables, and index 0 is bad for lua tables performance-wise. commit 7412546916cd0a0a490130a120255ffb719398b8 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:55:12 2021 +0700 Leave graph.max_value uninitialized at construction This is a user-visible change, but the previous behavior was probably not what user expected anyway, namely with graph.scale set the graph mysteriously couldn't zoom in onto values that are smaller than 1. commit eb81d7929a161e79c13262cfa73ac2e6a820e8ed Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:48:29 2021 +0700 Allow graph.max_value be nil This makes its behave symmetrically to min_value. Now it defaults to 1, when graph.scale is not set, and to the maximum value in the graph data, when graph.scale is set. commit f622a8430cbfde7007b58d7f4c9c76b08c91e997 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 12:01:13 2021 +0700 Calculate max values num in graph:add_value() more precisely At most so many values were kept in the widget values array, as there are pixels of usable widget width (i.e. sans border). This is exactly right for the surely most common case of graphs with 1px-wide bars, but is more than needed for graphs with step_width+step_spacing>1, and not enough for graphs with subpixel bars. Moreover this made graph autoscaling behave weirdly in graphs with thicker bars, because old values survived longer in the array and kept affecting min_value/max_value calculations even after they were shifted off-screen. The maximum number of kept values is now calculated exactly as the number of values that can be displayed at once, taking step_width and step_spacing into account. It might be worth adding a max_values_num property to the graph to let user override this calculation, in case they draw shapes in step_shape() outside of its supposed rectangle, so our assumptions of visibility are wrong, or the widget often gets resized, resulting in the annoyance of visibly truncated series. commit 0aa63249a18641251a1bf77423739164f0eab39f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 02:30:13 2021 +0700 Fix border_color/border_width bugs in graph widget add_value() wrongly assumed that border_width is always set, when border_color is, so if user set only the latter, an error occurred. add_value() assumed that border_width is always < width which led to awesome hanging up in an infinite loop, should that fail to be the case. The graph border_width property was driven by the border_color property, instead of the other way round. If border_color wasn't set, the border wasn't drawn all, even if user sets non-zero border_width, which is a confusing behavior, in contrast to all other widgets, which simply use a plethora of fallback colors to draw the border, when needed. This also meant that, despite being documented, the beautiful.graph_border_color fallback color could not and was not ever used. If the border_color was set though, an unset border_width was immediately assumed to be 1 instead of 0, also contrary to what other widgets do. Theme colors shouldn't influence widget layout, so this commit fixes this behavior to: 1) border_width always defaults to 0, if unset. 2) border_color falls back to beautiful.graph_border_color or white. Maybe one should also add a beautiful.graph_border_width theme variable, like some widgets do. commit dad4a3253b8fef971fb82720d2a407fcc0a879ad Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 03:11:27 2021 +0700 Implement multiple data series drawing for non-stacked graph The widget can already hold and draw multiple data groups, so it makes little sense to limit drawing only to the first group when graph.stack = false, probably leading to user's confusion. Now user can call add_value(v, group) with group > 1 and the data series will be drawn (over groups with lesser id-s) with graph.stack_colors[group] color, if stack_colors is set, or with graph.color, if stack_colors isn't set. Thus nothing visibly changes for all current users of non-stacked graphs with only single data series, but the new functionality is now there for those who need it. One can't fully emulate this functionality, e.g. with multiple graphs widgets in a wibox.layout.stack, because data series in a single widget can enjoy a common auto-scaling. commit 3e645c976184ab5f50d625271c25062db9ab7ed9 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:59:12 2021 +0700 Don't draw a stacked graph data group if there's no color for it If stack_colors[idx] is nil for the group `idx`, don't draw it at all (as opposed, e.g. to the effect of drawing it with a transparent color, or as though it had all values == 0, which both produce different results). This code is still subtly different from prior behavior, because now user can have a non-contiguous stack_colors table and turn the display of any data group on and off at will, whereas the code two commits earlier would just stop rendering as soon as ipairs(stack_colors) does, i.e. no data series were drawn past the first nil hole. This commit only looks large, but is mostly a whitespace change due to nesting from a pair of wrapping ifs. commit 8c0273786de1ade66bdd977699f98b588da9f487 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:32:42 2021 +0700 Unify stacked and non-stacked graph rendering Now stacked graphs support everything the nonstacked do, like step_width ~= 1 and custom step_shape-s. Technical notes: This code no longer calls cr:stroke() for every single value in a stacked graph, but rather paints a whole series and then cr:stroke/fill()-s it at all once with its color, improving performance considerably. Additionally the graph no longer relies on the presence of the stack_colors property to render series. It draws all data that it has, and, if there's no corresponding color in stack_colors, falls back to graph.color. This will be changed in the next commit to the prior behavior of only drawing those series for which there's a color in the stack_colors table, but that would've made this commit's diff much larger due to required nesting ifs over large code chunks. This commit also takes care to properly handle NaNs and negative values. Those are currently (almost) guaranteed to not occur in the values array due to clipping in graph:add_value(), but I intend to eventually drop the clipping, because it does nothing that draw() really relies upon, and allowing unclipped and NaN values is more flexible and less surprising, e.g. when user dynamically toggles graph.scale. Eventually I intend to implement drawing negative values in non-stacked graphs and possibly even in the stacked ones. commit 38c6589c51b2aa057d7c2f40182ecf32b5b2ff5e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 10:04:15 2021 +0700 Convert graph:draw() to use `self` commit f21f1b2550426df1c4ea8abde079b1ccef84b394 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 12:27:26 2021 +0700 Fix scaling for stacked graphs Stacked graphs have cumulative heights, values from all groups that end up in the same graph bar are effectively summed. Previous code didn't take that into account when calculating min/max_value for the purposes of rescaling, but simply examined every value separately. This code adds up all values up front and finds min/max among those, to determine proper scaling that would fit the graph exactly. commit 1ce4c7bf63bdd6c99c195d668347bec49355e921 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 09:49:35 2021 +0700 Draw shaped graph bars only up to the baseline Prior to this commit, every step_shape bar was, for some reason, drawn with height = full widget's height, regardless of the value it represents. So for the most bars their lower end was far outside the box and therefore clipped off. So e.g. one couldn't see the lower round edges of rounded rectangles. This commit fixes that by appropriately varying the height that is passed into step_shape(), so that lower edges of the bars always exactly touch the lower edge of the graph. If the user wants to restore the prior behavior, they can simply ignore the passed-in height in step_shape() and just always draw their shape with height = full widget height. But they couldn't replicate the effect of this commit in their rc.lua without essentially writing their own graph widget. commit e25185b08da23745b5815f85f6e9e43222ed2294 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 08:42:54 2021 +0700 Avoid incremental transform shifting during graph bar rendering Instead of letting addition errors and cognitive load accumulate simply use the same offset calculation that is used for lines and reset the transform back to the top-left corner after every drawn bar. It's conceptually simpler, and will be useful later for stacked graphs and more. It also fixes a discrepancy between plain and step_shape rendering. Prior to this commit, bar coloring occurred with a disturbed transform, which was centered at the last drawn bar, wherever it happened to be. This meant that nontrivial colors, like gradients and images, were arbitrarily offset when step_shape was used. commit f3e98866d8583c35d0b557c09f529299edc9ba2c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:45:29 2021 +0700 Refactor out graph bar height expression into a variable commit f54528d6892f5aaa4595917853c9e2130f743fdb Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:20:25 2021 +0700 Draw graph bars with lines only when step_width == 1 Before this commit graph bars of step_width < 1 were drawn with lines of width 1, but that's a bit unfortunate, because it led to much overpainting, while still ending up with a graphically incorrect result. Let's draw such bars with rectangles and let cairo do things with subpixels, getting more graphically accurate result, while still keeping the common case of step_width == 1 (supposedly) fast-drawn with lines. commit b6515dca67c18b78eb5657378131a62ab43d4cfc Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:54:07 2021 +0700 Fix unsharp edges when drawing graph bars with rectangles The 0.5 offset is only helpful for 1-width strokes, not for fills. commit 7f3db8f07e82f821bcd1dc418d66564f537609d6 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:21:01 2021 +0700 Do coordinate transformations even if the value isn't drawn The graph presently doesn't draw values < 0, but there was a discrepancy between drawing with and without step_shape. The default line/rectangle drawing reserves an empty place where the skipped value should've been, but no such empty place is reserved, when step_shape is used. This commit fixes that. Skipped values always have their empty space now. commit 7b731ebfe96af7a9dba9c57371da6761cf22c00c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:03:30 2021 +0700 Fix graph bar scaling when min_value is not 0 Previous calculation lead to underscaling of values so that even value = max_value wasn't drawn to full height. commit dca59fd8b7fff0e7e646f2dfc6bd92047028e5a8 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:18:40 2021 +0700 Calculate the horizontal graph bar offset only when needed commit 940ddf4b9825e9c3a91d276208c089d8c74e49db Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:17:23 2021 +0700 Call cr:move_to() only when drawing the graph with lines It's redundant otherwise and might even interfere with user's drawing in step_shape(). commit b5d5cd10ca29fb29cd751faaa95137c2fe89ff63 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:04:06 2021 +0700 Fix wrong horizontal offset calculations in widget.graph Indices in the loop are zero-based, so things like i-1 and i>1 aren't necessary and actually wouldn't have been correct even in a 1-based loop. step_shape = gears.shape.rectangle and step_shape = nil, which is supposed to default to rectangle according to docs, were drawn differently due to mismatched offset calculations, which led to problems especially noticeable when step_spacing is not zero, e.g: 1) whole graph shifted left too much, drawn partially off-screen. 2) whole graph shifted right too much, and the first bar placed closer to the rest than others. commit dc369fa198281e000afc1bd17326f3a264adf7d1 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 02:09:21 2021 +0700 Fix the "border_width affects graph bar width" bug Graph bars were drawn with lines of border_width thickness instead of 1, i.e. possibly too fat to be readable. commit 1ba73378cf29ed27a6ecd18716394779ceb543d9 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 00:13:03 2021 +0700 Simplify internal values representation in graph even more New values in graph._private.values were pushed to the end, and old values were removed from the beginning of the table. In draw() however newer values are drawn first, at the left edge of the graph, necessitating annoying idx = #values - idx conversions everywhere. In particular in stacked graph that means that values that are drawn together may have different indices. One has to shift the values array in add_value() either way, so one might as well insert new values at the beginning of the array and simplify the rest of the code. This doesn't seem like much yet, but it will prove to be much more convenient and less error-prone in what I'm going to do later. commit f70523ee68dbd6b67050ace4b82903a9b22440c7 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 13:35:10 2021 +0700 Simplify internal values representation in graph widget graph._private.values was either a table of values or a table of tables of values, depending on whether "stack" property is enabled, ... or a mix of both if user forgot to pass the second "group" parameter to graph:add_value(). This resulted in pretty fragile and confusing behavior, e.g. leaving user to wonder, why they see nothing more after setting stack=true for a graph in a previously working rc.lua, and demanding a very careful use of add_value() from the user, or else some values or even entire stacks would mysteriously vanish and errors in draw() get triggered, sometimes after a period of seemingly normal functioning. Now `values` is always a table of tables and the group parameter in add_value() defaults to 1, thus making the non-stacked graph a trivial variant of the stacked graph.
2021-05-20 17:14:03 +02:00
-- This color is used, when `group_colors` isn't set.
--
2021-03-28 11:33:38 +02:00
--@DOC_wibox_widget_graph_color_EXAMPLE@
--
-- @property color
-- @tparam color color The graph color.
-- @usebeautiful beautiful.graph_fg
-- @propemits true false
-- @see gears.color
2016-05-23 07:30:44 +02:00
--- Set the graph background color.
--
2021-03-28 11:33:38 +02:00
--@DOC_wibox_widget_graph_background_color_EXAMPLE@
--
-- @property background_color
-- @tparam color background_color The graph background color.
-- @usebeautiful beautiful.graph_bg
-- @propemits true false
-- @see gears.color
2016-05-23 07:30:44 +02:00
graph: complete widget overhaul and bug fixes Squashed commit of the following: commit 69821f51fe1e8652715658543e50631ece495090 Author: Alex Belykh <albel727@ngs.ru> Date: Mon May 10 19:17:55 2021 +0700 Refactor property handling in wibox.widget.graph In addition, baseline_value, step_width and step_spacing properties are all set to their default non-nil numeric values during instance creation now. commit 842eb429bf5df4b2ba46b2e6f7646afe89b1a3c3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 9 06:18:45 2021 +0700 Make graph:draw_values() private There's no documentation nor confidence that it's a good API, so I'm hiding it for now. commit 720746780574e4bad14a71fc4174955139c2eb50 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 04:04:13 2021 +0700 Document graph_should_draw_data_group() for posterity commit 7c73f3754ce6ba86b118f1170a6f9e406ab4841d Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:59:21 2021 +0700 Make graph:should_draw_data_group() private There's no confidence whether it's a good API, so I'm hiding it for now. commit b3539e20adb6423118af726ae4f8751ef864b5d2 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:36:44 2021 +0700 Make graph:preprocess_values() private There's no confidence whether it's a good API, so I'm hiding it for now. commit 2df4400e780a6260f8d0ab1c60734fbb11bf3c4f Author: Alex Belykh <albel727@ngs.ru> Date: Thu May 6 10:05:23 2021 +0700 Add spec/wibox/widget/graph_spec.lua commit ce01a9771ab439b8df841f1565aa4aa8a44a5e0d Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 08:16:18 2021 +0700 Improve graph.capacity documentation commit e28fade8f655802f5bf89154169f68d256b8e332 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 07:36:01 2021 +0700 Document graph:compute_drawn_values_num() commit dd68332292465422d08b48e9f0229f143beccbc6 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 05:24:23 2021 +0700 Document graph:pick_data_group_color() method commit ca6dc55f77aca88a6e9cc90bb6fd0045739f41f5 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 04:13:58 2021 +0700 Guard against setting data groups sparsely in graph:add_value() commit cdea5f126f7984e72924f6dffbd312c4fe3b032f Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 02:36:12 2021 +0700 Refactor scaling and baseline choice out of graph:draw() commit 10135deafe8a9390ba66ab657ebb1fb41d549e8c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 21:34:48 2021 +0700 Document graph.nan_color and graph.stack properties commit 0d73bb84ac66b87e16aee472bd1f299549f66a1c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 20:49:24 2021 +0700 Use a better example in docs for the graph.stack property commit c27d160a0826d033d49d6141608c40aa8a0642fa Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 16:05:38 2021 +0700 Document graph.baseline_value property commit 459bc8176c13d4ea32af0dcdc2399d3b929b1c6a Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:53:47 2021 +0700 Document graph.clamp_bars property commit e732ee0b3b007d213e689b98cc00ae7cb141ae08 Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:34 2021 +0700 Improve graph:add_value() and :clear() documentation commit ae73e3aa9f8d60409cbe4cb4da9612f31af4f38e Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:11 2021 +0700 Improve graph min_value/max_value/scale documentation commit a7350996a9def773b91b1631e6c0507e33d8f93b Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:27:36 2021 +0700 Cleanup graph examples to show only relevant things in docs commit 30740c26f4f41c16f4c8a3c339bcb7bd299e14cd Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 14:04:36 2021 +0700 Rename graph.stack_colors property to `group_colors` The colors are used for stacked and non-stacked graphs alike, so the name didn't make sense. Also document and clarify some color stuff. commit 1ea01bfd410971cf0d0c25fd74cd764d5eb7279d Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 21:25:41 2021 +0700 Add an example file for code coverage of some fringe graph cases commit eae68e39553f88a6275a0c65bf040b5d4c262b54 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 20:23:32 2021 +0700 Add step_shape to the graph/nan_color example for code coverage commit 8027147bc2eab34a97b49a428fb8cef27f122bed Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:55:18 2021 +0700 Add example files for graph.nan_color/nan_indication properties commit 89a25a165c52b2c15b6c4d95ecb777a9067e66f9 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:33:07 2021 +0700 Make widget graph:add_value(v) default v to NaN 0 as a default makes no sense and silences the error that is failing to specify a value, to which the user should rather be alerted. The graph widget has sane NaN handling now and will do just that. commit 2835d552440be48d356a7f7b1d64963696dd550b Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:20:48 2021 +0700 Implement graph.nan_color and nan_indication properties Now, whenever there's a NaN among values and graph.nan_indication is set, the corresponding area, where the value bar should have been drawn, is filled with the nan_color from top to bottom. The painting is done after all data is rendered, to make sure that it won't be overpainted and go unnoticed. NaNs among graph's values are inevitably a sign of some error and it is arguably not a sane default behavior to simply not render them at all. The user should take immediate note of any errors by default, instead of wondering at a mysteriously empty graph widget, which is why nan_indication is set to true by default. If the user wants to silence NaNs, they can always simply set it to false. nan_color if unset, defaults to a noticeable yellow-black pattern. As a direct and planned consequence, stacked graphs with negative values in them trigger the NaN handling too. But silencing NaN handling for those graphs is also possible and semi-sane, because then NaNs and negatives are ignored and the graph behaves (almost) like they were zeros. commit 9c1f8f08b400e01e19c534f3810c9c9ab485760c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 26 08:10:19 2021 +0700 Refactor out graph_map_value_to_widget_coordinates() function commit 1905991ddecabdae3e15ffb8ee5b18a1bee1a711 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 28 13:39:29 2021 +0700 Add an example file for graph.baseline_value property commit b31b39d66f4c037a9484b71a3f910703ae01fac0 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 07:03:49 2021 +0700 Add an example file for graph.clamp_bars=false/true comparison commit a1fc850e3047f9ee2c9ddeffacfbabf3ded34171 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 04:06:45 2021 +0700 Add an example file for graphs with negative values and shapes commit 2f012f8ff1bb5094c14e724a61e59a2a3a296bc8 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 01:06:05 2021 +0700 Add an example file for a graph displaying negative values commit 7b14fbd59a4c4c51962fd3110a1a900dd5f4eb98 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 11:18:26 2021 +0700 Add an example file for graphs with step_width < 1 commit dfd9a54544b637e2fbe3d1325f9cc24b3f4d2313 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 10:17:38 2021 +0700 Add an example file for graph.stack=false/true comparison Should also demonstrate that multiple data groups are possible for non-stacked graphs too. commit 5b532106c3fd255f9b66a66cfb88d977b489ab5d Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 09:34:14 2021 +0700 Add an example file for disabling graph data groups with nil colors commit ac6f1083c02fcb428f089a616f9b0ebaa5496b6c Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:57:25 2021 +0700 Add an example file for step_shape/width/spacing on stacked graphs commit b3703db6a5139e73e72c1cfd198ece2cd9be7df2 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:43:56 2021 +0700 Add an example file for scale=false/true on stacked graph commit b000aed4a12cb44fb501e1c8432a71a14b2f8f3f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 20 08:22:19 2021 +0700 Document deprecation of graph's width and height commit cb0ad617bd6c83a499896797318e3b0b588c3566 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 07:53:37 2021 +0700 Deprecate width and height properties of wibox.widget.graph commit 8c64e14ad91a074b784166a7054e53444cccb9d2 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 04:37:59 2021 +0700 Refactor graph:draw() to separate concerns Firstly, summation of group values for stacked graphs is extracted into graph:preprocess_values(). This makes graph:draw() smaller and exposes the fact, that stacked graphs are almost entirely just a form of a more general concept: pre-render data manipulation. It's something a user might want to customize, and now they can do it by monkey-patching the method. Secondly, the logic for deciding, if and with which color a data group should be drawn, is extracted into graph:should_draw_data_group() and get_data_group_color() methods respectively. This makes it customizable too and makes more obvious the fact, that it is a general thing that has nothing to do with stacked graphs in particular. With this there are only 5 lines remaining in graph:draw(), that could be said to be specific to stacked graphs, all pertaining to the `prev_y` variable, which merely preserves the bar coordinates from a previous data group. commit 2a32b6305d893f413303728a0a3da1dcd9a5591e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 22:52:37 2021 +0700 Pointlessly optimize graph:add_value() 1) Purge old values first, then insert the new one, if needed. Since Lua grows arrays in power of two steps, and our guess_capacity() also takes care to return even integers, it would be a shame to overstep the capacity by one by inserting first and thus getting the table size doubled only to never use the most of it. 2) Use a removal procedure that doesn't call the slow length operator at all. Previous code called it twice (once in table.remove) for every removed value. commit 680ea8f5d81e9040afb4662b3229e937e4b89e02 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:23:38 2021 +0700 Set graph data series color before the painting loop commit b970f0f221ae76250f2a94433ff79d653caa6e21 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:05:37 2021 +0700 Drop a large no longer necessary `if` in graph:draw_values() This commit is almost purely a whitespace change. commit 6b4f0541c70ec99d7f6e276bae64939f337120d6 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:00:50 2021 +0700 Extract values rendering code into graph:draw_values() method This is to avoid ungodly if nesting levels, which are needed to bail out of drawing values early and skip to drawing the border. The next commit will get rid of the if. commit e0950e4f99d1ea1c4b8cea2cc67629fa40030c2a Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 14:50:20 2021 +0700 Track graph draw() usage to guess necessary array capacity The last step of divorcing add_values() from width calculations, while ensuring that user doesn't observe a lack of data to draw. The widget now tracks an approximate number of values that it has been asked to draw() in _private.last_drawn_values_num, and truncates its values array based on that (if not overridden by the capacity property). commit 2e3d542282f35e7c0fed454da0f2eada0fcf3b78 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 13:35:12 2021 +0700 Add `capacity` property to graph widget This is the second step of divorcing add_values() from calculations involving widths. How many values are stored and how many values are drawn are distinct concepts. There isn't even a single definitive "how many values are drawn" because the widget could be rendered in several places with distinct widths simultaneously, and in every such place the data must be drawn appropriately, e.g. with auto-scaling, that takes only the visible data points into account. And the draw() method can do that now. The default capacity value of nil uses heuristics based on widget's _private.width to guess some capacity that would tolerate some dynamic widget resizing. If the user finds the heuristic failing them, they can set the capacity property to the desired maximum number of values that will be kept in the _private.values array (in each data group). commit 08d4a80686f7fe363551c35589a460839f60853e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 03:27:18 2021 +0700 Extract values size computation into compute_drawn_values_num() This is the first step of making add_values() not concern itself (directly) with widget dimensions. The user can monkey-patch the method to override how many values are (logically) drawn, because it affects graph auto-scaling and can be useful for rendering of step_shape-s that paint outside of their step_width. I didn't make it a full-blown function-valued property, because it seems a fringe functionality, which I'm too lazy to document. commit 3c5617c98ab1147618ed22e8414a77fc56b45f6e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 02:32:29 2021 +0700 Copy graph widget prototype methods over in bulk commit 169538837ece41a03d02ba8fbc0e4097062b67a3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun Apr 25 05:58:01 2021 +0700 Remove graph.baseline_y property After giving it some thought I've concluded, that it is unlikely to be useful as it is now. I'd better reserve the name for some more sensible functionality, like fixing the baseline_value defined axis at the given widget coordinate, adjusting the scaling accordingly, so min_value/max_value won't generally correspond to widget top and bottom anymore. commit 202ffaa9a0b820c7c471e1121d01b91f8f76cd38 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 12:06:55 2021 +0700 Add baseline_y and baseline_value properties to graph widget Now values in graph have a *baseline*, tunable with baseline_y and baseline_value properties. It is the (for now invisible) horizontal line from which all bars in the graph grow. I call it a "baseline" instead of "axis", because it doesn't actually have to correspond to the 0 value. But it can, and actually does now by default. baseline_y and baseline_value properties determine the vertical placement of the baseline in the widget. baseline_y is the position of the baseline in normalized widget coordinates, e.g. 0 corresponds to widget's top, 1 - to widget's bottom, and 0.5 - to widget's middle. So if e.g. one sets baseline_y = 0, then no matter how the widget is resized and how its values are scaled, bars in it will be drawn starting from the top. baseline_value is the position of the baseline in value coordinates. So if one sets baseline_value = 10, then no matter what happens, bars for values greater than 10 will be drawn as growing up from this line, and bars for values smaller as growing down. Where and if it will be seen in the widget, depends on the scaling. baseline_value = 0 is the familiar zero axis. baseline_y is unset by default and has priority, i.e. if it's set, then baseline_value is ignored. baseline_value defaults to 0. Prior to this commit, bars were always drawn from the bottom edge of the widget, or, in the new terms, the widget had baseline_y = 1. But it was indistinguishable from baseline_value = 0, because the widget clamped all negative values to 0, so the difference between the two could never be observed. baseline_value = 0 is clearly more useful and familiar to people, so now it is in force by default. commit df7e19abaebf0a871b5ca217b4cc3365533267a6 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 11:18:12 2021 +0700 Move value clamping from graph:add_value() into draw() Previously all values were forcibly clamped between 0 (not tunable at all) and max_value (1 by default). Thus graphs with negative values were not an option. NaN values were conflated with zeros, even though it may be useful to maintain the distinction and render them differently. And if user happened to dynamically change the max_value property, they discovered, that their data got truncated. Preserving user data as it was given is more flexible and makes things easier to debug. This commit does exactly that. So what happens now to the newly allowed values during draw()? There are 3 new cases to consider: 1) NaNs 2) Negatives 3) Values outside of min_value..max_value range NaNs are presently simply not drawn. An empty place is reserved where the value should've been, but nothing else is done. This is distinct from how a zero is handled, e.g. if step_shape is set, it is called with the height parameter = 0, which can still end up drawing something, depending on the shape. One could also consider addition of nan_color/nan_shape properties which could be drawn to alert user to a gap in their data. Negative values necessitate introduction of the concept of the zero axis, from which they will be drawn DOWN, in contrast to positive values. Where should this axis be drawn? How should step_shape-s with inherent direction, e.g. arrows, be drawn for negatives? This will be addressed in the next commit. Values outside of min/max_value range can imply shapes that go beyond widget dimensions. An arrow shape that goes beyond the roof is visibly different from an arrow that saturates and merely touches the widget top, and the user may reasonably desire either behavior. Therefore this commit introduces a boolean clamp_bars property, which defaults to true for reasons of robustness and backward compatibility with the behaviour prior to this commit. The property of course affects only the display of the values, the actual user data stays intact. commit c9c24fb1ab741cd6bb8074971d568e0646c51532 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:29:01 2021 +0700 Use rectangles for drawing graph bars of all sizes I have benchmarked various bar drawing strategies and graph sizes and found out that drawing graph bars with rectangles is as fast or even marginally faster than using fat lines. Thus the code that tries to special-case 1px bars as lines seems to be not worth the additional complexity it introduces, and this commit removes it. Incidentally, I've also established, that rectangles are more robust. If one draws graph bars and feeds cairo very large coordinates (~2^62), then corresponding rectangles fail to render, but all the other rectangles in the same series get rendered successfully, whereas a single large coordinate in a move_to/line_to() sequence is enough to cause all lines in the batch to fail to render. commit bc7f4be5e06405b5aa7fb443a5ace2b9b9575b1f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:01:42 2021 +0700 Cache cairo methods used in the inner loop of graph:draw() I have benchmarked the draw() method, both on cairo xcb and on argb32 image surfaces, and have discovered that cairo context method calls are unnaturally slow, to the point, that almost nothing else in the function matters. For example, drawing bars with vertical lines made the whole draw() function almost 2 times slower than doing the same with rectangles, merely because the former requires two method calls (move_to/line_to), and the latter only one. Simply caching hotpath cairo methods in local variables, reduced the difference between the drawing methods to a negligible one and made the whole function 4 times faster on a typical 100-bar graph, (1.4ms to 0.33ms on my laptop) thus bringing closer my secret dream of drawing live audio data with awesome graphs (kidding). I guess, LGI does something not very optimal there, like gobject-introspecting the method every time, or something. There might be some untapped optimization potential, possibly in upstream, like a method cache, from which everything in awesome could benefit, even though I imagine not many things are so call-intensive as the graph widget, to really feel the difference. commit 538df25bdf02b53d0bbe81809eef2e6fdb05ff6f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 07:32:37 2021 +0700 Use 1-based index in the inner graph draw loop It's used there only once for a calculation and then several times to index some tables, and index 0 is bad for lua tables performance-wise. commit 7412546916cd0a0a490130a120255ffb719398b8 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:55:12 2021 +0700 Leave graph.max_value uninitialized at construction This is a user-visible change, but the previous behavior was probably not what user expected anyway, namely with graph.scale set the graph mysteriously couldn't zoom in onto values that are smaller than 1. commit eb81d7929a161e79c13262cfa73ac2e6a820e8ed Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:48:29 2021 +0700 Allow graph.max_value be nil This makes its behave symmetrically to min_value. Now it defaults to 1, when graph.scale is not set, and to the maximum value in the graph data, when graph.scale is set. commit f622a8430cbfde7007b58d7f4c9c76b08c91e997 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 12:01:13 2021 +0700 Calculate max values num in graph:add_value() more precisely At most so many values were kept in the widget values array, as there are pixels of usable widget width (i.e. sans border). This is exactly right for the surely most common case of graphs with 1px-wide bars, but is more than needed for graphs with step_width+step_spacing>1, and not enough for graphs with subpixel bars. Moreover this made graph autoscaling behave weirdly in graphs with thicker bars, because old values survived longer in the array and kept affecting min_value/max_value calculations even after they were shifted off-screen. The maximum number of kept values is now calculated exactly as the number of values that can be displayed at once, taking step_width and step_spacing into account. It might be worth adding a max_values_num property to the graph to let user override this calculation, in case they draw shapes in step_shape() outside of its supposed rectangle, so our assumptions of visibility are wrong, or the widget often gets resized, resulting in the annoyance of visibly truncated series. commit 0aa63249a18641251a1bf77423739164f0eab39f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 02:30:13 2021 +0700 Fix border_color/border_width bugs in graph widget add_value() wrongly assumed that border_width is always set, when border_color is, so if user set only the latter, an error occurred. add_value() assumed that border_width is always < width which led to awesome hanging up in an infinite loop, should that fail to be the case. The graph border_width property was driven by the border_color property, instead of the other way round. If border_color wasn't set, the border wasn't drawn all, even if user sets non-zero border_width, which is a confusing behavior, in contrast to all other widgets, which simply use a plethora of fallback colors to draw the border, when needed. This also meant that, despite being documented, the beautiful.graph_border_color fallback color could not and was not ever used. If the border_color was set though, an unset border_width was immediately assumed to be 1 instead of 0, also contrary to what other widgets do. Theme colors shouldn't influence widget layout, so this commit fixes this behavior to: 1) border_width always defaults to 0, if unset. 2) border_color falls back to beautiful.graph_border_color or white. Maybe one should also add a beautiful.graph_border_width theme variable, like some widgets do. commit dad4a3253b8fef971fb82720d2a407fcc0a879ad Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 03:11:27 2021 +0700 Implement multiple data series drawing for non-stacked graph The widget can already hold and draw multiple data groups, so it makes little sense to limit drawing only to the first group when graph.stack = false, probably leading to user's confusion. Now user can call add_value(v, group) with group > 1 and the data series will be drawn (over groups with lesser id-s) with graph.stack_colors[group] color, if stack_colors is set, or with graph.color, if stack_colors isn't set. Thus nothing visibly changes for all current users of non-stacked graphs with only single data series, but the new functionality is now there for those who need it. One can't fully emulate this functionality, e.g. with multiple graphs widgets in a wibox.layout.stack, because data series in a single widget can enjoy a common auto-scaling. commit 3e645c976184ab5f50d625271c25062db9ab7ed9 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:59:12 2021 +0700 Don't draw a stacked graph data group if there's no color for it If stack_colors[idx] is nil for the group `idx`, don't draw it at all (as opposed, e.g. to the effect of drawing it with a transparent color, or as though it had all values == 0, which both produce different results). This code is still subtly different from prior behavior, because now user can have a non-contiguous stack_colors table and turn the display of any data group on and off at will, whereas the code two commits earlier would just stop rendering as soon as ipairs(stack_colors) does, i.e. no data series were drawn past the first nil hole. This commit only looks large, but is mostly a whitespace change due to nesting from a pair of wrapping ifs. commit 8c0273786de1ade66bdd977699f98b588da9f487 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:32:42 2021 +0700 Unify stacked and non-stacked graph rendering Now stacked graphs support everything the nonstacked do, like step_width ~= 1 and custom step_shape-s. Technical notes: This code no longer calls cr:stroke() for every single value in a stacked graph, but rather paints a whole series and then cr:stroke/fill()-s it at all once with its color, improving performance considerably. Additionally the graph no longer relies on the presence of the stack_colors property to render series. It draws all data that it has, and, if there's no corresponding color in stack_colors, falls back to graph.color. This will be changed in the next commit to the prior behavior of only drawing those series for which there's a color in the stack_colors table, but that would've made this commit's diff much larger due to required nesting ifs over large code chunks. This commit also takes care to properly handle NaNs and negative values. Those are currently (almost) guaranteed to not occur in the values array due to clipping in graph:add_value(), but I intend to eventually drop the clipping, because it does nothing that draw() really relies upon, and allowing unclipped and NaN values is more flexible and less surprising, e.g. when user dynamically toggles graph.scale. Eventually I intend to implement drawing negative values in non-stacked graphs and possibly even in the stacked ones. commit 38c6589c51b2aa057d7c2f40182ecf32b5b2ff5e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 10:04:15 2021 +0700 Convert graph:draw() to use `self` commit f21f1b2550426df1c4ea8abde079b1ccef84b394 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 12:27:26 2021 +0700 Fix scaling for stacked graphs Stacked graphs have cumulative heights, values from all groups that end up in the same graph bar are effectively summed. Previous code didn't take that into account when calculating min/max_value for the purposes of rescaling, but simply examined every value separately. This code adds up all values up front and finds min/max among those, to determine proper scaling that would fit the graph exactly. commit 1ce4c7bf63bdd6c99c195d668347bec49355e921 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 09:49:35 2021 +0700 Draw shaped graph bars only up to the baseline Prior to this commit, every step_shape bar was, for some reason, drawn with height = full widget's height, regardless of the value it represents. So for the most bars their lower end was far outside the box and therefore clipped off. So e.g. one couldn't see the lower round edges of rounded rectangles. This commit fixes that by appropriately varying the height that is passed into step_shape(), so that lower edges of the bars always exactly touch the lower edge of the graph. If the user wants to restore the prior behavior, they can simply ignore the passed-in height in step_shape() and just always draw their shape with height = full widget height. But they couldn't replicate the effect of this commit in their rc.lua without essentially writing their own graph widget. commit e25185b08da23745b5815f85f6e9e43222ed2294 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 08:42:54 2021 +0700 Avoid incremental transform shifting during graph bar rendering Instead of letting addition errors and cognitive load accumulate simply use the same offset calculation that is used for lines and reset the transform back to the top-left corner after every drawn bar. It's conceptually simpler, and will be useful later for stacked graphs and more. It also fixes a discrepancy between plain and step_shape rendering. Prior to this commit, bar coloring occurred with a disturbed transform, which was centered at the last drawn bar, wherever it happened to be. This meant that nontrivial colors, like gradients and images, were arbitrarily offset when step_shape was used. commit f3e98866d8583c35d0b557c09f529299edc9ba2c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:45:29 2021 +0700 Refactor out graph bar height expression into a variable commit f54528d6892f5aaa4595917853c9e2130f743fdb Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:20:25 2021 +0700 Draw graph bars with lines only when step_width == 1 Before this commit graph bars of step_width < 1 were drawn with lines of width 1, but that's a bit unfortunate, because it led to much overpainting, while still ending up with a graphically incorrect result. Let's draw such bars with rectangles and let cairo do things with subpixels, getting more graphically accurate result, while still keeping the common case of step_width == 1 (supposedly) fast-drawn with lines. commit b6515dca67c18b78eb5657378131a62ab43d4cfc Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:54:07 2021 +0700 Fix unsharp edges when drawing graph bars with rectangles The 0.5 offset is only helpful for 1-width strokes, not for fills. commit 7f3db8f07e82f821bcd1dc418d66564f537609d6 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:21:01 2021 +0700 Do coordinate transformations even if the value isn't drawn The graph presently doesn't draw values < 0, but there was a discrepancy between drawing with and without step_shape. The default line/rectangle drawing reserves an empty place where the skipped value should've been, but no such empty place is reserved, when step_shape is used. This commit fixes that. Skipped values always have their empty space now. commit 7b731ebfe96af7a9dba9c57371da6761cf22c00c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:03:30 2021 +0700 Fix graph bar scaling when min_value is not 0 Previous calculation lead to underscaling of values so that even value = max_value wasn't drawn to full height. commit dca59fd8b7fff0e7e646f2dfc6bd92047028e5a8 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:18:40 2021 +0700 Calculate the horizontal graph bar offset only when needed commit 940ddf4b9825e9c3a91d276208c089d8c74e49db Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:17:23 2021 +0700 Call cr:move_to() only when drawing the graph with lines It's redundant otherwise and might even interfere with user's drawing in step_shape(). commit b5d5cd10ca29fb29cd751faaa95137c2fe89ff63 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:04:06 2021 +0700 Fix wrong horizontal offset calculations in widget.graph Indices in the loop are zero-based, so things like i-1 and i>1 aren't necessary and actually wouldn't have been correct even in a 1-based loop. step_shape = gears.shape.rectangle and step_shape = nil, which is supposed to default to rectangle according to docs, were drawn differently due to mismatched offset calculations, which led to problems especially noticeable when step_spacing is not zero, e.g: 1) whole graph shifted left too much, drawn partially off-screen. 2) whole graph shifted right too much, and the first bar placed closer to the rest than others. commit dc369fa198281e000afc1bd17326f3a264adf7d1 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 02:09:21 2021 +0700 Fix the "border_width affects graph bar width" bug Graph bars were drawn with lines of border_width thickness instead of 1, i.e. possibly too fat to be readable. commit 1ba73378cf29ed27a6ecd18716394779ceb543d9 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 00:13:03 2021 +0700 Simplify internal values representation in graph even more New values in graph._private.values were pushed to the end, and old values were removed from the beginning of the table. In draw() however newer values are drawn first, at the left edge of the graph, necessitating annoying idx = #values - idx conversions everywhere. In particular in stacked graph that means that values that are drawn together may have different indices. One has to shift the values array in add_value() either way, so one might as well insert new values at the beginning of the array and simplify the rest of the code. This doesn't seem like much yet, but it will prove to be much more convenient and less error-prone in what I'm going to do later. commit f70523ee68dbd6b67050ace4b82903a9b22440c7 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 13:35:10 2021 +0700 Simplify internal values representation in graph widget graph._private.values was either a table of values or a table of tables of values, depending on whether "stack" property is enabled, ... or a mix of both if user forgot to pass the second "group" parameter to graph:add_value(). This resulted in pretty fragile and confusing behavior, e.g. leaving user to wonder, why they see nothing more after setting stack=true for a graph in a previously working rc.lua, and demanding a very careful use of add_value() from the user, or else some values or even entire stacks would mysteriously vanish and errors in draw() get triggered, sometimes after a period of seemingly normal functioning. Now `values` is always a table of tables and the group parameter in add_value() defaults to 1, thus making the non-stacked graph a trivial variant of the stacked graph.
2021-05-20 17:14:03 +02:00
--- Set the colors for data groups.
--
graph: complete widget overhaul and bug fixes Squashed commit of the following: commit 69821f51fe1e8652715658543e50631ece495090 Author: Alex Belykh <albel727@ngs.ru> Date: Mon May 10 19:17:55 2021 +0700 Refactor property handling in wibox.widget.graph In addition, baseline_value, step_width and step_spacing properties are all set to their default non-nil numeric values during instance creation now. commit 842eb429bf5df4b2ba46b2e6f7646afe89b1a3c3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 9 06:18:45 2021 +0700 Make graph:draw_values() private There's no documentation nor confidence that it's a good API, so I'm hiding it for now. commit 720746780574e4bad14a71fc4174955139c2eb50 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 04:04:13 2021 +0700 Document graph_should_draw_data_group() for posterity commit 7c73f3754ce6ba86b118f1170a6f9e406ab4841d Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:59:21 2021 +0700 Make graph:should_draw_data_group() private There's no confidence whether it's a good API, so I'm hiding it for now. commit b3539e20adb6423118af726ae4f8751ef864b5d2 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:36:44 2021 +0700 Make graph:preprocess_values() private There's no confidence whether it's a good API, so I'm hiding it for now. commit 2df4400e780a6260f8d0ab1c60734fbb11bf3c4f Author: Alex Belykh <albel727@ngs.ru> Date: Thu May 6 10:05:23 2021 +0700 Add spec/wibox/widget/graph_spec.lua commit ce01a9771ab439b8df841f1565aa4aa8a44a5e0d Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 08:16:18 2021 +0700 Improve graph.capacity documentation commit e28fade8f655802f5bf89154169f68d256b8e332 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 07:36:01 2021 +0700 Document graph:compute_drawn_values_num() commit dd68332292465422d08b48e9f0229f143beccbc6 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 05:24:23 2021 +0700 Document graph:pick_data_group_color() method commit ca6dc55f77aca88a6e9cc90bb6fd0045739f41f5 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 04:13:58 2021 +0700 Guard against setting data groups sparsely in graph:add_value() commit cdea5f126f7984e72924f6dffbd312c4fe3b032f Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 02:36:12 2021 +0700 Refactor scaling and baseline choice out of graph:draw() commit 10135deafe8a9390ba66ab657ebb1fb41d549e8c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 21:34:48 2021 +0700 Document graph.nan_color and graph.stack properties commit 0d73bb84ac66b87e16aee472bd1f299549f66a1c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 20:49:24 2021 +0700 Use a better example in docs for the graph.stack property commit c27d160a0826d033d49d6141608c40aa8a0642fa Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 16:05:38 2021 +0700 Document graph.baseline_value property commit 459bc8176c13d4ea32af0dcdc2399d3b929b1c6a Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:53:47 2021 +0700 Document graph.clamp_bars property commit e732ee0b3b007d213e689b98cc00ae7cb141ae08 Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:34 2021 +0700 Improve graph:add_value() and :clear() documentation commit ae73e3aa9f8d60409cbe4cb4da9612f31af4f38e Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:11 2021 +0700 Improve graph min_value/max_value/scale documentation commit a7350996a9def773b91b1631e6c0507e33d8f93b Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:27:36 2021 +0700 Cleanup graph examples to show only relevant things in docs commit 30740c26f4f41c16f4c8a3c339bcb7bd299e14cd Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 14:04:36 2021 +0700 Rename graph.stack_colors property to `group_colors` The colors are used for stacked and non-stacked graphs alike, so the name didn't make sense. Also document and clarify some color stuff. commit 1ea01bfd410971cf0d0c25fd74cd764d5eb7279d Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 21:25:41 2021 +0700 Add an example file for code coverage of some fringe graph cases commit eae68e39553f88a6275a0c65bf040b5d4c262b54 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 20:23:32 2021 +0700 Add step_shape to the graph/nan_color example for code coverage commit 8027147bc2eab34a97b49a428fb8cef27f122bed Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:55:18 2021 +0700 Add example files for graph.nan_color/nan_indication properties commit 89a25a165c52b2c15b6c4d95ecb777a9067e66f9 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:33:07 2021 +0700 Make widget graph:add_value(v) default v to NaN 0 as a default makes no sense and silences the error that is failing to specify a value, to which the user should rather be alerted. The graph widget has sane NaN handling now and will do just that. commit 2835d552440be48d356a7f7b1d64963696dd550b Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:20:48 2021 +0700 Implement graph.nan_color and nan_indication properties Now, whenever there's a NaN among values and graph.nan_indication is set, the corresponding area, where the value bar should have been drawn, is filled with the nan_color from top to bottom. The painting is done after all data is rendered, to make sure that it won't be overpainted and go unnoticed. NaNs among graph's values are inevitably a sign of some error and it is arguably not a sane default behavior to simply not render them at all. The user should take immediate note of any errors by default, instead of wondering at a mysteriously empty graph widget, which is why nan_indication is set to true by default. If the user wants to silence NaNs, they can always simply set it to false. nan_color if unset, defaults to a noticeable yellow-black pattern. As a direct and planned consequence, stacked graphs with negative values in them trigger the NaN handling too. But silencing NaN handling for those graphs is also possible and semi-sane, because then NaNs and negatives are ignored and the graph behaves (almost) like they were zeros. commit 9c1f8f08b400e01e19c534f3810c9c9ab485760c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 26 08:10:19 2021 +0700 Refactor out graph_map_value_to_widget_coordinates() function commit 1905991ddecabdae3e15ffb8ee5b18a1bee1a711 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 28 13:39:29 2021 +0700 Add an example file for graph.baseline_value property commit b31b39d66f4c037a9484b71a3f910703ae01fac0 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 07:03:49 2021 +0700 Add an example file for graph.clamp_bars=false/true comparison commit a1fc850e3047f9ee2c9ddeffacfbabf3ded34171 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 04:06:45 2021 +0700 Add an example file for graphs with negative values and shapes commit 2f012f8ff1bb5094c14e724a61e59a2a3a296bc8 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 01:06:05 2021 +0700 Add an example file for a graph displaying negative values commit 7b14fbd59a4c4c51962fd3110a1a900dd5f4eb98 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 11:18:26 2021 +0700 Add an example file for graphs with step_width < 1 commit dfd9a54544b637e2fbe3d1325f9cc24b3f4d2313 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 10:17:38 2021 +0700 Add an example file for graph.stack=false/true comparison Should also demonstrate that multiple data groups are possible for non-stacked graphs too. commit 5b532106c3fd255f9b66a66cfb88d977b489ab5d Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 09:34:14 2021 +0700 Add an example file for disabling graph data groups with nil colors commit ac6f1083c02fcb428f089a616f9b0ebaa5496b6c Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:57:25 2021 +0700 Add an example file for step_shape/width/spacing on stacked graphs commit b3703db6a5139e73e72c1cfd198ece2cd9be7df2 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:43:56 2021 +0700 Add an example file for scale=false/true on stacked graph commit b000aed4a12cb44fb501e1c8432a71a14b2f8f3f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 20 08:22:19 2021 +0700 Document deprecation of graph's width and height commit cb0ad617bd6c83a499896797318e3b0b588c3566 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 07:53:37 2021 +0700 Deprecate width and height properties of wibox.widget.graph commit 8c64e14ad91a074b784166a7054e53444cccb9d2 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 04:37:59 2021 +0700 Refactor graph:draw() to separate concerns Firstly, summation of group values for stacked graphs is extracted into graph:preprocess_values(). This makes graph:draw() smaller and exposes the fact, that stacked graphs are almost entirely just a form of a more general concept: pre-render data manipulation. It's something a user might want to customize, and now they can do it by monkey-patching the method. Secondly, the logic for deciding, if and with which color a data group should be drawn, is extracted into graph:should_draw_data_group() and get_data_group_color() methods respectively. This makes it customizable too and makes more obvious the fact, that it is a general thing that has nothing to do with stacked graphs in particular. With this there are only 5 lines remaining in graph:draw(), that could be said to be specific to stacked graphs, all pertaining to the `prev_y` variable, which merely preserves the bar coordinates from a previous data group. commit 2a32b6305d893f413303728a0a3da1dcd9a5591e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 22:52:37 2021 +0700 Pointlessly optimize graph:add_value() 1) Purge old values first, then insert the new one, if needed. Since Lua grows arrays in power of two steps, and our guess_capacity() also takes care to return even integers, it would be a shame to overstep the capacity by one by inserting first and thus getting the table size doubled only to never use the most of it. 2) Use a removal procedure that doesn't call the slow length operator at all. Previous code called it twice (once in table.remove) for every removed value. commit 680ea8f5d81e9040afb4662b3229e937e4b89e02 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:23:38 2021 +0700 Set graph data series color before the painting loop commit b970f0f221ae76250f2a94433ff79d653caa6e21 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:05:37 2021 +0700 Drop a large no longer necessary `if` in graph:draw_values() This commit is almost purely a whitespace change. commit 6b4f0541c70ec99d7f6e276bae64939f337120d6 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:00:50 2021 +0700 Extract values rendering code into graph:draw_values() method This is to avoid ungodly if nesting levels, which are needed to bail out of drawing values early and skip to drawing the border. The next commit will get rid of the if. commit e0950e4f99d1ea1c4b8cea2cc67629fa40030c2a Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 14:50:20 2021 +0700 Track graph draw() usage to guess necessary array capacity The last step of divorcing add_values() from width calculations, while ensuring that user doesn't observe a lack of data to draw. The widget now tracks an approximate number of values that it has been asked to draw() in _private.last_drawn_values_num, and truncates its values array based on that (if not overridden by the capacity property). commit 2e3d542282f35e7c0fed454da0f2eada0fcf3b78 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 13:35:12 2021 +0700 Add `capacity` property to graph widget This is the second step of divorcing add_values() from calculations involving widths. How many values are stored and how many values are drawn are distinct concepts. There isn't even a single definitive "how many values are drawn" because the widget could be rendered in several places with distinct widths simultaneously, and in every such place the data must be drawn appropriately, e.g. with auto-scaling, that takes only the visible data points into account. And the draw() method can do that now. The default capacity value of nil uses heuristics based on widget's _private.width to guess some capacity that would tolerate some dynamic widget resizing. If the user finds the heuristic failing them, they can set the capacity property to the desired maximum number of values that will be kept in the _private.values array (in each data group). commit 08d4a80686f7fe363551c35589a460839f60853e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 03:27:18 2021 +0700 Extract values size computation into compute_drawn_values_num() This is the first step of making add_values() not concern itself (directly) with widget dimensions. The user can monkey-patch the method to override how many values are (logically) drawn, because it affects graph auto-scaling and can be useful for rendering of step_shape-s that paint outside of their step_width. I didn't make it a full-blown function-valued property, because it seems a fringe functionality, which I'm too lazy to document. commit 3c5617c98ab1147618ed22e8414a77fc56b45f6e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 02:32:29 2021 +0700 Copy graph widget prototype methods over in bulk commit 169538837ece41a03d02ba8fbc0e4097062b67a3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun Apr 25 05:58:01 2021 +0700 Remove graph.baseline_y property After giving it some thought I've concluded, that it is unlikely to be useful as it is now. I'd better reserve the name for some more sensible functionality, like fixing the baseline_value defined axis at the given widget coordinate, adjusting the scaling accordingly, so min_value/max_value won't generally correspond to widget top and bottom anymore. commit 202ffaa9a0b820c7c471e1121d01b91f8f76cd38 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 12:06:55 2021 +0700 Add baseline_y and baseline_value properties to graph widget Now values in graph have a *baseline*, tunable with baseline_y and baseline_value properties. It is the (for now invisible) horizontal line from which all bars in the graph grow. I call it a "baseline" instead of "axis", because it doesn't actually have to correspond to the 0 value. But it can, and actually does now by default. baseline_y and baseline_value properties determine the vertical placement of the baseline in the widget. baseline_y is the position of the baseline in normalized widget coordinates, e.g. 0 corresponds to widget's top, 1 - to widget's bottom, and 0.5 - to widget's middle. So if e.g. one sets baseline_y = 0, then no matter how the widget is resized and how its values are scaled, bars in it will be drawn starting from the top. baseline_value is the position of the baseline in value coordinates. So if one sets baseline_value = 10, then no matter what happens, bars for values greater than 10 will be drawn as growing up from this line, and bars for values smaller as growing down. Where and if it will be seen in the widget, depends on the scaling. baseline_value = 0 is the familiar zero axis. baseline_y is unset by default and has priority, i.e. if it's set, then baseline_value is ignored. baseline_value defaults to 0. Prior to this commit, bars were always drawn from the bottom edge of the widget, or, in the new terms, the widget had baseline_y = 1. But it was indistinguishable from baseline_value = 0, because the widget clamped all negative values to 0, so the difference between the two could never be observed. baseline_value = 0 is clearly more useful and familiar to people, so now it is in force by default. commit df7e19abaebf0a871b5ca217b4cc3365533267a6 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 11:18:12 2021 +0700 Move value clamping from graph:add_value() into draw() Previously all values were forcibly clamped between 0 (not tunable at all) and max_value (1 by default). Thus graphs with negative values were not an option. NaN values were conflated with zeros, even though it may be useful to maintain the distinction and render them differently. And if user happened to dynamically change the max_value property, they discovered, that their data got truncated. Preserving user data as it was given is more flexible and makes things easier to debug. This commit does exactly that. So what happens now to the newly allowed values during draw()? There are 3 new cases to consider: 1) NaNs 2) Negatives 3) Values outside of min_value..max_value range NaNs are presently simply not drawn. An empty place is reserved where the value should've been, but nothing else is done. This is distinct from how a zero is handled, e.g. if step_shape is set, it is called with the height parameter = 0, which can still end up drawing something, depending on the shape. One could also consider addition of nan_color/nan_shape properties which could be drawn to alert user to a gap in their data. Negative values necessitate introduction of the concept of the zero axis, from which they will be drawn DOWN, in contrast to positive values. Where should this axis be drawn? How should step_shape-s with inherent direction, e.g. arrows, be drawn for negatives? This will be addressed in the next commit. Values outside of min/max_value range can imply shapes that go beyond widget dimensions. An arrow shape that goes beyond the roof is visibly different from an arrow that saturates and merely touches the widget top, and the user may reasonably desire either behavior. Therefore this commit introduces a boolean clamp_bars property, which defaults to true for reasons of robustness and backward compatibility with the behaviour prior to this commit. The property of course affects only the display of the values, the actual user data stays intact. commit c9c24fb1ab741cd6bb8074971d568e0646c51532 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:29:01 2021 +0700 Use rectangles for drawing graph bars of all sizes I have benchmarked various bar drawing strategies and graph sizes and found out that drawing graph bars with rectangles is as fast or even marginally faster than using fat lines. Thus the code that tries to special-case 1px bars as lines seems to be not worth the additional complexity it introduces, and this commit removes it. Incidentally, I've also established, that rectangles are more robust. If one draws graph bars and feeds cairo very large coordinates (~2^62), then corresponding rectangles fail to render, but all the other rectangles in the same series get rendered successfully, whereas a single large coordinate in a move_to/line_to() sequence is enough to cause all lines in the batch to fail to render. commit bc7f4be5e06405b5aa7fb443a5ace2b9b9575b1f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:01:42 2021 +0700 Cache cairo methods used in the inner loop of graph:draw() I have benchmarked the draw() method, both on cairo xcb and on argb32 image surfaces, and have discovered that cairo context method calls are unnaturally slow, to the point, that almost nothing else in the function matters. For example, drawing bars with vertical lines made the whole draw() function almost 2 times slower than doing the same with rectangles, merely because the former requires two method calls (move_to/line_to), and the latter only one. Simply caching hotpath cairo methods in local variables, reduced the difference between the drawing methods to a negligible one and made the whole function 4 times faster on a typical 100-bar graph, (1.4ms to 0.33ms on my laptop) thus bringing closer my secret dream of drawing live audio data with awesome graphs (kidding). I guess, LGI does something not very optimal there, like gobject-introspecting the method every time, or something. There might be some untapped optimization potential, possibly in upstream, like a method cache, from which everything in awesome could benefit, even though I imagine not many things are so call-intensive as the graph widget, to really feel the difference. commit 538df25bdf02b53d0bbe81809eef2e6fdb05ff6f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 07:32:37 2021 +0700 Use 1-based index in the inner graph draw loop It's used there only once for a calculation and then several times to index some tables, and index 0 is bad for lua tables performance-wise. commit 7412546916cd0a0a490130a120255ffb719398b8 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:55:12 2021 +0700 Leave graph.max_value uninitialized at construction This is a user-visible change, but the previous behavior was probably not what user expected anyway, namely with graph.scale set the graph mysteriously couldn't zoom in onto values that are smaller than 1. commit eb81d7929a161e79c13262cfa73ac2e6a820e8ed Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:48:29 2021 +0700 Allow graph.max_value be nil This makes its behave symmetrically to min_value. Now it defaults to 1, when graph.scale is not set, and to the maximum value in the graph data, when graph.scale is set. commit f622a8430cbfde7007b58d7f4c9c76b08c91e997 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 12:01:13 2021 +0700 Calculate max values num in graph:add_value() more precisely At most so many values were kept in the widget values array, as there are pixels of usable widget width (i.e. sans border). This is exactly right for the surely most common case of graphs with 1px-wide bars, but is more than needed for graphs with step_width+step_spacing>1, and not enough for graphs with subpixel bars. Moreover this made graph autoscaling behave weirdly in graphs with thicker bars, because old values survived longer in the array and kept affecting min_value/max_value calculations even after they were shifted off-screen. The maximum number of kept values is now calculated exactly as the number of values that can be displayed at once, taking step_width and step_spacing into account. It might be worth adding a max_values_num property to the graph to let user override this calculation, in case they draw shapes in step_shape() outside of its supposed rectangle, so our assumptions of visibility are wrong, or the widget often gets resized, resulting in the annoyance of visibly truncated series. commit 0aa63249a18641251a1bf77423739164f0eab39f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 02:30:13 2021 +0700 Fix border_color/border_width bugs in graph widget add_value() wrongly assumed that border_width is always set, when border_color is, so if user set only the latter, an error occurred. add_value() assumed that border_width is always < width which led to awesome hanging up in an infinite loop, should that fail to be the case. The graph border_width property was driven by the border_color property, instead of the other way round. If border_color wasn't set, the border wasn't drawn all, even if user sets non-zero border_width, which is a confusing behavior, in contrast to all other widgets, which simply use a plethora of fallback colors to draw the border, when needed. This also meant that, despite being documented, the beautiful.graph_border_color fallback color could not and was not ever used. If the border_color was set though, an unset border_width was immediately assumed to be 1 instead of 0, also contrary to what other widgets do. Theme colors shouldn't influence widget layout, so this commit fixes this behavior to: 1) border_width always defaults to 0, if unset. 2) border_color falls back to beautiful.graph_border_color or white. Maybe one should also add a beautiful.graph_border_width theme variable, like some widgets do. commit dad4a3253b8fef971fb82720d2a407fcc0a879ad Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 03:11:27 2021 +0700 Implement multiple data series drawing for non-stacked graph The widget can already hold and draw multiple data groups, so it makes little sense to limit drawing only to the first group when graph.stack = false, probably leading to user's confusion. Now user can call add_value(v, group) with group > 1 and the data series will be drawn (over groups with lesser id-s) with graph.stack_colors[group] color, if stack_colors is set, or with graph.color, if stack_colors isn't set. Thus nothing visibly changes for all current users of non-stacked graphs with only single data series, but the new functionality is now there for those who need it. One can't fully emulate this functionality, e.g. with multiple graphs widgets in a wibox.layout.stack, because data series in a single widget can enjoy a common auto-scaling. commit 3e645c976184ab5f50d625271c25062db9ab7ed9 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:59:12 2021 +0700 Don't draw a stacked graph data group if there's no color for it If stack_colors[idx] is nil for the group `idx`, don't draw it at all (as opposed, e.g. to the effect of drawing it with a transparent color, or as though it had all values == 0, which both produce different results). This code is still subtly different from prior behavior, because now user can have a non-contiguous stack_colors table and turn the display of any data group on and off at will, whereas the code two commits earlier would just stop rendering as soon as ipairs(stack_colors) does, i.e. no data series were drawn past the first nil hole. This commit only looks large, but is mostly a whitespace change due to nesting from a pair of wrapping ifs. commit 8c0273786de1ade66bdd977699f98b588da9f487 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:32:42 2021 +0700 Unify stacked and non-stacked graph rendering Now stacked graphs support everything the nonstacked do, like step_width ~= 1 and custom step_shape-s. Technical notes: This code no longer calls cr:stroke() for every single value in a stacked graph, but rather paints a whole series and then cr:stroke/fill()-s it at all once with its color, improving performance considerably. Additionally the graph no longer relies on the presence of the stack_colors property to render series. It draws all data that it has, and, if there's no corresponding color in stack_colors, falls back to graph.color. This will be changed in the next commit to the prior behavior of only drawing those series for which there's a color in the stack_colors table, but that would've made this commit's diff much larger due to required nesting ifs over large code chunks. This commit also takes care to properly handle NaNs and negative values. Those are currently (almost) guaranteed to not occur in the values array due to clipping in graph:add_value(), but I intend to eventually drop the clipping, because it does nothing that draw() really relies upon, and allowing unclipped and NaN values is more flexible and less surprising, e.g. when user dynamically toggles graph.scale. Eventually I intend to implement drawing negative values in non-stacked graphs and possibly even in the stacked ones. commit 38c6589c51b2aa057d7c2f40182ecf32b5b2ff5e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 10:04:15 2021 +0700 Convert graph:draw() to use `self` commit f21f1b2550426df1c4ea8abde079b1ccef84b394 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 12:27:26 2021 +0700 Fix scaling for stacked graphs Stacked graphs have cumulative heights, values from all groups that end up in the same graph bar are effectively summed. Previous code didn't take that into account when calculating min/max_value for the purposes of rescaling, but simply examined every value separately. This code adds up all values up front and finds min/max among those, to determine proper scaling that would fit the graph exactly. commit 1ce4c7bf63bdd6c99c195d668347bec49355e921 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 09:49:35 2021 +0700 Draw shaped graph bars only up to the baseline Prior to this commit, every step_shape bar was, for some reason, drawn with height = full widget's height, regardless of the value it represents. So for the most bars their lower end was far outside the box and therefore clipped off. So e.g. one couldn't see the lower round edges of rounded rectangles. This commit fixes that by appropriately varying the height that is passed into step_shape(), so that lower edges of the bars always exactly touch the lower edge of the graph. If the user wants to restore the prior behavior, they can simply ignore the passed-in height in step_shape() and just always draw their shape with height = full widget height. But they couldn't replicate the effect of this commit in their rc.lua without essentially writing their own graph widget. commit e25185b08da23745b5815f85f6e9e43222ed2294 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 08:42:54 2021 +0700 Avoid incremental transform shifting during graph bar rendering Instead of letting addition errors and cognitive load accumulate simply use the same offset calculation that is used for lines and reset the transform back to the top-left corner after every drawn bar. It's conceptually simpler, and will be useful later for stacked graphs and more. It also fixes a discrepancy between plain and step_shape rendering. Prior to this commit, bar coloring occurred with a disturbed transform, which was centered at the last drawn bar, wherever it happened to be. This meant that nontrivial colors, like gradients and images, were arbitrarily offset when step_shape was used. commit f3e98866d8583c35d0b557c09f529299edc9ba2c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:45:29 2021 +0700 Refactor out graph bar height expression into a variable commit f54528d6892f5aaa4595917853c9e2130f743fdb Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:20:25 2021 +0700 Draw graph bars with lines only when step_width == 1 Before this commit graph bars of step_width < 1 were drawn with lines of width 1, but that's a bit unfortunate, because it led to much overpainting, while still ending up with a graphically incorrect result. Let's draw such bars with rectangles and let cairo do things with subpixels, getting more graphically accurate result, while still keeping the common case of step_width == 1 (supposedly) fast-drawn with lines. commit b6515dca67c18b78eb5657378131a62ab43d4cfc Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:54:07 2021 +0700 Fix unsharp edges when drawing graph bars with rectangles The 0.5 offset is only helpful for 1-width strokes, not for fills. commit 7f3db8f07e82f821bcd1dc418d66564f537609d6 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:21:01 2021 +0700 Do coordinate transformations even if the value isn't drawn The graph presently doesn't draw values < 0, but there was a discrepancy between drawing with and without step_shape. The default line/rectangle drawing reserves an empty place where the skipped value should've been, but no such empty place is reserved, when step_shape is used. This commit fixes that. Skipped values always have their empty space now. commit 7b731ebfe96af7a9dba9c57371da6761cf22c00c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:03:30 2021 +0700 Fix graph bar scaling when min_value is not 0 Previous calculation lead to underscaling of values so that even value = max_value wasn't drawn to full height. commit dca59fd8b7fff0e7e646f2dfc6bd92047028e5a8 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:18:40 2021 +0700 Calculate the horizontal graph bar offset only when needed commit 940ddf4b9825e9c3a91d276208c089d8c74e49db Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:17:23 2021 +0700 Call cr:move_to() only when drawing the graph with lines It's redundant otherwise and might even interfere with user's drawing in step_shape(). commit b5d5cd10ca29fb29cd751faaa95137c2fe89ff63 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:04:06 2021 +0700 Fix wrong horizontal offset calculations in widget.graph Indices in the loop are zero-based, so things like i-1 and i>1 aren't necessary and actually wouldn't have been correct even in a 1-based loop. step_shape = gears.shape.rectangle and step_shape = nil, which is supposed to default to rectangle according to docs, were drawn differently due to mismatched offset calculations, which led to problems especially noticeable when step_spacing is not zero, e.g: 1) whole graph shifted left too much, drawn partially off-screen. 2) whole graph shifted right too much, and the first bar placed closer to the rest than others. commit dc369fa198281e000afc1bd17326f3a264adf7d1 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 02:09:21 2021 +0700 Fix the "border_width affects graph bar width" bug Graph bars were drawn with lines of border_width thickness instead of 1, i.e. possibly too fat to be readable. commit 1ba73378cf29ed27a6ecd18716394779ceb543d9 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 00:13:03 2021 +0700 Simplify internal values representation in graph even more New values in graph._private.values were pushed to the end, and old values were removed from the beginning of the table. In draw() however newer values are drawn first, at the left edge of the graph, necessitating annoying idx = #values - idx conversions everywhere. In particular in stacked graph that means that values that are drawn together may have different indices. One has to shift the values array in add_value() either way, so one might as well insert new values at the beginning of the array and simplify the rest of the code. This doesn't seem like much yet, but it will prove to be much more convenient and less error-prone in what I'm going to do later. commit f70523ee68dbd6b67050ace4b82903a9b22440c7 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 13:35:10 2021 +0700 Simplify internal values representation in graph widget graph._private.values was either a table of values or a table of tables of values, depending on whether "stack" property is enabled, ... or a mix of both if user forgot to pass the second "group" parameter to graph:add_value(). This resulted in pretty fragile and confusing behavior, e.g. leaving user to wonder, why they see nothing more after setting stack=true for a graph in a previously working rc.lua, and demanding a very careful use of add_value() from the user, or else some values or even entire stacks would mysteriously vanish and errors in draw() get triggered, sometimes after a period of seemingly normal functioning. Now `values` is always a table of tables and the group parameter in add_value() defaults to 1, thus making the non-stacked graph a trivial variant of the stacked graph.
2021-05-20 17:14:03 +02:00
-- Colors in this table are used to paint respective data groups.
-- When this property is unset (default), the `color` property is used
-- instead for all data groups.
-- When this property is set, but there's no color for a data group in it
-- (i.e. `group_colors`[group] is nil or false), then the respective
-- data group is disabled, i.e. not drawn.
2021-03-28 11:33:38 +02:00
--
graph: complete widget overhaul and bug fixes Squashed commit of the following: commit 69821f51fe1e8652715658543e50631ece495090 Author: Alex Belykh <albel727@ngs.ru> Date: Mon May 10 19:17:55 2021 +0700 Refactor property handling in wibox.widget.graph In addition, baseline_value, step_width and step_spacing properties are all set to their default non-nil numeric values during instance creation now. commit 842eb429bf5df4b2ba46b2e6f7646afe89b1a3c3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 9 06:18:45 2021 +0700 Make graph:draw_values() private There's no documentation nor confidence that it's a good API, so I'm hiding it for now. commit 720746780574e4bad14a71fc4174955139c2eb50 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 04:04:13 2021 +0700 Document graph_should_draw_data_group() for posterity commit 7c73f3754ce6ba86b118f1170a6f9e406ab4841d Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:59:21 2021 +0700 Make graph:should_draw_data_group() private There's no confidence whether it's a good API, so I'm hiding it for now. commit b3539e20adb6423118af726ae4f8751ef864b5d2 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:36:44 2021 +0700 Make graph:preprocess_values() private There's no confidence whether it's a good API, so I'm hiding it for now. commit 2df4400e780a6260f8d0ab1c60734fbb11bf3c4f Author: Alex Belykh <albel727@ngs.ru> Date: Thu May 6 10:05:23 2021 +0700 Add spec/wibox/widget/graph_spec.lua commit ce01a9771ab439b8df841f1565aa4aa8a44a5e0d Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 08:16:18 2021 +0700 Improve graph.capacity documentation commit e28fade8f655802f5bf89154169f68d256b8e332 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 07:36:01 2021 +0700 Document graph:compute_drawn_values_num() commit dd68332292465422d08b48e9f0229f143beccbc6 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 05:24:23 2021 +0700 Document graph:pick_data_group_color() method commit ca6dc55f77aca88a6e9cc90bb6fd0045739f41f5 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 04:13:58 2021 +0700 Guard against setting data groups sparsely in graph:add_value() commit cdea5f126f7984e72924f6dffbd312c4fe3b032f Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 02:36:12 2021 +0700 Refactor scaling and baseline choice out of graph:draw() commit 10135deafe8a9390ba66ab657ebb1fb41d549e8c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 21:34:48 2021 +0700 Document graph.nan_color and graph.stack properties commit 0d73bb84ac66b87e16aee472bd1f299549f66a1c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 20:49:24 2021 +0700 Use a better example in docs for the graph.stack property commit c27d160a0826d033d49d6141608c40aa8a0642fa Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 16:05:38 2021 +0700 Document graph.baseline_value property commit 459bc8176c13d4ea32af0dcdc2399d3b929b1c6a Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:53:47 2021 +0700 Document graph.clamp_bars property commit e732ee0b3b007d213e689b98cc00ae7cb141ae08 Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:34 2021 +0700 Improve graph:add_value() and :clear() documentation commit ae73e3aa9f8d60409cbe4cb4da9612f31af4f38e Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:11 2021 +0700 Improve graph min_value/max_value/scale documentation commit a7350996a9def773b91b1631e6c0507e33d8f93b Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:27:36 2021 +0700 Cleanup graph examples to show only relevant things in docs commit 30740c26f4f41c16f4c8a3c339bcb7bd299e14cd Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 14:04:36 2021 +0700 Rename graph.stack_colors property to `group_colors` The colors are used for stacked and non-stacked graphs alike, so the name didn't make sense. Also document and clarify some color stuff. commit 1ea01bfd410971cf0d0c25fd74cd764d5eb7279d Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 21:25:41 2021 +0700 Add an example file for code coverage of some fringe graph cases commit eae68e39553f88a6275a0c65bf040b5d4c262b54 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 20:23:32 2021 +0700 Add step_shape to the graph/nan_color example for code coverage commit 8027147bc2eab34a97b49a428fb8cef27f122bed Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:55:18 2021 +0700 Add example files for graph.nan_color/nan_indication properties commit 89a25a165c52b2c15b6c4d95ecb777a9067e66f9 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:33:07 2021 +0700 Make widget graph:add_value(v) default v to NaN 0 as a default makes no sense and silences the error that is failing to specify a value, to which the user should rather be alerted. The graph widget has sane NaN handling now and will do just that. commit 2835d552440be48d356a7f7b1d64963696dd550b Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:20:48 2021 +0700 Implement graph.nan_color and nan_indication properties Now, whenever there's a NaN among values and graph.nan_indication is set, the corresponding area, where the value bar should have been drawn, is filled with the nan_color from top to bottom. The painting is done after all data is rendered, to make sure that it won't be overpainted and go unnoticed. NaNs among graph's values are inevitably a sign of some error and it is arguably not a sane default behavior to simply not render them at all. The user should take immediate note of any errors by default, instead of wondering at a mysteriously empty graph widget, which is why nan_indication is set to true by default. If the user wants to silence NaNs, they can always simply set it to false. nan_color if unset, defaults to a noticeable yellow-black pattern. As a direct and planned consequence, stacked graphs with negative values in them trigger the NaN handling too. But silencing NaN handling for those graphs is also possible and semi-sane, because then NaNs and negatives are ignored and the graph behaves (almost) like they were zeros. commit 9c1f8f08b400e01e19c534f3810c9c9ab485760c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 26 08:10:19 2021 +0700 Refactor out graph_map_value_to_widget_coordinates() function commit 1905991ddecabdae3e15ffb8ee5b18a1bee1a711 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 28 13:39:29 2021 +0700 Add an example file for graph.baseline_value property commit b31b39d66f4c037a9484b71a3f910703ae01fac0 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 07:03:49 2021 +0700 Add an example file for graph.clamp_bars=false/true comparison commit a1fc850e3047f9ee2c9ddeffacfbabf3ded34171 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 04:06:45 2021 +0700 Add an example file for graphs with negative values and shapes commit 2f012f8ff1bb5094c14e724a61e59a2a3a296bc8 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 01:06:05 2021 +0700 Add an example file for a graph displaying negative values commit 7b14fbd59a4c4c51962fd3110a1a900dd5f4eb98 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 11:18:26 2021 +0700 Add an example file for graphs with step_width < 1 commit dfd9a54544b637e2fbe3d1325f9cc24b3f4d2313 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 10:17:38 2021 +0700 Add an example file for graph.stack=false/true comparison Should also demonstrate that multiple data groups are possible for non-stacked graphs too. commit 5b532106c3fd255f9b66a66cfb88d977b489ab5d Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 09:34:14 2021 +0700 Add an example file for disabling graph data groups with nil colors commit ac6f1083c02fcb428f089a616f9b0ebaa5496b6c Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:57:25 2021 +0700 Add an example file for step_shape/width/spacing on stacked graphs commit b3703db6a5139e73e72c1cfd198ece2cd9be7df2 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:43:56 2021 +0700 Add an example file for scale=false/true on stacked graph commit b000aed4a12cb44fb501e1c8432a71a14b2f8f3f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 20 08:22:19 2021 +0700 Document deprecation of graph's width and height commit cb0ad617bd6c83a499896797318e3b0b588c3566 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 07:53:37 2021 +0700 Deprecate width and height properties of wibox.widget.graph commit 8c64e14ad91a074b784166a7054e53444cccb9d2 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 04:37:59 2021 +0700 Refactor graph:draw() to separate concerns Firstly, summation of group values for stacked graphs is extracted into graph:preprocess_values(). This makes graph:draw() smaller and exposes the fact, that stacked graphs are almost entirely just a form of a more general concept: pre-render data manipulation. It's something a user might want to customize, and now they can do it by monkey-patching the method. Secondly, the logic for deciding, if and with which color a data group should be drawn, is extracted into graph:should_draw_data_group() and get_data_group_color() methods respectively. This makes it customizable too and makes more obvious the fact, that it is a general thing that has nothing to do with stacked graphs in particular. With this there are only 5 lines remaining in graph:draw(), that could be said to be specific to stacked graphs, all pertaining to the `prev_y` variable, which merely preserves the bar coordinates from a previous data group. commit 2a32b6305d893f413303728a0a3da1dcd9a5591e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 22:52:37 2021 +0700 Pointlessly optimize graph:add_value() 1) Purge old values first, then insert the new one, if needed. Since Lua grows arrays in power of two steps, and our guess_capacity() also takes care to return even integers, it would be a shame to overstep the capacity by one by inserting first and thus getting the table size doubled only to never use the most of it. 2) Use a removal procedure that doesn't call the slow length operator at all. Previous code called it twice (once in table.remove) for every removed value. commit 680ea8f5d81e9040afb4662b3229e937e4b89e02 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:23:38 2021 +0700 Set graph data series color before the painting loop commit b970f0f221ae76250f2a94433ff79d653caa6e21 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:05:37 2021 +0700 Drop a large no longer necessary `if` in graph:draw_values() This commit is almost purely a whitespace change. commit 6b4f0541c70ec99d7f6e276bae64939f337120d6 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:00:50 2021 +0700 Extract values rendering code into graph:draw_values() method This is to avoid ungodly if nesting levels, which are needed to bail out of drawing values early and skip to drawing the border. The next commit will get rid of the if. commit e0950e4f99d1ea1c4b8cea2cc67629fa40030c2a Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 14:50:20 2021 +0700 Track graph draw() usage to guess necessary array capacity The last step of divorcing add_values() from width calculations, while ensuring that user doesn't observe a lack of data to draw. The widget now tracks an approximate number of values that it has been asked to draw() in _private.last_drawn_values_num, and truncates its values array based on that (if not overridden by the capacity property). commit 2e3d542282f35e7c0fed454da0f2eada0fcf3b78 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 13:35:12 2021 +0700 Add `capacity` property to graph widget This is the second step of divorcing add_values() from calculations involving widths. How many values are stored and how many values are drawn are distinct concepts. There isn't even a single definitive "how many values are drawn" because the widget could be rendered in several places with distinct widths simultaneously, and in every such place the data must be drawn appropriately, e.g. with auto-scaling, that takes only the visible data points into account. And the draw() method can do that now. The default capacity value of nil uses heuristics based on widget's _private.width to guess some capacity that would tolerate some dynamic widget resizing. If the user finds the heuristic failing them, they can set the capacity property to the desired maximum number of values that will be kept in the _private.values array (in each data group). commit 08d4a80686f7fe363551c35589a460839f60853e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 03:27:18 2021 +0700 Extract values size computation into compute_drawn_values_num() This is the first step of making add_values() not concern itself (directly) with widget dimensions. The user can monkey-patch the method to override how many values are (logically) drawn, because it affects graph auto-scaling and can be useful for rendering of step_shape-s that paint outside of their step_width. I didn't make it a full-blown function-valued property, because it seems a fringe functionality, which I'm too lazy to document. commit 3c5617c98ab1147618ed22e8414a77fc56b45f6e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 02:32:29 2021 +0700 Copy graph widget prototype methods over in bulk commit 169538837ece41a03d02ba8fbc0e4097062b67a3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun Apr 25 05:58:01 2021 +0700 Remove graph.baseline_y property After giving it some thought I've concluded, that it is unlikely to be useful as it is now. I'd better reserve the name for some more sensible functionality, like fixing the baseline_value defined axis at the given widget coordinate, adjusting the scaling accordingly, so min_value/max_value won't generally correspond to widget top and bottom anymore. commit 202ffaa9a0b820c7c471e1121d01b91f8f76cd38 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 12:06:55 2021 +0700 Add baseline_y and baseline_value properties to graph widget Now values in graph have a *baseline*, tunable with baseline_y and baseline_value properties. It is the (for now invisible) horizontal line from which all bars in the graph grow. I call it a "baseline" instead of "axis", because it doesn't actually have to correspond to the 0 value. But it can, and actually does now by default. baseline_y and baseline_value properties determine the vertical placement of the baseline in the widget. baseline_y is the position of the baseline in normalized widget coordinates, e.g. 0 corresponds to widget's top, 1 - to widget's bottom, and 0.5 - to widget's middle. So if e.g. one sets baseline_y = 0, then no matter how the widget is resized and how its values are scaled, bars in it will be drawn starting from the top. baseline_value is the position of the baseline in value coordinates. So if one sets baseline_value = 10, then no matter what happens, bars for values greater than 10 will be drawn as growing up from this line, and bars for values smaller as growing down. Where and if it will be seen in the widget, depends on the scaling. baseline_value = 0 is the familiar zero axis. baseline_y is unset by default and has priority, i.e. if it's set, then baseline_value is ignored. baseline_value defaults to 0. Prior to this commit, bars were always drawn from the bottom edge of the widget, or, in the new terms, the widget had baseline_y = 1. But it was indistinguishable from baseline_value = 0, because the widget clamped all negative values to 0, so the difference between the two could never be observed. baseline_value = 0 is clearly more useful and familiar to people, so now it is in force by default. commit df7e19abaebf0a871b5ca217b4cc3365533267a6 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 11:18:12 2021 +0700 Move value clamping from graph:add_value() into draw() Previously all values were forcibly clamped between 0 (not tunable at all) and max_value (1 by default). Thus graphs with negative values were not an option. NaN values were conflated with zeros, even though it may be useful to maintain the distinction and render them differently. And if user happened to dynamically change the max_value property, they discovered, that their data got truncated. Preserving user data as it was given is more flexible and makes things easier to debug. This commit does exactly that. So what happens now to the newly allowed values during draw()? There are 3 new cases to consider: 1) NaNs 2) Negatives 3) Values outside of min_value..max_value range NaNs are presently simply not drawn. An empty place is reserved where the value should've been, but nothing else is done. This is distinct from how a zero is handled, e.g. if step_shape is set, it is called with the height parameter = 0, which can still end up drawing something, depending on the shape. One could also consider addition of nan_color/nan_shape properties which could be drawn to alert user to a gap in their data. Negative values necessitate introduction of the concept of the zero axis, from which they will be drawn DOWN, in contrast to positive values. Where should this axis be drawn? How should step_shape-s with inherent direction, e.g. arrows, be drawn for negatives? This will be addressed in the next commit. Values outside of min/max_value range can imply shapes that go beyond widget dimensions. An arrow shape that goes beyond the roof is visibly different from an arrow that saturates and merely touches the widget top, and the user may reasonably desire either behavior. Therefore this commit introduces a boolean clamp_bars property, which defaults to true for reasons of robustness and backward compatibility with the behaviour prior to this commit. The property of course affects only the display of the values, the actual user data stays intact. commit c9c24fb1ab741cd6bb8074971d568e0646c51532 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:29:01 2021 +0700 Use rectangles for drawing graph bars of all sizes I have benchmarked various bar drawing strategies and graph sizes and found out that drawing graph bars with rectangles is as fast or even marginally faster than using fat lines. Thus the code that tries to special-case 1px bars as lines seems to be not worth the additional complexity it introduces, and this commit removes it. Incidentally, I've also established, that rectangles are more robust. If one draws graph bars and feeds cairo very large coordinates (~2^62), then corresponding rectangles fail to render, but all the other rectangles in the same series get rendered successfully, whereas a single large coordinate in a move_to/line_to() sequence is enough to cause all lines in the batch to fail to render. commit bc7f4be5e06405b5aa7fb443a5ace2b9b9575b1f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:01:42 2021 +0700 Cache cairo methods used in the inner loop of graph:draw() I have benchmarked the draw() method, both on cairo xcb and on argb32 image surfaces, and have discovered that cairo context method calls are unnaturally slow, to the point, that almost nothing else in the function matters. For example, drawing bars with vertical lines made the whole draw() function almost 2 times slower than doing the same with rectangles, merely because the former requires two method calls (move_to/line_to), and the latter only one. Simply caching hotpath cairo methods in local variables, reduced the difference between the drawing methods to a negligible one and made the whole function 4 times faster on a typical 100-bar graph, (1.4ms to 0.33ms on my laptop) thus bringing closer my secret dream of drawing live audio data with awesome graphs (kidding). I guess, LGI does something not very optimal there, like gobject-introspecting the method every time, or something. There might be some untapped optimization potential, possibly in upstream, like a method cache, from which everything in awesome could benefit, even though I imagine not many things are so call-intensive as the graph widget, to really feel the difference. commit 538df25bdf02b53d0bbe81809eef2e6fdb05ff6f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 07:32:37 2021 +0700 Use 1-based index in the inner graph draw loop It's used there only once for a calculation and then several times to index some tables, and index 0 is bad for lua tables performance-wise. commit 7412546916cd0a0a490130a120255ffb719398b8 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:55:12 2021 +0700 Leave graph.max_value uninitialized at construction This is a user-visible change, but the previous behavior was probably not what user expected anyway, namely with graph.scale set the graph mysteriously couldn't zoom in onto values that are smaller than 1. commit eb81d7929a161e79c13262cfa73ac2e6a820e8ed Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:48:29 2021 +0700 Allow graph.max_value be nil This makes its behave symmetrically to min_value. Now it defaults to 1, when graph.scale is not set, and to the maximum value in the graph data, when graph.scale is set. commit f622a8430cbfde7007b58d7f4c9c76b08c91e997 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 12:01:13 2021 +0700 Calculate max values num in graph:add_value() more precisely At most so many values were kept in the widget values array, as there are pixels of usable widget width (i.e. sans border). This is exactly right for the surely most common case of graphs with 1px-wide bars, but is more than needed for graphs with step_width+step_spacing>1, and not enough for graphs with subpixel bars. Moreover this made graph autoscaling behave weirdly in graphs with thicker bars, because old values survived longer in the array and kept affecting min_value/max_value calculations even after they were shifted off-screen. The maximum number of kept values is now calculated exactly as the number of values that can be displayed at once, taking step_width and step_spacing into account. It might be worth adding a max_values_num property to the graph to let user override this calculation, in case they draw shapes in step_shape() outside of its supposed rectangle, so our assumptions of visibility are wrong, or the widget often gets resized, resulting in the annoyance of visibly truncated series. commit 0aa63249a18641251a1bf77423739164f0eab39f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 02:30:13 2021 +0700 Fix border_color/border_width bugs in graph widget add_value() wrongly assumed that border_width is always set, when border_color is, so if user set only the latter, an error occurred. add_value() assumed that border_width is always < width which led to awesome hanging up in an infinite loop, should that fail to be the case. The graph border_width property was driven by the border_color property, instead of the other way round. If border_color wasn't set, the border wasn't drawn all, even if user sets non-zero border_width, which is a confusing behavior, in contrast to all other widgets, which simply use a plethora of fallback colors to draw the border, when needed. This also meant that, despite being documented, the beautiful.graph_border_color fallback color could not and was not ever used. If the border_color was set though, an unset border_width was immediately assumed to be 1 instead of 0, also contrary to what other widgets do. Theme colors shouldn't influence widget layout, so this commit fixes this behavior to: 1) border_width always defaults to 0, if unset. 2) border_color falls back to beautiful.graph_border_color or white. Maybe one should also add a beautiful.graph_border_width theme variable, like some widgets do. commit dad4a3253b8fef971fb82720d2a407fcc0a879ad Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 03:11:27 2021 +0700 Implement multiple data series drawing for non-stacked graph The widget can already hold and draw multiple data groups, so it makes little sense to limit drawing only to the first group when graph.stack = false, probably leading to user's confusion. Now user can call add_value(v, group) with group > 1 and the data series will be drawn (over groups with lesser id-s) with graph.stack_colors[group] color, if stack_colors is set, or with graph.color, if stack_colors isn't set. Thus nothing visibly changes for all current users of non-stacked graphs with only single data series, but the new functionality is now there for those who need it. One can't fully emulate this functionality, e.g. with multiple graphs widgets in a wibox.layout.stack, because data series in a single widget can enjoy a common auto-scaling. commit 3e645c976184ab5f50d625271c25062db9ab7ed9 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:59:12 2021 +0700 Don't draw a stacked graph data group if there's no color for it If stack_colors[idx] is nil for the group `idx`, don't draw it at all (as opposed, e.g. to the effect of drawing it with a transparent color, or as though it had all values == 0, which both produce different results). This code is still subtly different from prior behavior, because now user can have a non-contiguous stack_colors table and turn the display of any data group on and off at will, whereas the code two commits earlier would just stop rendering as soon as ipairs(stack_colors) does, i.e. no data series were drawn past the first nil hole. This commit only looks large, but is mostly a whitespace change due to nesting from a pair of wrapping ifs. commit 8c0273786de1ade66bdd977699f98b588da9f487 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:32:42 2021 +0700 Unify stacked and non-stacked graph rendering Now stacked graphs support everything the nonstacked do, like step_width ~= 1 and custom step_shape-s. Technical notes: This code no longer calls cr:stroke() for every single value in a stacked graph, but rather paints a whole series and then cr:stroke/fill()-s it at all once with its color, improving performance considerably. Additionally the graph no longer relies on the presence of the stack_colors property to render series. It draws all data that it has, and, if there's no corresponding color in stack_colors, falls back to graph.color. This will be changed in the next commit to the prior behavior of only drawing those series for which there's a color in the stack_colors table, but that would've made this commit's diff much larger due to required nesting ifs over large code chunks. This commit also takes care to properly handle NaNs and negative values. Those are currently (almost) guaranteed to not occur in the values array due to clipping in graph:add_value(), but I intend to eventually drop the clipping, because it does nothing that draw() really relies upon, and allowing unclipped and NaN values is more flexible and less surprising, e.g. when user dynamically toggles graph.scale. Eventually I intend to implement drawing negative values in non-stacked graphs and possibly even in the stacked ones. commit 38c6589c51b2aa057d7c2f40182ecf32b5b2ff5e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 10:04:15 2021 +0700 Convert graph:draw() to use `self` commit f21f1b2550426df1c4ea8abde079b1ccef84b394 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 12:27:26 2021 +0700 Fix scaling for stacked graphs Stacked graphs have cumulative heights, values from all groups that end up in the same graph bar are effectively summed. Previous code didn't take that into account when calculating min/max_value for the purposes of rescaling, but simply examined every value separately. This code adds up all values up front and finds min/max among those, to determine proper scaling that would fit the graph exactly. commit 1ce4c7bf63bdd6c99c195d668347bec49355e921 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 09:49:35 2021 +0700 Draw shaped graph bars only up to the baseline Prior to this commit, every step_shape bar was, for some reason, drawn with height = full widget's height, regardless of the value it represents. So for the most bars their lower end was far outside the box and therefore clipped off. So e.g. one couldn't see the lower round edges of rounded rectangles. This commit fixes that by appropriately varying the height that is passed into step_shape(), so that lower edges of the bars always exactly touch the lower edge of the graph. If the user wants to restore the prior behavior, they can simply ignore the passed-in height in step_shape() and just always draw their shape with height = full widget height. But they couldn't replicate the effect of this commit in their rc.lua without essentially writing their own graph widget. commit e25185b08da23745b5815f85f6e9e43222ed2294 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 08:42:54 2021 +0700 Avoid incremental transform shifting during graph bar rendering Instead of letting addition errors and cognitive load accumulate simply use the same offset calculation that is used for lines and reset the transform back to the top-left corner after every drawn bar. It's conceptually simpler, and will be useful later for stacked graphs and more. It also fixes a discrepancy between plain and step_shape rendering. Prior to this commit, bar coloring occurred with a disturbed transform, which was centered at the last drawn bar, wherever it happened to be. This meant that nontrivial colors, like gradients and images, were arbitrarily offset when step_shape was used. commit f3e98866d8583c35d0b557c09f529299edc9ba2c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:45:29 2021 +0700 Refactor out graph bar height expression into a variable commit f54528d6892f5aaa4595917853c9e2130f743fdb Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:20:25 2021 +0700 Draw graph bars with lines only when step_width == 1 Before this commit graph bars of step_width < 1 were drawn with lines of width 1, but that's a bit unfortunate, because it led to much overpainting, while still ending up with a graphically incorrect result. Let's draw such bars with rectangles and let cairo do things with subpixels, getting more graphically accurate result, while still keeping the common case of step_width == 1 (supposedly) fast-drawn with lines. commit b6515dca67c18b78eb5657378131a62ab43d4cfc Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:54:07 2021 +0700 Fix unsharp edges when drawing graph bars with rectangles The 0.5 offset is only helpful for 1-width strokes, not for fills. commit 7f3db8f07e82f821bcd1dc418d66564f537609d6 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:21:01 2021 +0700 Do coordinate transformations even if the value isn't drawn The graph presently doesn't draw values < 0, but there was a discrepancy between drawing with and without step_shape. The default line/rectangle drawing reserves an empty place where the skipped value should've been, but no such empty place is reserved, when step_shape is used. This commit fixes that. Skipped values always have their empty space now. commit 7b731ebfe96af7a9dba9c57371da6761cf22c00c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:03:30 2021 +0700 Fix graph bar scaling when min_value is not 0 Previous calculation lead to underscaling of values so that even value = max_value wasn't drawn to full height. commit dca59fd8b7fff0e7e646f2dfc6bd92047028e5a8 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:18:40 2021 +0700 Calculate the horizontal graph bar offset only when needed commit 940ddf4b9825e9c3a91d276208c089d8c74e49db Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:17:23 2021 +0700 Call cr:move_to() only when drawing the graph with lines It's redundant otherwise and might even interfere with user's drawing in step_shape(). commit b5d5cd10ca29fb29cd751faaa95137c2fe89ff63 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:04:06 2021 +0700 Fix wrong horizontal offset calculations in widget.graph Indices in the loop are zero-based, so things like i-1 and i>1 aren't necessary and actually wouldn't have been correct even in a 1-based loop. step_shape = gears.shape.rectangle and step_shape = nil, which is supposed to default to rectangle according to docs, were drawn differently due to mismatched offset calculations, which led to problems especially noticeable when step_spacing is not zero, e.g: 1) whole graph shifted left too much, drawn partially off-screen. 2) whole graph shifted right too much, and the first bar placed closer to the rest than others. commit dc369fa198281e000afc1bd17326f3a264adf7d1 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 02:09:21 2021 +0700 Fix the "border_width affects graph bar width" bug Graph bars were drawn with lines of border_width thickness instead of 1, i.e. possibly too fat to be readable. commit 1ba73378cf29ed27a6ecd18716394779ceb543d9 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 00:13:03 2021 +0700 Simplify internal values representation in graph even more New values in graph._private.values were pushed to the end, and old values were removed from the beginning of the table. In draw() however newer values are drawn first, at the left edge of the graph, necessitating annoying idx = #values - idx conversions everywhere. In particular in stacked graph that means that values that are drawn together may have different indices. One has to shift the values array in add_value() either way, so one might as well insert new values at the beginning of the array and simplify the rest of the code. This doesn't seem like much yet, but it will prove to be much more convenient and less error-prone in what I'm going to do later. commit f70523ee68dbd6b67050ace4b82903a9b22440c7 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 13:35:10 2021 +0700 Simplify internal values representation in graph widget graph._private.values was either a table of values or a table of tables of values, depending on whether "stack" property is enabled, ... or a mix of both if user forgot to pass the second "group" parameter to graph:add_value(). This resulted in pretty fragile and confusing behavior, e.g. leaving user to wonder, why they see nothing more after setting stack=true for a graph in a previously working rc.lua, and demanding a very careful use of add_value() from the user, or else some values or even entire stacks would mysteriously vanish and errors in draw() get triggered, sometimes after a period of seemingly normal functioning. Now `values` is always a table of tables and the group parameter in add_value() defaults to 1, thus making the non-stacked graph a trivial variant of the stacked graph.
2021-05-20 17:14:03 +02:00
-- @DOC_wibox_widget_graph_stacked_group_disable_EXAMPLE@
--
-- @property group_colors
-- @tparam[opt=self.color] table group_colors A table with colors for data groups.
-- @tablerowtype List of color values.
graph: complete widget overhaul and bug fixes Squashed commit of the following: commit 69821f51fe1e8652715658543e50631ece495090 Author: Alex Belykh <albel727@ngs.ru> Date: Mon May 10 19:17:55 2021 +0700 Refactor property handling in wibox.widget.graph In addition, baseline_value, step_width and step_spacing properties are all set to their default non-nil numeric values during instance creation now. commit 842eb429bf5df4b2ba46b2e6f7646afe89b1a3c3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 9 06:18:45 2021 +0700 Make graph:draw_values() private There's no documentation nor confidence that it's a good API, so I'm hiding it for now. commit 720746780574e4bad14a71fc4174955139c2eb50 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 04:04:13 2021 +0700 Document graph_should_draw_data_group() for posterity commit 7c73f3754ce6ba86b118f1170a6f9e406ab4841d Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:59:21 2021 +0700 Make graph:should_draw_data_group() private There's no confidence whether it's a good API, so I'm hiding it for now. commit b3539e20adb6423118af726ae4f8751ef864b5d2 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:36:44 2021 +0700 Make graph:preprocess_values() private There's no confidence whether it's a good API, so I'm hiding it for now. commit 2df4400e780a6260f8d0ab1c60734fbb11bf3c4f Author: Alex Belykh <albel727@ngs.ru> Date: Thu May 6 10:05:23 2021 +0700 Add spec/wibox/widget/graph_spec.lua commit ce01a9771ab439b8df841f1565aa4aa8a44a5e0d Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 08:16:18 2021 +0700 Improve graph.capacity documentation commit e28fade8f655802f5bf89154169f68d256b8e332 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 07:36:01 2021 +0700 Document graph:compute_drawn_values_num() commit dd68332292465422d08b48e9f0229f143beccbc6 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 05:24:23 2021 +0700 Document graph:pick_data_group_color() method commit ca6dc55f77aca88a6e9cc90bb6fd0045739f41f5 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 04:13:58 2021 +0700 Guard against setting data groups sparsely in graph:add_value() commit cdea5f126f7984e72924f6dffbd312c4fe3b032f Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 02:36:12 2021 +0700 Refactor scaling and baseline choice out of graph:draw() commit 10135deafe8a9390ba66ab657ebb1fb41d549e8c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 21:34:48 2021 +0700 Document graph.nan_color and graph.stack properties commit 0d73bb84ac66b87e16aee472bd1f299549f66a1c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 20:49:24 2021 +0700 Use a better example in docs for the graph.stack property commit c27d160a0826d033d49d6141608c40aa8a0642fa Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 16:05:38 2021 +0700 Document graph.baseline_value property commit 459bc8176c13d4ea32af0dcdc2399d3b929b1c6a Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:53:47 2021 +0700 Document graph.clamp_bars property commit e732ee0b3b007d213e689b98cc00ae7cb141ae08 Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:34 2021 +0700 Improve graph:add_value() and :clear() documentation commit ae73e3aa9f8d60409cbe4cb4da9612f31af4f38e Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:11 2021 +0700 Improve graph min_value/max_value/scale documentation commit a7350996a9def773b91b1631e6c0507e33d8f93b Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:27:36 2021 +0700 Cleanup graph examples to show only relevant things in docs commit 30740c26f4f41c16f4c8a3c339bcb7bd299e14cd Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 14:04:36 2021 +0700 Rename graph.stack_colors property to `group_colors` The colors are used for stacked and non-stacked graphs alike, so the name didn't make sense. Also document and clarify some color stuff. commit 1ea01bfd410971cf0d0c25fd74cd764d5eb7279d Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 21:25:41 2021 +0700 Add an example file for code coverage of some fringe graph cases commit eae68e39553f88a6275a0c65bf040b5d4c262b54 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 20:23:32 2021 +0700 Add step_shape to the graph/nan_color example for code coverage commit 8027147bc2eab34a97b49a428fb8cef27f122bed Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:55:18 2021 +0700 Add example files for graph.nan_color/nan_indication properties commit 89a25a165c52b2c15b6c4d95ecb777a9067e66f9 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:33:07 2021 +0700 Make widget graph:add_value(v) default v to NaN 0 as a default makes no sense and silences the error that is failing to specify a value, to which the user should rather be alerted. The graph widget has sane NaN handling now and will do just that. commit 2835d552440be48d356a7f7b1d64963696dd550b Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:20:48 2021 +0700 Implement graph.nan_color and nan_indication properties Now, whenever there's a NaN among values and graph.nan_indication is set, the corresponding area, where the value bar should have been drawn, is filled with the nan_color from top to bottom. The painting is done after all data is rendered, to make sure that it won't be overpainted and go unnoticed. NaNs among graph's values are inevitably a sign of some error and it is arguably not a sane default behavior to simply not render them at all. The user should take immediate note of any errors by default, instead of wondering at a mysteriously empty graph widget, which is why nan_indication is set to true by default. If the user wants to silence NaNs, they can always simply set it to false. nan_color if unset, defaults to a noticeable yellow-black pattern. As a direct and planned consequence, stacked graphs with negative values in them trigger the NaN handling too. But silencing NaN handling for those graphs is also possible and semi-sane, because then NaNs and negatives are ignored and the graph behaves (almost) like they were zeros. commit 9c1f8f08b400e01e19c534f3810c9c9ab485760c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 26 08:10:19 2021 +0700 Refactor out graph_map_value_to_widget_coordinates() function commit 1905991ddecabdae3e15ffb8ee5b18a1bee1a711 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 28 13:39:29 2021 +0700 Add an example file for graph.baseline_value property commit b31b39d66f4c037a9484b71a3f910703ae01fac0 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 07:03:49 2021 +0700 Add an example file for graph.clamp_bars=false/true comparison commit a1fc850e3047f9ee2c9ddeffacfbabf3ded34171 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 04:06:45 2021 +0700 Add an example file for graphs with negative values and shapes commit 2f012f8ff1bb5094c14e724a61e59a2a3a296bc8 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 01:06:05 2021 +0700 Add an example file for a graph displaying negative values commit 7b14fbd59a4c4c51962fd3110a1a900dd5f4eb98 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 11:18:26 2021 +0700 Add an example file for graphs with step_width < 1 commit dfd9a54544b637e2fbe3d1325f9cc24b3f4d2313 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 10:17:38 2021 +0700 Add an example file for graph.stack=false/true comparison Should also demonstrate that multiple data groups are possible for non-stacked graphs too. commit 5b532106c3fd255f9b66a66cfb88d977b489ab5d Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 09:34:14 2021 +0700 Add an example file for disabling graph data groups with nil colors commit ac6f1083c02fcb428f089a616f9b0ebaa5496b6c Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:57:25 2021 +0700 Add an example file for step_shape/width/spacing on stacked graphs commit b3703db6a5139e73e72c1cfd198ece2cd9be7df2 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:43:56 2021 +0700 Add an example file for scale=false/true on stacked graph commit b000aed4a12cb44fb501e1c8432a71a14b2f8f3f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 20 08:22:19 2021 +0700 Document deprecation of graph's width and height commit cb0ad617bd6c83a499896797318e3b0b588c3566 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 07:53:37 2021 +0700 Deprecate width and height properties of wibox.widget.graph commit 8c64e14ad91a074b784166a7054e53444cccb9d2 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 04:37:59 2021 +0700 Refactor graph:draw() to separate concerns Firstly, summation of group values for stacked graphs is extracted into graph:preprocess_values(). This makes graph:draw() smaller and exposes the fact, that stacked graphs are almost entirely just a form of a more general concept: pre-render data manipulation. It's something a user might want to customize, and now they can do it by monkey-patching the method. Secondly, the logic for deciding, if and with which color a data group should be drawn, is extracted into graph:should_draw_data_group() and get_data_group_color() methods respectively. This makes it customizable too and makes more obvious the fact, that it is a general thing that has nothing to do with stacked graphs in particular. With this there are only 5 lines remaining in graph:draw(), that could be said to be specific to stacked graphs, all pertaining to the `prev_y` variable, which merely preserves the bar coordinates from a previous data group. commit 2a32b6305d893f413303728a0a3da1dcd9a5591e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 22:52:37 2021 +0700 Pointlessly optimize graph:add_value() 1) Purge old values first, then insert the new one, if needed. Since Lua grows arrays in power of two steps, and our guess_capacity() also takes care to return even integers, it would be a shame to overstep the capacity by one by inserting first and thus getting the table size doubled only to never use the most of it. 2) Use a removal procedure that doesn't call the slow length operator at all. Previous code called it twice (once in table.remove) for every removed value. commit 680ea8f5d81e9040afb4662b3229e937e4b89e02 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:23:38 2021 +0700 Set graph data series color before the painting loop commit b970f0f221ae76250f2a94433ff79d653caa6e21 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:05:37 2021 +0700 Drop a large no longer necessary `if` in graph:draw_values() This commit is almost purely a whitespace change. commit 6b4f0541c70ec99d7f6e276bae64939f337120d6 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:00:50 2021 +0700 Extract values rendering code into graph:draw_values() method This is to avoid ungodly if nesting levels, which are needed to bail out of drawing values early and skip to drawing the border. The next commit will get rid of the if. commit e0950e4f99d1ea1c4b8cea2cc67629fa40030c2a Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 14:50:20 2021 +0700 Track graph draw() usage to guess necessary array capacity The last step of divorcing add_values() from width calculations, while ensuring that user doesn't observe a lack of data to draw. The widget now tracks an approximate number of values that it has been asked to draw() in _private.last_drawn_values_num, and truncates its values array based on that (if not overridden by the capacity property). commit 2e3d542282f35e7c0fed454da0f2eada0fcf3b78 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 13:35:12 2021 +0700 Add `capacity` property to graph widget This is the second step of divorcing add_values() from calculations involving widths. How many values are stored and how many values are drawn are distinct concepts. There isn't even a single definitive "how many values are drawn" because the widget could be rendered in several places with distinct widths simultaneously, and in every such place the data must be drawn appropriately, e.g. with auto-scaling, that takes only the visible data points into account. And the draw() method can do that now. The default capacity value of nil uses heuristics based on widget's _private.width to guess some capacity that would tolerate some dynamic widget resizing. If the user finds the heuristic failing them, they can set the capacity property to the desired maximum number of values that will be kept in the _private.values array (in each data group). commit 08d4a80686f7fe363551c35589a460839f60853e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 03:27:18 2021 +0700 Extract values size computation into compute_drawn_values_num() This is the first step of making add_values() not concern itself (directly) with widget dimensions. The user can monkey-patch the method to override how many values are (logically) drawn, because it affects graph auto-scaling and can be useful for rendering of step_shape-s that paint outside of their step_width. I didn't make it a full-blown function-valued property, because it seems a fringe functionality, which I'm too lazy to document. commit 3c5617c98ab1147618ed22e8414a77fc56b45f6e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 02:32:29 2021 +0700 Copy graph widget prototype methods over in bulk commit 169538837ece41a03d02ba8fbc0e4097062b67a3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun Apr 25 05:58:01 2021 +0700 Remove graph.baseline_y property After giving it some thought I've concluded, that it is unlikely to be useful as it is now. I'd better reserve the name for some more sensible functionality, like fixing the baseline_value defined axis at the given widget coordinate, adjusting the scaling accordingly, so min_value/max_value won't generally correspond to widget top and bottom anymore. commit 202ffaa9a0b820c7c471e1121d01b91f8f76cd38 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 12:06:55 2021 +0700 Add baseline_y and baseline_value properties to graph widget Now values in graph have a *baseline*, tunable with baseline_y and baseline_value properties. It is the (for now invisible) horizontal line from which all bars in the graph grow. I call it a "baseline" instead of "axis", because it doesn't actually have to correspond to the 0 value. But it can, and actually does now by default. baseline_y and baseline_value properties determine the vertical placement of the baseline in the widget. baseline_y is the position of the baseline in normalized widget coordinates, e.g. 0 corresponds to widget's top, 1 - to widget's bottom, and 0.5 - to widget's middle. So if e.g. one sets baseline_y = 0, then no matter how the widget is resized and how its values are scaled, bars in it will be drawn starting from the top. baseline_value is the position of the baseline in value coordinates. So if one sets baseline_value = 10, then no matter what happens, bars for values greater than 10 will be drawn as growing up from this line, and bars for values smaller as growing down. Where and if it will be seen in the widget, depends on the scaling. baseline_value = 0 is the familiar zero axis. baseline_y is unset by default and has priority, i.e. if it's set, then baseline_value is ignored. baseline_value defaults to 0. Prior to this commit, bars were always drawn from the bottom edge of the widget, or, in the new terms, the widget had baseline_y = 1. But it was indistinguishable from baseline_value = 0, because the widget clamped all negative values to 0, so the difference between the two could never be observed. baseline_value = 0 is clearly more useful and familiar to people, so now it is in force by default. commit df7e19abaebf0a871b5ca217b4cc3365533267a6 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 11:18:12 2021 +0700 Move value clamping from graph:add_value() into draw() Previously all values were forcibly clamped between 0 (not tunable at all) and max_value (1 by default). Thus graphs with negative values were not an option. NaN values were conflated with zeros, even though it may be useful to maintain the distinction and render them differently. And if user happened to dynamically change the max_value property, they discovered, that their data got truncated. Preserving user data as it was given is more flexible and makes things easier to debug. This commit does exactly that. So what happens now to the newly allowed values during draw()? There are 3 new cases to consider: 1) NaNs 2) Negatives 3) Values outside of min_value..max_value range NaNs are presently simply not drawn. An empty place is reserved where the value should've been, but nothing else is done. This is distinct from how a zero is handled, e.g. if step_shape is set, it is called with the height parameter = 0, which can still end up drawing something, depending on the shape. One could also consider addition of nan_color/nan_shape properties which could be drawn to alert user to a gap in their data. Negative values necessitate introduction of the concept of the zero axis, from which they will be drawn DOWN, in contrast to positive values. Where should this axis be drawn? How should step_shape-s with inherent direction, e.g. arrows, be drawn for negatives? This will be addressed in the next commit. Values outside of min/max_value range can imply shapes that go beyond widget dimensions. An arrow shape that goes beyond the roof is visibly different from an arrow that saturates and merely touches the widget top, and the user may reasonably desire either behavior. Therefore this commit introduces a boolean clamp_bars property, which defaults to true for reasons of robustness and backward compatibility with the behaviour prior to this commit. The property of course affects only the display of the values, the actual user data stays intact. commit c9c24fb1ab741cd6bb8074971d568e0646c51532 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:29:01 2021 +0700 Use rectangles for drawing graph bars of all sizes I have benchmarked various bar drawing strategies and graph sizes and found out that drawing graph bars with rectangles is as fast or even marginally faster than using fat lines. Thus the code that tries to special-case 1px bars as lines seems to be not worth the additional complexity it introduces, and this commit removes it. Incidentally, I've also established, that rectangles are more robust. If one draws graph bars and feeds cairo very large coordinates (~2^62), then corresponding rectangles fail to render, but all the other rectangles in the same series get rendered successfully, whereas a single large coordinate in a move_to/line_to() sequence is enough to cause all lines in the batch to fail to render. commit bc7f4be5e06405b5aa7fb443a5ace2b9b9575b1f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:01:42 2021 +0700 Cache cairo methods used in the inner loop of graph:draw() I have benchmarked the draw() method, both on cairo xcb and on argb32 image surfaces, and have discovered that cairo context method calls are unnaturally slow, to the point, that almost nothing else in the function matters. For example, drawing bars with vertical lines made the whole draw() function almost 2 times slower than doing the same with rectangles, merely because the former requires two method calls (move_to/line_to), and the latter only one. Simply caching hotpath cairo methods in local variables, reduced the difference between the drawing methods to a negligible one and made the whole function 4 times faster on a typical 100-bar graph, (1.4ms to 0.33ms on my laptop) thus bringing closer my secret dream of drawing live audio data with awesome graphs (kidding). I guess, LGI does something not very optimal there, like gobject-introspecting the method every time, or something. There might be some untapped optimization potential, possibly in upstream, like a method cache, from which everything in awesome could benefit, even though I imagine not many things are so call-intensive as the graph widget, to really feel the difference. commit 538df25bdf02b53d0bbe81809eef2e6fdb05ff6f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 07:32:37 2021 +0700 Use 1-based index in the inner graph draw loop It's used there only once for a calculation and then several times to index some tables, and index 0 is bad for lua tables performance-wise. commit 7412546916cd0a0a490130a120255ffb719398b8 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:55:12 2021 +0700 Leave graph.max_value uninitialized at construction This is a user-visible change, but the previous behavior was probably not what user expected anyway, namely with graph.scale set the graph mysteriously couldn't zoom in onto values that are smaller than 1. commit eb81d7929a161e79c13262cfa73ac2e6a820e8ed Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:48:29 2021 +0700 Allow graph.max_value be nil This makes its behave symmetrically to min_value. Now it defaults to 1, when graph.scale is not set, and to the maximum value in the graph data, when graph.scale is set. commit f622a8430cbfde7007b58d7f4c9c76b08c91e997 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 12:01:13 2021 +0700 Calculate max values num in graph:add_value() more precisely At most so many values were kept in the widget values array, as there are pixels of usable widget width (i.e. sans border). This is exactly right for the surely most common case of graphs with 1px-wide bars, but is more than needed for graphs with step_width+step_spacing>1, and not enough for graphs with subpixel bars. Moreover this made graph autoscaling behave weirdly in graphs with thicker bars, because old values survived longer in the array and kept affecting min_value/max_value calculations even after they were shifted off-screen. The maximum number of kept values is now calculated exactly as the number of values that can be displayed at once, taking step_width and step_spacing into account. It might be worth adding a max_values_num property to the graph to let user override this calculation, in case they draw shapes in step_shape() outside of its supposed rectangle, so our assumptions of visibility are wrong, or the widget often gets resized, resulting in the annoyance of visibly truncated series. commit 0aa63249a18641251a1bf77423739164f0eab39f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 02:30:13 2021 +0700 Fix border_color/border_width bugs in graph widget add_value() wrongly assumed that border_width is always set, when border_color is, so if user set only the latter, an error occurred. add_value() assumed that border_width is always < width which led to awesome hanging up in an infinite loop, should that fail to be the case. The graph border_width property was driven by the border_color property, instead of the other way round. If border_color wasn't set, the border wasn't drawn all, even if user sets non-zero border_width, which is a confusing behavior, in contrast to all other widgets, which simply use a plethora of fallback colors to draw the border, when needed. This also meant that, despite being documented, the beautiful.graph_border_color fallback color could not and was not ever used. If the border_color was set though, an unset border_width was immediately assumed to be 1 instead of 0, also contrary to what other widgets do. Theme colors shouldn't influence widget layout, so this commit fixes this behavior to: 1) border_width always defaults to 0, if unset. 2) border_color falls back to beautiful.graph_border_color or white. Maybe one should also add a beautiful.graph_border_width theme variable, like some widgets do. commit dad4a3253b8fef971fb82720d2a407fcc0a879ad Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 03:11:27 2021 +0700 Implement multiple data series drawing for non-stacked graph The widget can already hold and draw multiple data groups, so it makes little sense to limit drawing only to the first group when graph.stack = false, probably leading to user's confusion. Now user can call add_value(v, group) with group > 1 and the data series will be drawn (over groups with lesser id-s) with graph.stack_colors[group] color, if stack_colors is set, or with graph.color, if stack_colors isn't set. Thus nothing visibly changes for all current users of non-stacked graphs with only single data series, but the new functionality is now there for those who need it. One can't fully emulate this functionality, e.g. with multiple graphs widgets in a wibox.layout.stack, because data series in a single widget can enjoy a common auto-scaling. commit 3e645c976184ab5f50d625271c25062db9ab7ed9 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:59:12 2021 +0700 Don't draw a stacked graph data group if there's no color for it If stack_colors[idx] is nil for the group `idx`, don't draw it at all (as opposed, e.g. to the effect of drawing it with a transparent color, or as though it had all values == 0, which both produce different results). This code is still subtly different from prior behavior, because now user can have a non-contiguous stack_colors table and turn the display of any data group on and off at will, whereas the code two commits earlier would just stop rendering as soon as ipairs(stack_colors) does, i.e. no data series were drawn past the first nil hole. This commit only looks large, but is mostly a whitespace change due to nesting from a pair of wrapping ifs. commit 8c0273786de1ade66bdd977699f98b588da9f487 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:32:42 2021 +0700 Unify stacked and non-stacked graph rendering Now stacked graphs support everything the nonstacked do, like step_width ~= 1 and custom step_shape-s. Technical notes: This code no longer calls cr:stroke() for every single value in a stacked graph, but rather paints a whole series and then cr:stroke/fill()-s it at all once with its color, improving performance considerably. Additionally the graph no longer relies on the presence of the stack_colors property to render series. It draws all data that it has, and, if there's no corresponding color in stack_colors, falls back to graph.color. This will be changed in the next commit to the prior behavior of only drawing those series for which there's a color in the stack_colors table, but that would've made this commit's diff much larger due to required nesting ifs over large code chunks. This commit also takes care to properly handle NaNs and negative values. Those are currently (almost) guaranteed to not occur in the values array due to clipping in graph:add_value(), but I intend to eventually drop the clipping, because it does nothing that draw() really relies upon, and allowing unclipped and NaN values is more flexible and less surprising, e.g. when user dynamically toggles graph.scale. Eventually I intend to implement drawing negative values in non-stacked graphs and possibly even in the stacked ones. commit 38c6589c51b2aa057d7c2f40182ecf32b5b2ff5e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 10:04:15 2021 +0700 Convert graph:draw() to use `self` commit f21f1b2550426df1c4ea8abde079b1ccef84b394 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 12:27:26 2021 +0700 Fix scaling for stacked graphs Stacked graphs have cumulative heights, values from all groups that end up in the same graph bar are effectively summed. Previous code didn't take that into account when calculating min/max_value for the purposes of rescaling, but simply examined every value separately. This code adds up all values up front and finds min/max among those, to determine proper scaling that would fit the graph exactly. commit 1ce4c7bf63bdd6c99c195d668347bec49355e921 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 09:49:35 2021 +0700 Draw shaped graph bars only up to the baseline Prior to this commit, every step_shape bar was, for some reason, drawn with height = full widget's height, regardless of the value it represents. So for the most bars their lower end was far outside the box and therefore clipped off. So e.g. one couldn't see the lower round edges of rounded rectangles. This commit fixes that by appropriately varying the height that is passed into step_shape(), so that lower edges of the bars always exactly touch the lower edge of the graph. If the user wants to restore the prior behavior, they can simply ignore the passed-in height in step_shape() and just always draw their shape with height = full widget height. But they couldn't replicate the effect of this commit in their rc.lua without essentially writing their own graph widget. commit e25185b08da23745b5815f85f6e9e43222ed2294 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 08:42:54 2021 +0700 Avoid incremental transform shifting during graph bar rendering Instead of letting addition errors and cognitive load accumulate simply use the same offset calculation that is used for lines and reset the transform back to the top-left corner after every drawn bar. It's conceptually simpler, and will be useful later for stacked graphs and more. It also fixes a discrepancy between plain and step_shape rendering. Prior to this commit, bar coloring occurred with a disturbed transform, which was centered at the last drawn bar, wherever it happened to be. This meant that nontrivial colors, like gradients and images, were arbitrarily offset when step_shape was used. commit f3e98866d8583c35d0b557c09f529299edc9ba2c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:45:29 2021 +0700 Refactor out graph bar height expression into a variable commit f54528d6892f5aaa4595917853c9e2130f743fdb Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:20:25 2021 +0700 Draw graph bars with lines only when step_width == 1 Before this commit graph bars of step_width < 1 were drawn with lines of width 1, but that's a bit unfortunate, because it led to much overpainting, while still ending up with a graphically incorrect result. Let's draw such bars with rectangles and let cairo do things with subpixels, getting more graphically accurate result, while still keeping the common case of step_width == 1 (supposedly) fast-drawn with lines. commit b6515dca67c18b78eb5657378131a62ab43d4cfc Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:54:07 2021 +0700 Fix unsharp edges when drawing graph bars with rectangles The 0.5 offset is only helpful for 1-width strokes, not for fills. commit 7f3db8f07e82f821bcd1dc418d66564f537609d6 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:21:01 2021 +0700 Do coordinate transformations even if the value isn't drawn The graph presently doesn't draw values < 0, but there was a discrepancy between drawing with and without step_shape. The default line/rectangle drawing reserves an empty place where the skipped value should've been, but no such empty place is reserved, when step_shape is used. This commit fixes that. Skipped values always have their empty space now. commit 7b731ebfe96af7a9dba9c57371da6761cf22c00c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:03:30 2021 +0700 Fix graph bar scaling when min_value is not 0 Previous calculation lead to underscaling of values so that even value = max_value wasn't drawn to full height. commit dca59fd8b7fff0e7e646f2dfc6bd92047028e5a8 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:18:40 2021 +0700 Calculate the horizontal graph bar offset only when needed commit 940ddf4b9825e9c3a91d276208c089d8c74e49db Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:17:23 2021 +0700 Call cr:move_to() only when drawing the graph with lines It's redundant otherwise and might even interfere with user's drawing in step_shape(). commit b5d5cd10ca29fb29cd751faaa95137c2fe89ff63 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:04:06 2021 +0700 Fix wrong horizontal offset calculations in widget.graph Indices in the loop are zero-based, so things like i-1 and i>1 aren't necessary and actually wouldn't have been correct even in a 1-based loop. step_shape = gears.shape.rectangle and step_shape = nil, which is supposed to default to rectangle according to docs, were drawn differently due to mismatched offset calculations, which led to problems especially noticeable when step_spacing is not zero, e.g: 1) whole graph shifted left too much, drawn partially off-screen. 2) whole graph shifted right too much, and the first bar placed closer to the rest than others. commit dc369fa198281e000afc1bd17326f3a264adf7d1 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 02:09:21 2021 +0700 Fix the "border_width affects graph bar width" bug Graph bars were drawn with lines of border_width thickness instead of 1, i.e. possibly too fat to be readable. commit 1ba73378cf29ed27a6ecd18716394779ceb543d9 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 00:13:03 2021 +0700 Simplify internal values representation in graph even more New values in graph._private.values were pushed to the end, and old values were removed from the beginning of the table. In draw() however newer values are drawn first, at the left edge of the graph, necessitating annoying idx = #values - idx conversions everywhere. In particular in stacked graph that means that values that are drawn together may have different indices. One has to shift the values array in add_value() either way, so one might as well insert new values at the beginning of the array and simplify the rest of the code. This doesn't seem like much yet, but it will prove to be much more convenient and less error-prone in what I'm going to do later. commit f70523ee68dbd6b67050ace4b82903a9b22440c7 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 13:35:10 2021 +0700 Simplify internal values representation in graph widget graph._private.values was either a table of values or a table of tables of values, depending on whether "stack" property is enabled, ... or a mix of both if user forgot to pass the second "group" parameter to graph:add_value(). This resulted in pretty fragile and confusing behavior, e.g. leaving user to wonder, why they see nothing more after setting stack=true for a graph in a previously working rc.lua, and demanding a very careful use of add_value() from the user, or else some values or even entire stacks would mysteriously vanish and errors in draw() get triggered, sometimes after a period of seemingly normal functioning. Now `values` is always a table of tables and the group parameter in add_value() defaults to 1, thus making the non-stacked graph a trivial variant of the stacked graph.
2021-05-20 17:14:03 +02:00
-- @see gears.color
--- The maximum value the graph should handle.
--
-- This value corresponds to the top of the graph.
-- If `scale` is also set, the graph never scales up below this value, but it
2016-05-23 07:30:44 +02:00
-- automatically scales down to make all data fit.
graph: complete widget overhaul and bug fixes Squashed commit of the following: commit 69821f51fe1e8652715658543e50631ece495090 Author: Alex Belykh <albel727@ngs.ru> Date: Mon May 10 19:17:55 2021 +0700 Refactor property handling in wibox.widget.graph In addition, baseline_value, step_width and step_spacing properties are all set to their default non-nil numeric values during instance creation now. commit 842eb429bf5df4b2ba46b2e6f7646afe89b1a3c3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 9 06:18:45 2021 +0700 Make graph:draw_values() private There's no documentation nor confidence that it's a good API, so I'm hiding it for now. commit 720746780574e4bad14a71fc4174955139c2eb50 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 04:04:13 2021 +0700 Document graph_should_draw_data_group() for posterity commit 7c73f3754ce6ba86b118f1170a6f9e406ab4841d Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:59:21 2021 +0700 Make graph:should_draw_data_group() private There's no confidence whether it's a good API, so I'm hiding it for now. commit b3539e20adb6423118af726ae4f8751ef864b5d2 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:36:44 2021 +0700 Make graph:preprocess_values() private There's no confidence whether it's a good API, so I'm hiding it for now. commit 2df4400e780a6260f8d0ab1c60734fbb11bf3c4f Author: Alex Belykh <albel727@ngs.ru> Date: Thu May 6 10:05:23 2021 +0700 Add spec/wibox/widget/graph_spec.lua commit ce01a9771ab439b8df841f1565aa4aa8a44a5e0d Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 08:16:18 2021 +0700 Improve graph.capacity documentation commit e28fade8f655802f5bf89154169f68d256b8e332 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 07:36:01 2021 +0700 Document graph:compute_drawn_values_num() commit dd68332292465422d08b48e9f0229f143beccbc6 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 05:24:23 2021 +0700 Document graph:pick_data_group_color() method commit ca6dc55f77aca88a6e9cc90bb6fd0045739f41f5 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 04:13:58 2021 +0700 Guard against setting data groups sparsely in graph:add_value() commit cdea5f126f7984e72924f6dffbd312c4fe3b032f Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 02:36:12 2021 +0700 Refactor scaling and baseline choice out of graph:draw() commit 10135deafe8a9390ba66ab657ebb1fb41d549e8c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 21:34:48 2021 +0700 Document graph.nan_color and graph.stack properties commit 0d73bb84ac66b87e16aee472bd1f299549f66a1c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 20:49:24 2021 +0700 Use a better example in docs for the graph.stack property commit c27d160a0826d033d49d6141608c40aa8a0642fa Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 16:05:38 2021 +0700 Document graph.baseline_value property commit 459bc8176c13d4ea32af0dcdc2399d3b929b1c6a Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:53:47 2021 +0700 Document graph.clamp_bars property commit e732ee0b3b007d213e689b98cc00ae7cb141ae08 Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:34 2021 +0700 Improve graph:add_value() and :clear() documentation commit ae73e3aa9f8d60409cbe4cb4da9612f31af4f38e Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:11 2021 +0700 Improve graph min_value/max_value/scale documentation commit a7350996a9def773b91b1631e6c0507e33d8f93b Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:27:36 2021 +0700 Cleanup graph examples to show only relevant things in docs commit 30740c26f4f41c16f4c8a3c339bcb7bd299e14cd Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 14:04:36 2021 +0700 Rename graph.stack_colors property to `group_colors` The colors are used for stacked and non-stacked graphs alike, so the name didn't make sense. Also document and clarify some color stuff. commit 1ea01bfd410971cf0d0c25fd74cd764d5eb7279d Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 21:25:41 2021 +0700 Add an example file for code coverage of some fringe graph cases commit eae68e39553f88a6275a0c65bf040b5d4c262b54 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 20:23:32 2021 +0700 Add step_shape to the graph/nan_color example for code coverage commit 8027147bc2eab34a97b49a428fb8cef27f122bed Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:55:18 2021 +0700 Add example files for graph.nan_color/nan_indication properties commit 89a25a165c52b2c15b6c4d95ecb777a9067e66f9 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:33:07 2021 +0700 Make widget graph:add_value(v) default v to NaN 0 as a default makes no sense and silences the error that is failing to specify a value, to which the user should rather be alerted. The graph widget has sane NaN handling now and will do just that. commit 2835d552440be48d356a7f7b1d64963696dd550b Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:20:48 2021 +0700 Implement graph.nan_color and nan_indication properties Now, whenever there's a NaN among values and graph.nan_indication is set, the corresponding area, where the value bar should have been drawn, is filled with the nan_color from top to bottom. The painting is done after all data is rendered, to make sure that it won't be overpainted and go unnoticed. NaNs among graph's values are inevitably a sign of some error and it is arguably not a sane default behavior to simply not render them at all. The user should take immediate note of any errors by default, instead of wondering at a mysteriously empty graph widget, which is why nan_indication is set to true by default. If the user wants to silence NaNs, they can always simply set it to false. nan_color if unset, defaults to a noticeable yellow-black pattern. As a direct and planned consequence, stacked graphs with negative values in them trigger the NaN handling too. But silencing NaN handling for those graphs is also possible and semi-sane, because then NaNs and negatives are ignored and the graph behaves (almost) like they were zeros. commit 9c1f8f08b400e01e19c534f3810c9c9ab485760c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 26 08:10:19 2021 +0700 Refactor out graph_map_value_to_widget_coordinates() function commit 1905991ddecabdae3e15ffb8ee5b18a1bee1a711 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 28 13:39:29 2021 +0700 Add an example file for graph.baseline_value property commit b31b39d66f4c037a9484b71a3f910703ae01fac0 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 07:03:49 2021 +0700 Add an example file for graph.clamp_bars=false/true comparison commit a1fc850e3047f9ee2c9ddeffacfbabf3ded34171 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 04:06:45 2021 +0700 Add an example file for graphs with negative values and shapes commit 2f012f8ff1bb5094c14e724a61e59a2a3a296bc8 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 01:06:05 2021 +0700 Add an example file for a graph displaying negative values commit 7b14fbd59a4c4c51962fd3110a1a900dd5f4eb98 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 11:18:26 2021 +0700 Add an example file for graphs with step_width < 1 commit dfd9a54544b637e2fbe3d1325f9cc24b3f4d2313 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 10:17:38 2021 +0700 Add an example file for graph.stack=false/true comparison Should also demonstrate that multiple data groups are possible for non-stacked graphs too. commit 5b532106c3fd255f9b66a66cfb88d977b489ab5d Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 09:34:14 2021 +0700 Add an example file for disabling graph data groups with nil colors commit ac6f1083c02fcb428f089a616f9b0ebaa5496b6c Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:57:25 2021 +0700 Add an example file for step_shape/width/spacing on stacked graphs commit b3703db6a5139e73e72c1cfd198ece2cd9be7df2 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:43:56 2021 +0700 Add an example file for scale=false/true on stacked graph commit b000aed4a12cb44fb501e1c8432a71a14b2f8f3f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 20 08:22:19 2021 +0700 Document deprecation of graph's width and height commit cb0ad617bd6c83a499896797318e3b0b588c3566 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 07:53:37 2021 +0700 Deprecate width and height properties of wibox.widget.graph commit 8c64e14ad91a074b784166a7054e53444cccb9d2 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 04:37:59 2021 +0700 Refactor graph:draw() to separate concerns Firstly, summation of group values for stacked graphs is extracted into graph:preprocess_values(). This makes graph:draw() smaller and exposes the fact, that stacked graphs are almost entirely just a form of a more general concept: pre-render data manipulation. It's something a user might want to customize, and now they can do it by monkey-patching the method. Secondly, the logic for deciding, if and with which color a data group should be drawn, is extracted into graph:should_draw_data_group() and get_data_group_color() methods respectively. This makes it customizable too and makes more obvious the fact, that it is a general thing that has nothing to do with stacked graphs in particular. With this there are only 5 lines remaining in graph:draw(), that could be said to be specific to stacked graphs, all pertaining to the `prev_y` variable, which merely preserves the bar coordinates from a previous data group. commit 2a32b6305d893f413303728a0a3da1dcd9a5591e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 22:52:37 2021 +0700 Pointlessly optimize graph:add_value() 1) Purge old values first, then insert the new one, if needed. Since Lua grows arrays in power of two steps, and our guess_capacity() also takes care to return even integers, it would be a shame to overstep the capacity by one by inserting first and thus getting the table size doubled only to never use the most of it. 2) Use a removal procedure that doesn't call the slow length operator at all. Previous code called it twice (once in table.remove) for every removed value. commit 680ea8f5d81e9040afb4662b3229e937e4b89e02 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:23:38 2021 +0700 Set graph data series color before the painting loop commit b970f0f221ae76250f2a94433ff79d653caa6e21 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:05:37 2021 +0700 Drop a large no longer necessary `if` in graph:draw_values() This commit is almost purely a whitespace change. commit 6b4f0541c70ec99d7f6e276bae64939f337120d6 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:00:50 2021 +0700 Extract values rendering code into graph:draw_values() method This is to avoid ungodly if nesting levels, which are needed to bail out of drawing values early and skip to drawing the border. The next commit will get rid of the if. commit e0950e4f99d1ea1c4b8cea2cc67629fa40030c2a Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 14:50:20 2021 +0700 Track graph draw() usage to guess necessary array capacity The last step of divorcing add_values() from width calculations, while ensuring that user doesn't observe a lack of data to draw. The widget now tracks an approximate number of values that it has been asked to draw() in _private.last_drawn_values_num, and truncates its values array based on that (if not overridden by the capacity property). commit 2e3d542282f35e7c0fed454da0f2eada0fcf3b78 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 13:35:12 2021 +0700 Add `capacity` property to graph widget This is the second step of divorcing add_values() from calculations involving widths. How many values are stored and how many values are drawn are distinct concepts. There isn't even a single definitive "how many values are drawn" because the widget could be rendered in several places with distinct widths simultaneously, and in every such place the data must be drawn appropriately, e.g. with auto-scaling, that takes only the visible data points into account. And the draw() method can do that now. The default capacity value of nil uses heuristics based on widget's _private.width to guess some capacity that would tolerate some dynamic widget resizing. If the user finds the heuristic failing them, they can set the capacity property to the desired maximum number of values that will be kept in the _private.values array (in each data group). commit 08d4a80686f7fe363551c35589a460839f60853e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 03:27:18 2021 +0700 Extract values size computation into compute_drawn_values_num() This is the first step of making add_values() not concern itself (directly) with widget dimensions. The user can monkey-patch the method to override how many values are (logically) drawn, because it affects graph auto-scaling and can be useful for rendering of step_shape-s that paint outside of their step_width. I didn't make it a full-blown function-valued property, because it seems a fringe functionality, which I'm too lazy to document. commit 3c5617c98ab1147618ed22e8414a77fc56b45f6e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 02:32:29 2021 +0700 Copy graph widget prototype methods over in bulk commit 169538837ece41a03d02ba8fbc0e4097062b67a3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun Apr 25 05:58:01 2021 +0700 Remove graph.baseline_y property After giving it some thought I've concluded, that it is unlikely to be useful as it is now. I'd better reserve the name for some more sensible functionality, like fixing the baseline_value defined axis at the given widget coordinate, adjusting the scaling accordingly, so min_value/max_value won't generally correspond to widget top and bottom anymore. commit 202ffaa9a0b820c7c471e1121d01b91f8f76cd38 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 12:06:55 2021 +0700 Add baseline_y and baseline_value properties to graph widget Now values in graph have a *baseline*, tunable with baseline_y and baseline_value properties. It is the (for now invisible) horizontal line from which all bars in the graph grow. I call it a "baseline" instead of "axis", because it doesn't actually have to correspond to the 0 value. But it can, and actually does now by default. baseline_y and baseline_value properties determine the vertical placement of the baseline in the widget. baseline_y is the position of the baseline in normalized widget coordinates, e.g. 0 corresponds to widget's top, 1 - to widget's bottom, and 0.5 - to widget's middle. So if e.g. one sets baseline_y = 0, then no matter how the widget is resized and how its values are scaled, bars in it will be drawn starting from the top. baseline_value is the position of the baseline in value coordinates. So if one sets baseline_value = 10, then no matter what happens, bars for values greater than 10 will be drawn as growing up from this line, and bars for values smaller as growing down. Where and if it will be seen in the widget, depends on the scaling. baseline_value = 0 is the familiar zero axis. baseline_y is unset by default and has priority, i.e. if it's set, then baseline_value is ignored. baseline_value defaults to 0. Prior to this commit, bars were always drawn from the bottom edge of the widget, or, in the new terms, the widget had baseline_y = 1. But it was indistinguishable from baseline_value = 0, because the widget clamped all negative values to 0, so the difference between the two could never be observed. baseline_value = 0 is clearly more useful and familiar to people, so now it is in force by default. commit df7e19abaebf0a871b5ca217b4cc3365533267a6 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 11:18:12 2021 +0700 Move value clamping from graph:add_value() into draw() Previously all values were forcibly clamped between 0 (not tunable at all) and max_value (1 by default). Thus graphs with negative values were not an option. NaN values were conflated with zeros, even though it may be useful to maintain the distinction and render them differently. And if user happened to dynamically change the max_value property, they discovered, that their data got truncated. Preserving user data as it was given is more flexible and makes things easier to debug. This commit does exactly that. So what happens now to the newly allowed values during draw()? There are 3 new cases to consider: 1) NaNs 2) Negatives 3) Values outside of min_value..max_value range NaNs are presently simply not drawn. An empty place is reserved where the value should've been, but nothing else is done. This is distinct from how a zero is handled, e.g. if step_shape is set, it is called with the height parameter = 0, which can still end up drawing something, depending on the shape. One could also consider addition of nan_color/nan_shape properties which could be drawn to alert user to a gap in their data. Negative values necessitate introduction of the concept of the zero axis, from which they will be drawn DOWN, in contrast to positive values. Where should this axis be drawn? How should step_shape-s with inherent direction, e.g. arrows, be drawn for negatives? This will be addressed in the next commit. Values outside of min/max_value range can imply shapes that go beyond widget dimensions. An arrow shape that goes beyond the roof is visibly different from an arrow that saturates and merely touches the widget top, and the user may reasonably desire either behavior. Therefore this commit introduces a boolean clamp_bars property, which defaults to true for reasons of robustness and backward compatibility with the behaviour prior to this commit. The property of course affects only the display of the values, the actual user data stays intact. commit c9c24fb1ab741cd6bb8074971d568e0646c51532 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:29:01 2021 +0700 Use rectangles for drawing graph bars of all sizes I have benchmarked various bar drawing strategies and graph sizes and found out that drawing graph bars with rectangles is as fast or even marginally faster than using fat lines. Thus the code that tries to special-case 1px bars as lines seems to be not worth the additional complexity it introduces, and this commit removes it. Incidentally, I've also established, that rectangles are more robust. If one draws graph bars and feeds cairo very large coordinates (~2^62), then corresponding rectangles fail to render, but all the other rectangles in the same series get rendered successfully, whereas a single large coordinate in a move_to/line_to() sequence is enough to cause all lines in the batch to fail to render. commit bc7f4be5e06405b5aa7fb443a5ace2b9b9575b1f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:01:42 2021 +0700 Cache cairo methods used in the inner loop of graph:draw() I have benchmarked the draw() method, both on cairo xcb and on argb32 image surfaces, and have discovered that cairo context method calls are unnaturally slow, to the point, that almost nothing else in the function matters. For example, drawing bars with vertical lines made the whole draw() function almost 2 times slower than doing the same with rectangles, merely because the former requires two method calls (move_to/line_to), and the latter only one. Simply caching hotpath cairo methods in local variables, reduced the difference between the drawing methods to a negligible one and made the whole function 4 times faster on a typical 100-bar graph, (1.4ms to 0.33ms on my laptop) thus bringing closer my secret dream of drawing live audio data with awesome graphs (kidding). I guess, LGI does something not very optimal there, like gobject-introspecting the method every time, or something. There might be some untapped optimization potential, possibly in upstream, like a method cache, from which everything in awesome could benefit, even though I imagine not many things are so call-intensive as the graph widget, to really feel the difference. commit 538df25bdf02b53d0bbe81809eef2e6fdb05ff6f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 07:32:37 2021 +0700 Use 1-based index in the inner graph draw loop It's used there only once for a calculation and then several times to index some tables, and index 0 is bad for lua tables performance-wise. commit 7412546916cd0a0a490130a120255ffb719398b8 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:55:12 2021 +0700 Leave graph.max_value uninitialized at construction This is a user-visible change, but the previous behavior was probably not what user expected anyway, namely with graph.scale set the graph mysteriously couldn't zoom in onto values that are smaller than 1. commit eb81d7929a161e79c13262cfa73ac2e6a820e8ed Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:48:29 2021 +0700 Allow graph.max_value be nil This makes its behave symmetrically to min_value. Now it defaults to 1, when graph.scale is not set, and to the maximum value in the graph data, when graph.scale is set. commit f622a8430cbfde7007b58d7f4c9c76b08c91e997 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 12:01:13 2021 +0700 Calculate max values num in graph:add_value() more precisely At most so many values were kept in the widget values array, as there are pixels of usable widget width (i.e. sans border). This is exactly right for the surely most common case of graphs with 1px-wide bars, but is more than needed for graphs with step_width+step_spacing>1, and not enough for graphs with subpixel bars. Moreover this made graph autoscaling behave weirdly in graphs with thicker bars, because old values survived longer in the array and kept affecting min_value/max_value calculations even after they were shifted off-screen. The maximum number of kept values is now calculated exactly as the number of values that can be displayed at once, taking step_width and step_spacing into account. It might be worth adding a max_values_num property to the graph to let user override this calculation, in case they draw shapes in step_shape() outside of its supposed rectangle, so our assumptions of visibility are wrong, or the widget often gets resized, resulting in the annoyance of visibly truncated series. commit 0aa63249a18641251a1bf77423739164f0eab39f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 02:30:13 2021 +0700 Fix border_color/border_width bugs in graph widget add_value() wrongly assumed that border_width is always set, when border_color is, so if user set only the latter, an error occurred. add_value() assumed that border_width is always < width which led to awesome hanging up in an infinite loop, should that fail to be the case. The graph border_width property was driven by the border_color property, instead of the other way round. If border_color wasn't set, the border wasn't drawn all, even if user sets non-zero border_width, which is a confusing behavior, in contrast to all other widgets, which simply use a plethora of fallback colors to draw the border, when needed. This also meant that, despite being documented, the beautiful.graph_border_color fallback color could not and was not ever used. If the border_color was set though, an unset border_width was immediately assumed to be 1 instead of 0, also contrary to what other widgets do. Theme colors shouldn't influence widget layout, so this commit fixes this behavior to: 1) border_width always defaults to 0, if unset. 2) border_color falls back to beautiful.graph_border_color or white. Maybe one should also add a beautiful.graph_border_width theme variable, like some widgets do. commit dad4a3253b8fef971fb82720d2a407fcc0a879ad Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 03:11:27 2021 +0700 Implement multiple data series drawing for non-stacked graph The widget can already hold and draw multiple data groups, so it makes little sense to limit drawing only to the first group when graph.stack = false, probably leading to user's confusion. Now user can call add_value(v, group) with group > 1 and the data series will be drawn (over groups with lesser id-s) with graph.stack_colors[group] color, if stack_colors is set, or with graph.color, if stack_colors isn't set. Thus nothing visibly changes for all current users of non-stacked graphs with only single data series, but the new functionality is now there for those who need it. One can't fully emulate this functionality, e.g. with multiple graphs widgets in a wibox.layout.stack, because data series in a single widget can enjoy a common auto-scaling. commit 3e645c976184ab5f50d625271c25062db9ab7ed9 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:59:12 2021 +0700 Don't draw a stacked graph data group if there's no color for it If stack_colors[idx] is nil for the group `idx`, don't draw it at all (as opposed, e.g. to the effect of drawing it with a transparent color, or as though it had all values == 0, which both produce different results). This code is still subtly different from prior behavior, because now user can have a non-contiguous stack_colors table and turn the display of any data group on and off at will, whereas the code two commits earlier would just stop rendering as soon as ipairs(stack_colors) does, i.e. no data series were drawn past the first nil hole. This commit only looks large, but is mostly a whitespace change due to nesting from a pair of wrapping ifs. commit 8c0273786de1ade66bdd977699f98b588da9f487 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:32:42 2021 +0700 Unify stacked and non-stacked graph rendering Now stacked graphs support everything the nonstacked do, like step_width ~= 1 and custom step_shape-s. Technical notes: This code no longer calls cr:stroke() for every single value in a stacked graph, but rather paints a whole series and then cr:stroke/fill()-s it at all once with its color, improving performance considerably. Additionally the graph no longer relies on the presence of the stack_colors property to render series. It draws all data that it has, and, if there's no corresponding color in stack_colors, falls back to graph.color. This will be changed in the next commit to the prior behavior of only drawing those series for which there's a color in the stack_colors table, but that would've made this commit's diff much larger due to required nesting ifs over large code chunks. This commit also takes care to properly handle NaNs and negative values. Those are currently (almost) guaranteed to not occur in the values array due to clipping in graph:add_value(), but I intend to eventually drop the clipping, because it does nothing that draw() really relies upon, and allowing unclipped and NaN values is more flexible and less surprising, e.g. when user dynamically toggles graph.scale. Eventually I intend to implement drawing negative values in non-stacked graphs and possibly even in the stacked ones. commit 38c6589c51b2aa057d7c2f40182ecf32b5b2ff5e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 10:04:15 2021 +0700 Convert graph:draw() to use `self` commit f21f1b2550426df1c4ea8abde079b1ccef84b394 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 12:27:26 2021 +0700 Fix scaling for stacked graphs Stacked graphs have cumulative heights, values from all groups that end up in the same graph bar are effectively summed. Previous code didn't take that into account when calculating min/max_value for the purposes of rescaling, but simply examined every value separately. This code adds up all values up front and finds min/max among those, to determine proper scaling that would fit the graph exactly. commit 1ce4c7bf63bdd6c99c195d668347bec49355e921 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 09:49:35 2021 +0700 Draw shaped graph bars only up to the baseline Prior to this commit, every step_shape bar was, for some reason, drawn with height = full widget's height, regardless of the value it represents. So for the most bars their lower end was far outside the box and therefore clipped off. So e.g. one couldn't see the lower round edges of rounded rectangles. This commit fixes that by appropriately varying the height that is passed into step_shape(), so that lower edges of the bars always exactly touch the lower edge of the graph. If the user wants to restore the prior behavior, they can simply ignore the passed-in height in step_shape() and just always draw their shape with height = full widget height. But they couldn't replicate the effect of this commit in their rc.lua without essentially writing their own graph widget. commit e25185b08da23745b5815f85f6e9e43222ed2294 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 08:42:54 2021 +0700 Avoid incremental transform shifting during graph bar rendering Instead of letting addition errors and cognitive load accumulate simply use the same offset calculation that is used for lines and reset the transform back to the top-left corner after every drawn bar. It's conceptually simpler, and will be useful later for stacked graphs and more. It also fixes a discrepancy between plain and step_shape rendering. Prior to this commit, bar coloring occurred with a disturbed transform, which was centered at the last drawn bar, wherever it happened to be. This meant that nontrivial colors, like gradients and images, were arbitrarily offset when step_shape was used. commit f3e98866d8583c35d0b557c09f529299edc9ba2c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:45:29 2021 +0700 Refactor out graph bar height expression into a variable commit f54528d6892f5aaa4595917853c9e2130f743fdb Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:20:25 2021 +0700 Draw graph bars with lines only when step_width == 1 Before this commit graph bars of step_width < 1 were drawn with lines of width 1, but that's a bit unfortunate, because it led to much overpainting, while still ending up with a graphically incorrect result. Let's draw such bars with rectangles and let cairo do things with subpixels, getting more graphically accurate result, while still keeping the common case of step_width == 1 (supposedly) fast-drawn with lines. commit b6515dca67c18b78eb5657378131a62ab43d4cfc Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:54:07 2021 +0700 Fix unsharp edges when drawing graph bars with rectangles The 0.5 offset is only helpful for 1-width strokes, not for fills. commit 7f3db8f07e82f821bcd1dc418d66564f537609d6 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:21:01 2021 +0700 Do coordinate transformations even if the value isn't drawn The graph presently doesn't draw values < 0, but there was a discrepancy between drawing with and without step_shape. The default line/rectangle drawing reserves an empty place where the skipped value should've been, but no such empty place is reserved, when step_shape is used. This commit fixes that. Skipped values always have their empty space now. commit 7b731ebfe96af7a9dba9c57371da6761cf22c00c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:03:30 2021 +0700 Fix graph bar scaling when min_value is not 0 Previous calculation lead to underscaling of values so that even value = max_value wasn't drawn to full height. commit dca59fd8b7fff0e7e646f2dfc6bd92047028e5a8 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:18:40 2021 +0700 Calculate the horizontal graph bar offset only when needed commit 940ddf4b9825e9c3a91d276208c089d8c74e49db Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:17:23 2021 +0700 Call cr:move_to() only when drawing the graph with lines It's redundant otherwise and might even interfere with user's drawing in step_shape(). commit b5d5cd10ca29fb29cd751faaa95137c2fe89ff63 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:04:06 2021 +0700 Fix wrong horizontal offset calculations in widget.graph Indices in the loop are zero-based, so things like i-1 and i>1 aren't necessary and actually wouldn't have been correct even in a 1-based loop. step_shape = gears.shape.rectangle and step_shape = nil, which is supposed to default to rectangle according to docs, were drawn differently due to mismatched offset calculations, which led to problems especially noticeable when step_spacing is not zero, e.g: 1) whole graph shifted left too much, drawn partially off-screen. 2) whole graph shifted right too much, and the first bar placed closer to the rest than others. commit dc369fa198281e000afc1bd17326f3a264adf7d1 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 02:09:21 2021 +0700 Fix the "border_width affects graph bar width" bug Graph bars were drawn with lines of border_width thickness instead of 1, i.e. possibly too fat to be readable. commit 1ba73378cf29ed27a6ecd18716394779ceb543d9 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 00:13:03 2021 +0700 Simplify internal values representation in graph even more New values in graph._private.values were pushed to the end, and old values were removed from the beginning of the table. In draw() however newer values are drawn first, at the left edge of the graph, necessitating annoying idx = #values - idx conversions everywhere. In particular in stacked graph that means that values that are drawn together may have different indices. One has to shift the values array in add_value() either way, so one might as well insert new values at the beginning of the array and simplify the rest of the code. This doesn't seem like much yet, but it will prove to be much more convenient and less error-prone in what I'm going to do later. commit f70523ee68dbd6b67050ace4b82903a9b22440c7 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 13:35:10 2021 +0700 Simplify internal values representation in graph widget graph._private.values was either a table of values or a table of tables of values, depending on whether "stack" property is enabled, ... or a mix of both if user forgot to pass the second "group" parameter to graph:add_value(). This resulted in pretty fragile and confusing behavior, e.g. leaving user to wonder, why they see nothing more after setting stack=true for a graph in a previously working rc.lua, and demanding a very careful use of add_value() from the user, or else some values or even entire stacks would mysteriously vanish and errors in draw() get triggered, sometimes after a period of seemingly normal functioning. Now `values` is always a table of tables and the group parameter in add_value() defaults to 1, thus making the non-stacked graph a trivial variant of the stacked graph.
2021-05-20 17:14:03 +02:00
-- If `scale` and `max_value` are unset, `max_value` defaults to 1.
--
-- @DOC_wibox_widget_graph_max_value_EXAMPLE@
2016-05-23 07:30:44 +02:00
--
-- @property max_value
-- @tparam[opt=1] number max_value
-- @negativeallowed true
-- @propemits true false
2016-05-23 07:30:44 +02:00
graph: complete widget overhaul and bug fixes Squashed commit of the following: commit 69821f51fe1e8652715658543e50631ece495090 Author: Alex Belykh <albel727@ngs.ru> Date: Mon May 10 19:17:55 2021 +0700 Refactor property handling in wibox.widget.graph In addition, baseline_value, step_width and step_spacing properties are all set to their default non-nil numeric values during instance creation now. commit 842eb429bf5df4b2ba46b2e6f7646afe89b1a3c3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 9 06:18:45 2021 +0700 Make graph:draw_values() private There's no documentation nor confidence that it's a good API, so I'm hiding it for now. commit 720746780574e4bad14a71fc4174955139c2eb50 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 04:04:13 2021 +0700 Document graph_should_draw_data_group() for posterity commit 7c73f3754ce6ba86b118f1170a6f9e406ab4841d Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:59:21 2021 +0700 Make graph:should_draw_data_group() private There's no confidence whether it's a good API, so I'm hiding it for now. commit b3539e20adb6423118af726ae4f8751ef864b5d2 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:36:44 2021 +0700 Make graph:preprocess_values() private There's no confidence whether it's a good API, so I'm hiding it for now. commit 2df4400e780a6260f8d0ab1c60734fbb11bf3c4f Author: Alex Belykh <albel727@ngs.ru> Date: Thu May 6 10:05:23 2021 +0700 Add spec/wibox/widget/graph_spec.lua commit ce01a9771ab439b8df841f1565aa4aa8a44a5e0d Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 08:16:18 2021 +0700 Improve graph.capacity documentation commit e28fade8f655802f5bf89154169f68d256b8e332 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 07:36:01 2021 +0700 Document graph:compute_drawn_values_num() commit dd68332292465422d08b48e9f0229f143beccbc6 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 05:24:23 2021 +0700 Document graph:pick_data_group_color() method commit ca6dc55f77aca88a6e9cc90bb6fd0045739f41f5 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 04:13:58 2021 +0700 Guard against setting data groups sparsely in graph:add_value() commit cdea5f126f7984e72924f6dffbd312c4fe3b032f Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 02:36:12 2021 +0700 Refactor scaling and baseline choice out of graph:draw() commit 10135deafe8a9390ba66ab657ebb1fb41d549e8c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 21:34:48 2021 +0700 Document graph.nan_color and graph.stack properties commit 0d73bb84ac66b87e16aee472bd1f299549f66a1c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 20:49:24 2021 +0700 Use a better example in docs for the graph.stack property commit c27d160a0826d033d49d6141608c40aa8a0642fa Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 16:05:38 2021 +0700 Document graph.baseline_value property commit 459bc8176c13d4ea32af0dcdc2399d3b929b1c6a Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:53:47 2021 +0700 Document graph.clamp_bars property commit e732ee0b3b007d213e689b98cc00ae7cb141ae08 Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:34 2021 +0700 Improve graph:add_value() and :clear() documentation commit ae73e3aa9f8d60409cbe4cb4da9612f31af4f38e Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:11 2021 +0700 Improve graph min_value/max_value/scale documentation commit a7350996a9def773b91b1631e6c0507e33d8f93b Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:27:36 2021 +0700 Cleanup graph examples to show only relevant things in docs commit 30740c26f4f41c16f4c8a3c339bcb7bd299e14cd Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 14:04:36 2021 +0700 Rename graph.stack_colors property to `group_colors` The colors are used for stacked and non-stacked graphs alike, so the name didn't make sense. Also document and clarify some color stuff. commit 1ea01bfd410971cf0d0c25fd74cd764d5eb7279d Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 21:25:41 2021 +0700 Add an example file for code coverage of some fringe graph cases commit eae68e39553f88a6275a0c65bf040b5d4c262b54 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 20:23:32 2021 +0700 Add step_shape to the graph/nan_color example for code coverage commit 8027147bc2eab34a97b49a428fb8cef27f122bed Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:55:18 2021 +0700 Add example files for graph.nan_color/nan_indication properties commit 89a25a165c52b2c15b6c4d95ecb777a9067e66f9 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:33:07 2021 +0700 Make widget graph:add_value(v) default v to NaN 0 as a default makes no sense and silences the error that is failing to specify a value, to which the user should rather be alerted. The graph widget has sane NaN handling now and will do just that. commit 2835d552440be48d356a7f7b1d64963696dd550b Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:20:48 2021 +0700 Implement graph.nan_color and nan_indication properties Now, whenever there's a NaN among values and graph.nan_indication is set, the corresponding area, where the value bar should have been drawn, is filled with the nan_color from top to bottom. The painting is done after all data is rendered, to make sure that it won't be overpainted and go unnoticed. NaNs among graph's values are inevitably a sign of some error and it is arguably not a sane default behavior to simply not render them at all. The user should take immediate note of any errors by default, instead of wondering at a mysteriously empty graph widget, which is why nan_indication is set to true by default. If the user wants to silence NaNs, they can always simply set it to false. nan_color if unset, defaults to a noticeable yellow-black pattern. As a direct and planned consequence, stacked graphs with negative values in them trigger the NaN handling too. But silencing NaN handling for those graphs is also possible and semi-sane, because then NaNs and negatives are ignored and the graph behaves (almost) like they were zeros. commit 9c1f8f08b400e01e19c534f3810c9c9ab485760c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 26 08:10:19 2021 +0700 Refactor out graph_map_value_to_widget_coordinates() function commit 1905991ddecabdae3e15ffb8ee5b18a1bee1a711 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 28 13:39:29 2021 +0700 Add an example file for graph.baseline_value property commit b31b39d66f4c037a9484b71a3f910703ae01fac0 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 07:03:49 2021 +0700 Add an example file for graph.clamp_bars=false/true comparison commit a1fc850e3047f9ee2c9ddeffacfbabf3ded34171 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 04:06:45 2021 +0700 Add an example file for graphs with negative values and shapes commit 2f012f8ff1bb5094c14e724a61e59a2a3a296bc8 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 01:06:05 2021 +0700 Add an example file for a graph displaying negative values commit 7b14fbd59a4c4c51962fd3110a1a900dd5f4eb98 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 11:18:26 2021 +0700 Add an example file for graphs with step_width < 1 commit dfd9a54544b637e2fbe3d1325f9cc24b3f4d2313 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 10:17:38 2021 +0700 Add an example file for graph.stack=false/true comparison Should also demonstrate that multiple data groups are possible for non-stacked graphs too. commit 5b532106c3fd255f9b66a66cfb88d977b489ab5d Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 09:34:14 2021 +0700 Add an example file for disabling graph data groups with nil colors commit ac6f1083c02fcb428f089a616f9b0ebaa5496b6c Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:57:25 2021 +0700 Add an example file for step_shape/width/spacing on stacked graphs commit b3703db6a5139e73e72c1cfd198ece2cd9be7df2 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:43:56 2021 +0700 Add an example file for scale=false/true on stacked graph commit b000aed4a12cb44fb501e1c8432a71a14b2f8f3f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 20 08:22:19 2021 +0700 Document deprecation of graph's width and height commit cb0ad617bd6c83a499896797318e3b0b588c3566 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 07:53:37 2021 +0700 Deprecate width and height properties of wibox.widget.graph commit 8c64e14ad91a074b784166a7054e53444cccb9d2 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 04:37:59 2021 +0700 Refactor graph:draw() to separate concerns Firstly, summation of group values for stacked graphs is extracted into graph:preprocess_values(). This makes graph:draw() smaller and exposes the fact, that stacked graphs are almost entirely just a form of a more general concept: pre-render data manipulation. It's something a user might want to customize, and now they can do it by monkey-patching the method. Secondly, the logic for deciding, if and with which color a data group should be drawn, is extracted into graph:should_draw_data_group() and get_data_group_color() methods respectively. This makes it customizable too and makes more obvious the fact, that it is a general thing that has nothing to do with stacked graphs in particular. With this there are only 5 lines remaining in graph:draw(), that could be said to be specific to stacked graphs, all pertaining to the `prev_y` variable, which merely preserves the bar coordinates from a previous data group. commit 2a32b6305d893f413303728a0a3da1dcd9a5591e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 22:52:37 2021 +0700 Pointlessly optimize graph:add_value() 1) Purge old values first, then insert the new one, if needed. Since Lua grows arrays in power of two steps, and our guess_capacity() also takes care to return even integers, it would be a shame to overstep the capacity by one by inserting first and thus getting the table size doubled only to never use the most of it. 2) Use a removal procedure that doesn't call the slow length operator at all. Previous code called it twice (once in table.remove) for every removed value. commit 680ea8f5d81e9040afb4662b3229e937e4b89e02 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:23:38 2021 +0700 Set graph data series color before the painting loop commit b970f0f221ae76250f2a94433ff79d653caa6e21 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:05:37 2021 +0700 Drop a large no longer necessary `if` in graph:draw_values() This commit is almost purely a whitespace change. commit 6b4f0541c70ec99d7f6e276bae64939f337120d6 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:00:50 2021 +0700 Extract values rendering code into graph:draw_values() method This is to avoid ungodly if nesting levels, which are needed to bail out of drawing values early and skip to drawing the border. The next commit will get rid of the if. commit e0950e4f99d1ea1c4b8cea2cc67629fa40030c2a Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 14:50:20 2021 +0700 Track graph draw() usage to guess necessary array capacity The last step of divorcing add_values() from width calculations, while ensuring that user doesn't observe a lack of data to draw. The widget now tracks an approximate number of values that it has been asked to draw() in _private.last_drawn_values_num, and truncates its values array based on that (if not overridden by the capacity property). commit 2e3d542282f35e7c0fed454da0f2eada0fcf3b78 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 13:35:12 2021 +0700 Add `capacity` property to graph widget This is the second step of divorcing add_values() from calculations involving widths. How many values are stored and how many values are drawn are distinct concepts. There isn't even a single definitive "how many values are drawn" because the widget could be rendered in several places with distinct widths simultaneously, and in every such place the data must be drawn appropriately, e.g. with auto-scaling, that takes only the visible data points into account. And the draw() method can do that now. The default capacity value of nil uses heuristics based on widget's _private.width to guess some capacity that would tolerate some dynamic widget resizing. If the user finds the heuristic failing them, they can set the capacity property to the desired maximum number of values that will be kept in the _private.values array (in each data group). commit 08d4a80686f7fe363551c35589a460839f60853e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 03:27:18 2021 +0700 Extract values size computation into compute_drawn_values_num() This is the first step of making add_values() not concern itself (directly) with widget dimensions. The user can monkey-patch the method to override how many values are (logically) drawn, because it affects graph auto-scaling and can be useful for rendering of step_shape-s that paint outside of their step_width. I didn't make it a full-blown function-valued property, because it seems a fringe functionality, which I'm too lazy to document. commit 3c5617c98ab1147618ed22e8414a77fc56b45f6e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 02:32:29 2021 +0700 Copy graph widget prototype methods over in bulk commit 169538837ece41a03d02ba8fbc0e4097062b67a3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun Apr 25 05:58:01 2021 +0700 Remove graph.baseline_y property After giving it some thought I've concluded, that it is unlikely to be useful as it is now. I'd better reserve the name for some more sensible functionality, like fixing the baseline_value defined axis at the given widget coordinate, adjusting the scaling accordingly, so min_value/max_value won't generally correspond to widget top and bottom anymore. commit 202ffaa9a0b820c7c471e1121d01b91f8f76cd38 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 12:06:55 2021 +0700 Add baseline_y and baseline_value properties to graph widget Now values in graph have a *baseline*, tunable with baseline_y and baseline_value properties. It is the (for now invisible) horizontal line from which all bars in the graph grow. I call it a "baseline" instead of "axis", because it doesn't actually have to correspond to the 0 value. But it can, and actually does now by default. baseline_y and baseline_value properties determine the vertical placement of the baseline in the widget. baseline_y is the position of the baseline in normalized widget coordinates, e.g. 0 corresponds to widget's top, 1 - to widget's bottom, and 0.5 - to widget's middle. So if e.g. one sets baseline_y = 0, then no matter how the widget is resized and how its values are scaled, bars in it will be drawn starting from the top. baseline_value is the position of the baseline in value coordinates. So if one sets baseline_value = 10, then no matter what happens, bars for values greater than 10 will be drawn as growing up from this line, and bars for values smaller as growing down. Where and if it will be seen in the widget, depends on the scaling. baseline_value = 0 is the familiar zero axis. baseline_y is unset by default and has priority, i.e. if it's set, then baseline_value is ignored. baseline_value defaults to 0. Prior to this commit, bars were always drawn from the bottom edge of the widget, or, in the new terms, the widget had baseline_y = 1. But it was indistinguishable from baseline_value = 0, because the widget clamped all negative values to 0, so the difference between the two could never be observed. baseline_value = 0 is clearly more useful and familiar to people, so now it is in force by default. commit df7e19abaebf0a871b5ca217b4cc3365533267a6 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 11:18:12 2021 +0700 Move value clamping from graph:add_value() into draw() Previously all values were forcibly clamped between 0 (not tunable at all) and max_value (1 by default). Thus graphs with negative values were not an option. NaN values were conflated with zeros, even though it may be useful to maintain the distinction and render them differently. And if user happened to dynamically change the max_value property, they discovered, that their data got truncated. Preserving user data as it was given is more flexible and makes things easier to debug. This commit does exactly that. So what happens now to the newly allowed values during draw()? There are 3 new cases to consider: 1) NaNs 2) Negatives 3) Values outside of min_value..max_value range NaNs are presently simply not drawn. An empty place is reserved where the value should've been, but nothing else is done. This is distinct from how a zero is handled, e.g. if step_shape is set, it is called with the height parameter = 0, which can still end up drawing something, depending on the shape. One could also consider addition of nan_color/nan_shape properties which could be drawn to alert user to a gap in their data. Negative values necessitate introduction of the concept of the zero axis, from which they will be drawn DOWN, in contrast to positive values. Where should this axis be drawn? How should step_shape-s with inherent direction, e.g. arrows, be drawn for negatives? This will be addressed in the next commit. Values outside of min/max_value range can imply shapes that go beyond widget dimensions. An arrow shape that goes beyond the roof is visibly different from an arrow that saturates and merely touches the widget top, and the user may reasonably desire either behavior. Therefore this commit introduces a boolean clamp_bars property, which defaults to true for reasons of robustness and backward compatibility with the behaviour prior to this commit. The property of course affects only the display of the values, the actual user data stays intact. commit c9c24fb1ab741cd6bb8074971d568e0646c51532 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:29:01 2021 +0700 Use rectangles for drawing graph bars of all sizes I have benchmarked various bar drawing strategies and graph sizes and found out that drawing graph bars with rectangles is as fast or even marginally faster than using fat lines. Thus the code that tries to special-case 1px bars as lines seems to be not worth the additional complexity it introduces, and this commit removes it. Incidentally, I've also established, that rectangles are more robust. If one draws graph bars and feeds cairo very large coordinates (~2^62), then corresponding rectangles fail to render, but all the other rectangles in the same series get rendered successfully, whereas a single large coordinate in a move_to/line_to() sequence is enough to cause all lines in the batch to fail to render. commit bc7f4be5e06405b5aa7fb443a5ace2b9b9575b1f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:01:42 2021 +0700 Cache cairo methods used in the inner loop of graph:draw() I have benchmarked the draw() method, both on cairo xcb and on argb32 image surfaces, and have discovered that cairo context method calls are unnaturally slow, to the point, that almost nothing else in the function matters. For example, drawing bars with vertical lines made the whole draw() function almost 2 times slower than doing the same with rectangles, merely because the former requires two method calls (move_to/line_to), and the latter only one. Simply caching hotpath cairo methods in local variables, reduced the difference between the drawing methods to a negligible one and made the whole function 4 times faster on a typical 100-bar graph, (1.4ms to 0.33ms on my laptop) thus bringing closer my secret dream of drawing live audio data with awesome graphs (kidding). I guess, LGI does something not very optimal there, like gobject-introspecting the method every time, or something. There might be some untapped optimization potential, possibly in upstream, like a method cache, from which everything in awesome could benefit, even though I imagine not many things are so call-intensive as the graph widget, to really feel the difference. commit 538df25bdf02b53d0bbe81809eef2e6fdb05ff6f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 07:32:37 2021 +0700 Use 1-based index in the inner graph draw loop It's used there only once for a calculation and then several times to index some tables, and index 0 is bad for lua tables performance-wise. commit 7412546916cd0a0a490130a120255ffb719398b8 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:55:12 2021 +0700 Leave graph.max_value uninitialized at construction This is a user-visible change, but the previous behavior was probably not what user expected anyway, namely with graph.scale set the graph mysteriously couldn't zoom in onto values that are smaller than 1. commit eb81d7929a161e79c13262cfa73ac2e6a820e8ed Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:48:29 2021 +0700 Allow graph.max_value be nil This makes its behave symmetrically to min_value. Now it defaults to 1, when graph.scale is not set, and to the maximum value in the graph data, when graph.scale is set. commit f622a8430cbfde7007b58d7f4c9c76b08c91e997 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 12:01:13 2021 +0700 Calculate max values num in graph:add_value() more precisely At most so many values were kept in the widget values array, as there are pixels of usable widget width (i.e. sans border). This is exactly right for the surely most common case of graphs with 1px-wide bars, but is more than needed for graphs with step_width+step_spacing>1, and not enough for graphs with subpixel bars. Moreover this made graph autoscaling behave weirdly in graphs with thicker bars, because old values survived longer in the array and kept affecting min_value/max_value calculations even after they were shifted off-screen. The maximum number of kept values is now calculated exactly as the number of values that can be displayed at once, taking step_width and step_spacing into account. It might be worth adding a max_values_num property to the graph to let user override this calculation, in case they draw shapes in step_shape() outside of its supposed rectangle, so our assumptions of visibility are wrong, or the widget often gets resized, resulting in the annoyance of visibly truncated series. commit 0aa63249a18641251a1bf77423739164f0eab39f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 02:30:13 2021 +0700 Fix border_color/border_width bugs in graph widget add_value() wrongly assumed that border_width is always set, when border_color is, so if user set only the latter, an error occurred. add_value() assumed that border_width is always < width which led to awesome hanging up in an infinite loop, should that fail to be the case. The graph border_width property was driven by the border_color property, instead of the other way round. If border_color wasn't set, the border wasn't drawn all, even if user sets non-zero border_width, which is a confusing behavior, in contrast to all other widgets, which simply use a plethora of fallback colors to draw the border, when needed. This also meant that, despite being documented, the beautiful.graph_border_color fallback color could not and was not ever used. If the border_color was set though, an unset border_width was immediately assumed to be 1 instead of 0, also contrary to what other widgets do. Theme colors shouldn't influence widget layout, so this commit fixes this behavior to: 1) border_width always defaults to 0, if unset. 2) border_color falls back to beautiful.graph_border_color or white. Maybe one should also add a beautiful.graph_border_width theme variable, like some widgets do. commit dad4a3253b8fef971fb82720d2a407fcc0a879ad Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 03:11:27 2021 +0700 Implement multiple data series drawing for non-stacked graph The widget can already hold and draw multiple data groups, so it makes little sense to limit drawing only to the first group when graph.stack = false, probably leading to user's confusion. Now user can call add_value(v, group) with group > 1 and the data series will be drawn (over groups with lesser id-s) with graph.stack_colors[group] color, if stack_colors is set, or with graph.color, if stack_colors isn't set. Thus nothing visibly changes for all current users of non-stacked graphs with only single data series, but the new functionality is now there for those who need it. One can't fully emulate this functionality, e.g. with multiple graphs widgets in a wibox.layout.stack, because data series in a single widget can enjoy a common auto-scaling. commit 3e645c976184ab5f50d625271c25062db9ab7ed9 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:59:12 2021 +0700 Don't draw a stacked graph data group if there's no color for it If stack_colors[idx] is nil for the group `idx`, don't draw it at all (as opposed, e.g. to the effect of drawing it with a transparent color, or as though it had all values == 0, which both produce different results). This code is still subtly different from prior behavior, because now user can have a non-contiguous stack_colors table and turn the display of any data group on and off at will, whereas the code two commits earlier would just stop rendering as soon as ipairs(stack_colors) does, i.e. no data series were drawn past the first nil hole. This commit only looks large, but is mostly a whitespace change due to nesting from a pair of wrapping ifs. commit 8c0273786de1ade66bdd977699f98b588da9f487 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:32:42 2021 +0700 Unify stacked and non-stacked graph rendering Now stacked graphs support everything the nonstacked do, like step_width ~= 1 and custom step_shape-s. Technical notes: This code no longer calls cr:stroke() for every single value in a stacked graph, but rather paints a whole series and then cr:stroke/fill()-s it at all once with its color, improving performance considerably. Additionally the graph no longer relies on the presence of the stack_colors property to render series. It draws all data that it has, and, if there's no corresponding color in stack_colors, falls back to graph.color. This will be changed in the next commit to the prior behavior of only drawing those series for which there's a color in the stack_colors table, but that would've made this commit's diff much larger due to required nesting ifs over large code chunks. This commit also takes care to properly handle NaNs and negative values. Those are currently (almost) guaranteed to not occur in the values array due to clipping in graph:add_value(), but I intend to eventually drop the clipping, because it does nothing that draw() really relies upon, and allowing unclipped and NaN values is more flexible and less surprising, e.g. when user dynamically toggles graph.scale. Eventually I intend to implement drawing negative values in non-stacked graphs and possibly even in the stacked ones. commit 38c6589c51b2aa057d7c2f40182ecf32b5b2ff5e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 10:04:15 2021 +0700 Convert graph:draw() to use `self` commit f21f1b2550426df1c4ea8abde079b1ccef84b394 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 12:27:26 2021 +0700 Fix scaling for stacked graphs Stacked graphs have cumulative heights, values from all groups that end up in the same graph bar are effectively summed. Previous code didn't take that into account when calculating min/max_value for the purposes of rescaling, but simply examined every value separately. This code adds up all values up front and finds min/max among those, to determine proper scaling that would fit the graph exactly. commit 1ce4c7bf63bdd6c99c195d668347bec49355e921 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 09:49:35 2021 +0700 Draw shaped graph bars only up to the baseline Prior to this commit, every step_shape bar was, for some reason, drawn with height = full widget's height, regardless of the value it represents. So for the most bars their lower end was far outside the box and therefore clipped off. So e.g. one couldn't see the lower round edges of rounded rectangles. This commit fixes that by appropriately varying the height that is passed into step_shape(), so that lower edges of the bars always exactly touch the lower edge of the graph. If the user wants to restore the prior behavior, they can simply ignore the passed-in height in step_shape() and just always draw their shape with height = full widget height. But they couldn't replicate the effect of this commit in their rc.lua without essentially writing their own graph widget. commit e25185b08da23745b5815f85f6e9e43222ed2294 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 08:42:54 2021 +0700 Avoid incremental transform shifting during graph bar rendering Instead of letting addition errors and cognitive load accumulate simply use the same offset calculation that is used for lines and reset the transform back to the top-left corner after every drawn bar. It's conceptually simpler, and will be useful later for stacked graphs and more. It also fixes a discrepancy between plain and step_shape rendering. Prior to this commit, bar coloring occurred with a disturbed transform, which was centered at the last drawn bar, wherever it happened to be. This meant that nontrivial colors, like gradients and images, were arbitrarily offset when step_shape was used. commit f3e98866d8583c35d0b557c09f529299edc9ba2c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:45:29 2021 +0700 Refactor out graph bar height expression into a variable commit f54528d6892f5aaa4595917853c9e2130f743fdb Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:20:25 2021 +0700 Draw graph bars with lines only when step_width == 1 Before this commit graph bars of step_width < 1 were drawn with lines of width 1, but that's a bit unfortunate, because it led to much overpainting, while still ending up with a graphically incorrect result. Let's draw such bars with rectangles and let cairo do things with subpixels, getting more graphically accurate result, while still keeping the common case of step_width == 1 (supposedly) fast-drawn with lines. commit b6515dca67c18b78eb5657378131a62ab43d4cfc Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:54:07 2021 +0700 Fix unsharp edges when drawing graph bars with rectangles The 0.5 offset is only helpful for 1-width strokes, not for fills. commit 7f3db8f07e82f821bcd1dc418d66564f537609d6 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:21:01 2021 +0700 Do coordinate transformations even if the value isn't drawn The graph presently doesn't draw values < 0, but there was a discrepancy between drawing with and without step_shape. The default line/rectangle drawing reserves an empty place where the skipped value should've been, but no such empty place is reserved, when step_shape is used. This commit fixes that. Skipped values always have their empty space now. commit 7b731ebfe96af7a9dba9c57371da6761cf22c00c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:03:30 2021 +0700 Fix graph bar scaling when min_value is not 0 Previous calculation lead to underscaling of values so that even value = max_value wasn't drawn to full height. commit dca59fd8b7fff0e7e646f2dfc6bd92047028e5a8 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:18:40 2021 +0700 Calculate the horizontal graph bar offset only when needed commit 940ddf4b9825e9c3a91d276208c089d8c74e49db Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:17:23 2021 +0700 Call cr:move_to() only when drawing the graph with lines It's redundant otherwise and might even interfere with user's drawing in step_shape(). commit b5d5cd10ca29fb29cd751faaa95137c2fe89ff63 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:04:06 2021 +0700 Fix wrong horizontal offset calculations in widget.graph Indices in the loop are zero-based, so things like i-1 and i>1 aren't necessary and actually wouldn't have been correct even in a 1-based loop. step_shape = gears.shape.rectangle and step_shape = nil, which is supposed to default to rectangle according to docs, were drawn differently due to mismatched offset calculations, which led to problems especially noticeable when step_spacing is not zero, e.g: 1) whole graph shifted left too much, drawn partially off-screen. 2) whole graph shifted right too much, and the first bar placed closer to the rest than others. commit dc369fa198281e000afc1bd17326f3a264adf7d1 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 02:09:21 2021 +0700 Fix the "border_width affects graph bar width" bug Graph bars were drawn with lines of border_width thickness instead of 1, i.e. possibly too fat to be readable. commit 1ba73378cf29ed27a6ecd18716394779ceb543d9 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 00:13:03 2021 +0700 Simplify internal values representation in graph even more New values in graph._private.values were pushed to the end, and old values were removed from the beginning of the table. In draw() however newer values are drawn first, at the left edge of the graph, necessitating annoying idx = #values - idx conversions everywhere. In particular in stacked graph that means that values that are drawn together may have different indices. One has to shift the values array in add_value() either way, so one might as well insert new values at the beginning of the array and simplify the rest of the code. This doesn't seem like much yet, but it will prove to be much more convenient and less error-prone in what I'm going to do later. commit f70523ee68dbd6b67050ace4b82903a9b22440c7 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 13:35:10 2021 +0700 Simplify internal values representation in graph widget graph._private.values was either a table of values or a table of tables of values, depending on whether "stack" property is enabled, ... or a mix of both if user forgot to pass the second "group" parameter to graph:add_value(). This resulted in pretty fragile and confusing behavior, e.g. leaving user to wonder, why they see nothing more after setting stack=true for a graph in a previously working rc.lua, and demanding a very careful use of add_value() from the user, or else some values or even entire stacks would mysteriously vanish and errors in draw() get triggered, sometimes after a period of seemingly normal functioning. Now `values` is always a table of tables and the group parameter in add_value() defaults to 1, thus making the non-stacked graph a trivial variant of the stacked graph.
2021-05-20 17:14:03 +02:00
--- The minimum value the graph should handle.
--
-- This value corresponds to the bottom of the graph.
-- If `scale` is also set, the graph never scales up above this value, but it
-- automatically scales down to make all data fit.
-- If `scale` and `min_value` are unset, `min_value` defaults to 0.
--
2021-03-28 11:33:38 +02:00
-- @DOC_wibox_widget_graph_min_value_EXAMPLE@
--
2016-08-16 06:59:37 +02:00
-- @property min_value
-- @tparam[opt=0] number min_value
-- @negativeallowed true
-- @propemits true false
2016-08-16 06:59:37 +02:00
--- Set the graph to automatically scale its values.
2016-05-23 07:30:44 +02:00
--
graph: complete widget overhaul and bug fixes Squashed commit of the following: commit 69821f51fe1e8652715658543e50631ece495090 Author: Alex Belykh <albel727@ngs.ru> Date: Mon May 10 19:17:55 2021 +0700 Refactor property handling in wibox.widget.graph In addition, baseline_value, step_width and step_spacing properties are all set to their default non-nil numeric values during instance creation now. commit 842eb429bf5df4b2ba46b2e6f7646afe89b1a3c3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 9 06:18:45 2021 +0700 Make graph:draw_values() private There's no documentation nor confidence that it's a good API, so I'm hiding it for now. commit 720746780574e4bad14a71fc4174955139c2eb50 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 04:04:13 2021 +0700 Document graph_should_draw_data_group() for posterity commit 7c73f3754ce6ba86b118f1170a6f9e406ab4841d Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:59:21 2021 +0700 Make graph:should_draw_data_group() private There's no confidence whether it's a good API, so I'm hiding it for now. commit b3539e20adb6423118af726ae4f8751ef864b5d2 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:36:44 2021 +0700 Make graph:preprocess_values() private There's no confidence whether it's a good API, so I'm hiding it for now. commit 2df4400e780a6260f8d0ab1c60734fbb11bf3c4f Author: Alex Belykh <albel727@ngs.ru> Date: Thu May 6 10:05:23 2021 +0700 Add spec/wibox/widget/graph_spec.lua commit ce01a9771ab439b8df841f1565aa4aa8a44a5e0d Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 08:16:18 2021 +0700 Improve graph.capacity documentation commit e28fade8f655802f5bf89154169f68d256b8e332 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 07:36:01 2021 +0700 Document graph:compute_drawn_values_num() commit dd68332292465422d08b48e9f0229f143beccbc6 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 05:24:23 2021 +0700 Document graph:pick_data_group_color() method commit ca6dc55f77aca88a6e9cc90bb6fd0045739f41f5 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 04:13:58 2021 +0700 Guard against setting data groups sparsely in graph:add_value() commit cdea5f126f7984e72924f6dffbd312c4fe3b032f Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 02:36:12 2021 +0700 Refactor scaling and baseline choice out of graph:draw() commit 10135deafe8a9390ba66ab657ebb1fb41d549e8c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 21:34:48 2021 +0700 Document graph.nan_color and graph.stack properties commit 0d73bb84ac66b87e16aee472bd1f299549f66a1c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 20:49:24 2021 +0700 Use a better example in docs for the graph.stack property commit c27d160a0826d033d49d6141608c40aa8a0642fa Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 16:05:38 2021 +0700 Document graph.baseline_value property commit 459bc8176c13d4ea32af0dcdc2399d3b929b1c6a Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:53:47 2021 +0700 Document graph.clamp_bars property commit e732ee0b3b007d213e689b98cc00ae7cb141ae08 Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:34 2021 +0700 Improve graph:add_value() and :clear() documentation commit ae73e3aa9f8d60409cbe4cb4da9612f31af4f38e Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:11 2021 +0700 Improve graph min_value/max_value/scale documentation commit a7350996a9def773b91b1631e6c0507e33d8f93b Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:27:36 2021 +0700 Cleanup graph examples to show only relevant things in docs commit 30740c26f4f41c16f4c8a3c339bcb7bd299e14cd Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 14:04:36 2021 +0700 Rename graph.stack_colors property to `group_colors` The colors are used for stacked and non-stacked graphs alike, so the name didn't make sense. Also document and clarify some color stuff. commit 1ea01bfd410971cf0d0c25fd74cd764d5eb7279d Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 21:25:41 2021 +0700 Add an example file for code coverage of some fringe graph cases commit eae68e39553f88a6275a0c65bf040b5d4c262b54 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 20:23:32 2021 +0700 Add step_shape to the graph/nan_color example for code coverage commit 8027147bc2eab34a97b49a428fb8cef27f122bed Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:55:18 2021 +0700 Add example files for graph.nan_color/nan_indication properties commit 89a25a165c52b2c15b6c4d95ecb777a9067e66f9 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:33:07 2021 +0700 Make widget graph:add_value(v) default v to NaN 0 as a default makes no sense and silences the error that is failing to specify a value, to which the user should rather be alerted. The graph widget has sane NaN handling now and will do just that. commit 2835d552440be48d356a7f7b1d64963696dd550b Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:20:48 2021 +0700 Implement graph.nan_color and nan_indication properties Now, whenever there's a NaN among values and graph.nan_indication is set, the corresponding area, where the value bar should have been drawn, is filled with the nan_color from top to bottom. The painting is done after all data is rendered, to make sure that it won't be overpainted and go unnoticed. NaNs among graph's values are inevitably a sign of some error and it is arguably not a sane default behavior to simply not render them at all. The user should take immediate note of any errors by default, instead of wondering at a mysteriously empty graph widget, which is why nan_indication is set to true by default. If the user wants to silence NaNs, they can always simply set it to false. nan_color if unset, defaults to a noticeable yellow-black pattern. As a direct and planned consequence, stacked graphs with negative values in them trigger the NaN handling too. But silencing NaN handling for those graphs is also possible and semi-sane, because then NaNs and negatives are ignored and the graph behaves (almost) like they were zeros. commit 9c1f8f08b400e01e19c534f3810c9c9ab485760c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 26 08:10:19 2021 +0700 Refactor out graph_map_value_to_widget_coordinates() function commit 1905991ddecabdae3e15ffb8ee5b18a1bee1a711 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 28 13:39:29 2021 +0700 Add an example file for graph.baseline_value property commit b31b39d66f4c037a9484b71a3f910703ae01fac0 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 07:03:49 2021 +0700 Add an example file for graph.clamp_bars=false/true comparison commit a1fc850e3047f9ee2c9ddeffacfbabf3ded34171 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 04:06:45 2021 +0700 Add an example file for graphs with negative values and shapes commit 2f012f8ff1bb5094c14e724a61e59a2a3a296bc8 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 01:06:05 2021 +0700 Add an example file for a graph displaying negative values commit 7b14fbd59a4c4c51962fd3110a1a900dd5f4eb98 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 11:18:26 2021 +0700 Add an example file for graphs with step_width < 1 commit dfd9a54544b637e2fbe3d1325f9cc24b3f4d2313 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 10:17:38 2021 +0700 Add an example file for graph.stack=false/true comparison Should also demonstrate that multiple data groups are possible for non-stacked graphs too. commit 5b532106c3fd255f9b66a66cfb88d977b489ab5d Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 09:34:14 2021 +0700 Add an example file for disabling graph data groups with nil colors commit ac6f1083c02fcb428f089a616f9b0ebaa5496b6c Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:57:25 2021 +0700 Add an example file for step_shape/width/spacing on stacked graphs commit b3703db6a5139e73e72c1cfd198ece2cd9be7df2 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:43:56 2021 +0700 Add an example file for scale=false/true on stacked graph commit b000aed4a12cb44fb501e1c8432a71a14b2f8f3f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 20 08:22:19 2021 +0700 Document deprecation of graph's width and height commit cb0ad617bd6c83a499896797318e3b0b588c3566 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 07:53:37 2021 +0700 Deprecate width and height properties of wibox.widget.graph commit 8c64e14ad91a074b784166a7054e53444cccb9d2 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 04:37:59 2021 +0700 Refactor graph:draw() to separate concerns Firstly, summation of group values for stacked graphs is extracted into graph:preprocess_values(). This makes graph:draw() smaller and exposes the fact, that stacked graphs are almost entirely just a form of a more general concept: pre-render data manipulation. It's something a user might want to customize, and now they can do it by monkey-patching the method. Secondly, the logic for deciding, if and with which color a data group should be drawn, is extracted into graph:should_draw_data_group() and get_data_group_color() methods respectively. This makes it customizable too and makes more obvious the fact, that it is a general thing that has nothing to do with stacked graphs in particular. With this there are only 5 lines remaining in graph:draw(), that could be said to be specific to stacked graphs, all pertaining to the `prev_y` variable, which merely preserves the bar coordinates from a previous data group. commit 2a32b6305d893f413303728a0a3da1dcd9a5591e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 22:52:37 2021 +0700 Pointlessly optimize graph:add_value() 1) Purge old values first, then insert the new one, if needed. Since Lua grows arrays in power of two steps, and our guess_capacity() also takes care to return even integers, it would be a shame to overstep the capacity by one by inserting first and thus getting the table size doubled only to never use the most of it. 2) Use a removal procedure that doesn't call the slow length operator at all. Previous code called it twice (once in table.remove) for every removed value. commit 680ea8f5d81e9040afb4662b3229e937e4b89e02 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:23:38 2021 +0700 Set graph data series color before the painting loop commit b970f0f221ae76250f2a94433ff79d653caa6e21 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:05:37 2021 +0700 Drop a large no longer necessary `if` in graph:draw_values() This commit is almost purely a whitespace change. commit 6b4f0541c70ec99d7f6e276bae64939f337120d6 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:00:50 2021 +0700 Extract values rendering code into graph:draw_values() method This is to avoid ungodly if nesting levels, which are needed to bail out of drawing values early and skip to drawing the border. The next commit will get rid of the if. commit e0950e4f99d1ea1c4b8cea2cc67629fa40030c2a Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 14:50:20 2021 +0700 Track graph draw() usage to guess necessary array capacity The last step of divorcing add_values() from width calculations, while ensuring that user doesn't observe a lack of data to draw. The widget now tracks an approximate number of values that it has been asked to draw() in _private.last_drawn_values_num, and truncates its values array based on that (if not overridden by the capacity property). commit 2e3d542282f35e7c0fed454da0f2eada0fcf3b78 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 13:35:12 2021 +0700 Add `capacity` property to graph widget This is the second step of divorcing add_values() from calculations involving widths. How many values are stored and how many values are drawn are distinct concepts. There isn't even a single definitive "how many values are drawn" because the widget could be rendered in several places with distinct widths simultaneously, and in every such place the data must be drawn appropriately, e.g. with auto-scaling, that takes only the visible data points into account. And the draw() method can do that now. The default capacity value of nil uses heuristics based on widget's _private.width to guess some capacity that would tolerate some dynamic widget resizing. If the user finds the heuristic failing them, they can set the capacity property to the desired maximum number of values that will be kept in the _private.values array (in each data group). commit 08d4a80686f7fe363551c35589a460839f60853e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 03:27:18 2021 +0700 Extract values size computation into compute_drawn_values_num() This is the first step of making add_values() not concern itself (directly) with widget dimensions. The user can monkey-patch the method to override how many values are (logically) drawn, because it affects graph auto-scaling and can be useful for rendering of step_shape-s that paint outside of their step_width. I didn't make it a full-blown function-valued property, because it seems a fringe functionality, which I'm too lazy to document. commit 3c5617c98ab1147618ed22e8414a77fc56b45f6e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 02:32:29 2021 +0700 Copy graph widget prototype methods over in bulk commit 169538837ece41a03d02ba8fbc0e4097062b67a3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun Apr 25 05:58:01 2021 +0700 Remove graph.baseline_y property After giving it some thought I've concluded, that it is unlikely to be useful as it is now. I'd better reserve the name for some more sensible functionality, like fixing the baseline_value defined axis at the given widget coordinate, adjusting the scaling accordingly, so min_value/max_value won't generally correspond to widget top and bottom anymore. commit 202ffaa9a0b820c7c471e1121d01b91f8f76cd38 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 12:06:55 2021 +0700 Add baseline_y and baseline_value properties to graph widget Now values in graph have a *baseline*, tunable with baseline_y and baseline_value properties. It is the (for now invisible) horizontal line from which all bars in the graph grow. I call it a "baseline" instead of "axis", because it doesn't actually have to correspond to the 0 value. But it can, and actually does now by default. baseline_y and baseline_value properties determine the vertical placement of the baseline in the widget. baseline_y is the position of the baseline in normalized widget coordinates, e.g. 0 corresponds to widget's top, 1 - to widget's bottom, and 0.5 - to widget's middle. So if e.g. one sets baseline_y = 0, then no matter how the widget is resized and how its values are scaled, bars in it will be drawn starting from the top. baseline_value is the position of the baseline in value coordinates. So if one sets baseline_value = 10, then no matter what happens, bars for values greater than 10 will be drawn as growing up from this line, and bars for values smaller as growing down. Where and if it will be seen in the widget, depends on the scaling. baseline_value = 0 is the familiar zero axis. baseline_y is unset by default and has priority, i.e. if it's set, then baseline_value is ignored. baseline_value defaults to 0. Prior to this commit, bars were always drawn from the bottom edge of the widget, or, in the new terms, the widget had baseline_y = 1. But it was indistinguishable from baseline_value = 0, because the widget clamped all negative values to 0, so the difference between the two could never be observed. baseline_value = 0 is clearly more useful and familiar to people, so now it is in force by default. commit df7e19abaebf0a871b5ca217b4cc3365533267a6 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 11:18:12 2021 +0700 Move value clamping from graph:add_value() into draw() Previously all values were forcibly clamped between 0 (not tunable at all) and max_value (1 by default). Thus graphs with negative values were not an option. NaN values were conflated with zeros, even though it may be useful to maintain the distinction and render them differently. And if user happened to dynamically change the max_value property, they discovered, that their data got truncated. Preserving user data as it was given is more flexible and makes things easier to debug. This commit does exactly that. So what happens now to the newly allowed values during draw()? There are 3 new cases to consider: 1) NaNs 2) Negatives 3) Values outside of min_value..max_value range NaNs are presently simply not drawn. An empty place is reserved where the value should've been, but nothing else is done. This is distinct from how a zero is handled, e.g. if step_shape is set, it is called with the height parameter = 0, which can still end up drawing something, depending on the shape. One could also consider addition of nan_color/nan_shape properties which could be drawn to alert user to a gap in their data. Negative values necessitate introduction of the concept of the zero axis, from which they will be drawn DOWN, in contrast to positive values. Where should this axis be drawn? How should step_shape-s with inherent direction, e.g. arrows, be drawn for negatives? This will be addressed in the next commit. Values outside of min/max_value range can imply shapes that go beyond widget dimensions. An arrow shape that goes beyond the roof is visibly different from an arrow that saturates and merely touches the widget top, and the user may reasonably desire either behavior. Therefore this commit introduces a boolean clamp_bars property, which defaults to true for reasons of robustness and backward compatibility with the behaviour prior to this commit. The property of course affects only the display of the values, the actual user data stays intact. commit c9c24fb1ab741cd6bb8074971d568e0646c51532 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:29:01 2021 +0700 Use rectangles for drawing graph bars of all sizes I have benchmarked various bar drawing strategies and graph sizes and found out that drawing graph bars with rectangles is as fast or even marginally faster than using fat lines. Thus the code that tries to special-case 1px bars as lines seems to be not worth the additional complexity it introduces, and this commit removes it. Incidentally, I've also established, that rectangles are more robust. If one draws graph bars and feeds cairo very large coordinates (~2^62), then corresponding rectangles fail to render, but all the other rectangles in the same series get rendered successfully, whereas a single large coordinate in a move_to/line_to() sequence is enough to cause all lines in the batch to fail to render. commit bc7f4be5e06405b5aa7fb443a5ace2b9b9575b1f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:01:42 2021 +0700 Cache cairo methods used in the inner loop of graph:draw() I have benchmarked the draw() method, both on cairo xcb and on argb32 image surfaces, and have discovered that cairo context method calls are unnaturally slow, to the point, that almost nothing else in the function matters. For example, drawing bars with vertical lines made the whole draw() function almost 2 times slower than doing the same with rectangles, merely because the former requires two method calls (move_to/line_to), and the latter only one. Simply caching hotpath cairo methods in local variables, reduced the difference between the drawing methods to a negligible one and made the whole function 4 times faster on a typical 100-bar graph, (1.4ms to 0.33ms on my laptop) thus bringing closer my secret dream of drawing live audio data with awesome graphs (kidding). I guess, LGI does something not very optimal there, like gobject-introspecting the method every time, or something. There might be some untapped optimization potential, possibly in upstream, like a method cache, from which everything in awesome could benefit, even though I imagine not many things are so call-intensive as the graph widget, to really feel the difference. commit 538df25bdf02b53d0bbe81809eef2e6fdb05ff6f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 07:32:37 2021 +0700 Use 1-based index in the inner graph draw loop It's used there only once for a calculation and then several times to index some tables, and index 0 is bad for lua tables performance-wise. commit 7412546916cd0a0a490130a120255ffb719398b8 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:55:12 2021 +0700 Leave graph.max_value uninitialized at construction This is a user-visible change, but the previous behavior was probably not what user expected anyway, namely with graph.scale set the graph mysteriously couldn't zoom in onto values that are smaller than 1. commit eb81d7929a161e79c13262cfa73ac2e6a820e8ed Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:48:29 2021 +0700 Allow graph.max_value be nil This makes its behave symmetrically to min_value. Now it defaults to 1, when graph.scale is not set, and to the maximum value in the graph data, when graph.scale is set. commit f622a8430cbfde7007b58d7f4c9c76b08c91e997 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 12:01:13 2021 +0700 Calculate max values num in graph:add_value() more precisely At most so many values were kept in the widget values array, as there are pixels of usable widget width (i.e. sans border). This is exactly right for the surely most common case of graphs with 1px-wide bars, but is more than needed for graphs with step_width+step_spacing>1, and not enough for graphs with subpixel bars. Moreover this made graph autoscaling behave weirdly in graphs with thicker bars, because old values survived longer in the array and kept affecting min_value/max_value calculations even after they were shifted off-screen. The maximum number of kept values is now calculated exactly as the number of values that can be displayed at once, taking step_width and step_spacing into account. It might be worth adding a max_values_num property to the graph to let user override this calculation, in case they draw shapes in step_shape() outside of its supposed rectangle, so our assumptions of visibility are wrong, or the widget often gets resized, resulting in the annoyance of visibly truncated series. commit 0aa63249a18641251a1bf77423739164f0eab39f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 02:30:13 2021 +0700 Fix border_color/border_width bugs in graph widget add_value() wrongly assumed that border_width is always set, when border_color is, so if user set only the latter, an error occurred. add_value() assumed that border_width is always < width which led to awesome hanging up in an infinite loop, should that fail to be the case. The graph border_width property was driven by the border_color property, instead of the other way round. If border_color wasn't set, the border wasn't drawn all, even if user sets non-zero border_width, which is a confusing behavior, in contrast to all other widgets, which simply use a plethora of fallback colors to draw the border, when needed. This also meant that, despite being documented, the beautiful.graph_border_color fallback color could not and was not ever used. If the border_color was set though, an unset border_width was immediately assumed to be 1 instead of 0, also contrary to what other widgets do. Theme colors shouldn't influence widget layout, so this commit fixes this behavior to: 1) border_width always defaults to 0, if unset. 2) border_color falls back to beautiful.graph_border_color or white. Maybe one should also add a beautiful.graph_border_width theme variable, like some widgets do. commit dad4a3253b8fef971fb82720d2a407fcc0a879ad Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 03:11:27 2021 +0700 Implement multiple data series drawing for non-stacked graph The widget can already hold and draw multiple data groups, so it makes little sense to limit drawing only to the first group when graph.stack = false, probably leading to user's confusion. Now user can call add_value(v, group) with group > 1 and the data series will be drawn (over groups with lesser id-s) with graph.stack_colors[group] color, if stack_colors is set, or with graph.color, if stack_colors isn't set. Thus nothing visibly changes for all current users of non-stacked graphs with only single data series, but the new functionality is now there for those who need it. One can't fully emulate this functionality, e.g. with multiple graphs widgets in a wibox.layout.stack, because data series in a single widget can enjoy a common auto-scaling. commit 3e645c976184ab5f50d625271c25062db9ab7ed9 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:59:12 2021 +0700 Don't draw a stacked graph data group if there's no color for it If stack_colors[idx] is nil for the group `idx`, don't draw it at all (as opposed, e.g. to the effect of drawing it with a transparent color, or as though it had all values == 0, which both produce different results). This code is still subtly different from prior behavior, because now user can have a non-contiguous stack_colors table and turn the display of any data group on and off at will, whereas the code two commits earlier would just stop rendering as soon as ipairs(stack_colors) does, i.e. no data series were drawn past the first nil hole. This commit only looks large, but is mostly a whitespace change due to nesting from a pair of wrapping ifs. commit 8c0273786de1ade66bdd977699f98b588da9f487 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:32:42 2021 +0700 Unify stacked and non-stacked graph rendering Now stacked graphs support everything the nonstacked do, like step_width ~= 1 and custom step_shape-s. Technical notes: This code no longer calls cr:stroke() for every single value in a stacked graph, but rather paints a whole series and then cr:stroke/fill()-s it at all once with its color, improving performance considerably. Additionally the graph no longer relies on the presence of the stack_colors property to render series. It draws all data that it has, and, if there's no corresponding color in stack_colors, falls back to graph.color. This will be changed in the next commit to the prior behavior of only drawing those series for which there's a color in the stack_colors table, but that would've made this commit's diff much larger due to required nesting ifs over large code chunks. This commit also takes care to properly handle NaNs and negative values. Those are currently (almost) guaranteed to not occur in the values array due to clipping in graph:add_value(), but I intend to eventually drop the clipping, because it does nothing that draw() really relies upon, and allowing unclipped and NaN values is more flexible and less surprising, e.g. when user dynamically toggles graph.scale. Eventually I intend to implement drawing negative values in non-stacked graphs and possibly even in the stacked ones. commit 38c6589c51b2aa057d7c2f40182ecf32b5b2ff5e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 10:04:15 2021 +0700 Convert graph:draw() to use `self` commit f21f1b2550426df1c4ea8abde079b1ccef84b394 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 12:27:26 2021 +0700 Fix scaling for stacked graphs Stacked graphs have cumulative heights, values from all groups that end up in the same graph bar are effectively summed. Previous code didn't take that into account when calculating min/max_value for the purposes of rescaling, but simply examined every value separately. This code adds up all values up front and finds min/max among those, to determine proper scaling that would fit the graph exactly. commit 1ce4c7bf63bdd6c99c195d668347bec49355e921 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 09:49:35 2021 +0700 Draw shaped graph bars only up to the baseline Prior to this commit, every step_shape bar was, for some reason, drawn with height = full widget's height, regardless of the value it represents. So for the most bars their lower end was far outside the box and therefore clipped off. So e.g. one couldn't see the lower round edges of rounded rectangles. This commit fixes that by appropriately varying the height that is passed into step_shape(), so that lower edges of the bars always exactly touch the lower edge of the graph. If the user wants to restore the prior behavior, they can simply ignore the passed-in height in step_shape() and just always draw their shape with height = full widget height. But they couldn't replicate the effect of this commit in their rc.lua without essentially writing their own graph widget. commit e25185b08da23745b5815f85f6e9e43222ed2294 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 08:42:54 2021 +0700 Avoid incremental transform shifting during graph bar rendering Instead of letting addition errors and cognitive load accumulate simply use the same offset calculation that is used for lines and reset the transform back to the top-left corner after every drawn bar. It's conceptually simpler, and will be useful later for stacked graphs and more. It also fixes a discrepancy between plain and step_shape rendering. Prior to this commit, bar coloring occurred with a disturbed transform, which was centered at the last drawn bar, wherever it happened to be. This meant that nontrivial colors, like gradients and images, were arbitrarily offset when step_shape was used. commit f3e98866d8583c35d0b557c09f529299edc9ba2c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:45:29 2021 +0700 Refactor out graph bar height expression into a variable commit f54528d6892f5aaa4595917853c9e2130f743fdb Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:20:25 2021 +0700 Draw graph bars with lines only when step_width == 1 Before this commit graph bars of step_width < 1 were drawn with lines of width 1, but that's a bit unfortunate, because it led to much overpainting, while still ending up with a graphically incorrect result. Let's draw such bars with rectangles and let cairo do things with subpixels, getting more graphically accurate result, while still keeping the common case of step_width == 1 (supposedly) fast-drawn with lines. commit b6515dca67c18b78eb5657378131a62ab43d4cfc Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:54:07 2021 +0700 Fix unsharp edges when drawing graph bars with rectangles The 0.5 offset is only helpful for 1-width strokes, not for fills. commit 7f3db8f07e82f821bcd1dc418d66564f537609d6 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:21:01 2021 +0700 Do coordinate transformations even if the value isn't drawn The graph presently doesn't draw values < 0, but there was a discrepancy between drawing with and without step_shape. The default line/rectangle drawing reserves an empty place where the skipped value should've been, but no such empty place is reserved, when step_shape is used. This commit fixes that. Skipped values always have their empty space now. commit 7b731ebfe96af7a9dba9c57371da6761cf22c00c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:03:30 2021 +0700 Fix graph bar scaling when min_value is not 0 Previous calculation lead to underscaling of values so that even value = max_value wasn't drawn to full height. commit dca59fd8b7fff0e7e646f2dfc6bd92047028e5a8 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:18:40 2021 +0700 Calculate the horizontal graph bar offset only when needed commit 940ddf4b9825e9c3a91d276208c089d8c74e49db Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:17:23 2021 +0700 Call cr:move_to() only when drawing the graph with lines It's redundant otherwise and might even interfere with user's drawing in step_shape(). commit b5d5cd10ca29fb29cd751faaa95137c2fe89ff63 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:04:06 2021 +0700 Fix wrong horizontal offset calculations in widget.graph Indices in the loop are zero-based, so things like i-1 and i>1 aren't necessary and actually wouldn't have been correct even in a 1-based loop. step_shape = gears.shape.rectangle and step_shape = nil, which is supposed to default to rectangle according to docs, were drawn differently due to mismatched offset calculations, which led to problems especially noticeable when step_spacing is not zero, e.g: 1) whole graph shifted left too much, drawn partially off-screen. 2) whole graph shifted right too much, and the first bar placed closer to the rest than others. commit dc369fa198281e000afc1bd17326f3a264adf7d1 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 02:09:21 2021 +0700 Fix the "border_width affects graph bar width" bug Graph bars were drawn with lines of border_width thickness instead of 1, i.e. possibly too fat to be readable. commit 1ba73378cf29ed27a6ecd18716394779ceb543d9 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 00:13:03 2021 +0700 Simplify internal values representation in graph even more New values in graph._private.values were pushed to the end, and old values were removed from the beginning of the table. In draw() however newer values are drawn first, at the left edge of the graph, necessitating annoying idx = #values - idx conversions everywhere. In particular in stacked graph that means that values that are drawn together may have different indices. One has to shift the values array in add_value() either way, so one might as well insert new values at the beginning of the array and simplify the rest of the code. This doesn't seem like much yet, but it will prove to be much more convenient and less error-prone in what I'm going to do later. commit f70523ee68dbd6b67050ace4b82903a9b22440c7 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 13:35:10 2021 +0700 Simplify internal values representation in graph widget graph._private.values was either a table of values or a table of tables of values, depending on whether "stack" property is enabled, ... or a mix of both if user forgot to pass the second "group" parameter to graph:add_value(). This resulted in pretty fragile and confusing behavior, e.g. leaving user to wonder, why they see nothing more after setting stack=true for a graph in a previously working rc.lua, and demanding a very careful use of add_value() from the user, or else some values or even entire stacks would mysteriously vanish and errors in draw() get triggered, sometimes after a period of seemingly normal functioning. Now `values` is always a table of tables and the group parameter in add_value() defaults to 1, thus making the non-stacked graph a trivial variant of the stacked graph.
2021-05-20 17:14:03 +02:00
-- If this property is set to true, the graph calculates
-- effective `min_value` and `max_value` based on the displayed data,
-- so that all data fits on screen. The properties themselves aren't changed,
-- but the graph is drawn as though `min_value`(`max_value`) were equal to
-- the minimum(maximum) value among itself and the currently drawn values.
-- If `min_value`(`max_value`) is unset, then only the drawn values
-- are considered in this calculation.
--
-- @DOC_wibox_widget_graph_scale1_EXAMPLE@
2021-03-28 11:33:38 +02:00
--
-- @property scale
-- @tparam[opt=false] boolean scale
-- @propemits true false
2016-05-23 07:30:44 +02:00
graph: complete widget overhaul and bug fixes Squashed commit of the following: commit 69821f51fe1e8652715658543e50631ece495090 Author: Alex Belykh <albel727@ngs.ru> Date: Mon May 10 19:17:55 2021 +0700 Refactor property handling in wibox.widget.graph In addition, baseline_value, step_width and step_spacing properties are all set to their default non-nil numeric values during instance creation now. commit 842eb429bf5df4b2ba46b2e6f7646afe89b1a3c3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 9 06:18:45 2021 +0700 Make graph:draw_values() private There's no documentation nor confidence that it's a good API, so I'm hiding it for now. commit 720746780574e4bad14a71fc4174955139c2eb50 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 04:04:13 2021 +0700 Document graph_should_draw_data_group() for posterity commit 7c73f3754ce6ba86b118f1170a6f9e406ab4841d Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:59:21 2021 +0700 Make graph:should_draw_data_group() private There's no confidence whether it's a good API, so I'm hiding it for now. commit b3539e20adb6423118af726ae4f8751ef864b5d2 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:36:44 2021 +0700 Make graph:preprocess_values() private There's no confidence whether it's a good API, so I'm hiding it for now. commit 2df4400e780a6260f8d0ab1c60734fbb11bf3c4f Author: Alex Belykh <albel727@ngs.ru> Date: Thu May 6 10:05:23 2021 +0700 Add spec/wibox/widget/graph_spec.lua commit ce01a9771ab439b8df841f1565aa4aa8a44a5e0d Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 08:16:18 2021 +0700 Improve graph.capacity documentation commit e28fade8f655802f5bf89154169f68d256b8e332 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 07:36:01 2021 +0700 Document graph:compute_drawn_values_num() commit dd68332292465422d08b48e9f0229f143beccbc6 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 05:24:23 2021 +0700 Document graph:pick_data_group_color() method commit ca6dc55f77aca88a6e9cc90bb6fd0045739f41f5 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 04:13:58 2021 +0700 Guard against setting data groups sparsely in graph:add_value() commit cdea5f126f7984e72924f6dffbd312c4fe3b032f Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 02:36:12 2021 +0700 Refactor scaling and baseline choice out of graph:draw() commit 10135deafe8a9390ba66ab657ebb1fb41d549e8c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 21:34:48 2021 +0700 Document graph.nan_color and graph.stack properties commit 0d73bb84ac66b87e16aee472bd1f299549f66a1c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 20:49:24 2021 +0700 Use a better example in docs for the graph.stack property commit c27d160a0826d033d49d6141608c40aa8a0642fa Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 16:05:38 2021 +0700 Document graph.baseline_value property commit 459bc8176c13d4ea32af0dcdc2399d3b929b1c6a Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:53:47 2021 +0700 Document graph.clamp_bars property commit e732ee0b3b007d213e689b98cc00ae7cb141ae08 Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:34 2021 +0700 Improve graph:add_value() and :clear() documentation commit ae73e3aa9f8d60409cbe4cb4da9612f31af4f38e Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:11 2021 +0700 Improve graph min_value/max_value/scale documentation commit a7350996a9def773b91b1631e6c0507e33d8f93b Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:27:36 2021 +0700 Cleanup graph examples to show only relevant things in docs commit 30740c26f4f41c16f4c8a3c339bcb7bd299e14cd Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 14:04:36 2021 +0700 Rename graph.stack_colors property to `group_colors` The colors are used for stacked and non-stacked graphs alike, so the name didn't make sense. Also document and clarify some color stuff. commit 1ea01bfd410971cf0d0c25fd74cd764d5eb7279d Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 21:25:41 2021 +0700 Add an example file for code coverage of some fringe graph cases commit eae68e39553f88a6275a0c65bf040b5d4c262b54 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 20:23:32 2021 +0700 Add step_shape to the graph/nan_color example for code coverage commit 8027147bc2eab34a97b49a428fb8cef27f122bed Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:55:18 2021 +0700 Add example files for graph.nan_color/nan_indication properties commit 89a25a165c52b2c15b6c4d95ecb777a9067e66f9 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:33:07 2021 +0700 Make widget graph:add_value(v) default v to NaN 0 as a default makes no sense and silences the error that is failing to specify a value, to which the user should rather be alerted. The graph widget has sane NaN handling now and will do just that. commit 2835d552440be48d356a7f7b1d64963696dd550b Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:20:48 2021 +0700 Implement graph.nan_color and nan_indication properties Now, whenever there's a NaN among values and graph.nan_indication is set, the corresponding area, where the value bar should have been drawn, is filled with the nan_color from top to bottom. The painting is done after all data is rendered, to make sure that it won't be overpainted and go unnoticed. NaNs among graph's values are inevitably a sign of some error and it is arguably not a sane default behavior to simply not render them at all. The user should take immediate note of any errors by default, instead of wondering at a mysteriously empty graph widget, which is why nan_indication is set to true by default. If the user wants to silence NaNs, they can always simply set it to false. nan_color if unset, defaults to a noticeable yellow-black pattern. As a direct and planned consequence, stacked graphs with negative values in them trigger the NaN handling too. But silencing NaN handling for those graphs is also possible and semi-sane, because then NaNs and negatives are ignored and the graph behaves (almost) like they were zeros. commit 9c1f8f08b400e01e19c534f3810c9c9ab485760c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 26 08:10:19 2021 +0700 Refactor out graph_map_value_to_widget_coordinates() function commit 1905991ddecabdae3e15ffb8ee5b18a1bee1a711 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 28 13:39:29 2021 +0700 Add an example file for graph.baseline_value property commit b31b39d66f4c037a9484b71a3f910703ae01fac0 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 07:03:49 2021 +0700 Add an example file for graph.clamp_bars=false/true comparison commit a1fc850e3047f9ee2c9ddeffacfbabf3ded34171 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 04:06:45 2021 +0700 Add an example file for graphs with negative values and shapes commit 2f012f8ff1bb5094c14e724a61e59a2a3a296bc8 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 01:06:05 2021 +0700 Add an example file for a graph displaying negative values commit 7b14fbd59a4c4c51962fd3110a1a900dd5f4eb98 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 11:18:26 2021 +0700 Add an example file for graphs with step_width < 1 commit dfd9a54544b637e2fbe3d1325f9cc24b3f4d2313 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 10:17:38 2021 +0700 Add an example file for graph.stack=false/true comparison Should also demonstrate that multiple data groups are possible for non-stacked graphs too. commit 5b532106c3fd255f9b66a66cfb88d977b489ab5d Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 09:34:14 2021 +0700 Add an example file for disabling graph data groups with nil colors commit ac6f1083c02fcb428f089a616f9b0ebaa5496b6c Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:57:25 2021 +0700 Add an example file for step_shape/width/spacing on stacked graphs commit b3703db6a5139e73e72c1cfd198ece2cd9be7df2 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:43:56 2021 +0700 Add an example file for scale=false/true on stacked graph commit b000aed4a12cb44fb501e1c8432a71a14b2f8f3f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 20 08:22:19 2021 +0700 Document deprecation of graph's width and height commit cb0ad617bd6c83a499896797318e3b0b588c3566 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 07:53:37 2021 +0700 Deprecate width and height properties of wibox.widget.graph commit 8c64e14ad91a074b784166a7054e53444cccb9d2 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 04:37:59 2021 +0700 Refactor graph:draw() to separate concerns Firstly, summation of group values for stacked graphs is extracted into graph:preprocess_values(). This makes graph:draw() smaller and exposes the fact, that stacked graphs are almost entirely just a form of a more general concept: pre-render data manipulation. It's something a user might want to customize, and now they can do it by monkey-patching the method. Secondly, the logic for deciding, if and with which color a data group should be drawn, is extracted into graph:should_draw_data_group() and get_data_group_color() methods respectively. This makes it customizable too and makes more obvious the fact, that it is a general thing that has nothing to do with stacked graphs in particular. With this there are only 5 lines remaining in graph:draw(), that could be said to be specific to stacked graphs, all pertaining to the `prev_y` variable, which merely preserves the bar coordinates from a previous data group. commit 2a32b6305d893f413303728a0a3da1dcd9a5591e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 22:52:37 2021 +0700 Pointlessly optimize graph:add_value() 1) Purge old values first, then insert the new one, if needed. Since Lua grows arrays in power of two steps, and our guess_capacity() also takes care to return even integers, it would be a shame to overstep the capacity by one by inserting first and thus getting the table size doubled only to never use the most of it. 2) Use a removal procedure that doesn't call the slow length operator at all. Previous code called it twice (once in table.remove) for every removed value. commit 680ea8f5d81e9040afb4662b3229e937e4b89e02 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:23:38 2021 +0700 Set graph data series color before the painting loop commit b970f0f221ae76250f2a94433ff79d653caa6e21 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:05:37 2021 +0700 Drop a large no longer necessary `if` in graph:draw_values() This commit is almost purely a whitespace change. commit 6b4f0541c70ec99d7f6e276bae64939f337120d6 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:00:50 2021 +0700 Extract values rendering code into graph:draw_values() method This is to avoid ungodly if nesting levels, which are needed to bail out of drawing values early and skip to drawing the border. The next commit will get rid of the if. commit e0950e4f99d1ea1c4b8cea2cc67629fa40030c2a Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 14:50:20 2021 +0700 Track graph draw() usage to guess necessary array capacity The last step of divorcing add_values() from width calculations, while ensuring that user doesn't observe a lack of data to draw. The widget now tracks an approximate number of values that it has been asked to draw() in _private.last_drawn_values_num, and truncates its values array based on that (if not overridden by the capacity property). commit 2e3d542282f35e7c0fed454da0f2eada0fcf3b78 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 13:35:12 2021 +0700 Add `capacity` property to graph widget This is the second step of divorcing add_values() from calculations involving widths. How many values are stored and how many values are drawn are distinct concepts. There isn't even a single definitive "how many values are drawn" because the widget could be rendered in several places with distinct widths simultaneously, and in every such place the data must be drawn appropriately, e.g. with auto-scaling, that takes only the visible data points into account. And the draw() method can do that now. The default capacity value of nil uses heuristics based on widget's _private.width to guess some capacity that would tolerate some dynamic widget resizing. If the user finds the heuristic failing them, they can set the capacity property to the desired maximum number of values that will be kept in the _private.values array (in each data group). commit 08d4a80686f7fe363551c35589a460839f60853e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 03:27:18 2021 +0700 Extract values size computation into compute_drawn_values_num() This is the first step of making add_values() not concern itself (directly) with widget dimensions. The user can monkey-patch the method to override how many values are (logically) drawn, because it affects graph auto-scaling and can be useful for rendering of step_shape-s that paint outside of their step_width. I didn't make it a full-blown function-valued property, because it seems a fringe functionality, which I'm too lazy to document. commit 3c5617c98ab1147618ed22e8414a77fc56b45f6e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 02:32:29 2021 +0700 Copy graph widget prototype methods over in bulk commit 169538837ece41a03d02ba8fbc0e4097062b67a3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun Apr 25 05:58:01 2021 +0700 Remove graph.baseline_y property After giving it some thought I've concluded, that it is unlikely to be useful as it is now. I'd better reserve the name for some more sensible functionality, like fixing the baseline_value defined axis at the given widget coordinate, adjusting the scaling accordingly, so min_value/max_value won't generally correspond to widget top and bottom anymore. commit 202ffaa9a0b820c7c471e1121d01b91f8f76cd38 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 12:06:55 2021 +0700 Add baseline_y and baseline_value properties to graph widget Now values in graph have a *baseline*, tunable with baseline_y and baseline_value properties. It is the (for now invisible) horizontal line from which all bars in the graph grow. I call it a "baseline" instead of "axis", because it doesn't actually have to correspond to the 0 value. But it can, and actually does now by default. baseline_y and baseline_value properties determine the vertical placement of the baseline in the widget. baseline_y is the position of the baseline in normalized widget coordinates, e.g. 0 corresponds to widget's top, 1 - to widget's bottom, and 0.5 - to widget's middle. So if e.g. one sets baseline_y = 0, then no matter how the widget is resized and how its values are scaled, bars in it will be drawn starting from the top. baseline_value is the position of the baseline in value coordinates. So if one sets baseline_value = 10, then no matter what happens, bars for values greater than 10 will be drawn as growing up from this line, and bars for values smaller as growing down. Where and if it will be seen in the widget, depends on the scaling. baseline_value = 0 is the familiar zero axis. baseline_y is unset by default and has priority, i.e. if it's set, then baseline_value is ignored. baseline_value defaults to 0. Prior to this commit, bars were always drawn from the bottom edge of the widget, or, in the new terms, the widget had baseline_y = 1. But it was indistinguishable from baseline_value = 0, because the widget clamped all negative values to 0, so the difference between the two could never be observed. baseline_value = 0 is clearly more useful and familiar to people, so now it is in force by default. commit df7e19abaebf0a871b5ca217b4cc3365533267a6 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 11:18:12 2021 +0700 Move value clamping from graph:add_value() into draw() Previously all values were forcibly clamped between 0 (not tunable at all) and max_value (1 by default). Thus graphs with negative values were not an option. NaN values were conflated with zeros, even though it may be useful to maintain the distinction and render them differently. And if user happened to dynamically change the max_value property, they discovered, that their data got truncated. Preserving user data as it was given is more flexible and makes things easier to debug. This commit does exactly that. So what happens now to the newly allowed values during draw()? There are 3 new cases to consider: 1) NaNs 2) Negatives 3) Values outside of min_value..max_value range NaNs are presently simply not drawn. An empty place is reserved where the value should've been, but nothing else is done. This is distinct from how a zero is handled, e.g. if step_shape is set, it is called with the height parameter = 0, which can still end up drawing something, depending on the shape. One could also consider addition of nan_color/nan_shape properties which could be drawn to alert user to a gap in their data. Negative values necessitate introduction of the concept of the zero axis, from which they will be drawn DOWN, in contrast to positive values. Where should this axis be drawn? How should step_shape-s with inherent direction, e.g. arrows, be drawn for negatives? This will be addressed in the next commit. Values outside of min/max_value range can imply shapes that go beyond widget dimensions. An arrow shape that goes beyond the roof is visibly different from an arrow that saturates and merely touches the widget top, and the user may reasonably desire either behavior. Therefore this commit introduces a boolean clamp_bars property, which defaults to true for reasons of robustness and backward compatibility with the behaviour prior to this commit. The property of course affects only the display of the values, the actual user data stays intact. commit c9c24fb1ab741cd6bb8074971d568e0646c51532 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:29:01 2021 +0700 Use rectangles for drawing graph bars of all sizes I have benchmarked various bar drawing strategies and graph sizes and found out that drawing graph bars with rectangles is as fast or even marginally faster than using fat lines. Thus the code that tries to special-case 1px bars as lines seems to be not worth the additional complexity it introduces, and this commit removes it. Incidentally, I've also established, that rectangles are more robust. If one draws graph bars and feeds cairo very large coordinates (~2^62), then corresponding rectangles fail to render, but all the other rectangles in the same series get rendered successfully, whereas a single large coordinate in a move_to/line_to() sequence is enough to cause all lines in the batch to fail to render. commit bc7f4be5e06405b5aa7fb443a5ace2b9b9575b1f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:01:42 2021 +0700 Cache cairo methods used in the inner loop of graph:draw() I have benchmarked the draw() method, both on cairo xcb and on argb32 image surfaces, and have discovered that cairo context method calls are unnaturally slow, to the point, that almost nothing else in the function matters. For example, drawing bars with vertical lines made the whole draw() function almost 2 times slower than doing the same with rectangles, merely because the former requires two method calls (move_to/line_to), and the latter only one. Simply caching hotpath cairo methods in local variables, reduced the difference between the drawing methods to a negligible one and made the whole function 4 times faster on a typical 100-bar graph, (1.4ms to 0.33ms on my laptop) thus bringing closer my secret dream of drawing live audio data with awesome graphs (kidding). I guess, LGI does something not very optimal there, like gobject-introspecting the method every time, or something. There might be some untapped optimization potential, possibly in upstream, like a method cache, from which everything in awesome could benefit, even though I imagine not many things are so call-intensive as the graph widget, to really feel the difference. commit 538df25bdf02b53d0bbe81809eef2e6fdb05ff6f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 07:32:37 2021 +0700 Use 1-based index in the inner graph draw loop It's used there only once for a calculation and then several times to index some tables, and index 0 is bad for lua tables performance-wise. commit 7412546916cd0a0a490130a120255ffb719398b8 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:55:12 2021 +0700 Leave graph.max_value uninitialized at construction This is a user-visible change, but the previous behavior was probably not what user expected anyway, namely with graph.scale set the graph mysteriously couldn't zoom in onto values that are smaller than 1. commit eb81d7929a161e79c13262cfa73ac2e6a820e8ed Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:48:29 2021 +0700 Allow graph.max_value be nil This makes its behave symmetrically to min_value. Now it defaults to 1, when graph.scale is not set, and to the maximum value in the graph data, when graph.scale is set. commit f622a8430cbfde7007b58d7f4c9c76b08c91e997 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 12:01:13 2021 +0700 Calculate max values num in graph:add_value() more precisely At most so many values were kept in the widget values array, as there are pixels of usable widget width (i.e. sans border). This is exactly right for the surely most common case of graphs with 1px-wide bars, but is more than needed for graphs with step_width+step_spacing>1, and not enough for graphs with subpixel bars. Moreover this made graph autoscaling behave weirdly in graphs with thicker bars, because old values survived longer in the array and kept affecting min_value/max_value calculations even after they were shifted off-screen. The maximum number of kept values is now calculated exactly as the number of values that can be displayed at once, taking step_width and step_spacing into account. It might be worth adding a max_values_num property to the graph to let user override this calculation, in case they draw shapes in step_shape() outside of its supposed rectangle, so our assumptions of visibility are wrong, or the widget often gets resized, resulting in the annoyance of visibly truncated series. commit 0aa63249a18641251a1bf77423739164f0eab39f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 02:30:13 2021 +0700 Fix border_color/border_width bugs in graph widget add_value() wrongly assumed that border_width is always set, when border_color is, so if user set only the latter, an error occurred. add_value() assumed that border_width is always < width which led to awesome hanging up in an infinite loop, should that fail to be the case. The graph border_width property was driven by the border_color property, instead of the other way round. If border_color wasn't set, the border wasn't drawn all, even if user sets non-zero border_width, which is a confusing behavior, in contrast to all other widgets, which simply use a plethora of fallback colors to draw the border, when needed. This also meant that, despite being documented, the beautiful.graph_border_color fallback color could not and was not ever used. If the border_color was set though, an unset border_width was immediately assumed to be 1 instead of 0, also contrary to what other widgets do. Theme colors shouldn't influence widget layout, so this commit fixes this behavior to: 1) border_width always defaults to 0, if unset. 2) border_color falls back to beautiful.graph_border_color or white. Maybe one should also add a beautiful.graph_border_width theme variable, like some widgets do. commit dad4a3253b8fef971fb82720d2a407fcc0a879ad Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 03:11:27 2021 +0700 Implement multiple data series drawing for non-stacked graph The widget can already hold and draw multiple data groups, so it makes little sense to limit drawing only to the first group when graph.stack = false, probably leading to user's confusion. Now user can call add_value(v, group) with group > 1 and the data series will be drawn (over groups with lesser id-s) with graph.stack_colors[group] color, if stack_colors is set, or with graph.color, if stack_colors isn't set. Thus nothing visibly changes for all current users of non-stacked graphs with only single data series, but the new functionality is now there for those who need it. One can't fully emulate this functionality, e.g. with multiple graphs widgets in a wibox.layout.stack, because data series in a single widget can enjoy a common auto-scaling. commit 3e645c976184ab5f50d625271c25062db9ab7ed9 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:59:12 2021 +0700 Don't draw a stacked graph data group if there's no color for it If stack_colors[idx] is nil for the group `idx`, don't draw it at all (as opposed, e.g. to the effect of drawing it with a transparent color, or as though it had all values == 0, which both produce different results). This code is still subtly different from prior behavior, because now user can have a non-contiguous stack_colors table and turn the display of any data group on and off at will, whereas the code two commits earlier would just stop rendering as soon as ipairs(stack_colors) does, i.e. no data series were drawn past the first nil hole. This commit only looks large, but is mostly a whitespace change due to nesting from a pair of wrapping ifs. commit 8c0273786de1ade66bdd977699f98b588da9f487 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:32:42 2021 +0700 Unify stacked and non-stacked graph rendering Now stacked graphs support everything the nonstacked do, like step_width ~= 1 and custom step_shape-s. Technical notes: This code no longer calls cr:stroke() for every single value in a stacked graph, but rather paints a whole series and then cr:stroke/fill()-s it at all once with its color, improving performance considerably. Additionally the graph no longer relies on the presence of the stack_colors property to render series. It draws all data that it has, and, if there's no corresponding color in stack_colors, falls back to graph.color. This will be changed in the next commit to the prior behavior of only drawing those series for which there's a color in the stack_colors table, but that would've made this commit's diff much larger due to required nesting ifs over large code chunks. This commit also takes care to properly handle NaNs and negative values. Those are currently (almost) guaranteed to not occur in the values array due to clipping in graph:add_value(), but I intend to eventually drop the clipping, because it does nothing that draw() really relies upon, and allowing unclipped and NaN values is more flexible and less surprising, e.g. when user dynamically toggles graph.scale. Eventually I intend to implement drawing negative values in non-stacked graphs and possibly even in the stacked ones. commit 38c6589c51b2aa057d7c2f40182ecf32b5b2ff5e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 10:04:15 2021 +0700 Convert graph:draw() to use `self` commit f21f1b2550426df1c4ea8abde079b1ccef84b394 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 12:27:26 2021 +0700 Fix scaling for stacked graphs Stacked graphs have cumulative heights, values from all groups that end up in the same graph bar are effectively summed. Previous code didn't take that into account when calculating min/max_value for the purposes of rescaling, but simply examined every value separately. This code adds up all values up front and finds min/max among those, to determine proper scaling that would fit the graph exactly. commit 1ce4c7bf63bdd6c99c195d668347bec49355e921 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 09:49:35 2021 +0700 Draw shaped graph bars only up to the baseline Prior to this commit, every step_shape bar was, for some reason, drawn with height = full widget's height, regardless of the value it represents. So for the most bars their lower end was far outside the box and therefore clipped off. So e.g. one couldn't see the lower round edges of rounded rectangles. This commit fixes that by appropriately varying the height that is passed into step_shape(), so that lower edges of the bars always exactly touch the lower edge of the graph. If the user wants to restore the prior behavior, they can simply ignore the passed-in height in step_shape() and just always draw their shape with height = full widget height. But they couldn't replicate the effect of this commit in their rc.lua without essentially writing their own graph widget. commit e25185b08da23745b5815f85f6e9e43222ed2294 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 08:42:54 2021 +0700 Avoid incremental transform shifting during graph bar rendering Instead of letting addition errors and cognitive load accumulate simply use the same offset calculation that is used for lines and reset the transform back to the top-left corner after every drawn bar. It's conceptually simpler, and will be useful later for stacked graphs and more. It also fixes a discrepancy between plain and step_shape rendering. Prior to this commit, bar coloring occurred with a disturbed transform, which was centered at the last drawn bar, wherever it happened to be. This meant that nontrivial colors, like gradients and images, were arbitrarily offset when step_shape was used. commit f3e98866d8583c35d0b557c09f529299edc9ba2c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:45:29 2021 +0700 Refactor out graph bar height expression into a variable commit f54528d6892f5aaa4595917853c9e2130f743fdb Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:20:25 2021 +0700 Draw graph bars with lines only when step_width == 1 Before this commit graph bars of step_width < 1 were drawn with lines of width 1, but that's a bit unfortunate, because it led to much overpainting, while still ending up with a graphically incorrect result. Let's draw such bars with rectangles and let cairo do things with subpixels, getting more graphically accurate result, while still keeping the common case of step_width == 1 (supposedly) fast-drawn with lines. commit b6515dca67c18b78eb5657378131a62ab43d4cfc Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:54:07 2021 +0700 Fix unsharp edges when drawing graph bars with rectangles The 0.5 offset is only helpful for 1-width strokes, not for fills. commit 7f3db8f07e82f821bcd1dc418d66564f537609d6 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:21:01 2021 +0700 Do coordinate transformations even if the value isn't drawn The graph presently doesn't draw values < 0, but there was a discrepancy between drawing with and without step_shape. The default line/rectangle drawing reserves an empty place where the skipped value should've been, but no such empty place is reserved, when step_shape is used. This commit fixes that. Skipped values always have their empty space now. commit 7b731ebfe96af7a9dba9c57371da6761cf22c00c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:03:30 2021 +0700 Fix graph bar scaling when min_value is not 0 Previous calculation lead to underscaling of values so that even value = max_value wasn't drawn to full height. commit dca59fd8b7fff0e7e646f2dfc6bd92047028e5a8 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:18:40 2021 +0700 Calculate the horizontal graph bar offset only when needed commit 940ddf4b9825e9c3a91d276208c089d8c74e49db Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:17:23 2021 +0700 Call cr:move_to() only when drawing the graph with lines It's redundant otherwise and might even interfere with user's drawing in step_shape(). commit b5d5cd10ca29fb29cd751faaa95137c2fe89ff63 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:04:06 2021 +0700 Fix wrong horizontal offset calculations in widget.graph Indices in the loop are zero-based, so things like i-1 and i>1 aren't necessary and actually wouldn't have been correct even in a 1-based loop. step_shape = gears.shape.rectangle and step_shape = nil, which is supposed to default to rectangle according to docs, were drawn differently due to mismatched offset calculations, which led to problems especially noticeable when step_spacing is not zero, e.g: 1) whole graph shifted left too much, drawn partially off-screen. 2) whole graph shifted right too much, and the first bar placed closer to the rest than others. commit dc369fa198281e000afc1bd17326f3a264adf7d1 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 02:09:21 2021 +0700 Fix the "border_width affects graph bar width" bug Graph bars were drawn with lines of border_width thickness instead of 1, i.e. possibly too fat to be readable. commit 1ba73378cf29ed27a6ecd18716394779ceb543d9 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 00:13:03 2021 +0700 Simplify internal values representation in graph even more New values in graph._private.values were pushed to the end, and old values were removed from the beginning of the table. In draw() however newer values are drawn first, at the left edge of the graph, necessitating annoying idx = #values - idx conversions everywhere. In particular in stacked graph that means that values that are drawn together may have different indices. One has to shift the values array in add_value() either way, so one might as well insert new values at the beginning of the array and simplify the rest of the code. This doesn't seem like much yet, but it will prove to be much more convenient and less error-prone in what I'm going to do later. commit f70523ee68dbd6b67050ace4b82903a9b22440c7 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 13:35:10 2021 +0700 Simplify internal values representation in graph widget graph._private.values was either a table of values or a table of tables of values, depending on whether "stack" property is enabled, ... or a mix of both if user forgot to pass the second "group" parameter to graph:add_value(). This resulted in pretty fragile and confusing behavior, e.g. leaving user to wonder, why they see nothing more after setting stack=true for a graph in a previously working rc.lua, and demanding a very careful use of add_value() from the user, or else some values or even entire stacks would mysteriously vanish and errors in draw() get triggered, sometimes after a period of seemingly normal functioning. Now `values` is always a table of tables and the group parameter in add_value() defaults to 1, thus making the non-stacked graph a trivial variant of the stacked graph.
2021-05-20 17:14:03 +02:00
--- Clamp graph bars to keep them inside the widget for out-of-range values.
--
-- Drawing values outside the [`min_value`, `max_value`] range leads to
-- bar shapes that exceed physical widget dimensions.
-- Most of the time this doesn't matter, because bar shapes are rectangles
-- and bar heights aren't large enough to trigger errors in the drawing system.
-- However for some shapes and values it does make a difference and leads
-- to visibly different and/or invalid result.
--
-- When this property is set to true (the default), the graph clamps
-- bars' heights to keep them within the graph.
--
-- @DOC_wibox_widget_graph_clamp_bars_EXAMPLE@
--
graph: complete widget overhaul and bug fixes Squashed commit of the following: commit 69821f51fe1e8652715658543e50631ece495090 Author: Alex Belykh <albel727@ngs.ru> Date: Mon May 10 19:17:55 2021 +0700 Refactor property handling in wibox.widget.graph In addition, baseline_value, step_width and step_spacing properties are all set to their default non-nil numeric values during instance creation now. commit 842eb429bf5df4b2ba46b2e6f7646afe89b1a3c3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 9 06:18:45 2021 +0700 Make graph:draw_values() private There's no documentation nor confidence that it's a good API, so I'm hiding it for now. commit 720746780574e4bad14a71fc4174955139c2eb50 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 04:04:13 2021 +0700 Document graph_should_draw_data_group() for posterity commit 7c73f3754ce6ba86b118f1170a6f9e406ab4841d Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:59:21 2021 +0700 Make graph:should_draw_data_group() private There's no confidence whether it's a good API, so I'm hiding it for now. commit b3539e20adb6423118af726ae4f8751ef864b5d2 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:36:44 2021 +0700 Make graph:preprocess_values() private There's no confidence whether it's a good API, so I'm hiding it for now. commit 2df4400e780a6260f8d0ab1c60734fbb11bf3c4f Author: Alex Belykh <albel727@ngs.ru> Date: Thu May 6 10:05:23 2021 +0700 Add spec/wibox/widget/graph_spec.lua commit ce01a9771ab439b8df841f1565aa4aa8a44a5e0d Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 08:16:18 2021 +0700 Improve graph.capacity documentation commit e28fade8f655802f5bf89154169f68d256b8e332 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 07:36:01 2021 +0700 Document graph:compute_drawn_values_num() commit dd68332292465422d08b48e9f0229f143beccbc6 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 05:24:23 2021 +0700 Document graph:pick_data_group_color() method commit ca6dc55f77aca88a6e9cc90bb6fd0045739f41f5 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 04:13:58 2021 +0700 Guard against setting data groups sparsely in graph:add_value() commit cdea5f126f7984e72924f6dffbd312c4fe3b032f Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 02:36:12 2021 +0700 Refactor scaling and baseline choice out of graph:draw() commit 10135deafe8a9390ba66ab657ebb1fb41d549e8c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 21:34:48 2021 +0700 Document graph.nan_color and graph.stack properties commit 0d73bb84ac66b87e16aee472bd1f299549f66a1c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 20:49:24 2021 +0700 Use a better example in docs for the graph.stack property commit c27d160a0826d033d49d6141608c40aa8a0642fa Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 16:05:38 2021 +0700 Document graph.baseline_value property commit 459bc8176c13d4ea32af0dcdc2399d3b929b1c6a Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:53:47 2021 +0700 Document graph.clamp_bars property commit e732ee0b3b007d213e689b98cc00ae7cb141ae08 Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:34 2021 +0700 Improve graph:add_value() and :clear() documentation commit ae73e3aa9f8d60409cbe4cb4da9612f31af4f38e Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:11 2021 +0700 Improve graph min_value/max_value/scale documentation commit a7350996a9def773b91b1631e6c0507e33d8f93b Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:27:36 2021 +0700 Cleanup graph examples to show only relevant things in docs commit 30740c26f4f41c16f4c8a3c339bcb7bd299e14cd Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 14:04:36 2021 +0700 Rename graph.stack_colors property to `group_colors` The colors are used for stacked and non-stacked graphs alike, so the name didn't make sense. Also document and clarify some color stuff. commit 1ea01bfd410971cf0d0c25fd74cd764d5eb7279d Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 21:25:41 2021 +0700 Add an example file for code coverage of some fringe graph cases commit eae68e39553f88a6275a0c65bf040b5d4c262b54 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 20:23:32 2021 +0700 Add step_shape to the graph/nan_color example for code coverage commit 8027147bc2eab34a97b49a428fb8cef27f122bed Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:55:18 2021 +0700 Add example files for graph.nan_color/nan_indication properties commit 89a25a165c52b2c15b6c4d95ecb777a9067e66f9 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:33:07 2021 +0700 Make widget graph:add_value(v) default v to NaN 0 as a default makes no sense and silences the error that is failing to specify a value, to which the user should rather be alerted. The graph widget has sane NaN handling now and will do just that. commit 2835d552440be48d356a7f7b1d64963696dd550b Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:20:48 2021 +0700 Implement graph.nan_color and nan_indication properties Now, whenever there's a NaN among values and graph.nan_indication is set, the corresponding area, where the value bar should have been drawn, is filled with the nan_color from top to bottom. The painting is done after all data is rendered, to make sure that it won't be overpainted and go unnoticed. NaNs among graph's values are inevitably a sign of some error and it is arguably not a sane default behavior to simply not render them at all. The user should take immediate note of any errors by default, instead of wondering at a mysteriously empty graph widget, which is why nan_indication is set to true by default. If the user wants to silence NaNs, they can always simply set it to false. nan_color if unset, defaults to a noticeable yellow-black pattern. As a direct and planned consequence, stacked graphs with negative values in them trigger the NaN handling too. But silencing NaN handling for those graphs is also possible and semi-sane, because then NaNs and negatives are ignored and the graph behaves (almost) like they were zeros. commit 9c1f8f08b400e01e19c534f3810c9c9ab485760c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 26 08:10:19 2021 +0700 Refactor out graph_map_value_to_widget_coordinates() function commit 1905991ddecabdae3e15ffb8ee5b18a1bee1a711 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 28 13:39:29 2021 +0700 Add an example file for graph.baseline_value property commit b31b39d66f4c037a9484b71a3f910703ae01fac0 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 07:03:49 2021 +0700 Add an example file for graph.clamp_bars=false/true comparison commit a1fc850e3047f9ee2c9ddeffacfbabf3ded34171 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 04:06:45 2021 +0700 Add an example file for graphs with negative values and shapes commit 2f012f8ff1bb5094c14e724a61e59a2a3a296bc8 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 01:06:05 2021 +0700 Add an example file for a graph displaying negative values commit 7b14fbd59a4c4c51962fd3110a1a900dd5f4eb98 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 11:18:26 2021 +0700 Add an example file for graphs with step_width < 1 commit dfd9a54544b637e2fbe3d1325f9cc24b3f4d2313 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 10:17:38 2021 +0700 Add an example file for graph.stack=false/true comparison Should also demonstrate that multiple data groups are possible for non-stacked graphs too. commit 5b532106c3fd255f9b66a66cfb88d977b489ab5d Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 09:34:14 2021 +0700 Add an example file for disabling graph data groups with nil colors commit ac6f1083c02fcb428f089a616f9b0ebaa5496b6c Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:57:25 2021 +0700 Add an example file for step_shape/width/spacing on stacked graphs commit b3703db6a5139e73e72c1cfd198ece2cd9be7df2 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:43:56 2021 +0700 Add an example file for scale=false/true on stacked graph commit b000aed4a12cb44fb501e1c8432a71a14b2f8f3f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 20 08:22:19 2021 +0700 Document deprecation of graph's width and height commit cb0ad617bd6c83a499896797318e3b0b588c3566 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 07:53:37 2021 +0700 Deprecate width and height properties of wibox.widget.graph commit 8c64e14ad91a074b784166a7054e53444cccb9d2 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 04:37:59 2021 +0700 Refactor graph:draw() to separate concerns Firstly, summation of group values for stacked graphs is extracted into graph:preprocess_values(). This makes graph:draw() smaller and exposes the fact, that stacked graphs are almost entirely just a form of a more general concept: pre-render data manipulation. It's something a user might want to customize, and now they can do it by monkey-patching the method. Secondly, the logic for deciding, if and with which color a data group should be drawn, is extracted into graph:should_draw_data_group() and get_data_group_color() methods respectively. This makes it customizable too and makes more obvious the fact, that it is a general thing that has nothing to do with stacked graphs in particular. With this there are only 5 lines remaining in graph:draw(), that could be said to be specific to stacked graphs, all pertaining to the `prev_y` variable, which merely preserves the bar coordinates from a previous data group. commit 2a32b6305d893f413303728a0a3da1dcd9a5591e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 22:52:37 2021 +0700 Pointlessly optimize graph:add_value() 1) Purge old values first, then insert the new one, if needed. Since Lua grows arrays in power of two steps, and our guess_capacity() also takes care to return even integers, it would be a shame to overstep the capacity by one by inserting first and thus getting the table size doubled only to never use the most of it. 2) Use a removal procedure that doesn't call the slow length operator at all. Previous code called it twice (once in table.remove) for every removed value. commit 680ea8f5d81e9040afb4662b3229e937e4b89e02 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:23:38 2021 +0700 Set graph data series color before the painting loop commit b970f0f221ae76250f2a94433ff79d653caa6e21 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:05:37 2021 +0700 Drop a large no longer necessary `if` in graph:draw_values() This commit is almost purely a whitespace change. commit 6b4f0541c70ec99d7f6e276bae64939f337120d6 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:00:50 2021 +0700 Extract values rendering code into graph:draw_values() method This is to avoid ungodly if nesting levels, which are needed to bail out of drawing values early and skip to drawing the border. The next commit will get rid of the if. commit e0950e4f99d1ea1c4b8cea2cc67629fa40030c2a Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 14:50:20 2021 +0700 Track graph draw() usage to guess necessary array capacity The last step of divorcing add_values() from width calculations, while ensuring that user doesn't observe a lack of data to draw. The widget now tracks an approximate number of values that it has been asked to draw() in _private.last_drawn_values_num, and truncates its values array based on that (if not overridden by the capacity property). commit 2e3d542282f35e7c0fed454da0f2eada0fcf3b78 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 13:35:12 2021 +0700 Add `capacity` property to graph widget This is the second step of divorcing add_values() from calculations involving widths. How many values are stored and how many values are drawn are distinct concepts. There isn't even a single definitive "how many values are drawn" because the widget could be rendered in several places with distinct widths simultaneously, and in every such place the data must be drawn appropriately, e.g. with auto-scaling, that takes only the visible data points into account. And the draw() method can do that now. The default capacity value of nil uses heuristics based on widget's _private.width to guess some capacity that would tolerate some dynamic widget resizing. If the user finds the heuristic failing them, they can set the capacity property to the desired maximum number of values that will be kept in the _private.values array (in each data group). commit 08d4a80686f7fe363551c35589a460839f60853e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 03:27:18 2021 +0700 Extract values size computation into compute_drawn_values_num() This is the first step of making add_values() not concern itself (directly) with widget dimensions. The user can monkey-patch the method to override how many values are (logically) drawn, because it affects graph auto-scaling and can be useful for rendering of step_shape-s that paint outside of their step_width. I didn't make it a full-blown function-valued property, because it seems a fringe functionality, which I'm too lazy to document. commit 3c5617c98ab1147618ed22e8414a77fc56b45f6e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 02:32:29 2021 +0700 Copy graph widget prototype methods over in bulk commit 169538837ece41a03d02ba8fbc0e4097062b67a3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun Apr 25 05:58:01 2021 +0700 Remove graph.baseline_y property After giving it some thought I've concluded, that it is unlikely to be useful as it is now. I'd better reserve the name for some more sensible functionality, like fixing the baseline_value defined axis at the given widget coordinate, adjusting the scaling accordingly, so min_value/max_value won't generally correspond to widget top and bottom anymore. commit 202ffaa9a0b820c7c471e1121d01b91f8f76cd38 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 12:06:55 2021 +0700 Add baseline_y and baseline_value properties to graph widget Now values in graph have a *baseline*, tunable with baseline_y and baseline_value properties. It is the (for now invisible) horizontal line from which all bars in the graph grow. I call it a "baseline" instead of "axis", because it doesn't actually have to correspond to the 0 value. But it can, and actually does now by default. baseline_y and baseline_value properties determine the vertical placement of the baseline in the widget. baseline_y is the position of the baseline in normalized widget coordinates, e.g. 0 corresponds to widget's top, 1 - to widget's bottom, and 0.5 - to widget's middle. So if e.g. one sets baseline_y = 0, then no matter how the widget is resized and how its values are scaled, bars in it will be drawn starting from the top. baseline_value is the position of the baseline in value coordinates. So if one sets baseline_value = 10, then no matter what happens, bars for values greater than 10 will be drawn as growing up from this line, and bars for values smaller as growing down. Where and if it will be seen in the widget, depends on the scaling. baseline_value = 0 is the familiar zero axis. baseline_y is unset by default and has priority, i.e. if it's set, then baseline_value is ignored. baseline_value defaults to 0. Prior to this commit, bars were always drawn from the bottom edge of the widget, or, in the new terms, the widget had baseline_y = 1. But it was indistinguishable from baseline_value = 0, because the widget clamped all negative values to 0, so the difference between the two could never be observed. baseline_value = 0 is clearly more useful and familiar to people, so now it is in force by default. commit df7e19abaebf0a871b5ca217b4cc3365533267a6 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 11:18:12 2021 +0700 Move value clamping from graph:add_value() into draw() Previously all values were forcibly clamped between 0 (not tunable at all) and max_value (1 by default). Thus graphs with negative values were not an option. NaN values were conflated with zeros, even though it may be useful to maintain the distinction and render them differently. And if user happened to dynamically change the max_value property, they discovered, that their data got truncated. Preserving user data as it was given is more flexible and makes things easier to debug. This commit does exactly that. So what happens now to the newly allowed values during draw()? There are 3 new cases to consider: 1) NaNs 2) Negatives 3) Values outside of min_value..max_value range NaNs are presently simply not drawn. An empty place is reserved where the value should've been, but nothing else is done. This is distinct from how a zero is handled, e.g. if step_shape is set, it is called with the height parameter = 0, which can still end up drawing something, depending on the shape. One could also consider addition of nan_color/nan_shape properties which could be drawn to alert user to a gap in their data. Negative values necessitate introduction of the concept of the zero axis, from which they will be drawn DOWN, in contrast to positive values. Where should this axis be drawn? How should step_shape-s with inherent direction, e.g. arrows, be drawn for negatives? This will be addressed in the next commit. Values outside of min/max_value range can imply shapes that go beyond widget dimensions. An arrow shape that goes beyond the roof is visibly different from an arrow that saturates and merely touches the widget top, and the user may reasonably desire either behavior. Therefore this commit introduces a boolean clamp_bars property, which defaults to true for reasons of robustness and backward compatibility with the behaviour prior to this commit. The property of course affects only the display of the values, the actual user data stays intact. commit c9c24fb1ab741cd6bb8074971d568e0646c51532 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:29:01 2021 +0700 Use rectangles for drawing graph bars of all sizes I have benchmarked various bar drawing strategies and graph sizes and found out that drawing graph bars with rectangles is as fast or even marginally faster than using fat lines. Thus the code that tries to special-case 1px bars as lines seems to be not worth the additional complexity it introduces, and this commit removes it. Incidentally, I've also established, that rectangles are more robust. If one draws graph bars and feeds cairo very large coordinates (~2^62), then corresponding rectangles fail to render, but all the other rectangles in the same series get rendered successfully, whereas a single large coordinate in a move_to/line_to() sequence is enough to cause all lines in the batch to fail to render. commit bc7f4be5e06405b5aa7fb443a5ace2b9b9575b1f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:01:42 2021 +0700 Cache cairo methods used in the inner loop of graph:draw() I have benchmarked the draw() method, both on cairo xcb and on argb32 image surfaces, and have discovered that cairo context method calls are unnaturally slow, to the point, that almost nothing else in the function matters. For example, drawing bars with vertical lines made the whole draw() function almost 2 times slower than doing the same with rectangles, merely because the former requires two method calls (move_to/line_to), and the latter only one. Simply caching hotpath cairo methods in local variables, reduced the difference between the drawing methods to a negligible one and made the whole function 4 times faster on a typical 100-bar graph, (1.4ms to 0.33ms on my laptop) thus bringing closer my secret dream of drawing live audio data with awesome graphs (kidding). I guess, LGI does something not very optimal there, like gobject-introspecting the method every time, or something. There might be some untapped optimization potential, possibly in upstream, like a method cache, from which everything in awesome could benefit, even though I imagine not many things are so call-intensive as the graph widget, to really feel the difference. commit 538df25bdf02b53d0bbe81809eef2e6fdb05ff6f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 07:32:37 2021 +0700 Use 1-based index in the inner graph draw loop It's used there only once for a calculation and then several times to index some tables, and index 0 is bad for lua tables performance-wise. commit 7412546916cd0a0a490130a120255ffb719398b8 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:55:12 2021 +0700 Leave graph.max_value uninitialized at construction This is a user-visible change, but the previous behavior was probably not what user expected anyway, namely with graph.scale set the graph mysteriously couldn't zoom in onto values that are smaller than 1. commit eb81d7929a161e79c13262cfa73ac2e6a820e8ed Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:48:29 2021 +0700 Allow graph.max_value be nil This makes its behave symmetrically to min_value. Now it defaults to 1, when graph.scale is not set, and to the maximum value in the graph data, when graph.scale is set. commit f622a8430cbfde7007b58d7f4c9c76b08c91e997 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 12:01:13 2021 +0700 Calculate max values num in graph:add_value() more precisely At most so many values were kept in the widget values array, as there are pixels of usable widget width (i.e. sans border). This is exactly right for the surely most common case of graphs with 1px-wide bars, but is more than needed for graphs with step_width+step_spacing>1, and not enough for graphs with subpixel bars. Moreover this made graph autoscaling behave weirdly in graphs with thicker bars, because old values survived longer in the array and kept affecting min_value/max_value calculations even after they were shifted off-screen. The maximum number of kept values is now calculated exactly as the number of values that can be displayed at once, taking step_width and step_spacing into account. It might be worth adding a max_values_num property to the graph to let user override this calculation, in case they draw shapes in step_shape() outside of its supposed rectangle, so our assumptions of visibility are wrong, or the widget often gets resized, resulting in the annoyance of visibly truncated series. commit 0aa63249a18641251a1bf77423739164f0eab39f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 02:30:13 2021 +0700 Fix border_color/border_width bugs in graph widget add_value() wrongly assumed that border_width is always set, when border_color is, so if user set only the latter, an error occurred. add_value() assumed that border_width is always < width which led to awesome hanging up in an infinite loop, should that fail to be the case. The graph border_width property was driven by the border_color property, instead of the other way round. If border_color wasn't set, the border wasn't drawn all, even if user sets non-zero border_width, which is a confusing behavior, in contrast to all other widgets, which simply use a plethora of fallback colors to draw the border, when needed. This also meant that, despite being documented, the beautiful.graph_border_color fallback color could not and was not ever used. If the border_color was set though, an unset border_width was immediately assumed to be 1 instead of 0, also contrary to what other widgets do. Theme colors shouldn't influence widget layout, so this commit fixes this behavior to: 1) border_width always defaults to 0, if unset. 2) border_color falls back to beautiful.graph_border_color or white. Maybe one should also add a beautiful.graph_border_width theme variable, like some widgets do. commit dad4a3253b8fef971fb82720d2a407fcc0a879ad Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 03:11:27 2021 +0700 Implement multiple data series drawing for non-stacked graph The widget can already hold and draw multiple data groups, so it makes little sense to limit drawing only to the first group when graph.stack = false, probably leading to user's confusion. Now user can call add_value(v, group) with group > 1 and the data series will be drawn (over groups with lesser id-s) with graph.stack_colors[group] color, if stack_colors is set, or with graph.color, if stack_colors isn't set. Thus nothing visibly changes for all current users of non-stacked graphs with only single data series, but the new functionality is now there for those who need it. One can't fully emulate this functionality, e.g. with multiple graphs widgets in a wibox.layout.stack, because data series in a single widget can enjoy a common auto-scaling. commit 3e645c976184ab5f50d625271c25062db9ab7ed9 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:59:12 2021 +0700 Don't draw a stacked graph data group if there's no color for it If stack_colors[idx] is nil for the group `idx`, don't draw it at all (as opposed, e.g. to the effect of drawing it with a transparent color, or as though it had all values == 0, which both produce different results). This code is still subtly different from prior behavior, because now user can have a non-contiguous stack_colors table and turn the display of any data group on and off at will, whereas the code two commits earlier would just stop rendering as soon as ipairs(stack_colors) does, i.e. no data series were drawn past the first nil hole. This commit only looks large, but is mostly a whitespace change due to nesting from a pair of wrapping ifs. commit 8c0273786de1ade66bdd977699f98b588da9f487 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:32:42 2021 +0700 Unify stacked and non-stacked graph rendering Now stacked graphs support everything the nonstacked do, like step_width ~= 1 and custom step_shape-s. Technical notes: This code no longer calls cr:stroke() for every single value in a stacked graph, but rather paints a whole series and then cr:stroke/fill()-s it at all once with its color, improving performance considerably. Additionally the graph no longer relies on the presence of the stack_colors property to render series. It draws all data that it has, and, if there's no corresponding color in stack_colors, falls back to graph.color. This will be changed in the next commit to the prior behavior of only drawing those series for which there's a color in the stack_colors table, but that would've made this commit's diff much larger due to required nesting ifs over large code chunks. This commit also takes care to properly handle NaNs and negative values. Those are currently (almost) guaranteed to not occur in the values array due to clipping in graph:add_value(), but I intend to eventually drop the clipping, because it does nothing that draw() really relies upon, and allowing unclipped and NaN values is more flexible and less surprising, e.g. when user dynamically toggles graph.scale. Eventually I intend to implement drawing negative values in non-stacked graphs and possibly even in the stacked ones. commit 38c6589c51b2aa057d7c2f40182ecf32b5b2ff5e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 10:04:15 2021 +0700 Convert graph:draw() to use `self` commit f21f1b2550426df1c4ea8abde079b1ccef84b394 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 12:27:26 2021 +0700 Fix scaling for stacked graphs Stacked graphs have cumulative heights, values from all groups that end up in the same graph bar are effectively summed. Previous code didn't take that into account when calculating min/max_value for the purposes of rescaling, but simply examined every value separately. This code adds up all values up front and finds min/max among those, to determine proper scaling that would fit the graph exactly. commit 1ce4c7bf63bdd6c99c195d668347bec49355e921 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 09:49:35 2021 +0700 Draw shaped graph bars only up to the baseline Prior to this commit, every step_shape bar was, for some reason, drawn with height = full widget's height, regardless of the value it represents. So for the most bars their lower end was far outside the box and therefore clipped off. So e.g. one couldn't see the lower round edges of rounded rectangles. This commit fixes that by appropriately varying the height that is passed into step_shape(), so that lower edges of the bars always exactly touch the lower edge of the graph. If the user wants to restore the prior behavior, they can simply ignore the passed-in height in step_shape() and just always draw their shape with height = full widget height. But they couldn't replicate the effect of this commit in their rc.lua without essentially writing their own graph widget. commit e25185b08da23745b5815f85f6e9e43222ed2294 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 08:42:54 2021 +0700 Avoid incremental transform shifting during graph bar rendering Instead of letting addition errors and cognitive load accumulate simply use the same offset calculation that is used for lines and reset the transform back to the top-left corner after every drawn bar. It's conceptually simpler, and will be useful later for stacked graphs and more. It also fixes a discrepancy between plain and step_shape rendering. Prior to this commit, bar coloring occurred with a disturbed transform, which was centered at the last drawn bar, wherever it happened to be. This meant that nontrivial colors, like gradients and images, were arbitrarily offset when step_shape was used. commit f3e98866d8583c35d0b557c09f529299edc9ba2c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:45:29 2021 +0700 Refactor out graph bar height expression into a variable commit f54528d6892f5aaa4595917853c9e2130f743fdb Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:20:25 2021 +0700 Draw graph bars with lines only when step_width == 1 Before this commit graph bars of step_width < 1 were drawn with lines of width 1, but that's a bit unfortunate, because it led to much overpainting, while still ending up with a graphically incorrect result. Let's draw such bars with rectangles and let cairo do things with subpixels, getting more graphically accurate result, while still keeping the common case of step_width == 1 (supposedly) fast-drawn with lines. commit b6515dca67c18b78eb5657378131a62ab43d4cfc Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:54:07 2021 +0700 Fix unsharp edges when drawing graph bars with rectangles The 0.5 offset is only helpful for 1-width strokes, not for fills. commit 7f3db8f07e82f821bcd1dc418d66564f537609d6 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:21:01 2021 +0700 Do coordinate transformations even if the value isn't drawn The graph presently doesn't draw values < 0, but there was a discrepancy between drawing with and without step_shape. The default line/rectangle drawing reserves an empty place where the skipped value should've been, but no such empty place is reserved, when step_shape is used. This commit fixes that. Skipped values always have their empty space now. commit 7b731ebfe96af7a9dba9c57371da6761cf22c00c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:03:30 2021 +0700 Fix graph bar scaling when min_value is not 0 Previous calculation lead to underscaling of values so that even value = max_value wasn't drawn to full height. commit dca59fd8b7fff0e7e646f2dfc6bd92047028e5a8 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:18:40 2021 +0700 Calculate the horizontal graph bar offset only when needed commit 940ddf4b9825e9c3a91d276208c089d8c74e49db Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:17:23 2021 +0700 Call cr:move_to() only when drawing the graph with lines It's redundant otherwise and might even interfere with user's drawing in step_shape(). commit b5d5cd10ca29fb29cd751faaa95137c2fe89ff63 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:04:06 2021 +0700 Fix wrong horizontal offset calculations in widget.graph Indices in the loop are zero-based, so things like i-1 and i>1 aren't necessary and actually wouldn't have been correct even in a 1-based loop. step_shape = gears.shape.rectangle and step_shape = nil, which is supposed to default to rectangle according to docs, were drawn differently due to mismatched offset calculations, which led to problems especially noticeable when step_spacing is not zero, e.g: 1) whole graph shifted left too much, drawn partially off-screen. 2) whole graph shifted right too much, and the first bar placed closer to the rest than others. commit dc369fa198281e000afc1bd17326f3a264adf7d1 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 02:09:21 2021 +0700 Fix the "border_width affects graph bar width" bug Graph bars were drawn with lines of border_width thickness instead of 1, i.e. possibly too fat to be readable. commit 1ba73378cf29ed27a6ecd18716394779ceb543d9 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 00:13:03 2021 +0700 Simplify internal values representation in graph even more New values in graph._private.values were pushed to the end, and old values were removed from the beginning of the table. In draw() however newer values are drawn first, at the left edge of the graph, necessitating annoying idx = #values - idx conversions everywhere. In particular in stacked graph that means that values that are drawn together may have different indices. One has to shift the values array in add_value() either way, so one might as well insert new values at the beginning of the array and simplify the rest of the code. This doesn't seem like much yet, but it will prove to be much more convenient and less error-prone in what I'm going to do later. commit f70523ee68dbd6b67050ace4b82903a9b22440c7 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 13:35:10 2021 +0700 Simplify internal values representation in graph widget graph._private.values was either a table of values or a table of tables of values, depending on whether "stack" property is enabled, ... or a mix of both if user forgot to pass the second "group" parameter to graph:add_value(). This resulted in pretty fragile and confusing behavior, e.g. leaving user to wonder, why they see nothing more after setting stack=true for a graph in a previously working rc.lua, and demanding a very careful use of add_value() from the user, or else some values or even entire stacks would mysteriously vanish and errors in draw() get triggered, sometimes after a period of seemingly normal functioning. Now `values` is always a table of tables and the group parameter in add_value() defaults to 1, thus making the non-stacked graph a trivial variant of the stacked graph.
2021-05-20 17:14:03 +02:00
-- @property clamp_bars
-- @tparam[opt=false] boolean clamp_bars
graph: complete widget overhaul and bug fixes Squashed commit of the following: commit 69821f51fe1e8652715658543e50631ece495090 Author: Alex Belykh <albel727@ngs.ru> Date: Mon May 10 19:17:55 2021 +0700 Refactor property handling in wibox.widget.graph In addition, baseline_value, step_width and step_spacing properties are all set to their default non-nil numeric values during instance creation now. commit 842eb429bf5df4b2ba46b2e6f7646afe89b1a3c3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 9 06:18:45 2021 +0700 Make graph:draw_values() private There's no documentation nor confidence that it's a good API, so I'm hiding it for now. commit 720746780574e4bad14a71fc4174955139c2eb50 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 04:04:13 2021 +0700 Document graph_should_draw_data_group() for posterity commit 7c73f3754ce6ba86b118f1170a6f9e406ab4841d Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:59:21 2021 +0700 Make graph:should_draw_data_group() private There's no confidence whether it's a good API, so I'm hiding it for now. commit b3539e20adb6423118af726ae4f8751ef864b5d2 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:36:44 2021 +0700 Make graph:preprocess_values() private There's no confidence whether it's a good API, so I'm hiding it for now. commit 2df4400e780a6260f8d0ab1c60734fbb11bf3c4f Author: Alex Belykh <albel727@ngs.ru> Date: Thu May 6 10:05:23 2021 +0700 Add spec/wibox/widget/graph_spec.lua commit ce01a9771ab439b8df841f1565aa4aa8a44a5e0d Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 08:16:18 2021 +0700 Improve graph.capacity documentation commit e28fade8f655802f5bf89154169f68d256b8e332 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 07:36:01 2021 +0700 Document graph:compute_drawn_values_num() commit dd68332292465422d08b48e9f0229f143beccbc6 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 05:24:23 2021 +0700 Document graph:pick_data_group_color() method commit ca6dc55f77aca88a6e9cc90bb6fd0045739f41f5 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 04:13:58 2021 +0700 Guard against setting data groups sparsely in graph:add_value() commit cdea5f126f7984e72924f6dffbd312c4fe3b032f Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 02:36:12 2021 +0700 Refactor scaling and baseline choice out of graph:draw() commit 10135deafe8a9390ba66ab657ebb1fb41d549e8c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 21:34:48 2021 +0700 Document graph.nan_color and graph.stack properties commit 0d73bb84ac66b87e16aee472bd1f299549f66a1c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 20:49:24 2021 +0700 Use a better example in docs for the graph.stack property commit c27d160a0826d033d49d6141608c40aa8a0642fa Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 16:05:38 2021 +0700 Document graph.baseline_value property commit 459bc8176c13d4ea32af0dcdc2399d3b929b1c6a Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:53:47 2021 +0700 Document graph.clamp_bars property commit e732ee0b3b007d213e689b98cc00ae7cb141ae08 Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:34 2021 +0700 Improve graph:add_value() and :clear() documentation commit ae73e3aa9f8d60409cbe4cb4da9612f31af4f38e Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:11 2021 +0700 Improve graph min_value/max_value/scale documentation commit a7350996a9def773b91b1631e6c0507e33d8f93b Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:27:36 2021 +0700 Cleanup graph examples to show only relevant things in docs commit 30740c26f4f41c16f4c8a3c339bcb7bd299e14cd Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 14:04:36 2021 +0700 Rename graph.stack_colors property to `group_colors` The colors are used for stacked and non-stacked graphs alike, so the name didn't make sense. Also document and clarify some color stuff. commit 1ea01bfd410971cf0d0c25fd74cd764d5eb7279d Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 21:25:41 2021 +0700 Add an example file for code coverage of some fringe graph cases commit eae68e39553f88a6275a0c65bf040b5d4c262b54 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 20:23:32 2021 +0700 Add step_shape to the graph/nan_color example for code coverage commit 8027147bc2eab34a97b49a428fb8cef27f122bed Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:55:18 2021 +0700 Add example files for graph.nan_color/nan_indication properties commit 89a25a165c52b2c15b6c4d95ecb777a9067e66f9 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:33:07 2021 +0700 Make widget graph:add_value(v) default v to NaN 0 as a default makes no sense and silences the error that is failing to specify a value, to which the user should rather be alerted. The graph widget has sane NaN handling now and will do just that. commit 2835d552440be48d356a7f7b1d64963696dd550b Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:20:48 2021 +0700 Implement graph.nan_color and nan_indication properties Now, whenever there's a NaN among values and graph.nan_indication is set, the corresponding area, where the value bar should have been drawn, is filled with the nan_color from top to bottom. The painting is done after all data is rendered, to make sure that it won't be overpainted and go unnoticed. NaNs among graph's values are inevitably a sign of some error and it is arguably not a sane default behavior to simply not render them at all. The user should take immediate note of any errors by default, instead of wondering at a mysteriously empty graph widget, which is why nan_indication is set to true by default. If the user wants to silence NaNs, they can always simply set it to false. nan_color if unset, defaults to a noticeable yellow-black pattern. As a direct and planned consequence, stacked graphs with negative values in them trigger the NaN handling too. But silencing NaN handling for those graphs is also possible and semi-sane, because then NaNs and negatives are ignored and the graph behaves (almost) like they were zeros. commit 9c1f8f08b400e01e19c534f3810c9c9ab485760c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 26 08:10:19 2021 +0700 Refactor out graph_map_value_to_widget_coordinates() function commit 1905991ddecabdae3e15ffb8ee5b18a1bee1a711 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 28 13:39:29 2021 +0700 Add an example file for graph.baseline_value property commit b31b39d66f4c037a9484b71a3f910703ae01fac0 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 07:03:49 2021 +0700 Add an example file for graph.clamp_bars=false/true comparison commit a1fc850e3047f9ee2c9ddeffacfbabf3ded34171 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 04:06:45 2021 +0700 Add an example file for graphs with negative values and shapes commit 2f012f8ff1bb5094c14e724a61e59a2a3a296bc8 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 01:06:05 2021 +0700 Add an example file for a graph displaying negative values commit 7b14fbd59a4c4c51962fd3110a1a900dd5f4eb98 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 11:18:26 2021 +0700 Add an example file for graphs with step_width < 1 commit dfd9a54544b637e2fbe3d1325f9cc24b3f4d2313 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 10:17:38 2021 +0700 Add an example file for graph.stack=false/true comparison Should also demonstrate that multiple data groups are possible for non-stacked graphs too. commit 5b532106c3fd255f9b66a66cfb88d977b489ab5d Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 09:34:14 2021 +0700 Add an example file for disabling graph data groups with nil colors commit ac6f1083c02fcb428f089a616f9b0ebaa5496b6c Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:57:25 2021 +0700 Add an example file for step_shape/width/spacing on stacked graphs commit b3703db6a5139e73e72c1cfd198ece2cd9be7df2 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:43:56 2021 +0700 Add an example file for scale=false/true on stacked graph commit b000aed4a12cb44fb501e1c8432a71a14b2f8f3f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 20 08:22:19 2021 +0700 Document deprecation of graph's width and height commit cb0ad617bd6c83a499896797318e3b0b588c3566 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 07:53:37 2021 +0700 Deprecate width and height properties of wibox.widget.graph commit 8c64e14ad91a074b784166a7054e53444cccb9d2 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 04:37:59 2021 +0700 Refactor graph:draw() to separate concerns Firstly, summation of group values for stacked graphs is extracted into graph:preprocess_values(). This makes graph:draw() smaller and exposes the fact, that stacked graphs are almost entirely just a form of a more general concept: pre-render data manipulation. It's something a user might want to customize, and now they can do it by monkey-patching the method. Secondly, the logic for deciding, if and with which color a data group should be drawn, is extracted into graph:should_draw_data_group() and get_data_group_color() methods respectively. This makes it customizable too and makes more obvious the fact, that it is a general thing that has nothing to do with stacked graphs in particular. With this there are only 5 lines remaining in graph:draw(), that could be said to be specific to stacked graphs, all pertaining to the `prev_y` variable, which merely preserves the bar coordinates from a previous data group. commit 2a32b6305d893f413303728a0a3da1dcd9a5591e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 22:52:37 2021 +0700 Pointlessly optimize graph:add_value() 1) Purge old values first, then insert the new one, if needed. Since Lua grows arrays in power of two steps, and our guess_capacity() also takes care to return even integers, it would be a shame to overstep the capacity by one by inserting first and thus getting the table size doubled only to never use the most of it. 2) Use a removal procedure that doesn't call the slow length operator at all. Previous code called it twice (once in table.remove) for every removed value. commit 680ea8f5d81e9040afb4662b3229e937e4b89e02 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:23:38 2021 +0700 Set graph data series color before the painting loop commit b970f0f221ae76250f2a94433ff79d653caa6e21 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:05:37 2021 +0700 Drop a large no longer necessary `if` in graph:draw_values() This commit is almost purely a whitespace change. commit 6b4f0541c70ec99d7f6e276bae64939f337120d6 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:00:50 2021 +0700 Extract values rendering code into graph:draw_values() method This is to avoid ungodly if nesting levels, which are needed to bail out of drawing values early and skip to drawing the border. The next commit will get rid of the if. commit e0950e4f99d1ea1c4b8cea2cc67629fa40030c2a Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 14:50:20 2021 +0700 Track graph draw() usage to guess necessary array capacity The last step of divorcing add_values() from width calculations, while ensuring that user doesn't observe a lack of data to draw. The widget now tracks an approximate number of values that it has been asked to draw() in _private.last_drawn_values_num, and truncates its values array based on that (if not overridden by the capacity property). commit 2e3d542282f35e7c0fed454da0f2eada0fcf3b78 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 13:35:12 2021 +0700 Add `capacity` property to graph widget This is the second step of divorcing add_values() from calculations involving widths. How many values are stored and how many values are drawn are distinct concepts. There isn't even a single definitive "how many values are drawn" because the widget could be rendered in several places with distinct widths simultaneously, and in every such place the data must be drawn appropriately, e.g. with auto-scaling, that takes only the visible data points into account. And the draw() method can do that now. The default capacity value of nil uses heuristics based on widget's _private.width to guess some capacity that would tolerate some dynamic widget resizing. If the user finds the heuristic failing them, they can set the capacity property to the desired maximum number of values that will be kept in the _private.values array (in each data group). commit 08d4a80686f7fe363551c35589a460839f60853e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 03:27:18 2021 +0700 Extract values size computation into compute_drawn_values_num() This is the first step of making add_values() not concern itself (directly) with widget dimensions. The user can monkey-patch the method to override how many values are (logically) drawn, because it affects graph auto-scaling and can be useful for rendering of step_shape-s that paint outside of their step_width. I didn't make it a full-blown function-valued property, because it seems a fringe functionality, which I'm too lazy to document. commit 3c5617c98ab1147618ed22e8414a77fc56b45f6e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 02:32:29 2021 +0700 Copy graph widget prototype methods over in bulk commit 169538837ece41a03d02ba8fbc0e4097062b67a3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun Apr 25 05:58:01 2021 +0700 Remove graph.baseline_y property After giving it some thought I've concluded, that it is unlikely to be useful as it is now. I'd better reserve the name for some more sensible functionality, like fixing the baseline_value defined axis at the given widget coordinate, adjusting the scaling accordingly, so min_value/max_value won't generally correspond to widget top and bottom anymore. commit 202ffaa9a0b820c7c471e1121d01b91f8f76cd38 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 12:06:55 2021 +0700 Add baseline_y and baseline_value properties to graph widget Now values in graph have a *baseline*, tunable with baseline_y and baseline_value properties. It is the (for now invisible) horizontal line from which all bars in the graph grow. I call it a "baseline" instead of "axis", because it doesn't actually have to correspond to the 0 value. But it can, and actually does now by default. baseline_y and baseline_value properties determine the vertical placement of the baseline in the widget. baseline_y is the position of the baseline in normalized widget coordinates, e.g. 0 corresponds to widget's top, 1 - to widget's bottom, and 0.5 - to widget's middle. So if e.g. one sets baseline_y = 0, then no matter how the widget is resized and how its values are scaled, bars in it will be drawn starting from the top. baseline_value is the position of the baseline in value coordinates. So if one sets baseline_value = 10, then no matter what happens, bars for values greater than 10 will be drawn as growing up from this line, and bars for values smaller as growing down. Where and if it will be seen in the widget, depends on the scaling. baseline_value = 0 is the familiar zero axis. baseline_y is unset by default and has priority, i.e. if it's set, then baseline_value is ignored. baseline_value defaults to 0. Prior to this commit, bars were always drawn from the bottom edge of the widget, or, in the new terms, the widget had baseline_y = 1. But it was indistinguishable from baseline_value = 0, because the widget clamped all negative values to 0, so the difference between the two could never be observed. baseline_value = 0 is clearly more useful and familiar to people, so now it is in force by default. commit df7e19abaebf0a871b5ca217b4cc3365533267a6 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 11:18:12 2021 +0700 Move value clamping from graph:add_value() into draw() Previously all values were forcibly clamped between 0 (not tunable at all) and max_value (1 by default). Thus graphs with negative values were not an option. NaN values were conflated with zeros, even though it may be useful to maintain the distinction and render them differently. And if user happened to dynamically change the max_value property, they discovered, that their data got truncated. Preserving user data as it was given is more flexible and makes things easier to debug. This commit does exactly that. So what happens now to the newly allowed values during draw()? There are 3 new cases to consider: 1) NaNs 2) Negatives 3) Values outside of min_value..max_value range NaNs are presently simply not drawn. An empty place is reserved where the value should've been, but nothing else is done. This is distinct from how a zero is handled, e.g. if step_shape is set, it is called with the height parameter = 0, which can still end up drawing something, depending on the shape. One could also consider addition of nan_color/nan_shape properties which could be drawn to alert user to a gap in their data. Negative values necessitate introduction of the concept of the zero axis, from which they will be drawn DOWN, in contrast to positive values. Where should this axis be drawn? How should step_shape-s with inherent direction, e.g. arrows, be drawn for negatives? This will be addressed in the next commit. Values outside of min/max_value range can imply shapes that go beyond widget dimensions. An arrow shape that goes beyond the roof is visibly different from an arrow that saturates and merely touches the widget top, and the user may reasonably desire either behavior. Therefore this commit introduces a boolean clamp_bars property, which defaults to true for reasons of robustness and backward compatibility with the behaviour prior to this commit. The property of course affects only the display of the values, the actual user data stays intact. commit c9c24fb1ab741cd6bb8074971d568e0646c51532 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:29:01 2021 +0700 Use rectangles for drawing graph bars of all sizes I have benchmarked various bar drawing strategies and graph sizes and found out that drawing graph bars with rectangles is as fast or even marginally faster than using fat lines. Thus the code that tries to special-case 1px bars as lines seems to be not worth the additional complexity it introduces, and this commit removes it. Incidentally, I've also established, that rectangles are more robust. If one draws graph bars and feeds cairo very large coordinates (~2^62), then corresponding rectangles fail to render, but all the other rectangles in the same series get rendered successfully, whereas a single large coordinate in a move_to/line_to() sequence is enough to cause all lines in the batch to fail to render. commit bc7f4be5e06405b5aa7fb443a5ace2b9b9575b1f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:01:42 2021 +0700 Cache cairo methods used in the inner loop of graph:draw() I have benchmarked the draw() method, both on cairo xcb and on argb32 image surfaces, and have discovered that cairo context method calls are unnaturally slow, to the point, that almost nothing else in the function matters. For example, drawing bars with vertical lines made the whole draw() function almost 2 times slower than doing the same with rectangles, merely because the former requires two method calls (move_to/line_to), and the latter only one. Simply caching hotpath cairo methods in local variables, reduced the difference between the drawing methods to a negligible one and made the whole function 4 times faster on a typical 100-bar graph, (1.4ms to 0.33ms on my laptop) thus bringing closer my secret dream of drawing live audio data with awesome graphs (kidding). I guess, LGI does something not very optimal there, like gobject-introspecting the method every time, or something. There might be some untapped optimization potential, possibly in upstream, like a method cache, from which everything in awesome could benefit, even though I imagine not many things are so call-intensive as the graph widget, to really feel the difference. commit 538df25bdf02b53d0bbe81809eef2e6fdb05ff6f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 07:32:37 2021 +0700 Use 1-based index in the inner graph draw loop It's used there only once for a calculation and then several times to index some tables, and index 0 is bad for lua tables performance-wise. commit 7412546916cd0a0a490130a120255ffb719398b8 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:55:12 2021 +0700 Leave graph.max_value uninitialized at construction This is a user-visible change, but the previous behavior was probably not what user expected anyway, namely with graph.scale set the graph mysteriously couldn't zoom in onto values that are smaller than 1. commit eb81d7929a161e79c13262cfa73ac2e6a820e8ed Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:48:29 2021 +0700 Allow graph.max_value be nil This makes its behave symmetrically to min_value. Now it defaults to 1, when graph.scale is not set, and to the maximum value in the graph data, when graph.scale is set. commit f622a8430cbfde7007b58d7f4c9c76b08c91e997 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 12:01:13 2021 +0700 Calculate max values num in graph:add_value() more precisely At most so many values were kept in the widget values array, as there are pixels of usable widget width (i.e. sans border). This is exactly right for the surely most common case of graphs with 1px-wide bars, but is more than needed for graphs with step_width+step_spacing>1, and not enough for graphs with subpixel bars. Moreover this made graph autoscaling behave weirdly in graphs with thicker bars, because old values survived longer in the array and kept affecting min_value/max_value calculations even after they were shifted off-screen. The maximum number of kept values is now calculated exactly as the number of values that can be displayed at once, taking step_width and step_spacing into account. It might be worth adding a max_values_num property to the graph to let user override this calculation, in case they draw shapes in step_shape() outside of its supposed rectangle, so our assumptions of visibility are wrong, or the widget often gets resized, resulting in the annoyance of visibly truncated series. commit 0aa63249a18641251a1bf77423739164f0eab39f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 02:30:13 2021 +0700 Fix border_color/border_width bugs in graph widget add_value() wrongly assumed that border_width is always set, when border_color is, so if user set only the latter, an error occurred. add_value() assumed that border_width is always < width which led to awesome hanging up in an infinite loop, should that fail to be the case. The graph border_width property was driven by the border_color property, instead of the other way round. If border_color wasn't set, the border wasn't drawn all, even if user sets non-zero border_width, which is a confusing behavior, in contrast to all other widgets, which simply use a plethora of fallback colors to draw the border, when needed. This also meant that, despite being documented, the beautiful.graph_border_color fallback color could not and was not ever used. If the border_color was set though, an unset border_width was immediately assumed to be 1 instead of 0, also contrary to what other widgets do. Theme colors shouldn't influence widget layout, so this commit fixes this behavior to: 1) border_width always defaults to 0, if unset. 2) border_color falls back to beautiful.graph_border_color or white. Maybe one should also add a beautiful.graph_border_width theme variable, like some widgets do. commit dad4a3253b8fef971fb82720d2a407fcc0a879ad Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 03:11:27 2021 +0700 Implement multiple data series drawing for non-stacked graph The widget can already hold and draw multiple data groups, so it makes little sense to limit drawing only to the first group when graph.stack = false, probably leading to user's confusion. Now user can call add_value(v, group) with group > 1 and the data series will be drawn (over groups with lesser id-s) with graph.stack_colors[group] color, if stack_colors is set, or with graph.color, if stack_colors isn't set. Thus nothing visibly changes for all current users of non-stacked graphs with only single data series, but the new functionality is now there for those who need it. One can't fully emulate this functionality, e.g. with multiple graphs widgets in a wibox.layout.stack, because data series in a single widget can enjoy a common auto-scaling. commit 3e645c976184ab5f50d625271c25062db9ab7ed9 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:59:12 2021 +0700 Don't draw a stacked graph data group if there's no color for it If stack_colors[idx] is nil for the group `idx`, don't draw it at all (as opposed, e.g. to the effect of drawing it with a transparent color, or as though it had all values == 0, which both produce different results). This code is still subtly different from prior behavior, because now user can have a non-contiguous stack_colors table and turn the display of any data group on and off at will, whereas the code two commits earlier would just stop rendering as soon as ipairs(stack_colors) does, i.e. no data series were drawn past the first nil hole. This commit only looks large, but is mostly a whitespace change due to nesting from a pair of wrapping ifs. commit 8c0273786de1ade66bdd977699f98b588da9f487 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:32:42 2021 +0700 Unify stacked and non-stacked graph rendering Now stacked graphs support everything the nonstacked do, like step_width ~= 1 and custom step_shape-s. Technical notes: This code no longer calls cr:stroke() for every single value in a stacked graph, but rather paints a whole series and then cr:stroke/fill()-s it at all once with its color, improving performance considerably. Additionally the graph no longer relies on the presence of the stack_colors property to render series. It draws all data that it has, and, if there's no corresponding color in stack_colors, falls back to graph.color. This will be changed in the next commit to the prior behavior of only drawing those series for which there's a color in the stack_colors table, but that would've made this commit's diff much larger due to required nesting ifs over large code chunks. This commit also takes care to properly handle NaNs and negative values. Those are currently (almost) guaranteed to not occur in the values array due to clipping in graph:add_value(), but I intend to eventually drop the clipping, because it does nothing that draw() really relies upon, and allowing unclipped and NaN values is more flexible and less surprising, e.g. when user dynamically toggles graph.scale. Eventually I intend to implement drawing negative values in non-stacked graphs and possibly even in the stacked ones. commit 38c6589c51b2aa057d7c2f40182ecf32b5b2ff5e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 10:04:15 2021 +0700 Convert graph:draw() to use `self` commit f21f1b2550426df1c4ea8abde079b1ccef84b394 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 12:27:26 2021 +0700 Fix scaling for stacked graphs Stacked graphs have cumulative heights, values from all groups that end up in the same graph bar are effectively summed. Previous code didn't take that into account when calculating min/max_value for the purposes of rescaling, but simply examined every value separately. This code adds up all values up front and finds min/max among those, to determine proper scaling that would fit the graph exactly. commit 1ce4c7bf63bdd6c99c195d668347bec49355e921 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 09:49:35 2021 +0700 Draw shaped graph bars only up to the baseline Prior to this commit, every step_shape bar was, for some reason, drawn with height = full widget's height, regardless of the value it represents. So for the most bars their lower end was far outside the box and therefore clipped off. So e.g. one couldn't see the lower round edges of rounded rectangles. This commit fixes that by appropriately varying the height that is passed into step_shape(), so that lower edges of the bars always exactly touch the lower edge of the graph. If the user wants to restore the prior behavior, they can simply ignore the passed-in height in step_shape() and just always draw their shape with height = full widget height. But they couldn't replicate the effect of this commit in their rc.lua without essentially writing their own graph widget. commit e25185b08da23745b5815f85f6e9e43222ed2294 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 08:42:54 2021 +0700 Avoid incremental transform shifting during graph bar rendering Instead of letting addition errors and cognitive load accumulate simply use the same offset calculation that is used for lines and reset the transform back to the top-left corner after every drawn bar. It's conceptually simpler, and will be useful later for stacked graphs and more. It also fixes a discrepancy between plain and step_shape rendering. Prior to this commit, bar coloring occurred with a disturbed transform, which was centered at the last drawn bar, wherever it happened to be. This meant that nontrivial colors, like gradients and images, were arbitrarily offset when step_shape was used. commit f3e98866d8583c35d0b557c09f529299edc9ba2c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:45:29 2021 +0700 Refactor out graph bar height expression into a variable commit f54528d6892f5aaa4595917853c9e2130f743fdb Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:20:25 2021 +0700 Draw graph bars with lines only when step_width == 1 Before this commit graph bars of step_width < 1 were drawn with lines of width 1, but that's a bit unfortunate, because it led to much overpainting, while still ending up with a graphically incorrect result. Let's draw such bars with rectangles and let cairo do things with subpixels, getting more graphically accurate result, while still keeping the common case of step_width == 1 (supposedly) fast-drawn with lines. commit b6515dca67c18b78eb5657378131a62ab43d4cfc Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:54:07 2021 +0700 Fix unsharp edges when drawing graph bars with rectangles The 0.5 offset is only helpful for 1-width strokes, not for fills. commit 7f3db8f07e82f821bcd1dc418d66564f537609d6 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:21:01 2021 +0700 Do coordinate transformations even if the value isn't drawn The graph presently doesn't draw values < 0, but there was a discrepancy between drawing with and without step_shape. The default line/rectangle drawing reserves an empty place where the skipped value should've been, but no such empty place is reserved, when step_shape is used. This commit fixes that. Skipped values always have their empty space now. commit 7b731ebfe96af7a9dba9c57371da6761cf22c00c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:03:30 2021 +0700 Fix graph bar scaling when min_value is not 0 Previous calculation lead to underscaling of values so that even value = max_value wasn't drawn to full height. commit dca59fd8b7fff0e7e646f2dfc6bd92047028e5a8 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:18:40 2021 +0700 Calculate the horizontal graph bar offset only when needed commit 940ddf4b9825e9c3a91d276208c089d8c74e49db Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:17:23 2021 +0700 Call cr:move_to() only when drawing the graph with lines It's redundant otherwise and might even interfere with user's drawing in step_shape(). commit b5d5cd10ca29fb29cd751faaa95137c2fe89ff63 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:04:06 2021 +0700 Fix wrong horizontal offset calculations in widget.graph Indices in the loop are zero-based, so things like i-1 and i>1 aren't necessary and actually wouldn't have been correct even in a 1-based loop. step_shape = gears.shape.rectangle and step_shape = nil, which is supposed to default to rectangle according to docs, were drawn differently due to mismatched offset calculations, which led to problems especially noticeable when step_spacing is not zero, e.g: 1) whole graph shifted left too much, drawn partially off-screen. 2) whole graph shifted right too much, and the first bar placed closer to the rest than others. commit dc369fa198281e000afc1bd17326f3a264adf7d1 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 02:09:21 2021 +0700 Fix the "border_width affects graph bar width" bug Graph bars were drawn with lines of border_width thickness instead of 1, i.e. possibly too fat to be readable. commit 1ba73378cf29ed27a6ecd18716394779ceb543d9 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 00:13:03 2021 +0700 Simplify internal values representation in graph even more New values in graph._private.values were pushed to the end, and old values were removed from the beginning of the table. In draw() however newer values are drawn first, at the left edge of the graph, necessitating annoying idx = #values - idx conversions everywhere. In particular in stacked graph that means that values that are drawn together may have different indices. One has to shift the values array in add_value() either way, so one might as well insert new values at the beginning of the array and simplify the rest of the code. This doesn't seem like much yet, but it will prove to be much more convenient and less error-prone in what I'm going to do later. commit f70523ee68dbd6b67050ace4b82903a9b22440c7 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 13:35:10 2021 +0700 Simplify internal values representation in graph widget graph._private.values was either a table of values or a table of tables of values, depending on whether "stack" property is enabled, ... or a mix of both if user forgot to pass the second "group" parameter to graph:add_value(). This resulted in pretty fragile and confusing behavior, e.g. leaving user to wonder, why they see nothing more after setting stack=true for a graph in a previously working rc.lua, and demanding a very careful use of add_value() from the user, or else some values or even entire stacks would mysteriously vanish and errors in draw() get triggered, sometimes after a period of seemingly normal functioning. Now `values` is always a table of tables and the group parameter in add_value() defaults to 1, thus making the non-stacked graph a trivial variant of the stacked graph.
2021-05-20 17:14:03 +02:00
-- @propemits true false
--- The value corresponding to the starting point of graph bars.
graph: complete widget overhaul and bug fixes Squashed commit of the following: commit 69821f51fe1e8652715658543e50631ece495090 Author: Alex Belykh <albel727@ngs.ru> Date: Mon May 10 19:17:55 2021 +0700 Refactor property handling in wibox.widget.graph In addition, baseline_value, step_width and step_spacing properties are all set to their default non-nil numeric values during instance creation now. commit 842eb429bf5df4b2ba46b2e6f7646afe89b1a3c3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 9 06:18:45 2021 +0700 Make graph:draw_values() private There's no documentation nor confidence that it's a good API, so I'm hiding it for now. commit 720746780574e4bad14a71fc4174955139c2eb50 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 04:04:13 2021 +0700 Document graph_should_draw_data_group() for posterity commit 7c73f3754ce6ba86b118f1170a6f9e406ab4841d Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:59:21 2021 +0700 Make graph:should_draw_data_group() private There's no confidence whether it's a good API, so I'm hiding it for now. commit b3539e20adb6423118af726ae4f8751ef864b5d2 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:36:44 2021 +0700 Make graph:preprocess_values() private There's no confidence whether it's a good API, so I'm hiding it for now. commit 2df4400e780a6260f8d0ab1c60734fbb11bf3c4f Author: Alex Belykh <albel727@ngs.ru> Date: Thu May 6 10:05:23 2021 +0700 Add spec/wibox/widget/graph_spec.lua commit ce01a9771ab439b8df841f1565aa4aa8a44a5e0d Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 08:16:18 2021 +0700 Improve graph.capacity documentation commit e28fade8f655802f5bf89154169f68d256b8e332 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 07:36:01 2021 +0700 Document graph:compute_drawn_values_num() commit dd68332292465422d08b48e9f0229f143beccbc6 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 05:24:23 2021 +0700 Document graph:pick_data_group_color() method commit ca6dc55f77aca88a6e9cc90bb6fd0045739f41f5 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 04:13:58 2021 +0700 Guard against setting data groups sparsely in graph:add_value() commit cdea5f126f7984e72924f6dffbd312c4fe3b032f Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 02:36:12 2021 +0700 Refactor scaling and baseline choice out of graph:draw() commit 10135deafe8a9390ba66ab657ebb1fb41d549e8c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 21:34:48 2021 +0700 Document graph.nan_color and graph.stack properties commit 0d73bb84ac66b87e16aee472bd1f299549f66a1c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 20:49:24 2021 +0700 Use a better example in docs for the graph.stack property commit c27d160a0826d033d49d6141608c40aa8a0642fa Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 16:05:38 2021 +0700 Document graph.baseline_value property commit 459bc8176c13d4ea32af0dcdc2399d3b929b1c6a Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:53:47 2021 +0700 Document graph.clamp_bars property commit e732ee0b3b007d213e689b98cc00ae7cb141ae08 Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:34 2021 +0700 Improve graph:add_value() and :clear() documentation commit ae73e3aa9f8d60409cbe4cb4da9612f31af4f38e Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:11 2021 +0700 Improve graph min_value/max_value/scale documentation commit a7350996a9def773b91b1631e6c0507e33d8f93b Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:27:36 2021 +0700 Cleanup graph examples to show only relevant things in docs commit 30740c26f4f41c16f4c8a3c339bcb7bd299e14cd Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 14:04:36 2021 +0700 Rename graph.stack_colors property to `group_colors` The colors are used for stacked and non-stacked graphs alike, so the name didn't make sense. Also document and clarify some color stuff. commit 1ea01bfd410971cf0d0c25fd74cd764d5eb7279d Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 21:25:41 2021 +0700 Add an example file for code coverage of some fringe graph cases commit eae68e39553f88a6275a0c65bf040b5d4c262b54 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 20:23:32 2021 +0700 Add step_shape to the graph/nan_color example for code coverage commit 8027147bc2eab34a97b49a428fb8cef27f122bed Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:55:18 2021 +0700 Add example files for graph.nan_color/nan_indication properties commit 89a25a165c52b2c15b6c4d95ecb777a9067e66f9 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:33:07 2021 +0700 Make widget graph:add_value(v) default v to NaN 0 as a default makes no sense and silences the error that is failing to specify a value, to which the user should rather be alerted. The graph widget has sane NaN handling now and will do just that. commit 2835d552440be48d356a7f7b1d64963696dd550b Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:20:48 2021 +0700 Implement graph.nan_color and nan_indication properties Now, whenever there's a NaN among values and graph.nan_indication is set, the corresponding area, where the value bar should have been drawn, is filled with the nan_color from top to bottom. The painting is done after all data is rendered, to make sure that it won't be overpainted and go unnoticed. NaNs among graph's values are inevitably a sign of some error and it is arguably not a sane default behavior to simply not render them at all. The user should take immediate note of any errors by default, instead of wondering at a mysteriously empty graph widget, which is why nan_indication is set to true by default. If the user wants to silence NaNs, they can always simply set it to false. nan_color if unset, defaults to a noticeable yellow-black pattern. As a direct and planned consequence, stacked graphs with negative values in them trigger the NaN handling too. But silencing NaN handling for those graphs is also possible and semi-sane, because then NaNs and negatives are ignored and the graph behaves (almost) like they were zeros. commit 9c1f8f08b400e01e19c534f3810c9c9ab485760c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 26 08:10:19 2021 +0700 Refactor out graph_map_value_to_widget_coordinates() function commit 1905991ddecabdae3e15ffb8ee5b18a1bee1a711 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 28 13:39:29 2021 +0700 Add an example file for graph.baseline_value property commit b31b39d66f4c037a9484b71a3f910703ae01fac0 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 07:03:49 2021 +0700 Add an example file for graph.clamp_bars=false/true comparison commit a1fc850e3047f9ee2c9ddeffacfbabf3ded34171 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 04:06:45 2021 +0700 Add an example file for graphs with negative values and shapes commit 2f012f8ff1bb5094c14e724a61e59a2a3a296bc8 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 01:06:05 2021 +0700 Add an example file for a graph displaying negative values commit 7b14fbd59a4c4c51962fd3110a1a900dd5f4eb98 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 11:18:26 2021 +0700 Add an example file for graphs with step_width < 1 commit dfd9a54544b637e2fbe3d1325f9cc24b3f4d2313 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 10:17:38 2021 +0700 Add an example file for graph.stack=false/true comparison Should also demonstrate that multiple data groups are possible for non-stacked graphs too. commit 5b532106c3fd255f9b66a66cfb88d977b489ab5d Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 09:34:14 2021 +0700 Add an example file for disabling graph data groups with nil colors commit ac6f1083c02fcb428f089a616f9b0ebaa5496b6c Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:57:25 2021 +0700 Add an example file for step_shape/width/spacing on stacked graphs commit b3703db6a5139e73e72c1cfd198ece2cd9be7df2 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:43:56 2021 +0700 Add an example file for scale=false/true on stacked graph commit b000aed4a12cb44fb501e1c8432a71a14b2f8f3f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 20 08:22:19 2021 +0700 Document deprecation of graph's width and height commit cb0ad617bd6c83a499896797318e3b0b588c3566 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 07:53:37 2021 +0700 Deprecate width and height properties of wibox.widget.graph commit 8c64e14ad91a074b784166a7054e53444cccb9d2 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 04:37:59 2021 +0700 Refactor graph:draw() to separate concerns Firstly, summation of group values for stacked graphs is extracted into graph:preprocess_values(). This makes graph:draw() smaller and exposes the fact, that stacked graphs are almost entirely just a form of a more general concept: pre-render data manipulation. It's something a user might want to customize, and now they can do it by monkey-patching the method. Secondly, the logic for deciding, if and with which color a data group should be drawn, is extracted into graph:should_draw_data_group() and get_data_group_color() methods respectively. This makes it customizable too and makes more obvious the fact, that it is a general thing that has nothing to do with stacked graphs in particular. With this there are only 5 lines remaining in graph:draw(), that could be said to be specific to stacked graphs, all pertaining to the `prev_y` variable, which merely preserves the bar coordinates from a previous data group. commit 2a32b6305d893f413303728a0a3da1dcd9a5591e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 22:52:37 2021 +0700 Pointlessly optimize graph:add_value() 1) Purge old values first, then insert the new one, if needed. Since Lua grows arrays in power of two steps, and our guess_capacity() also takes care to return even integers, it would be a shame to overstep the capacity by one by inserting first and thus getting the table size doubled only to never use the most of it. 2) Use a removal procedure that doesn't call the slow length operator at all. Previous code called it twice (once in table.remove) for every removed value. commit 680ea8f5d81e9040afb4662b3229e937e4b89e02 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:23:38 2021 +0700 Set graph data series color before the painting loop commit b970f0f221ae76250f2a94433ff79d653caa6e21 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:05:37 2021 +0700 Drop a large no longer necessary `if` in graph:draw_values() This commit is almost purely a whitespace change. commit 6b4f0541c70ec99d7f6e276bae64939f337120d6 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:00:50 2021 +0700 Extract values rendering code into graph:draw_values() method This is to avoid ungodly if nesting levels, which are needed to bail out of drawing values early and skip to drawing the border. The next commit will get rid of the if. commit e0950e4f99d1ea1c4b8cea2cc67629fa40030c2a Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 14:50:20 2021 +0700 Track graph draw() usage to guess necessary array capacity The last step of divorcing add_values() from width calculations, while ensuring that user doesn't observe a lack of data to draw. The widget now tracks an approximate number of values that it has been asked to draw() in _private.last_drawn_values_num, and truncates its values array based on that (if not overridden by the capacity property). commit 2e3d542282f35e7c0fed454da0f2eada0fcf3b78 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 13:35:12 2021 +0700 Add `capacity` property to graph widget This is the second step of divorcing add_values() from calculations involving widths. How many values are stored and how many values are drawn are distinct concepts. There isn't even a single definitive "how many values are drawn" because the widget could be rendered in several places with distinct widths simultaneously, and in every such place the data must be drawn appropriately, e.g. with auto-scaling, that takes only the visible data points into account. And the draw() method can do that now. The default capacity value of nil uses heuristics based on widget's _private.width to guess some capacity that would tolerate some dynamic widget resizing. If the user finds the heuristic failing them, they can set the capacity property to the desired maximum number of values that will be kept in the _private.values array (in each data group). commit 08d4a80686f7fe363551c35589a460839f60853e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 03:27:18 2021 +0700 Extract values size computation into compute_drawn_values_num() This is the first step of making add_values() not concern itself (directly) with widget dimensions. The user can monkey-patch the method to override how many values are (logically) drawn, because it affects graph auto-scaling and can be useful for rendering of step_shape-s that paint outside of their step_width. I didn't make it a full-blown function-valued property, because it seems a fringe functionality, which I'm too lazy to document. commit 3c5617c98ab1147618ed22e8414a77fc56b45f6e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 02:32:29 2021 +0700 Copy graph widget prototype methods over in bulk commit 169538837ece41a03d02ba8fbc0e4097062b67a3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun Apr 25 05:58:01 2021 +0700 Remove graph.baseline_y property After giving it some thought I've concluded, that it is unlikely to be useful as it is now. I'd better reserve the name for some more sensible functionality, like fixing the baseline_value defined axis at the given widget coordinate, adjusting the scaling accordingly, so min_value/max_value won't generally correspond to widget top and bottom anymore. commit 202ffaa9a0b820c7c471e1121d01b91f8f76cd38 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 12:06:55 2021 +0700 Add baseline_y and baseline_value properties to graph widget Now values in graph have a *baseline*, tunable with baseline_y and baseline_value properties. It is the (for now invisible) horizontal line from which all bars in the graph grow. I call it a "baseline" instead of "axis", because it doesn't actually have to correspond to the 0 value. But it can, and actually does now by default. baseline_y and baseline_value properties determine the vertical placement of the baseline in the widget. baseline_y is the position of the baseline in normalized widget coordinates, e.g. 0 corresponds to widget's top, 1 - to widget's bottom, and 0.5 - to widget's middle. So if e.g. one sets baseline_y = 0, then no matter how the widget is resized and how its values are scaled, bars in it will be drawn starting from the top. baseline_value is the position of the baseline in value coordinates. So if one sets baseline_value = 10, then no matter what happens, bars for values greater than 10 will be drawn as growing up from this line, and bars for values smaller as growing down. Where and if it will be seen in the widget, depends on the scaling. baseline_value = 0 is the familiar zero axis. baseline_y is unset by default and has priority, i.e. if it's set, then baseline_value is ignored. baseline_value defaults to 0. Prior to this commit, bars were always drawn from the bottom edge of the widget, or, in the new terms, the widget had baseline_y = 1. But it was indistinguishable from baseline_value = 0, because the widget clamped all negative values to 0, so the difference between the two could never be observed. baseline_value = 0 is clearly more useful and familiar to people, so now it is in force by default. commit df7e19abaebf0a871b5ca217b4cc3365533267a6 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 11:18:12 2021 +0700 Move value clamping from graph:add_value() into draw() Previously all values were forcibly clamped between 0 (not tunable at all) and max_value (1 by default). Thus graphs with negative values were not an option. NaN values were conflated with zeros, even though it may be useful to maintain the distinction and render them differently. And if user happened to dynamically change the max_value property, they discovered, that their data got truncated. Preserving user data as it was given is more flexible and makes things easier to debug. This commit does exactly that. So what happens now to the newly allowed values during draw()? There are 3 new cases to consider: 1) NaNs 2) Negatives 3) Values outside of min_value..max_value range NaNs are presently simply not drawn. An empty place is reserved where the value should've been, but nothing else is done. This is distinct from how a zero is handled, e.g. if step_shape is set, it is called with the height parameter = 0, which can still end up drawing something, depending on the shape. One could also consider addition of nan_color/nan_shape properties which could be drawn to alert user to a gap in their data. Negative values necessitate introduction of the concept of the zero axis, from which they will be drawn DOWN, in contrast to positive values. Where should this axis be drawn? How should step_shape-s with inherent direction, e.g. arrows, be drawn for negatives? This will be addressed in the next commit. Values outside of min/max_value range can imply shapes that go beyond widget dimensions. An arrow shape that goes beyond the roof is visibly different from an arrow that saturates and merely touches the widget top, and the user may reasonably desire either behavior. Therefore this commit introduces a boolean clamp_bars property, which defaults to true for reasons of robustness and backward compatibility with the behaviour prior to this commit. The property of course affects only the display of the values, the actual user data stays intact. commit c9c24fb1ab741cd6bb8074971d568e0646c51532 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:29:01 2021 +0700 Use rectangles for drawing graph bars of all sizes I have benchmarked various bar drawing strategies and graph sizes and found out that drawing graph bars with rectangles is as fast or even marginally faster than using fat lines. Thus the code that tries to special-case 1px bars as lines seems to be not worth the additional complexity it introduces, and this commit removes it. Incidentally, I've also established, that rectangles are more robust. If one draws graph bars and feeds cairo very large coordinates (~2^62), then corresponding rectangles fail to render, but all the other rectangles in the same series get rendered successfully, whereas a single large coordinate in a move_to/line_to() sequence is enough to cause all lines in the batch to fail to render. commit bc7f4be5e06405b5aa7fb443a5ace2b9b9575b1f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:01:42 2021 +0700 Cache cairo methods used in the inner loop of graph:draw() I have benchmarked the draw() method, both on cairo xcb and on argb32 image surfaces, and have discovered that cairo context method calls are unnaturally slow, to the point, that almost nothing else in the function matters. For example, drawing bars with vertical lines made the whole draw() function almost 2 times slower than doing the same with rectangles, merely because the former requires two method calls (move_to/line_to), and the latter only one. Simply caching hotpath cairo methods in local variables, reduced the difference between the drawing methods to a negligible one and made the whole function 4 times faster on a typical 100-bar graph, (1.4ms to 0.33ms on my laptop) thus bringing closer my secret dream of drawing live audio data with awesome graphs (kidding). I guess, LGI does something not very optimal there, like gobject-introspecting the method every time, or something. There might be some untapped optimization potential, possibly in upstream, like a method cache, from which everything in awesome could benefit, even though I imagine not many things are so call-intensive as the graph widget, to really feel the difference. commit 538df25bdf02b53d0bbe81809eef2e6fdb05ff6f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 07:32:37 2021 +0700 Use 1-based index in the inner graph draw loop It's used there only once for a calculation and then several times to index some tables, and index 0 is bad for lua tables performance-wise. commit 7412546916cd0a0a490130a120255ffb719398b8 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:55:12 2021 +0700 Leave graph.max_value uninitialized at construction This is a user-visible change, but the previous behavior was probably not what user expected anyway, namely with graph.scale set the graph mysteriously couldn't zoom in onto values that are smaller than 1. commit eb81d7929a161e79c13262cfa73ac2e6a820e8ed Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:48:29 2021 +0700 Allow graph.max_value be nil This makes its behave symmetrically to min_value. Now it defaults to 1, when graph.scale is not set, and to the maximum value in the graph data, when graph.scale is set. commit f622a8430cbfde7007b58d7f4c9c76b08c91e997 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 12:01:13 2021 +0700 Calculate max values num in graph:add_value() more precisely At most so many values were kept in the widget values array, as there are pixels of usable widget width (i.e. sans border). This is exactly right for the surely most common case of graphs with 1px-wide bars, but is more than needed for graphs with step_width+step_spacing>1, and not enough for graphs with subpixel bars. Moreover this made graph autoscaling behave weirdly in graphs with thicker bars, because old values survived longer in the array and kept affecting min_value/max_value calculations even after they were shifted off-screen. The maximum number of kept values is now calculated exactly as the number of values that can be displayed at once, taking step_width and step_spacing into account. It might be worth adding a max_values_num property to the graph to let user override this calculation, in case they draw shapes in step_shape() outside of its supposed rectangle, so our assumptions of visibility are wrong, or the widget often gets resized, resulting in the annoyance of visibly truncated series. commit 0aa63249a18641251a1bf77423739164f0eab39f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 02:30:13 2021 +0700 Fix border_color/border_width bugs in graph widget add_value() wrongly assumed that border_width is always set, when border_color is, so if user set only the latter, an error occurred. add_value() assumed that border_width is always < width which led to awesome hanging up in an infinite loop, should that fail to be the case. The graph border_width property was driven by the border_color property, instead of the other way round. If border_color wasn't set, the border wasn't drawn all, even if user sets non-zero border_width, which is a confusing behavior, in contrast to all other widgets, which simply use a plethora of fallback colors to draw the border, when needed. This also meant that, despite being documented, the beautiful.graph_border_color fallback color could not and was not ever used. If the border_color was set though, an unset border_width was immediately assumed to be 1 instead of 0, also contrary to what other widgets do. Theme colors shouldn't influence widget layout, so this commit fixes this behavior to: 1) border_width always defaults to 0, if unset. 2) border_color falls back to beautiful.graph_border_color or white. Maybe one should also add a beautiful.graph_border_width theme variable, like some widgets do. commit dad4a3253b8fef971fb82720d2a407fcc0a879ad Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 03:11:27 2021 +0700 Implement multiple data series drawing for non-stacked graph The widget can already hold and draw multiple data groups, so it makes little sense to limit drawing only to the first group when graph.stack = false, probably leading to user's confusion. Now user can call add_value(v, group) with group > 1 and the data series will be drawn (over groups with lesser id-s) with graph.stack_colors[group] color, if stack_colors is set, or with graph.color, if stack_colors isn't set. Thus nothing visibly changes for all current users of non-stacked graphs with only single data series, but the new functionality is now there for those who need it. One can't fully emulate this functionality, e.g. with multiple graphs widgets in a wibox.layout.stack, because data series in a single widget can enjoy a common auto-scaling. commit 3e645c976184ab5f50d625271c25062db9ab7ed9 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:59:12 2021 +0700 Don't draw a stacked graph data group if there's no color for it If stack_colors[idx] is nil for the group `idx`, don't draw it at all (as opposed, e.g. to the effect of drawing it with a transparent color, or as though it had all values == 0, which both produce different results). This code is still subtly different from prior behavior, because now user can have a non-contiguous stack_colors table and turn the display of any data group on and off at will, whereas the code two commits earlier would just stop rendering as soon as ipairs(stack_colors) does, i.e. no data series were drawn past the first nil hole. This commit only looks large, but is mostly a whitespace change due to nesting from a pair of wrapping ifs. commit 8c0273786de1ade66bdd977699f98b588da9f487 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:32:42 2021 +0700 Unify stacked and non-stacked graph rendering Now stacked graphs support everything the nonstacked do, like step_width ~= 1 and custom step_shape-s. Technical notes: This code no longer calls cr:stroke() for every single value in a stacked graph, but rather paints a whole series and then cr:stroke/fill()-s it at all once with its color, improving performance considerably. Additionally the graph no longer relies on the presence of the stack_colors property to render series. It draws all data that it has, and, if there's no corresponding color in stack_colors, falls back to graph.color. This will be changed in the next commit to the prior behavior of only drawing those series for which there's a color in the stack_colors table, but that would've made this commit's diff much larger due to required nesting ifs over large code chunks. This commit also takes care to properly handle NaNs and negative values. Those are currently (almost) guaranteed to not occur in the values array due to clipping in graph:add_value(), but I intend to eventually drop the clipping, because it does nothing that draw() really relies upon, and allowing unclipped and NaN values is more flexible and less surprising, e.g. when user dynamically toggles graph.scale. Eventually I intend to implement drawing negative values in non-stacked graphs and possibly even in the stacked ones. commit 38c6589c51b2aa057d7c2f40182ecf32b5b2ff5e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 10:04:15 2021 +0700 Convert graph:draw() to use `self` commit f21f1b2550426df1c4ea8abde079b1ccef84b394 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 12:27:26 2021 +0700 Fix scaling for stacked graphs Stacked graphs have cumulative heights, values from all groups that end up in the same graph bar are effectively summed. Previous code didn't take that into account when calculating min/max_value for the purposes of rescaling, but simply examined every value separately. This code adds up all values up front and finds min/max among those, to determine proper scaling that would fit the graph exactly. commit 1ce4c7bf63bdd6c99c195d668347bec49355e921 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 09:49:35 2021 +0700 Draw shaped graph bars only up to the baseline Prior to this commit, every step_shape bar was, for some reason, drawn with height = full widget's height, regardless of the value it represents. So for the most bars their lower end was far outside the box and therefore clipped off. So e.g. one couldn't see the lower round edges of rounded rectangles. This commit fixes that by appropriately varying the height that is passed into step_shape(), so that lower edges of the bars always exactly touch the lower edge of the graph. If the user wants to restore the prior behavior, they can simply ignore the passed-in height in step_shape() and just always draw their shape with height = full widget height. But they couldn't replicate the effect of this commit in their rc.lua without essentially writing their own graph widget. commit e25185b08da23745b5815f85f6e9e43222ed2294 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 08:42:54 2021 +0700 Avoid incremental transform shifting during graph bar rendering Instead of letting addition errors and cognitive load accumulate simply use the same offset calculation that is used for lines and reset the transform back to the top-left corner after every drawn bar. It's conceptually simpler, and will be useful later for stacked graphs and more. It also fixes a discrepancy between plain and step_shape rendering. Prior to this commit, bar coloring occurred with a disturbed transform, which was centered at the last drawn bar, wherever it happened to be. This meant that nontrivial colors, like gradients and images, were arbitrarily offset when step_shape was used. commit f3e98866d8583c35d0b557c09f529299edc9ba2c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:45:29 2021 +0700 Refactor out graph bar height expression into a variable commit f54528d6892f5aaa4595917853c9e2130f743fdb Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:20:25 2021 +0700 Draw graph bars with lines only when step_width == 1 Before this commit graph bars of step_width < 1 were drawn with lines of width 1, but that's a bit unfortunate, because it led to much overpainting, while still ending up with a graphically incorrect result. Let's draw such bars with rectangles and let cairo do things with subpixels, getting more graphically accurate result, while still keeping the common case of step_width == 1 (supposedly) fast-drawn with lines. commit b6515dca67c18b78eb5657378131a62ab43d4cfc Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:54:07 2021 +0700 Fix unsharp edges when drawing graph bars with rectangles The 0.5 offset is only helpful for 1-width strokes, not for fills. commit 7f3db8f07e82f821bcd1dc418d66564f537609d6 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:21:01 2021 +0700 Do coordinate transformations even if the value isn't drawn The graph presently doesn't draw values < 0, but there was a discrepancy between drawing with and without step_shape. The default line/rectangle drawing reserves an empty place where the skipped value should've been, but no such empty place is reserved, when step_shape is used. This commit fixes that. Skipped values always have their empty space now. commit 7b731ebfe96af7a9dba9c57371da6761cf22c00c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:03:30 2021 +0700 Fix graph bar scaling when min_value is not 0 Previous calculation lead to underscaling of values so that even value = max_value wasn't drawn to full height. commit dca59fd8b7fff0e7e646f2dfc6bd92047028e5a8 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:18:40 2021 +0700 Calculate the horizontal graph bar offset only when needed commit 940ddf4b9825e9c3a91d276208c089d8c74e49db Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:17:23 2021 +0700 Call cr:move_to() only when drawing the graph with lines It's redundant otherwise and might even interfere with user's drawing in step_shape(). commit b5d5cd10ca29fb29cd751faaa95137c2fe89ff63 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:04:06 2021 +0700 Fix wrong horizontal offset calculations in widget.graph Indices in the loop are zero-based, so things like i-1 and i>1 aren't necessary and actually wouldn't have been correct even in a 1-based loop. step_shape = gears.shape.rectangle and step_shape = nil, which is supposed to default to rectangle according to docs, were drawn differently due to mismatched offset calculations, which led to problems especially noticeable when step_spacing is not zero, e.g: 1) whole graph shifted left too much, drawn partially off-screen. 2) whole graph shifted right too much, and the first bar placed closer to the rest than others. commit dc369fa198281e000afc1bd17326f3a264adf7d1 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 02:09:21 2021 +0700 Fix the "border_width affects graph bar width" bug Graph bars were drawn with lines of border_width thickness instead of 1, i.e. possibly too fat to be readable. commit 1ba73378cf29ed27a6ecd18716394779ceb543d9 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 00:13:03 2021 +0700 Simplify internal values representation in graph even more New values in graph._private.values were pushed to the end, and old values were removed from the beginning of the table. In draw() however newer values are drawn first, at the left edge of the graph, necessitating annoying idx = #values - idx conversions everywhere. In particular in stacked graph that means that values that are drawn together may have different indices. One has to shift the values array in add_value() either way, so one might as well insert new values at the beginning of the array and simplify the rest of the code. This doesn't seem like much yet, but it will prove to be much more convenient and less error-prone in what I'm going to do later. commit f70523ee68dbd6b67050ace4b82903a9b22440c7 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 13:35:10 2021 +0700 Simplify internal values representation in graph widget graph._private.values was either a table of values or a table of tables of values, depending on whether "stack" property is enabled, ... or a mix of both if user forgot to pass the second "group" parameter to graph:add_value(). This resulted in pretty fragile and confusing behavior, e.g. leaving user to wonder, why they see nothing more after setting stack=true for a graph in a previously working rc.lua, and demanding a very careful use of add_value() from the user, or else some values or even entire stacks would mysteriously vanish and errors in draw() get triggered, sometimes after a period of seemingly normal functioning. Now `values` is always a table of tables and the group parameter in add_value() defaults to 1, thus making the non-stacked graph a trivial variant of the stacked graph.
2021-05-20 17:14:03 +02:00
--
-- @DOC_wibox_widget_graph_baseline_value_EXAMPLE@
--
-- @property baseline_value
-- @tparam[opt=0] number baseline_value
-- @negativeallowed true
graph: complete widget overhaul and bug fixes Squashed commit of the following: commit 69821f51fe1e8652715658543e50631ece495090 Author: Alex Belykh <albel727@ngs.ru> Date: Mon May 10 19:17:55 2021 +0700 Refactor property handling in wibox.widget.graph In addition, baseline_value, step_width and step_spacing properties are all set to their default non-nil numeric values during instance creation now. commit 842eb429bf5df4b2ba46b2e6f7646afe89b1a3c3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 9 06:18:45 2021 +0700 Make graph:draw_values() private There's no documentation nor confidence that it's a good API, so I'm hiding it for now. commit 720746780574e4bad14a71fc4174955139c2eb50 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 04:04:13 2021 +0700 Document graph_should_draw_data_group() for posterity commit 7c73f3754ce6ba86b118f1170a6f9e406ab4841d Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:59:21 2021 +0700 Make graph:should_draw_data_group() private There's no confidence whether it's a good API, so I'm hiding it for now. commit b3539e20adb6423118af726ae4f8751ef864b5d2 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:36:44 2021 +0700 Make graph:preprocess_values() private There's no confidence whether it's a good API, so I'm hiding it for now. commit 2df4400e780a6260f8d0ab1c60734fbb11bf3c4f Author: Alex Belykh <albel727@ngs.ru> Date: Thu May 6 10:05:23 2021 +0700 Add spec/wibox/widget/graph_spec.lua commit ce01a9771ab439b8df841f1565aa4aa8a44a5e0d Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 08:16:18 2021 +0700 Improve graph.capacity documentation commit e28fade8f655802f5bf89154169f68d256b8e332 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 07:36:01 2021 +0700 Document graph:compute_drawn_values_num() commit dd68332292465422d08b48e9f0229f143beccbc6 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 05:24:23 2021 +0700 Document graph:pick_data_group_color() method commit ca6dc55f77aca88a6e9cc90bb6fd0045739f41f5 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 04:13:58 2021 +0700 Guard against setting data groups sparsely in graph:add_value() commit cdea5f126f7984e72924f6dffbd312c4fe3b032f Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 02:36:12 2021 +0700 Refactor scaling and baseline choice out of graph:draw() commit 10135deafe8a9390ba66ab657ebb1fb41d549e8c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 21:34:48 2021 +0700 Document graph.nan_color and graph.stack properties commit 0d73bb84ac66b87e16aee472bd1f299549f66a1c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 20:49:24 2021 +0700 Use a better example in docs for the graph.stack property commit c27d160a0826d033d49d6141608c40aa8a0642fa Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 16:05:38 2021 +0700 Document graph.baseline_value property commit 459bc8176c13d4ea32af0dcdc2399d3b929b1c6a Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:53:47 2021 +0700 Document graph.clamp_bars property commit e732ee0b3b007d213e689b98cc00ae7cb141ae08 Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:34 2021 +0700 Improve graph:add_value() and :clear() documentation commit ae73e3aa9f8d60409cbe4cb4da9612f31af4f38e Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:11 2021 +0700 Improve graph min_value/max_value/scale documentation commit a7350996a9def773b91b1631e6c0507e33d8f93b Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:27:36 2021 +0700 Cleanup graph examples to show only relevant things in docs commit 30740c26f4f41c16f4c8a3c339bcb7bd299e14cd Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 14:04:36 2021 +0700 Rename graph.stack_colors property to `group_colors` The colors are used for stacked and non-stacked graphs alike, so the name didn't make sense. Also document and clarify some color stuff. commit 1ea01bfd410971cf0d0c25fd74cd764d5eb7279d Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 21:25:41 2021 +0700 Add an example file for code coverage of some fringe graph cases commit eae68e39553f88a6275a0c65bf040b5d4c262b54 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 20:23:32 2021 +0700 Add step_shape to the graph/nan_color example for code coverage commit 8027147bc2eab34a97b49a428fb8cef27f122bed Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:55:18 2021 +0700 Add example files for graph.nan_color/nan_indication properties commit 89a25a165c52b2c15b6c4d95ecb777a9067e66f9 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:33:07 2021 +0700 Make widget graph:add_value(v) default v to NaN 0 as a default makes no sense and silences the error that is failing to specify a value, to which the user should rather be alerted. The graph widget has sane NaN handling now and will do just that. commit 2835d552440be48d356a7f7b1d64963696dd550b Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:20:48 2021 +0700 Implement graph.nan_color and nan_indication properties Now, whenever there's a NaN among values and graph.nan_indication is set, the corresponding area, where the value bar should have been drawn, is filled with the nan_color from top to bottom. The painting is done after all data is rendered, to make sure that it won't be overpainted and go unnoticed. NaNs among graph's values are inevitably a sign of some error and it is arguably not a sane default behavior to simply not render them at all. The user should take immediate note of any errors by default, instead of wondering at a mysteriously empty graph widget, which is why nan_indication is set to true by default. If the user wants to silence NaNs, they can always simply set it to false. nan_color if unset, defaults to a noticeable yellow-black pattern. As a direct and planned consequence, stacked graphs with negative values in them trigger the NaN handling too. But silencing NaN handling for those graphs is also possible and semi-sane, because then NaNs and negatives are ignored and the graph behaves (almost) like they were zeros. commit 9c1f8f08b400e01e19c534f3810c9c9ab485760c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 26 08:10:19 2021 +0700 Refactor out graph_map_value_to_widget_coordinates() function commit 1905991ddecabdae3e15ffb8ee5b18a1bee1a711 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 28 13:39:29 2021 +0700 Add an example file for graph.baseline_value property commit b31b39d66f4c037a9484b71a3f910703ae01fac0 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 07:03:49 2021 +0700 Add an example file for graph.clamp_bars=false/true comparison commit a1fc850e3047f9ee2c9ddeffacfbabf3ded34171 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 04:06:45 2021 +0700 Add an example file for graphs with negative values and shapes commit 2f012f8ff1bb5094c14e724a61e59a2a3a296bc8 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 01:06:05 2021 +0700 Add an example file for a graph displaying negative values commit 7b14fbd59a4c4c51962fd3110a1a900dd5f4eb98 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 11:18:26 2021 +0700 Add an example file for graphs with step_width < 1 commit dfd9a54544b637e2fbe3d1325f9cc24b3f4d2313 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 10:17:38 2021 +0700 Add an example file for graph.stack=false/true comparison Should also demonstrate that multiple data groups are possible for non-stacked graphs too. commit 5b532106c3fd255f9b66a66cfb88d977b489ab5d Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 09:34:14 2021 +0700 Add an example file for disabling graph data groups with nil colors commit ac6f1083c02fcb428f089a616f9b0ebaa5496b6c Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:57:25 2021 +0700 Add an example file for step_shape/width/spacing on stacked graphs commit b3703db6a5139e73e72c1cfd198ece2cd9be7df2 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:43:56 2021 +0700 Add an example file for scale=false/true on stacked graph commit b000aed4a12cb44fb501e1c8432a71a14b2f8f3f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 20 08:22:19 2021 +0700 Document deprecation of graph's width and height commit cb0ad617bd6c83a499896797318e3b0b588c3566 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 07:53:37 2021 +0700 Deprecate width and height properties of wibox.widget.graph commit 8c64e14ad91a074b784166a7054e53444cccb9d2 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 04:37:59 2021 +0700 Refactor graph:draw() to separate concerns Firstly, summation of group values for stacked graphs is extracted into graph:preprocess_values(). This makes graph:draw() smaller and exposes the fact, that stacked graphs are almost entirely just a form of a more general concept: pre-render data manipulation. It's something a user might want to customize, and now they can do it by monkey-patching the method. Secondly, the logic for deciding, if and with which color a data group should be drawn, is extracted into graph:should_draw_data_group() and get_data_group_color() methods respectively. This makes it customizable too and makes more obvious the fact, that it is a general thing that has nothing to do with stacked graphs in particular. With this there are only 5 lines remaining in graph:draw(), that could be said to be specific to stacked graphs, all pertaining to the `prev_y` variable, which merely preserves the bar coordinates from a previous data group. commit 2a32b6305d893f413303728a0a3da1dcd9a5591e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 22:52:37 2021 +0700 Pointlessly optimize graph:add_value() 1) Purge old values first, then insert the new one, if needed. Since Lua grows arrays in power of two steps, and our guess_capacity() also takes care to return even integers, it would be a shame to overstep the capacity by one by inserting first and thus getting the table size doubled only to never use the most of it. 2) Use a removal procedure that doesn't call the slow length operator at all. Previous code called it twice (once in table.remove) for every removed value. commit 680ea8f5d81e9040afb4662b3229e937e4b89e02 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:23:38 2021 +0700 Set graph data series color before the painting loop commit b970f0f221ae76250f2a94433ff79d653caa6e21 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:05:37 2021 +0700 Drop a large no longer necessary `if` in graph:draw_values() This commit is almost purely a whitespace change. commit 6b4f0541c70ec99d7f6e276bae64939f337120d6 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:00:50 2021 +0700 Extract values rendering code into graph:draw_values() method This is to avoid ungodly if nesting levels, which are needed to bail out of drawing values early and skip to drawing the border. The next commit will get rid of the if. commit e0950e4f99d1ea1c4b8cea2cc67629fa40030c2a Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 14:50:20 2021 +0700 Track graph draw() usage to guess necessary array capacity The last step of divorcing add_values() from width calculations, while ensuring that user doesn't observe a lack of data to draw. The widget now tracks an approximate number of values that it has been asked to draw() in _private.last_drawn_values_num, and truncates its values array based on that (if not overridden by the capacity property). commit 2e3d542282f35e7c0fed454da0f2eada0fcf3b78 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 13:35:12 2021 +0700 Add `capacity` property to graph widget This is the second step of divorcing add_values() from calculations involving widths. How many values are stored and how many values are drawn are distinct concepts. There isn't even a single definitive "how many values are drawn" because the widget could be rendered in several places with distinct widths simultaneously, and in every such place the data must be drawn appropriately, e.g. with auto-scaling, that takes only the visible data points into account. And the draw() method can do that now. The default capacity value of nil uses heuristics based on widget's _private.width to guess some capacity that would tolerate some dynamic widget resizing. If the user finds the heuristic failing them, they can set the capacity property to the desired maximum number of values that will be kept in the _private.values array (in each data group). commit 08d4a80686f7fe363551c35589a460839f60853e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 03:27:18 2021 +0700 Extract values size computation into compute_drawn_values_num() This is the first step of making add_values() not concern itself (directly) with widget dimensions. The user can monkey-patch the method to override how many values are (logically) drawn, because it affects graph auto-scaling and can be useful for rendering of step_shape-s that paint outside of their step_width. I didn't make it a full-blown function-valued property, because it seems a fringe functionality, which I'm too lazy to document. commit 3c5617c98ab1147618ed22e8414a77fc56b45f6e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 02:32:29 2021 +0700 Copy graph widget prototype methods over in bulk commit 169538837ece41a03d02ba8fbc0e4097062b67a3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun Apr 25 05:58:01 2021 +0700 Remove graph.baseline_y property After giving it some thought I've concluded, that it is unlikely to be useful as it is now. I'd better reserve the name for some more sensible functionality, like fixing the baseline_value defined axis at the given widget coordinate, adjusting the scaling accordingly, so min_value/max_value won't generally correspond to widget top and bottom anymore. commit 202ffaa9a0b820c7c471e1121d01b91f8f76cd38 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 12:06:55 2021 +0700 Add baseline_y and baseline_value properties to graph widget Now values in graph have a *baseline*, tunable with baseline_y and baseline_value properties. It is the (for now invisible) horizontal line from which all bars in the graph grow. I call it a "baseline" instead of "axis", because it doesn't actually have to correspond to the 0 value. But it can, and actually does now by default. baseline_y and baseline_value properties determine the vertical placement of the baseline in the widget. baseline_y is the position of the baseline in normalized widget coordinates, e.g. 0 corresponds to widget's top, 1 - to widget's bottom, and 0.5 - to widget's middle. So if e.g. one sets baseline_y = 0, then no matter how the widget is resized and how its values are scaled, bars in it will be drawn starting from the top. baseline_value is the position of the baseline in value coordinates. So if one sets baseline_value = 10, then no matter what happens, bars for values greater than 10 will be drawn as growing up from this line, and bars for values smaller as growing down. Where and if it will be seen in the widget, depends on the scaling. baseline_value = 0 is the familiar zero axis. baseline_y is unset by default and has priority, i.e. if it's set, then baseline_value is ignored. baseline_value defaults to 0. Prior to this commit, bars were always drawn from the bottom edge of the widget, or, in the new terms, the widget had baseline_y = 1. But it was indistinguishable from baseline_value = 0, because the widget clamped all negative values to 0, so the difference between the two could never be observed. baseline_value = 0 is clearly more useful and familiar to people, so now it is in force by default. commit df7e19abaebf0a871b5ca217b4cc3365533267a6 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 11:18:12 2021 +0700 Move value clamping from graph:add_value() into draw() Previously all values were forcibly clamped between 0 (not tunable at all) and max_value (1 by default). Thus graphs with negative values were not an option. NaN values were conflated with zeros, even though it may be useful to maintain the distinction and render them differently. And if user happened to dynamically change the max_value property, they discovered, that their data got truncated. Preserving user data as it was given is more flexible and makes things easier to debug. This commit does exactly that. So what happens now to the newly allowed values during draw()? There are 3 new cases to consider: 1) NaNs 2) Negatives 3) Values outside of min_value..max_value range NaNs are presently simply not drawn. An empty place is reserved where the value should've been, but nothing else is done. This is distinct from how a zero is handled, e.g. if step_shape is set, it is called with the height parameter = 0, which can still end up drawing something, depending on the shape. One could also consider addition of nan_color/nan_shape properties which could be drawn to alert user to a gap in their data. Negative values necessitate introduction of the concept of the zero axis, from which they will be drawn DOWN, in contrast to positive values. Where should this axis be drawn? How should step_shape-s with inherent direction, e.g. arrows, be drawn for negatives? This will be addressed in the next commit. Values outside of min/max_value range can imply shapes that go beyond widget dimensions. An arrow shape that goes beyond the roof is visibly different from an arrow that saturates and merely touches the widget top, and the user may reasonably desire either behavior. Therefore this commit introduces a boolean clamp_bars property, which defaults to true for reasons of robustness and backward compatibility with the behaviour prior to this commit. The property of course affects only the display of the values, the actual user data stays intact. commit c9c24fb1ab741cd6bb8074971d568e0646c51532 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:29:01 2021 +0700 Use rectangles for drawing graph bars of all sizes I have benchmarked various bar drawing strategies and graph sizes and found out that drawing graph bars with rectangles is as fast or even marginally faster than using fat lines. Thus the code that tries to special-case 1px bars as lines seems to be not worth the additional complexity it introduces, and this commit removes it. Incidentally, I've also established, that rectangles are more robust. If one draws graph bars and feeds cairo very large coordinates (~2^62), then corresponding rectangles fail to render, but all the other rectangles in the same series get rendered successfully, whereas a single large coordinate in a move_to/line_to() sequence is enough to cause all lines in the batch to fail to render. commit bc7f4be5e06405b5aa7fb443a5ace2b9b9575b1f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:01:42 2021 +0700 Cache cairo methods used in the inner loop of graph:draw() I have benchmarked the draw() method, both on cairo xcb and on argb32 image surfaces, and have discovered that cairo context method calls are unnaturally slow, to the point, that almost nothing else in the function matters. For example, drawing bars with vertical lines made the whole draw() function almost 2 times slower than doing the same with rectangles, merely because the former requires two method calls (move_to/line_to), and the latter only one. Simply caching hotpath cairo methods in local variables, reduced the difference between the drawing methods to a negligible one and made the whole function 4 times faster on a typical 100-bar graph, (1.4ms to 0.33ms on my laptop) thus bringing closer my secret dream of drawing live audio data with awesome graphs (kidding). I guess, LGI does something not very optimal there, like gobject-introspecting the method every time, or something. There might be some untapped optimization potential, possibly in upstream, like a method cache, from which everything in awesome could benefit, even though I imagine not many things are so call-intensive as the graph widget, to really feel the difference. commit 538df25bdf02b53d0bbe81809eef2e6fdb05ff6f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 07:32:37 2021 +0700 Use 1-based index in the inner graph draw loop It's used there only once for a calculation and then several times to index some tables, and index 0 is bad for lua tables performance-wise. commit 7412546916cd0a0a490130a120255ffb719398b8 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:55:12 2021 +0700 Leave graph.max_value uninitialized at construction This is a user-visible change, but the previous behavior was probably not what user expected anyway, namely with graph.scale set the graph mysteriously couldn't zoom in onto values that are smaller than 1. commit eb81d7929a161e79c13262cfa73ac2e6a820e8ed Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:48:29 2021 +0700 Allow graph.max_value be nil This makes its behave symmetrically to min_value. Now it defaults to 1, when graph.scale is not set, and to the maximum value in the graph data, when graph.scale is set. commit f622a8430cbfde7007b58d7f4c9c76b08c91e997 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 12:01:13 2021 +0700 Calculate max values num in graph:add_value() more precisely At most so many values were kept in the widget values array, as there are pixels of usable widget width (i.e. sans border). This is exactly right for the surely most common case of graphs with 1px-wide bars, but is more than needed for graphs with step_width+step_spacing>1, and not enough for graphs with subpixel bars. Moreover this made graph autoscaling behave weirdly in graphs with thicker bars, because old values survived longer in the array and kept affecting min_value/max_value calculations even after they were shifted off-screen. The maximum number of kept values is now calculated exactly as the number of values that can be displayed at once, taking step_width and step_spacing into account. It might be worth adding a max_values_num property to the graph to let user override this calculation, in case they draw shapes in step_shape() outside of its supposed rectangle, so our assumptions of visibility are wrong, or the widget often gets resized, resulting in the annoyance of visibly truncated series. commit 0aa63249a18641251a1bf77423739164f0eab39f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 02:30:13 2021 +0700 Fix border_color/border_width bugs in graph widget add_value() wrongly assumed that border_width is always set, when border_color is, so if user set only the latter, an error occurred. add_value() assumed that border_width is always < width which led to awesome hanging up in an infinite loop, should that fail to be the case. The graph border_width property was driven by the border_color property, instead of the other way round. If border_color wasn't set, the border wasn't drawn all, even if user sets non-zero border_width, which is a confusing behavior, in contrast to all other widgets, which simply use a plethora of fallback colors to draw the border, when needed. This also meant that, despite being documented, the beautiful.graph_border_color fallback color could not and was not ever used. If the border_color was set though, an unset border_width was immediately assumed to be 1 instead of 0, also contrary to what other widgets do. Theme colors shouldn't influence widget layout, so this commit fixes this behavior to: 1) border_width always defaults to 0, if unset. 2) border_color falls back to beautiful.graph_border_color or white. Maybe one should also add a beautiful.graph_border_width theme variable, like some widgets do. commit dad4a3253b8fef971fb82720d2a407fcc0a879ad Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 03:11:27 2021 +0700 Implement multiple data series drawing for non-stacked graph The widget can already hold and draw multiple data groups, so it makes little sense to limit drawing only to the first group when graph.stack = false, probably leading to user's confusion. Now user can call add_value(v, group) with group > 1 and the data series will be drawn (over groups with lesser id-s) with graph.stack_colors[group] color, if stack_colors is set, or with graph.color, if stack_colors isn't set. Thus nothing visibly changes for all current users of non-stacked graphs with only single data series, but the new functionality is now there for those who need it. One can't fully emulate this functionality, e.g. with multiple graphs widgets in a wibox.layout.stack, because data series in a single widget can enjoy a common auto-scaling. commit 3e645c976184ab5f50d625271c25062db9ab7ed9 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:59:12 2021 +0700 Don't draw a stacked graph data group if there's no color for it If stack_colors[idx] is nil for the group `idx`, don't draw it at all (as opposed, e.g. to the effect of drawing it with a transparent color, or as though it had all values == 0, which both produce different results). This code is still subtly different from prior behavior, because now user can have a non-contiguous stack_colors table and turn the display of any data group on and off at will, whereas the code two commits earlier would just stop rendering as soon as ipairs(stack_colors) does, i.e. no data series were drawn past the first nil hole. This commit only looks large, but is mostly a whitespace change due to nesting from a pair of wrapping ifs. commit 8c0273786de1ade66bdd977699f98b588da9f487 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:32:42 2021 +0700 Unify stacked and non-stacked graph rendering Now stacked graphs support everything the nonstacked do, like step_width ~= 1 and custom step_shape-s. Technical notes: This code no longer calls cr:stroke() for every single value in a stacked graph, but rather paints a whole series and then cr:stroke/fill()-s it at all once with its color, improving performance considerably. Additionally the graph no longer relies on the presence of the stack_colors property to render series. It draws all data that it has, and, if there's no corresponding color in stack_colors, falls back to graph.color. This will be changed in the next commit to the prior behavior of only drawing those series for which there's a color in the stack_colors table, but that would've made this commit's diff much larger due to required nesting ifs over large code chunks. This commit also takes care to properly handle NaNs and negative values. Those are currently (almost) guaranteed to not occur in the values array due to clipping in graph:add_value(), but I intend to eventually drop the clipping, because it does nothing that draw() really relies upon, and allowing unclipped and NaN values is more flexible and less surprising, e.g. when user dynamically toggles graph.scale. Eventually I intend to implement drawing negative values in non-stacked graphs and possibly even in the stacked ones. commit 38c6589c51b2aa057d7c2f40182ecf32b5b2ff5e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 10:04:15 2021 +0700 Convert graph:draw() to use `self` commit f21f1b2550426df1c4ea8abde079b1ccef84b394 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 12:27:26 2021 +0700 Fix scaling for stacked graphs Stacked graphs have cumulative heights, values from all groups that end up in the same graph bar are effectively summed. Previous code didn't take that into account when calculating min/max_value for the purposes of rescaling, but simply examined every value separately. This code adds up all values up front and finds min/max among those, to determine proper scaling that would fit the graph exactly. commit 1ce4c7bf63bdd6c99c195d668347bec49355e921 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 09:49:35 2021 +0700 Draw shaped graph bars only up to the baseline Prior to this commit, every step_shape bar was, for some reason, drawn with height = full widget's height, regardless of the value it represents. So for the most bars their lower end was far outside the box and therefore clipped off. So e.g. one couldn't see the lower round edges of rounded rectangles. This commit fixes that by appropriately varying the height that is passed into step_shape(), so that lower edges of the bars always exactly touch the lower edge of the graph. If the user wants to restore the prior behavior, they can simply ignore the passed-in height in step_shape() and just always draw their shape with height = full widget height. But they couldn't replicate the effect of this commit in their rc.lua without essentially writing their own graph widget. commit e25185b08da23745b5815f85f6e9e43222ed2294 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 08:42:54 2021 +0700 Avoid incremental transform shifting during graph bar rendering Instead of letting addition errors and cognitive load accumulate simply use the same offset calculation that is used for lines and reset the transform back to the top-left corner after every drawn bar. It's conceptually simpler, and will be useful later for stacked graphs and more. It also fixes a discrepancy between plain and step_shape rendering. Prior to this commit, bar coloring occurred with a disturbed transform, which was centered at the last drawn bar, wherever it happened to be. This meant that nontrivial colors, like gradients and images, were arbitrarily offset when step_shape was used. commit f3e98866d8583c35d0b557c09f529299edc9ba2c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:45:29 2021 +0700 Refactor out graph bar height expression into a variable commit f54528d6892f5aaa4595917853c9e2130f743fdb Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:20:25 2021 +0700 Draw graph bars with lines only when step_width == 1 Before this commit graph bars of step_width < 1 were drawn with lines of width 1, but that's a bit unfortunate, because it led to much overpainting, while still ending up with a graphically incorrect result. Let's draw such bars with rectangles and let cairo do things with subpixels, getting more graphically accurate result, while still keeping the common case of step_width == 1 (supposedly) fast-drawn with lines. commit b6515dca67c18b78eb5657378131a62ab43d4cfc Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:54:07 2021 +0700 Fix unsharp edges when drawing graph bars with rectangles The 0.5 offset is only helpful for 1-width strokes, not for fills. commit 7f3db8f07e82f821bcd1dc418d66564f537609d6 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:21:01 2021 +0700 Do coordinate transformations even if the value isn't drawn The graph presently doesn't draw values < 0, but there was a discrepancy between drawing with and without step_shape. The default line/rectangle drawing reserves an empty place where the skipped value should've been, but no such empty place is reserved, when step_shape is used. This commit fixes that. Skipped values always have their empty space now. commit 7b731ebfe96af7a9dba9c57371da6761cf22c00c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:03:30 2021 +0700 Fix graph bar scaling when min_value is not 0 Previous calculation lead to underscaling of values so that even value = max_value wasn't drawn to full height. commit dca59fd8b7fff0e7e646f2dfc6bd92047028e5a8 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:18:40 2021 +0700 Calculate the horizontal graph bar offset only when needed commit 940ddf4b9825e9c3a91d276208c089d8c74e49db Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:17:23 2021 +0700 Call cr:move_to() only when drawing the graph with lines It's redundant otherwise and might even interfere with user's drawing in step_shape(). commit b5d5cd10ca29fb29cd751faaa95137c2fe89ff63 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:04:06 2021 +0700 Fix wrong horizontal offset calculations in widget.graph Indices in the loop are zero-based, so things like i-1 and i>1 aren't necessary and actually wouldn't have been correct even in a 1-based loop. step_shape = gears.shape.rectangle and step_shape = nil, which is supposed to default to rectangle according to docs, were drawn differently due to mismatched offset calculations, which led to problems especially noticeable when step_spacing is not zero, e.g: 1) whole graph shifted left too much, drawn partially off-screen. 2) whole graph shifted right too much, and the first bar placed closer to the rest than others. commit dc369fa198281e000afc1bd17326f3a264adf7d1 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 02:09:21 2021 +0700 Fix the "border_width affects graph bar width" bug Graph bars were drawn with lines of border_width thickness instead of 1, i.e. possibly too fat to be readable. commit 1ba73378cf29ed27a6ecd18716394779ceb543d9 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 00:13:03 2021 +0700 Simplify internal values representation in graph even more New values in graph._private.values were pushed to the end, and old values were removed from the beginning of the table. In draw() however newer values are drawn first, at the left edge of the graph, necessitating annoying idx = #values - idx conversions everywhere. In particular in stacked graph that means that values that are drawn together may have different indices. One has to shift the values array in add_value() either way, so one might as well insert new values at the beginning of the array and simplify the rest of the code. This doesn't seem like much yet, but it will prove to be much more convenient and less error-prone in what I'm going to do later. commit f70523ee68dbd6b67050ace4b82903a9b22440c7 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 13:35:10 2021 +0700 Simplify internal values representation in graph widget graph._private.values was either a table of values or a table of tables of values, depending on whether "stack" property is enabled, ... or a mix of both if user forgot to pass the second "group" parameter to graph:add_value(). This resulted in pretty fragile and confusing behavior, e.g. leaving user to wonder, why they see nothing more after setting stack=true for a graph in a previously working rc.lua, and demanding a very careful use of add_value() from the user, or else some values or even entire stacks would mysteriously vanish and errors in draw() get triggered, sometimes after a period of seemingly normal functioning. Now `values` is always a table of tables and the group parameter in add_value() defaults to 1, thus making the non-stacked graph a trivial variant of the stacked graph.
2021-05-20 17:14:03 +02:00
-- @propemits true false
--- Set the width or the individual steps.
--
--@DOC_wibox_widget_graph_step_EXAMPLE@
--
-- @property step_width
-- @tparam[opt=1] number step_width
-- @propertyunit pixel
-- @negativeallowed false
-- @propemits true false
--- Set the spacing between the steps.
--
2021-03-28 11:33:38 +02:00
--@DOC_wibox_widget_graph_step_spacing_EXAMPLE@
--
-- @property step_spacing
-- @tparam[opt=0] number step_spacing
-- @propertyunit pixel
-- @negativeallowed false
-- @propemits true false
--- The step shape.
--
2021-03-28 11:33:38 +02:00
--@DOC_wibox_widget_graph_step_shape_EXAMPLE@
--
-- @property step_shape
-- @tparam[opt=gears.rect.rectangle] shape step_shape
-- @propemits true false
-- @see gears.shape
2016-05-23 07:30:44 +02:00
--- Set the graph to draw stacks. Default is false.
--
graph: complete widget overhaul and bug fixes Squashed commit of the following: commit 69821f51fe1e8652715658543e50631ece495090 Author: Alex Belykh <albel727@ngs.ru> Date: Mon May 10 19:17:55 2021 +0700 Refactor property handling in wibox.widget.graph In addition, baseline_value, step_width and step_spacing properties are all set to their default non-nil numeric values during instance creation now. commit 842eb429bf5df4b2ba46b2e6f7646afe89b1a3c3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 9 06:18:45 2021 +0700 Make graph:draw_values() private There's no documentation nor confidence that it's a good API, so I'm hiding it for now. commit 720746780574e4bad14a71fc4174955139c2eb50 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 04:04:13 2021 +0700 Document graph_should_draw_data_group() for posterity commit 7c73f3754ce6ba86b118f1170a6f9e406ab4841d Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:59:21 2021 +0700 Make graph:should_draw_data_group() private There's no confidence whether it's a good API, so I'm hiding it for now. commit b3539e20adb6423118af726ae4f8751ef864b5d2 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:36:44 2021 +0700 Make graph:preprocess_values() private There's no confidence whether it's a good API, so I'm hiding it for now. commit 2df4400e780a6260f8d0ab1c60734fbb11bf3c4f Author: Alex Belykh <albel727@ngs.ru> Date: Thu May 6 10:05:23 2021 +0700 Add spec/wibox/widget/graph_spec.lua commit ce01a9771ab439b8df841f1565aa4aa8a44a5e0d Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 08:16:18 2021 +0700 Improve graph.capacity documentation commit e28fade8f655802f5bf89154169f68d256b8e332 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 07:36:01 2021 +0700 Document graph:compute_drawn_values_num() commit dd68332292465422d08b48e9f0229f143beccbc6 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 05:24:23 2021 +0700 Document graph:pick_data_group_color() method commit ca6dc55f77aca88a6e9cc90bb6fd0045739f41f5 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 04:13:58 2021 +0700 Guard against setting data groups sparsely in graph:add_value() commit cdea5f126f7984e72924f6dffbd312c4fe3b032f Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 02:36:12 2021 +0700 Refactor scaling and baseline choice out of graph:draw() commit 10135deafe8a9390ba66ab657ebb1fb41d549e8c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 21:34:48 2021 +0700 Document graph.nan_color and graph.stack properties commit 0d73bb84ac66b87e16aee472bd1f299549f66a1c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 20:49:24 2021 +0700 Use a better example in docs for the graph.stack property commit c27d160a0826d033d49d6141608c40aa8a0642fa Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 16:05:38 2021 +0700 Document graph.baseline_value property commit 459bc8176c13d4ea32af0dcdc2399d3b929b1c6a Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:53:47 2021 +0700 Document graph.clamp_bars property commit e732ee0b3b007d213e689b98cc00ae7cb141ae08 Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:34 2021 +0700 Improve graph:add_value() and :clear() documentation commit ae73e3aa9f8d60409cbe4cb4da9612f31af4f38e Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:11 2021 +0700 Improve graph min_value/max_value/scale documentation commit a7350996a9def773b91b1631e6c0507e33d8f93b Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:27:36 2021 +0700 Cleanup graph examples to show only relevant things in docs commit 30740c26f4f41c16f4c8a3c339bcb7bd299e14cd Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 14:04:36 2021 +0700 Rename graph.stack_colors property to `group_colors` The colors are used for stacked and non-stacked graphs alike, so the name didn't make sense. Also document and clarify some color stuff. commit 1ea01bfd410971cf0d0c25fd74cd764d5eb7279d Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 21:25:41 2021 +0700 Add an example file for code coverage of some fringe graph cases commit eae68e39553f88a6275a0c65bf040b5d4c262b54 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 20:23:32 2021 +0700 Add step_shape to the graph/nan_color example for code coverage commit 8027147bc2eab34a97b49a428fb8cef27f122bed Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:55:18 2021 +0700 Add example files for graph.nan_color/nan_indication properties commit 89a25a165c52b2c15b6c4d95ecb777a9067e66f9 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:33:07 2021 +0700 Make widget graph:add_value(v) default v to NaN 0 as a default makes no sense and silences the error that is failing to specify a value, to which the user should rather be alerted. The graph widget has sane NaN handling now and will do just that. commit 2835d552440be48d356a7f7b1d64963696dd550b Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:20:48 2021 +0700 Implement graph.nan_color and nan_indication properties Now, whenever there's a NaN among values and graph.nan_indication is set, the corresponding area, where the value bar should have been drawn, is filled with the nan_color from top to bottom. The painting is done after all data is rendered, to make sure that it won't be overpainted and go unnoticed. NaNs among graph's values are inevitably a sign of some error and it is arguably not a sane default behavior to simply not render them at all. The user should take immediate note of any errors by default, instead of wondering at a mysteriously empty graph widget, which is why nan_indication is set to true by default. If the user wants to silence NaNs, they can always simply set it to false. nan_color if unset, defaults to a noticeable yellow-black pattern. As a direct and planned consequence, stacked graphs with negative values in them trigger the NaN handling too. But silencing NaN handling for those graphs is also possible and semi-sane, because then NaNs and negatives are ignored and the graph behaves (almost) like they were zeros. commit 9c1f8f08b400e01e19c534f3810c9c9ab485760c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 26 08:10:19 2021 +0700 Refactor out graph_map_value_to_widget_coordinates() function commit 1905991ddecabdae3e15ffb8ee5b18a1bee1a711 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 28 13:39:29 2021 +0700 Add an example file for graph.baseline_value property commit b31b39d66f4c037a9484b71a3f910703ae01fac0 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 07:03:49 2021 +0700 Add an example file for graph.clamp_bars=false/true comparison commit a1fc850e3047f9ee2c9ddeffacfbabf3ded34171 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 04:06:45 2021 +0700 Add an example file for graphs with negative values and shapes commit 2f012f8ff1bb5094c14e724a61e59a2a3a296bc8 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 01:06:05 2021 +0700 Add an example file for a graph displaying negative values commit 7b14fbd59a4c4c51962fd3110a1a900dd5f4eb98 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 11:18:26 2021 +0700 Add an example file for graphs with step_width < 1 commit dfd9a54544b637e2fbe3d1325f9cc24b3f4d2313 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 10:17:38 2021 +0700 Add an example file for graph.stack=false/true comparison Should also demonstrate that multiple data groups are possible for non-stacked graphs too. commit 5b532106c3fd255f9b66a66cfb88d977b489ab5d Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 09:34:14 2021 +0700 Add an example file for disabling graph data groups with nil colors commit ac6f1083c02fcb428f089a616f9b0ebaa5496b6c Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:57:25 2021 +0700 Add an example file for step_shape/width/spacing on stacked graphs commit b3703db6a5139e73e72c1cfd198ece2cd9be7df2 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:43:56 2021 +0700 Add an example file for scale=false/true on stacked graph commit b000aed4a12cb44fb501e1c8432a71a14b2f8f3f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 20 08:22:19 2021 +0700 Document deprecation of graph's width and height commit cb0ad617bd6c83a499896797318e3b0b588c3566 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 07:53:37 2021 +0700 Deprecate width and height properties of wibox.widget.graph commit 8c64e14ad91a074b784166a7054e53444cccb9d2 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 04:37:59 2021 +0700 Refactor graph:draw() to separate concerns Firstly, summation of group values for stacked graphs is extracted into graph:preprocess_values(). This makes graph:draw() smaller and exposes the fact, that stacked graphs are almost entirely just a form of a more general concept: pre-render data manipulation. It's something a user might want to customize, and now they can do it by monkey-patching the method. Secondly, the logic for deciding, if and with which color a data group should be drawn, is extracted into graph:should_draw_data_group() and get_data_group_color() methods respectively. This makes it customizable too and makes more obvious the fact, that it is a general thing that has nothing to do with stacked graphs in particular. With this there are only 5 lines remaining in graph:draw(), that could be said to be specific to stacked graphs, all pertaining to the `prev_y` variable, which merely preserves the bar coordinates from a previous data group. commit 2a32b6305d893f413303728a0a3da1dcd9a5591e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 22:52:37 2021 +0700 Pointlessly optimize graph:add_value() 1) Purge old values first, then insert the new one, if needed. Since Lua grows arrays in power of two steps, and our guess_capacity() also takes care to return even integers, it would be a shame to overstep the capacity by one by inserting first and thus getting the table size doubled only to never use the most of it. 2) Use a removal procedure that doesn't call the slow length operator at all. Previous code called it twice (once in table.remove) for every removed value. commit 680ea8f5d81e9040afb4662b3229e937e4b89e02 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:23:38 2021 +0700 Set graph data series color before the painting loop commit b970f0f221ae76250f2a94433ff79d653caa6e21 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:05:37 2021 +0700 Drop a large no longer necessary `if` in graph:draw_values() This commit is almost purely a whitespace change. commit 6b4f0541c70ec99d7f6e276bae64939f337120d6 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:00:50 2021 +0700 Extract values rendering code into graph:draw_values() method This is to avoid ungodly if nesting levels, which are needed to bail out of drawing values early and skip to drawing the border. The next commit will get rid of the if. commit e0950e4f99d1ea1c4b8cea2cc67629fa40030c2a Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 14:50:20 2021 +0700 Track graph draw() usage to guess necessary array capacity The last step of divorcing add_values() from width calculations, while ensuring that user doesn't observe a lack of data to draw. The widget now tracks an approximate number of values that it has been asked to draw() in _private.last_drawn_values_num, and truncates its values array based on that (if not overridden by the capacity property). commit 2e3d542282f35e7c0fed454da0f2eada0fcf3b78 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 13:35:12 2021 +0700 Add `capacity` property to graph widget This is the second step of divorcing add_values() from calculations involving widths. How many values are stored and how many values are drawn are distinct concepts. There isn't even a single definitive "how many values are drawn" because the widget could be rendered in several places with distinct widths simultaneously, and in every such place the data must be drawn appropriately, e.g. with auto-scaling, that takes only the visible data points into account. And the draw() method can do that now. The default capacity value of nil uses heuristics based on widget's _private.width to guess some capacity that would tolerate some dynamic widget resizing. If the user finds the heuristic failing them, they can set the capacity property to the desired maximum number of values that will be kept in the _private.values array (in each data group). commit 08d4a80686f7fe363551c35589a460839f60853e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 03:27:18 2021 +0700 Extract values size computation into compute_drawn_values_num() This is the first step of making add_values() not concern itself (directly) with widget dimensions. The user can monkey-patch the method to override how many values are (logically) drawn, because it affects graph auto-scaling and can be useful for rendering of step_shape-s that paint outside of their step_width. I didn't make it a full-blown function-valued property, because it seems a fringe functionality, which I'm too lazy to document. commit 3c5617c98ab1147618ed22e8414a77fc56b45f6e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 02:32:29 2021 +0700 Copy graph widget prototype methods over in bulk commit 169538837ece41a03d02ba8fbc0e4097062b67a3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun Apr 25 05:58:01 2021 +0700 Remove graph.baseline_y property After giving it some thought I've concluded, that it is unlikely to be useful as it is now. I'd better reserve the name for some more sensible functionality, like fixing the baseline_value defined axis at the given widget coordinate, adjusting the scaling accordingly, so min_value/max_value won't generally correspond to widget top and bottom anymore. commit 202ffaa9a0b820c7c471e1121d01b91f8f76cd38 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 12:06:55 2021 +0700 Add baseline_y and baseline_value properties to graph widget Now values in graph have a *baseline*, tunable with baseline_y and baseline_value properties. It is the (for now invisible) horizontal line from which all bars in the graph grow. I call it a "baseline" instead of "axis", because it doesn't actually have to correspond to the 0 value. But it can, and actually does now by default. baseline_y and baseline_value properties determine the vertical placement of the baseline in the widget. baseline_y is the position of the baseline in normalized widget coordinates, e.g. 0 corresponds to widget's top, 1 - to widget's bottom, and 0.5 - to widget's middle. So if e.g. one sets baseline_y = 0, then no matter how the widget is resized and how its values are scaled, bars in it will be drawn starting from the top. baseline_value is the position of the baseline in value coordinates. So if one sets baseline_value = 10, then no matter what happens, bars for values greater than 10 will be drawn as growing up from this line, and bars for values smaller as growing down. Where and if it will be seen in the widget, depends on the scaling. baseline_value = 0 is the familiar zero axis. baseline_y is unset by default and has priority, i.e. if it's set, then baseline_value is ignored. baseline_value defaults to 0. Prior to this commit, bars were always drawn from the bottom edge of the widget, or, in the new terms, the widget had baseline_y = 1. But it was indistinguishable from baseline_value = 0, because the widget clamped all negative values to 0, so the difference between the two could never be observed. baseline_value = 0 is clearly more useful and familiar to people, so now it is in force by default. commit df7e19abaebf0a871b5ca217b4cc3365533267a6 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 11:18:12 2021 +0700 Move value clamping from graph:add_value() into draw() Previously all values were forcibly clamped between 0 (not tunable at all) and max_value (1 by default). Thus graphs with negative values were not an option. NaN values were conflated with zeros, even though it may be useful to maintain the distinction and render them differently. And if user happened to dynamically change the max_value property, they discovered, that their data got truncated. Preserving user data as it was given is more flexible and makes things easier to debug. This commit does exactly that. So what happens now to the newly allowed values during draw()? There are 3 new cases to consider: 1) NaNs 2) Negatives 3) Values outside of min_value..max_value range NaNs are presently simply not drawn. An empty place is reserved where the value should've been, but nothing else is done. This is distinct from how a zero is handled, e.g. if step_shape is set, it is called with the height parameter = 0, which can still end up drawing something, depending on the shape. One could also consider addition of nan_color/nan_shape properties which could be drawn to alert user to a gap in their data. Negative values necessitate introduction of the concept of the zero axis, from which they will be drawn DOWN, in contrast to positive values. Where should this axis be drawn? How should step_shape-s with inherent direction, e.g. arrows, be drawn for negatives? This will be addressed in the next commit. Values outside of min/max_value range can imply shapes that go beyond widget dimensions. An arrow shape that goes beyond the roof is visibly different from an arrow that saturates and merely touches the widget top, and the user may reasonably desire either behavior. Therefore this commit introduces a boolean clamp_bars property, which defaults to true for reasons of robustness and backward compatibility with the behaviour prior to this commit. The property of course affects only the display of the values, the actual user data stays intact. commit c9c24fb1ab741cd6bb8074971d568e0646c51532 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:29:01 2021 +0700 Use rectangles for drawing graph bars of all sizes I have benchmarked various bar drawing strategies and graph sizes and found out that drawing graph bars with rectangles is as fast or even marginally faster than using fat lines. Thus the code that tries to special-case 1px bars as lines seems to be not worth the additional complexity it introduces, and this commit removes it. Incidentally, I've also established, that rectangles are more robust. If one draws graph bars and feeds cairo very large coordinates (~2^62), then corresponding rectangles fail to render, but all the other rectangles in the same series get rendered successfully, whereas a single large coordinate in a move_to/line_to() sequence is enough to cause all lines in the batch to fail to render. commit bc7f4be5e06405b5aa7fb443a5ace2b9b9575b1f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:01:42 2021 +0700 Cache cairo methods used in the inner loop of graph:draw() I have benchmarked the draw() method, both on cairo xcb and on argb32 image surfaces, and have discovered that cairo context method calls are unnaturally slow, to the point, that almost nothing else in the function matters. For example, drawing bars with vertical lines made the whole draw() function almost 2 times slower than doing the same with rectangles, merely because the former requires two method calls (move_to/line_to), and the latter only one. Simply caching hotpath cairo methods in local variables, reduced the difference between the drawing methods to a negligible one and made the whole function 4 times faster on a typical 100-bar graph, (1.4ms to 0.33ms on my laptop) thus bringing closer my secret dream of drawing live audio data with awesome graphs (kidding). I guess, LGI does something not very optimal there, like gobject-introspecting the method every time, or something. There might be some untapped optimization potential, possibly in upstream, like a method cache, from which everything in awesome could benefit, even though I imagine not many things are so call-intensive as the graph widget, to really feel the difference. commit 538df25bdf02b53d0bbe81809eef2e6fdb05ff6f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 07:32:37 2021 +0700 Use 1-based index in the inner graph draw loop It's used there only once for a calculation and then several times to index some tables, and index 0 is bad for lua tables performance-wise. commit 7412546916cd0a0a490130a120255ffb719398b8 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:55:12 2021 +0700 Leave graph.max_value uninitialized at construction This is a user-visible change, but the previous behavior was probably not what user expected anyway, namely with graph.scale set the graph mysteriously couldn't zoom in onto values that are smaller than 1. commit eb81d7929a161e79c13262cfa73ac2e6a820e8ed Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:48:29 2021 +0700 Allow graph.max_value be nil This makes its behave symmetrically to min_value. Now it defaults to 1, when graph.scale is not set, and to the maximum value in the graph data, when graph.scale is set. commit f622a8430cbfde7007b58d7f4c9c76b08c91e997 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 12:01:13 2021 +0700 Calculate max values num in graph:add_value() more precisely At most so many values were kept in the widget values array, as there are pixels of usable widget width (i.e. sans border). This is exactly right for the surely most common case of graphs with 1px-wide bars, but is more than needed for graphs with step_width+step_spacing>1, and not enough for graphs with subpixel bars. Moreover this made graph autoscaling behave weirdly in graphs with thicker bars, because old values survived longer in the array and kept affecting min_value/max_value calculations even after they were shifted off-screen. The maximum number of kept values is now calculated exactly as the number of values that can be displayed at once, taking step_width and step_spacing into account. It might be worth adding a max_values_num property to the graph to let user override this calculation, in case they draw shapes in step_shape() outside of its supposed rectangle, so our assumptions of visibility are wrong, or the widget often gets resized, resulting in the annoyance of visibly truncated series. commit 0aa63249a18641251a1bf77423739164f0eab39f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 02:30:13 2021 +0700 Fix border_color/border_width bugs in graph widget add_value() wrongly assumed that border_width is always set, when border_color is, so if user set only the latter, an error occurred. add_value() assumed that border_width is always < width which led to awesome hanging up in an infinite loop, should that fail to be the case. The graph border_width property was driven by the border_color property, instead of the other way round. If border_color wasn't set, the border wasn't drawn all, even if user sets non-zero border_width, which is a confusing behavior, in contrast to all other widgets, which simply use a plethora of fallback colors to draw the border, when needed. This also meant that, despite being documented, the beautiful.graph_border_color fallback color could not and was not ever used. If the border_color was set though, an unset border_width was immediately assumed to be 1 instead of 0, also contrary to what other widgets do. Theme colors shouldn't influence widget layout, so this commit fixes this behavior to: 1) border_width always defaults to 0, if unset. 2) border_color falls back to beautiful.graph_border_color or white. Maybe one should also add a beautiful.graph_border_width theme variable, like some widgets do. commit dad4a3253b8fef971fb82720d2a407fcc0a879ad Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 03:11:27 2021 +0700 Implement multiple data series drawing for non-stacked graph The widget can already hold and draw multiple data groups, so it makes little sense to limit drawing only to the first group when graph.stack = false, probably leading to user's confusion. Now user can call add_value(v, group) with group > 1 and the data series will be drawn (over groups with lesser id-s) with graph.stack_colors[group] color, if stack_colors is set, or with graph.color, if stack_colors isn't set. Thus nothing visibly changes for all current users of non-stacked graphs with only single data series, but the new functionality is now there for those who need it. One can't fully emulate this functionality, e.g. with multiple graphs widgets in a wibox.layout.stack, because data series in a single widget can enjoy a common auto-scaling. commit 3e645c976184ab5f50d625271c25062db9ab7ed9 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:59:12 2021 +0700 Don't draw a stacked graph data group if there's no color for it If stack_colors[idx] is nil for the group `idx`, don't draw it at all (as opposed, e.g. to the effect of drawing it with a transparent color, or as though it had all values == 0, which both produce different results). This code is still subtly different from prior behavior, because now user can have a non-contiguous stack_colors table and turn the display of any data group on and off at will, whereas the code two commits earlier would just stop rendering as soon as ipairs(stack_colors) does, i.e. no data series were drawn past the first nil hole. This commit only looks large, but is mostly a whitespace change due to nesting from a pair of wrapping ifs. commit 8c0273786de1ade66bdd977699f98b588da9f487 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:32:42 2021 +0700 Unify stacked and non-stacked graph rendering Now stacked graphs support everything the nonstacked do, like step_width ~= 1 and custom step_shape-s. Technical notes: This code no longer calls cr:stroke() for every single value in a stacked graph, but rather paints a whole series and then cr:stroke/fill()-s it at all once with its color, improving performance considerably. Additionally the graph no longer relies on the presence of the stack_colors property to render series. It draws all data that it has, and, if there's no corresponding color in stack_colors, falls back to graph.color. This will be changed in the next commit to the prior behavior of only drawing those series for which there's a color in the stack_colors table, but that would've made this commit's diff much larger due to required nesting ifs over large code chunks. This commit also takes care to properly handle NaNs and negative values. Those are currently (almost) guaranteed to not occur in the values array due to clipping in graph:add_value(), but I intend to eventually drop the clipping, because it does nothing that draw() really relies upon, and allowing unclipped and NaN values is more flexible and less surprising, e.g. when user dynamically toggles graph.scale. Eventually I intend to implement drawing negative values in non-stacked graphs and possibly even in the stacked ones. commit 38c6589c51b2aa057d7c2f40182ecf32b5b2ff5e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 10:04:15 2021 +0700 Convert graph:draw() to use `self` commit f21f1b2550426df1c4ea8abde079b1ccef84b394 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 12:27:26 2021 +0700 Fix scaling for stacked graphs Stacked graphs have cumulative heights, values from all groups that end up in the same graph bar are effectively summed. Previous code didn't take that into account when calculating min/max_value for the purposes of rescaling, but simply examined every value separately. This code adds up all values up front and finds min/max among those, to determine proper scaling that would fit the graph exactly. commit 1ce4c7bf63bdd6c99c195d668347bec49355e921 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 09:49:35 2021 +0700 Draw shaped graph bars only up to the baseline Prior to this commit, every step_shape bar was, for some reason, drawn with height = full widget's height, regardless of the value it represents. So for the most bars their lower end was far outside the box and therefore clipped off. So e.g. one couldn't see the lower round edges of rounded rectangles. This commit fixes that by appropriately varying the height that is passed into step_shape(), so that lower edges of the bars always exactly touch the lower edge of the graph. If the user wants to restore the prior behavior, they can simply ignore the passed-in height in step_shape() and just always draw their shape with height = full widget height. But they couldn't replicate the effect of this commit in their rc.lua without essentially writing their own graph widget. commit e25185b08da23745b5815f85f6e9e43222ed2294 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 08:42:54 2021 +0700 Avoid incremental transform shifting during graph bar rendering Instead of letting addition errors and cognitive load accumulate simply use the same offset calculation that is used for lines and reset the transform back to the top-left corner after every drawn bar. It's conceptually simpler, and will be useful later for stacked graphs and more. It also fixes a discrepancy between plain and step_shape rendering. Prior to this commit, bar coloring occurred with a disturbed transform, which was centered at the last drawn bar, wherever it happened to be. This meant that nontrivial colors, like gradients and images, were arbitrarily offset when step_shape was used. commit f3e98866d8583c35d0b557c09f529299edc9ba2c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:45:29 2021 +0700 Refactor out graph bar height expression into a variable commit f54528d6892f5aaa4595917853c9e2130f743fdb Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:20:25 2021 +0700 Draw graph bars with lines only when step_width == 1 Before this commit graph bars of step_width < 1 were drawn with lines of width 1, but that's a bit unfortunate, because it led to much overpainting, while still ending up with a graphically incorrect result. Let's draw such bars with rectangles and let cairo do things with subpixels, getting more graphically accurate result, while still keeping the common case of step_width == 1 (supposedly) fast-drawn with lines. commit b6515dca67c18b78eb5657378131a62ab43d4cfc Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:54:07 2021 +0700 Fix unsharp edges when drawing graph bars with rectangles The 0.5 offset is only helpful for 1-width strokes, not for fills. commit 7f3db8f07e82f821bcd1dc418d66564f537609d6 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:21:01 2021 +0700 Do coordinate transformations even if the value isn't drawn The graph presently doesn't draw values < 0, but there was a discrepancy between drawing with and without step_shape. The default line/rectangle drawing reserves an empty place where the skipped value should've been, but no such empty place is reserved, when step_shape is used. This commit fixes that. Skipped values always have their empty space now. commit 7b731ebfe96af7a9dba9c57371da6761cf22c00c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:03:30 2021 +0700 Fix graph bar scaling when min_value is not 0 Previous calculation lead to underscaling of values so that even value = max_value wasn't drawn to full height. commit dca59fd8b7fff0e7e646f2dfc6bd92047028e5a8 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:18:40 2021 +0700 Calculate the horizontal graph bar offset only when needed commit 940ddf4b9825e9c3a91d276208c089d8c74e49db Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:17:23 2021 +0700 Call cr:move_to() only when drawing the graph with lines It's redundant otherwise and might even interfere with user's drawing in step_shape(). commit b5d5cd10ca29fb29cd751faaa95137c2fe89ff63 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:04:06 2021 +0700 Fix wrong horizontal offset calculations in widget.graph Indices in the loop are zero-based, so things like i-1 and i>1 aren't necessary and actually wouldn't have been correct even in a 1-based loop. step_shape = gears.shape.rectangle and step_shape = nil, which is supposed to default to rectangle according to docs, were drawn differently due to mismatched offset calculations, which led to problems especially noticeable when step_spacing is not zero, e.g: 1) whole graph shifted left too much, drawn partially off-screen. 2) whole graph shifted right too much, and the first bar placed closer to the rest than others. commit dc369fa198281e000afc1bd17326f3a264adf7d1 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 02:09:21 2021 +0700 Fix the "border_width affects graph bar width" bug Graph bars were drawn with lines of border_width thickness instead of 1, i.e. possibly too fat to be readable. commit 1ba73378cf29ed27a6ecd18716394779ceb543d9 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 00:13:03 2021 +0700 Simplify internal values representation in graph even more New values in graph._private.values were pushed to the end, and old values were removed from the beginning of the table. In draw() however newer values are drawn first, at the left edge of the graph, necessitating annoying idx = #values - idx conversions everywhere. In particular in stacked graph that means that values that are drawn together may have different indices. One has to shift the values array in add_value() either way, so one might as well insert new values at the beginning of the array and simplify the rest of the code. This doesn't seem like much yet, but it will prove to be much more convenient and less error-prone in what I'm going to do later. commit f70523ee68dbd6b67050ace4b82903a9b22440c7 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 13:35:10 2021 +0700 Simplify internal values representation in graph widget graph._private.values was either a table of values or a table of tables of values, depending on whether "stack" property is enabled, ... or a mix of both if user forgot to pass the second "group" parameter to graph:add_value(). This resulted in pretty fragile and confusing behavior, e.g. leaving user to wonder, why they see nothing more after setting stack=true for a graph in a previously working rc.lua, and demanding a very careful use of add_value() from the user, or else some values or even entire stacks would mysteriously vanish and errors in draw() get triggered, sometimes after a period of seemingly normal functioning. Now `values` is always a table of tables and the group parameter in add_value() defaults to 1, thus making the non-stacked graph a trivial variant of the stacked graph.
2021-05-20 17:14:03 +02:00
-- When set to true, bars of each successive data group are drawn on top of
-- bars of previous groups, instead of the baseline.
-- This necessitates all data values to be non-negative.
-- Negative values, if present, will trigger @{nan_color|NaN indication}.
--
-- @DOC_wibox_widget_graph_normal_vs_stacked_EXAMPLE@
--
-- @property stack
-- @tparam[opt=false] boolean stack
-- @propemits true false
2016-05-23 07:30:44 +02:00
graph: complete widget overhaul and bug fixes Squashed commit of the following: commit 69821f51fe1e8652715658543e50631ece495090 Author: Alex Belykh <albel727@ngs.ru> Date: Mon May 10 19:17:55 2021 +0700 Refactor property handling in wibox.widget.graph In addition, baseline_value, step_width and step_spacing properties are all set to their default non-nil numeric values during instance creation now. commit 842eb429bf5df4b2ba46b2e6f7646afe89b1a3c3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 9 06:18:45 2021 +0700 Make graph:draw_values() private There's no documentation nor confidence that it's a good API, so I'm hiding it for now. commit 720746780574e4bad14a71fc4174955139c2eb50 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 04:04:13 2021 +0700 Document graph_should_draw_data_group() for posterity commit 7c73f3754ce6ba86b118f1170a6f9e406ab4841d Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:59:21 2021 +0700 Make graph:should_draw_data_group() private There's no confidence whether it's a good API, so I'm hiding it for now. commit b3539e20adb6423118af726ae4f8751ef864b5d2 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:36:44 2021 +0700 Make graph:preprocess_values() private There's no confidence whether it's a good API, so I'm hiding it for now. commit 2df4400e780a6260f8d0ab1c60734fbb11bf3c4f Author: Alex Belykh <albel727@ngs.ru> Date: Thu May 6 10:05:23 2021 +0700 Add spec/wibox/widget/graph_spec.lua commit ce01a9771ab439b8df841f1565aa4aa8a44a5e0d Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 08:16:18 2021 +0700 Improve graph.capacity documentation commit e28fade8f655802f5bf89154169f68d256b8e332 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 07:36:01 2021 +0700 Document graph:compute_drawn_values_num() commit dd68332292465422d08b48e9f0229f143beccbc6 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 05:24:23 2021 +0700 Document graph:pick_data_group_color() method commit ca6dc55f77aca88a6e9cc90bb6fd0045739f41f5 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 04:13:58 2021 +0700 Guard against setting data groups sparsely in graph:add_value() commit cdea5f126f7984e72924f6dffbd312c4fe3b032f Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 02:36:12 2021 +0700 Refactor scaling and baseline choice out of graph:draw() commit 10135deafe8a9390ba66ab657ebb1fb41d549e8c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 21:34:48 2021 +0700 Document graph.nan_color and graph.stack properties commit 0d73bb84ac66b87e16aee472bd1f299549f66a1c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 20:49:24 2021 +0700 Use a better example in docs for the graph.stack property commit c27d160a0826d033d49d6141608c40aa8a0642fa Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 16:05:38 2021 +0700 Document graph.baseline_value property commit 459bc8176c13d4ea32af0dcdc2399d3b929b1c6a Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:53:47 2021 +0700 Document graph.clamp_bars property commit e732ee0b3b007d213e689b98cc00ae7cb141ae08 Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:34 2021 +0700 Improve graph:add_value() and :clear() documentation commit ae73e3aa9f8d60409cbe4cb4da9612f31af4f38e Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:11 2021 +0700 Improve graph min_value/max_value/scale documentation commit a7350996a9def773b91b1631e6c0507e33d8f93b Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:27:36 2021 +0700 Cleanup graph examples to show only relevant things in docs commit 30740c26f4f41c16f4c8a3c339bcb7bd299e14cd Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 14:04:36 2021 +0700 Rename graph.stack_colors property to `group_colors` The colors are used for stacked and non-stacked graphs alike, so the name didn't make sense. Also document and clarify some color stuff. commit 1ea01bfd410971cf0d0c25fd74cd764d5eb7279d Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 21:25:41 2021 +0700 Add an example file for code coverage of some fringe graph cases commit eae68e39553f88a6275a0c65bf040b5d4c262b54 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 20:23:32 2021 +0700 Add step_shape to the graph/nan_color example for code coverage commit 8027147bc2eab34a97b49a428fb8cef27f122bed Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:55:18 2021 +0700 Add example files for graph.nan_color/nan_indication properties commit 89a25a165c52b2c15b6c4d95ecb777a9067e66f9 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:33:07 2021 +0700 Make widget graph:add_value(v) default v to NaN 0 as a default makes no sense and silences the error that is failing to specify a value, to which the user should rather be alerted. The graph widget has sane NaN handling now and will do just that. commit 2835d552440be48d356a7f7b1d64963696dd550b Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:20:48 2021 +0700 Implement graph.nan_color and nan_indication properties Now, whenever there's a NaN among values and graph.nan_indication is set, the corresponding area, where the value bar should have been drawn, is filled with the nan_color from top to bottom. The painting is done after all data is rendered, to make sure that it won't be overpainted and go unnoticed. NaNs among graph's values are inevitably a sign of some error and it is arguably not a sane default behavior to simply not render them at all. The user should take immediate note of any errors by default, instead of wondering at a mysteriously empty graph widget, which is why nan_indication is set to true by default. If the user wants to silence NaNs, they can always simply set it to false. nan_color if unset, defaults to a noticeable yellow-black pattern. As a direct and planned consequence, stacked graphs with negative values in them trigger the NaN handling too. But silencing NaN handling for those graphs is also possible and semi-sane, because then NaNs and negatives are ignored and the graph behaves (almost) like they were zeros. commit 9c1f8f08b400e01e19c534f3810c9c9ab485760c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 26 08:10:19 2021 +0700 Refactor out graph_map_value_to_widget_coordinates() function commit 1905991ddecabdae3e15ffb8ee5b18a1bee1a711 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 28 13:39:29 2021 +0700 Add an example file for graph.baseline_value property commit b31b39d66f4c037a9484b71a3f910703ae01fac0 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 07:03:49 2021 +0700 Add an example file for graph.clamp_bars=false/true comparison commit a1fc850e3047f9ee2c9ddeffacfbabf3ded34171 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 04:06:45 2021 +0700 Add an example file for graphs with negative values and shapes commit 2f012f8ff1bb5094c14e724a61e59a2a3a296bc8 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 01:06:05 2021 +0700 Add an example file for a graph displaying negative values commit 7b14fbd59a4c4c51962fd3110a1a900dd5f4eb98 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 11:18:26 2021 +0700 Add an example file for graphs with step_width < 1 commit dfd9a54544b637e2fbe3d1325f9cc24b3f4d2313 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 10:17:38 2021 +0700 Add an example file for graph.stack=false/true comparison Should also demonstrate that multiple data groups are possible for non-stacked graphs too. commit 5b532106c3fd255f9b66a66cfb88d977b489ab5d Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 09:34:14 2021 +0700 Add an example file for disabling graph data groups with nil colors commit ac6f1083c02fcb428f089a616f9b0ebaa5496b6c Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:57:25 2021 +0700 Add an example file for step_shape/width/spacing on stacked graphs commit b3703db6a5139e73e72c1cfd198ece2cd9be7df2 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:43:56 2021 +0700 Add an example file for scale=false/true on stacked graph commit b000aed4a12cb44fb501e1c8432a71a14b2f8f3f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 20 08:22:19 2021 +0700 Document deprecation of graph's width and height commit cb0ad617bd6c83a499896797318e3b0b588c3566 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 07:53:37 2021 +0700 Deprecate width and height properties of wibox.widget.graph commit 8c64e14ad91a074b784166a7054e53444cccb9d2 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 04:37:59 2021 +0700 Refactor graph:draw() to separate concerns Firstly, summation of group values for stacked graphs is extracted into graph:preprocess_values(). This makes graph:draw() smaller and exposes the fact, that stacked graphs are almost entirely just a form of a more general concept: pre-render data manipulation. It's something a user might want to customize, and now they can do it by monkey-patching the method. Secondly, the logic for deciding, if and with which color a data group should be drawn, is extracted into graph:should_draw_data_group() and get_data_group_color() methods respectively. This makes it customizable too and makes more obvious the fact, that it is a general thing that has nothing to do with stacked graphs in particular. With this there are only 5 lines remaining in graph:draw(), that could be said to be specific to stacked graphs, all pertaining to the `prev_y` variable, which merely preserves the bar coordinates from a previous data group. commit 2a32b6305d893f413303728a0a3da1dcd9a5591e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 22:52:37 2021 +0700 Pointlessly optimize graph:add_value() 1) Purge old values first, then insert the new one, if needed. Since Lua grows arrays in power of two steps, and our guess_capacity() also takes care to return even integers, it would be a shame to overstep the capacity by one by inserting first and thus getting the table size doubled only to never use the most of it. 2) Use a removal procedure that doesn't call the slow length operator at all. Previous code called it twice (once in table.remove) for every removed value. commit 680ea8f5d81e9040afb4662b3229e937e4b89e02 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:23:38 2021 +0700 Set graph data series color before the painting loop commit b970f0f221ae76250f2a94433ff79d653caa6e21 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:05:37 2021 +0700 Drop a large no longer necessary `if` in graph:draw_values() This commit is almost purely a whitespace change. commit 6b4f0541c70ec99d7f6e276bae64939f337120d6 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:00:50 2021 +0700 Extract values rendering code into graph:draw_values() method This is to avoid ungodly if nesting levels, which are needed to bail out of drawing values early and skip to drawing the border. The next commit will get rid of the if. commit e0950e4f99d1ea1c4b8cea2cc67629fa40030c2a Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 14:50:20 2021 +0700 Track graph draw() usage to guess necessary array capacity The last step of divorcing add_values() from width calculations, while ensuring that user doesn't observe a lack of data to draw. The widget now tracks an approximate number of values that it has been asked to draw() in _private.last_drawn_values_num, and truncates its values array based on that (if not overridden by the capacity property). commit 2e3d542282f35e7c0fed454da0f2eada0fcf3b78 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 13:35:12 2021 +0700 Add `capacity` property to graph widget This is the second step of divorcing add_values() from calculations involving widths. How many values are stored and how many values are drawn are distinct concepts. There isn't even a single definitive "how many values are drawn" because the widget could be rendered in several places with distinct widths simultaneously, and in every such place the data must be drawn appropriately, e.g. with auto-scaling, that takes only the visible data points into account. And the draw() method can do that now. The default capacity value of nil uses heuristics based on widget's _private.width to guess some capacity that would tolerate some dynamic widget resizing. If the user finds the heuristic failing them, they can set the capacity property to the desired maximum number of values that will be kept in the _private.values array (in each data group). commit 08d4a80686f7fe363551c35589a460839f60853e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 03:27:18 2021 +0700 Extract values size computation into compute_drawn_values_num() This is the first step of making add_values() not concern itself (directly) with widget dimensions. The user can monkey-patch the method to override how many values are (logically) drawn, because it affects graph auto-scaling and can be useful for rendering of step_shape-s that paint outside of their step_width. I didn't make it a full-blown function-valued property, because it seems a fringe functionality, which I'm too lazy to document. commit 3c5617c98ab1147618ed22e8414a77fc56b45f6e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 02:32:29 2021 +0700 Copy graph widget prototype methods over in bulk commit 169538837ece41a03d02ba8fbc0e4097062b67a3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun Apr 25 05:58:01 2021 +0700 Remove graph.baseline_y property After giving it some thought I've concluded, that it is unlikely to be useful as it is now. I'd better reserve the name for some more sensible functionality, like fixing the baseline_value defined axis at the given widget coordinate, adjusting the scaling accordingly, so min_value/max_value won't generally correspond to widget top and bottom anymore. commit 202ffaa9a0b820c7c471e1121d01b91f8f76cd38 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 12:06:55 2021 +0700 Add baseline_y and baseline_value properties to graph widget Now values in graph have a *baseline*, tunable with baseline_y and baseline_value properties. It is the (for now invisible) horizontal line from which all bars in the graph grow. I call it a "baseline" instead of "axis", because it doesn't actually have to correspond to the 0 value. But it can, and actually does now by default. baseline_y and baseline_value properties determine the vertical placement of the baseline in the widget. baseline_y is the position of the baseline in normalized widget coordinates, e.g. 0 corresponds to widget's top, 1 - to widget's bottom, and 0.5 - to widget's middle. So if e.g. one sets baseline_y = 0, then no matter how the widget is resized and how its values are scaled, bars in it will be drawn starting from the top. baseline_value is the position of the baseline in value coordinates. So if one sets baseline_value = 10, then no matter what happens, bars for values greater than 10 will be drawn as growing up from this line, and bars for values smaller as growing down. Where and if it will be seen in the widget, depends on the scaling. baseline_value = 0 is the familiar zero axis. baseline_y is unset by default and has priority, i.e. if it's set, then baseline_value is ignored. baseline_value defaults to 0. Prior to this commit, bars were always drawn from the bottom edge of the widget, or, in the new terms, the widget had baseline_y = 1. But it was indistinguishable from baseline_value = 0, because the widget clamped all negative values to 0, so the difference between the two could never be observed. baseline_value = 0 is clearly more useful and familiar to people, so now it is in force by default. commit df7e19abaebf0a871b5ca217b4cc3365533267a6 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 11:18:12 2021 +0700 Move value clamping from graph:add_value() into draw() Previously all values were forcibly clamped between 0 (not tunable at all) and max_value (1 by default). Thus graphs with negative values were not an option. NaN values were conflated with zeros, even though it may be useful to maintain the distinction and render them differently. And if user happened to dynamically change the max_value property, they discovered, that their data got truncated. Preserving user data as it was given is more flexible and makes things easier to debug. This commit does exactly that. So what happens now to the newly allowed values during draw()? There are 3 new cases to consider: 1) NaNs 2) Negatives 3) Values outside of min_value..max_value range NaNs are presently simply not drawn. An empty place is reserved where the value should've been, but nothing else is done. This is distinct from how a zero is handled, e.g. if step_shape is set, it is called with the height parameter = 0, which can still end up drawing something, depending on the shape. One could also consider addition of nan_color/nan_shape properties which could be drawn to alert user to a gap in their data. Negative values necessitate introduction of the concept of the zero axis, from which they will be drawn DOWN, in contrast to positive values. Where should this axis be drawn? How should step_shape-s with inherent direction, e.g. arrows, be drawn for negatives? This will be addressed in the next commit. Values outside of min/max_value range can imply shapes that go beyond widget dimensions. An arrow shape that goes beyond the roof is visibly different from an arrow that saturates and merely touches the widget top, and the user may reasonably desire either behavior. Therefore this commit introduces a boolean clamp_bars property, which defaults to true for reasons of robustness and backward compatibility with the behaviour prior to this commit. The property of course affects only the display of the values, the actual user data stays intact. commit c9c24fb1ab741cd6bb8074971d568e0646c51532 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:29:01 2021 +0700 Use rectangles for drawing graph bars of all sizes I have benchmarked various bar drawing strategies and graph sizes and found out that drawing graph bars with rectangles is as fast or even marginally faster than using fat lines. Thus the code that tries to special-case 1px bars as lines seems to be not worth the additional complexity it introduces, and this commit removes it. Incidentally, I've also established, that rectangles are more robust. If one draws graph bars and feeds cairo very large coordinates (~2^62), then corresponding rectangles fail to render, but all the other rectangles in the same series get rendered successfully, whereas a single large coordinate in a move_to/line_to() sequence is enough to cause all lines in the batch to fail to render. commit bc7f4be5e06405b5aa7fb443a5ace2b9b9575b1f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:01:42 2021 +0700 Cache cairo methods used in the inner loop of graph:draw() I have benchmarked the draw() method, both on cairo xcb and on argb32 image surfaces, and have discovered that cairo context method calls are unnaturally slow, to the point, that almost nothing else in the function matters. For example, drawing bars with vertical lines made the whole draw() function almost 2 times slower than doing the same with rectangles, merely because the former requires two method calls (move_to/line_to), and the latter only one. Simply caching hotpath cairo methods in local variables, reduced the difference between the drawing methods to a negligible one and made the whole function 4 times faster on a typical 100-bar graph, (1.4ms to 0.33ms on my laptop) thus bringing closer my secret dream of drawing live audio data with awesome graphs (kidding). I guess, LGI does something not very optimal there, like gobject-introspecting the method every time, or something. There might be some untapped optimization potential, possibly in upstream, like a method cache, from which everything in awesome could benefit, even though I imagine not many things are so call-intensive as the graph widget, to really feel the difference. commit 538df25bdf02b53d0bbe81809eef2e6fdb05ff6f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 07:32:37 2021 +0700 Use 1-based index in the inner graph draw loop It's used there only once for a calculation and then several times to index some tables, and index 0 is bad for lua tables performance-wise. commit 7412546916cd0a0a490130a120255ffb719398b8 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:55:12 2021 +0700 Leave graph.max_value uninitialized at construction This is a user-visible change, but the previous behavior was probably not what user expected anyway, namely with graph.scale set the graph mysteriously couldn't zoom in onto values that are smaller than 1. commit eb81d7929a161e79c13262cfa73ac2e6a820e8ed Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:48:29 2021 +0700 Allow graph.max_value be nil This makes its behave symmetrically to min_value. Now it defaults to 1, when graph.scale is not set, and to the maximum value in the graph data, when graph.scale is set. commit f622a8430cbfde7007b58d7f4c9c76b08c91e997 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 12:01:13 2021 +0700 Calculate max values num in graph:add_value() more precisely At most so many values were kept in the widget values array, as there are pixels of usable widget width (i.e. sans border). This is exactly right for the surely most common case of graphs with 1px-wide bars, but is more than needed for graphs with step_width+step_spacing>1, and not enough for graphs with subpixel bars. Moreover this made graph autoscaling behave weirdly in graphs with thicker bars, because old values survived longer in the array and kept affecting min_value/max_value calculations even after they were shifted off-screen. The maximum number of kept values is now calculated exactly as the number of values that can be displayed at once, taking step_width and step_spacing into account. It might be worth adding a max_values_num property to the graph to let user override this calculation, in case they draw shapes in step_shape() outside of its supposed rectangle, so our assumptions of visibility are wrong, or the widget often gets resized, resulting in the annoyance of visibly truncated series. commit 0aa63249a18641251a1bf77423739164f0eab39f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 02:30:13 2021 +0700 Fix border_color/border_width bugs in graph widget add_value() wrongly assumed that border_width is always set, when border_color is, so if user set only the latter, an error occurred. add_value() assumed that border_width is always < width which led to awesome hanging up in an infinite loop, should that fail to be the case. The graph border_width property was driven by the border_color property, instead of the other way round. If border_color wasn't set, the border wasn't drawn all, even if user sets non-zero border_width, which is a confusing behavior, in contrast to all other widgets, which simply use a plethora of fallback colors to draw the border, when needed. This also meant that, despite being documented, the beautiful.graph_border_color fallback color could not and was not ever used. If the border_color was set though, an unset border_width was immediately assumed to be 1 instead of 0, also contrary to what other widgets do. Theme colors shouldn't influence widget layout, so this commit fixes this behavior to: 1) border_width always defaults to 0, if unset. 2) border_color falls back to beautiful.graph_border_color or white. Maybe one should also add a beautiful.graph_border_width theme variable, like some widgets do. commit dad4a3253b8fef971fb82720d2a407fcc0a879ad Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 03:11:27 2021 +0700 Implement multiple data series drawing for non-stacked graph The widget can already hold and draw multiple data groups, so it makes little sense to limit drawing only to the first group when graph.stack = false, probably leading to user's confusion. Now user can call add_value(v, group) with group > 1 and the data series will be drawn (over groups with lesser id-s) with graph.stack_colors[group] color, if stack_colors is set, or with graph.color, if stack_colors isn't set. Thus nothing visibly changes for all current users of non-stacked graphs with only single data series, but the new functionality is now there for those who need it. One can't fully emulate this functionality, e.g. with multiple graphs widgets in a wibox.layout.stack, because data series in a single widget can enjoy a common auto-scaling. commit 3e645c976184ab5f50d625271c25062db9ab7ed9 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:59:12 2021 +0700 Don't draw a stacked graph data group if there's no color for it If stack_colors[idx] is nil for the group `idx`, don't draw it at all (as opposed, e.g. to the effect of drawing it with a transparent color, or as though it had all values == 0, which both produce different results). This code is still subtly different from prior behavior, because now user can have a non-contiguous stack_colors table and turn the display of any data group on and off at will, whereas the code two commits earlier would just stop rendering as soon as ipairs(stack_colors) does, i.e. no data series were drawn past the first nil hole. This commit only looks large, but is mostly a whitespace change due to nesting from a pair of wrapping ifs. commit 8c0273786de1ade66bdd977699f98b588da9f487 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:32:42 2021 +0700 Unify stacked and non-stacked graph rendering Now stacked graphs support everything the nonstacked do, like step_width ~= 1 and custom step_shape-s. Technical notes: This code no longer calls cr:stroke() for every single value in a stacked graph, but rather paints a whole series and then cr:stroke/fill()-s it at all once with its color, improving performance considerably. Additionally the graph no longer relies on the presence of the stack_colors property to render series. It draws all data that it has, and, if there's no corresponding color in stack_colors, falls back to graph.color. This will be changed in the next commit to the prior behavior of only drawing those series for which there's a color in the stack_colors table, but that would've made this commit's diff much larger due to required nesting ifs over large code chunks. This commit also takes care to properly handle NaNs and negative values. Those are currently (almost) guaranteed to not occur in the values array due to clipping in graph:add_value(), but I intend to eventually drop the clipping, because it does nothing that draw() really relies upon, and allowing unclipped and NaN values is more flexible and less surprising, e.g. when user dynamically toggles graph.scale. Eventually I intend to implement drawing negative values in non-stacked graphs and possibly even in the stacked ones. commit 38c6589c51b2aa057d7c2f40182ecf32b5b2ff5e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 10:04:15 2021 +0700 Convert graph:draw() to use `self` commit f21f1b2550426df1c4ea8abde079b1ccef84b394 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 12:27:26 2021 +0700 Fix scaling for stacked graphs Stacked graphs have cumulative heights, values from all groups that end up in the same graph bar are effectively summed. Previous code didn't take that into account when calculating min/max_value for the purposes of rescaling, but simply examined every value separately. This code adds up all values up front and finds min/max among those, to determine proper scaling that would fit the graph exactly. commit 1ce4c7bf63bdd6c99c195d668347bec49355e921 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 09:49:35 2021 +0700 Draw shaped graph bars only up to the baseline Prior to this commit, every step_shape bar was, for some reason, drawn with height = full widget's height, regardless of the value it represents. So for the most bars their lower end was far outside the box and therefore clipped off. So e.g. one couldn't see the lower round edges of rounded rectangles. This commit fixes that by appropriately varying the height that is passed into step_shape(), so that lower edges of the bars always exactly touch the lower edge of the graph. If the user wants to restore the prior behavior, they can simply ignore the passed-in height in step_shape() and just always draw their shape with height = full widget height. But they couldn't replicate the effect of this commit in their rc.lua without essentially writing their own graph widget. commit e25185b08da23745b5815f85f6e9e43222ed2294 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 08:42:54 2021 +0700 Avoid incremental transform shifting during graph bar rendering Instead of letting addition errors and cognitive load accumulate simply use the same offset calculation that is used for lines and reset the transform back to the top-left corner after every drawn bar. It's conceptually simpler, and will be useful later for stacked graphs and more. It also fixes a discrepancy between plain and step_shape rendering. Prior to this commit, bar coloring occurred with a disturbed transform, which was centered at the last drawn bar, wherever it happened to be. This meant that nontrivial colors, like gradients and images, were arbitrarily offset when step_shape was used. commit f3e98866d8583c35d0b557c09f529299edc9ba2c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:45:29 2021 +0700 Refactor out graph bar height expression into a variable commit f54528d6892f5aaa4595917853c9e2130f743fdb Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:20:25 2021 +0700 Draw graph bars with lines only when step_width == 1 Before this commit graph bars of step_width < 1 were drawn with lines of width 1, but that's a bit unfortunate, because it led to much overpainting, while still ending up with a graphically incorrect result. Let's draw such bars with rectangles and let cairo do things with subpixels, getting more graphically accurate result, while still keeping the common case of step_width == 1 (supposedly) fast-drawn with lines. commit b6515dca67c18b78eb5657378131a62ab43d4cfc Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:54:07 2021 +0700 Fix unsharp edges when drawing graph bars with rectangles The 0.5 offset is only helpful for 1-width strokes, not for fills. commit 7f3db8f07e82f821bcd1dc418d66564f537609d6 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:21:01 2021 +0700 Do coordinate transformations even if the value isn't drawn The graph presently doesn't draw values < 0, but there was a discrepancy between drawing with and without step_shape. The default line/rectangle drawing reserves an empty place where the skipped value should've been, but no such empty place is reserved, when step_shape is used. This commit fixes that. Skipped values always have their empty space now. commit 7b731ebfe96af7a9dba9c57371da6761cf22c00c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:03:30 2021 +0700 Fix graph bar scaling when min_value is not 0 Previous calculation lead to underscaling of values so that even value = max_value wasn't drawn to full height. commit dca59fd8b7fff0e7e646f2dfc6bd92047028e5a8 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:18:40 2021 +0700 Calculate the horizontal graph bar offset only when needed commit 940ddf4b9825e9c3a91d276208c089d8c74e49db Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:17:23 2021 +0700 Call cr:move_to() only when drawing the graph with lines It's redundant otherwise and might even interfere with user's drawing in step_shape(). commit b5d5cd10ca29fb29cd751faaa95137c2fe89ff63 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:04:06 2021 +0700 Fix wrong horizontal offset calculations in widget.graph Indices in the loop are zero-based, so things like i-1 and i>1 aren't necessary and actually wouldn't have been correct even in a 1-based loop. step_shape = gears.shape.rectangle and step_shape = nil, which is supposed to default to rectangle according to docs, were drawn differently due to mismatched offset calculations, which led to problems especially noticeable when step_spacing is not zero, e.g: 1) whole graph shifted left too much, drawn partially off-screen. 2) whole graph shifted right too much, and the first bar placed closer to the rest than others. commit dc369fa198281e000afc1bd17326f3a264adf7d1 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 02:09:21 2021 +0700 Fix the "border_width affects graph bar width" bug Graph bars were drawn with lines of border_width thickness instead of 1, i.e. possibly too fat to be readable. commit 1ba73378cf29ed27a6ecd18716394779ceb543d9 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 00:13:03 2021 +0700 Simplify internal values representation in graph even more New values in graph._private.values were pushed to the end, and old values were removed from the beginning of the table. In draw() however newer values are drawn first, at the left edge of the graph, necessitating annoying idx = #values - idx conversions everywhere. In particular in stacked graph that means that values that are drawn together may have different indices. One has to shift the values array in add_value() either way, so one might as well insert new values at the beginning of the array and simplify the rest of the code. This doesn't seem like much yet, but it will prove to be much more convenient and less error-prone in what I'm going to do later. commit f70523ee68dbd6b67050ace4b82903a9b22440c7 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 13:35:10 2021 +0700 Simplify internal values representation in graph widget graph._private.values was either a table of values or a table of tables of values, depending on whether "stack" property is enabled, ... or a mix of both if user forgot to pass the second "group" parameter to graph:add_value(). This resulted in pretty fragile and confusing behavior, e.g. leaving user to wonder, why they see nothing more after setting stack=true for a graph in a previously working rc.lua, and demanding a very careful use of add_value() from the user, or else some values or even entire stacks would mysteriously vanish and errors in draw() get triggered, sometimes after a period of seemingly normal functioning. Now `values` is always a table of tables and the group parameter in add_value() defaults to 1, thus making the non-stacked graph a trivial variant of the stacked graph.
2021-05-20 17:14:03 +02:00
--- Display NaN indication. Default is true.
--
-- When the data contains [NaN](https://en.wikipedia.org/wiki/NaN) values,
-- and `nan_indication` is set, the corresponding area,
-- where the value bar should have been drawn, is filled
-- with the `nan_color` from top to bottom.
-- The painting is done after all other data is rendered,
-- to make sure that it won't be overpainted and go unnoticed.
2016-05-23 07:30:44 +02:00
--
graph: complete widget overhaul and bug fixes Squashed commit of the following: commit 69821f51fe1e8652715658543e50631ece495090 Author: Alex Belykh <albel727@ngs.ru> Date: Mon May 10 19:17:55 2021 +0700 Refactor property handling in wibox.widget.graph In addition, baseline_value, step_width and step_spacing properties are all set to their default non-nil numeric values during instance creation now. commit 842eb429bf5df4b2ba46b2e6f7646afe89b1a3c3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 9 06:18:45 2021 +0700 Make graph:draw_values() private There's no documentation nor confidence that it's a good API, so I'm hiding it for now. commit 720746780574e4bad14a71fc4174955139c2eb50 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 04:04:13 2021 +0700 Document graph_should_draw_data_group() for posterity commit 7c73f3754ce6ba86b118f1170a6f9e406ab4841d Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:59:21 2021 +0700 Make graph:should_draw_data_group() private There's no confidence whether it's a good API, so I'm hiding it for now. commit b3539e20adb6423118af726ae4f8751ef864b5d2 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:36:44 2021 +0700 Make graph:preprocess_values() private There's no confidence whether it's a good API, so I'm hiding it for now. commit 2df4400e780a6260f8d0ab1c60734fbb11bf3c4f Author: Alex Belykh <albel727@ngs.ru> Date: Thu May 6 10:05:23 2021 +0700 Add spec/wibox/widget/graph_spec.lua commit ce01a9771ab439b8df841f1565aa4aa8a44a5e0d Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 08:16:18 2021 +0700 Improve graph.capacity documentation commit e28fade8f655802f5bf89154169f68d256b8e332 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 07:36:01 2021 +0700 Document graph:compute_drawn_values_num() commit dd68332292465422d08b48e9f0229f143beccbc6 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 05:24:23 2021 +0700 Document graph:pick_data_group_color() method commit ca6dc55f77aca88a6e9cc90bb6fd0045739f41f5 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 04:13:58 2021 +0700 Guard against setting data groups sparsely in graph:add_value() commit cdea5f126f7984e72924f6dffbd312c4fe3b032f Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 02:36:12 2021 +0700 Refactor scaling and baseline choice out of graph:draw() commit 10135deafe8a9390ba66ab657ebb1fb41d549e8c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 21:34:48 2021 +0700 Document graph.nan_color and graph.stack properties commit 0d73bb84ac66b87e16aee472bd1f299549f66a1c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 20:49:24 2021 +0700 Use a better example in docs for the graph.stack property commit c27d160a0826d033d49d6141608c40aa8a0642fa Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 16:05:38 2021 +0700 Document graph.baseline_value property commit 459bc8176c13d4ea32af0dcdc2399d3b929b1c6a Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:53:47 2021 +0700 Document graph.clamp_bars property commit e732ee0b3b007d213e689b98cc00ae7cb141ae08 Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:34 2021 +0700 Improve graph:add_value() and :clear() documentation commit ae73e3aa9f8d60409cbe4cb4da9612f31af4f38e Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:11 2021 +0700 Improve graph min_value/max_value/scale documentation commit a7350996a9def773b91b1631e6c0507e33d8f93b Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:27:36 2021 +0700 Cleanup graph examples to show only relevant things in docs commit 30740c26f4f41c16f4c8a3c339bcb7bd299e14cd Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 14:04:36 2021 +0700 Rename graph.stack_colors property to `group_colors` The colors are used for stacked and non-stacked graphs alike, so the name didn't make sense. Also document and clarify some color stuff. commit 1ea01bfd410971cf0d0c25fd74cd764d5eb7279d Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 21:25:41 2021 +0700 Add an example file for code coverage of some fringe graph cases commit eae68e39553f88a6275a0c65bf040b5d4c262b54 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 20:23:32 2021 +0700 Add step_shape to the graph/nan_color example for code coverage commit 8027147bc2eab34a97b49a428fb8cef27f122bed Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:55:18 2021 +0700 Add example files for graph.nan_color/nan_indication properties commit 89a25a165c52b2c15b6c4d95ecb777a9067e66f9 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:33:07 2021 +0700 Make widget graph:add_value(v) default v to NaN 0 as a default makes no sense and silences the error that is failing to specify a value, to which the user should rather be alerted. The graph widget has sane NaN handling now and will do just that. commit 2835d552440be48d356a7f7b1d64963696dd550b Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:20:48 2021 +0700 Implement graph.nan_color and nan_indication properties Now, whenever there's a NaN among values and graph.nan_indication is set, the corresponding area, where the value bar should have been drawn, is filled with the nan_color from top to bottom. The painting is done after all data is rendered, to make sure that it won't be overpainted and go unnoticed. NaNs among graph's values are inevitably a sign of some error and it is arguably not a sane default behavior to simply not render them at all. The user should take immediate note of any errors by default, instead of wondering at a mysteriously empty graph widget, which is why nan_indication is set to true by default. If the user wants to silence NaNs, they can always simply set it to false. nan_color if unset, defaults to a noticeable yellow-black pattern. As a direct and planned consequence, stacked graphs with negative values in them trigger the NaN handling too. But silencing NaN handling for those graphs is also possible and semi-sane, because then NaNs and negatives are ignored and the graph behaves (almost) like they were zeros. commit 9c1f8f08b400e01e19c534f3810c9c9ab485760c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 26 08:10:19 2021 +0700 Refactor out graph_map_value_to_widget_coordinates() function commit 1905991ddecabdae3e15ffb8ee5b18a1bee1a711 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 28 13:39:29 2021 +0700 Add an example file for graph.baseline_value property commit b31b39d66f4c037a9484b71a3f910703ae01fac0 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 07:03:49 2021 +0700 Add an example file for graph.clamp_bars=false/true comparison commit a1fc850e3047f9ee2c9ddeffacfbabf3ded34171 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 04:06:45 2021 +0700 Add an example file for graphs with negative values and shapes commit 2f012f8ff1bb5094c14e724a61e59a2a3a296bc8 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 01:06:05 2021 +0700 Add an example file for a graph displaying negative values commit 7b14fbd59a4c4c51962fd3110a1a900dd5f4eb98 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 11:18:26 2021 +0700 Add an example file for graphs with step_width < 1 commit dfd9a54544b637e2fbe3d1325f9cc24b3f4d2313 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 10:17:38 2021 +0700 Add an example file for graph.stack=false/true comparison Should also demonstrate that multiple data groups are possible for non-stacked graphs too. commit 5b532106c3fd255f9b66a66cfb88d977b489ab5d Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 09:34:14 2021 +0700 Add an example file for disabling graph data groups with nil colors commit ac6f1083c02fcb428f089a616f9b0ebaa5496b6c Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:57:25 2021 +0700 Add an example file for step_shape/width/spacing on stacked graphs commit b3703db6a5139e73e72c1cfd198ece2cd9be7df2 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:43:56 2021 +0700 Add an example file for scale=false/true on stacked graph commit b000aed4a12cb44fb501e1c8432a71a14b2f8f3f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 20 08:22:19 2021 +0700 Document deprecation of graph's width and height commit cb0ad617bd6c83a499896797318e3b0b588c3566 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 07:53:37 2021 +0700 Deprecate width and height properties of wibox.widget.graph commit 8c64e14ad91a074b784166a7054e53444cccb9d2 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 04:37:59 2021 +0700 Refactor graph:draw() to separate concerns Firstly, summation of group values for stacked graphs is extracted into graph:preprocess_values(). This makes graph:draw() smaller and exposes the fact, that stacked graphs are almost entirely just a form of a more general concept: pre-render data manipulation. It's something a user might want to customize, and now they can do it by monkey-patching the method. Secondly, the logic for deciding, if and with which color a data group should be drawn, is extracted into graph:should_draw_data_group() and get_data_group_color() methods respectively. This makes it customizable too and makes more obvious the fact, that it is a general thing that has nothing to do with stacked graphs in particular. With this there are only 5 lines remaining in graph:draw(), that could be said to be specific to stacked graphs, all pertaining to the `prev_y` variable, which merely preserves the bar coordinates from a previous data group. commit 2a32b6305d893f413303728a0a3da1dcd9a5591e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 22:52:37 2021 +0700 Pointlessly optimize graph:add_value() 1) Purge old values first, then insert the new one, if needed. Since Lua grows arrays in power of two steps, and our guess_capacity() also takes care to return even integers, it would be a shame to overstep the capacity by one by inserting first and thus getting the table size doubled only to never use the most of it. 2) Use a removal procedure that doesn't call the slow length operator at all. Previous code called it twice (once in table.remove) for every removed value. commit 680ea8f5d81e9040afb4662b3229e937e4b89e02 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:23:38 2021 +0700 Set graph data series color before the painting loop commit b970f0f221ae76250f2a94433ff79d653caa6e21 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:05:37 2021 +0700 Drop a large no longer necessary `if` in graph:draw_values() This commit is almost purely a whitespace change. commit 6b4f0541c70ec99d7f6e276bae64939f337120d6 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:00:50 2021 +0700 Extract values rendering code into graph:draw_values() method This is to avoid ungodly if nesting levels, which are needed to bail out of drawing values early and skip to drawing the border. The next commit will get rid of the if. commit e0950e4f99d1ea1c4b8cea2cc67629fa40030c2a Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 14:50:20 2021 +0700 Track graph draw() usage to guess necessary array capacity The last step of divorcing add_values() from width calculations, while ensuring that user doesn't observe a lack of data to draw. The widget now tracks an approximate number of values that it has been asked to draw() in _private.last_drawn_values_num, and truncates its values array based on that (if not overridden by the capacity property). commit 2e3d542282f35e7c0fed454da0f2eada0fcf3b78 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 13:35:12 2021 +0700 Add `capacity` property to graph widget This is the second step of divorcing add_values() from calculations involving widths. How many values are stored and how many values are drawn are distinct concepts. There isn't even a single definitive "how many values are drawn" because the widget could be rendered in several places with distinct widths simultaneously, and in every such place the data must be drawn appropriately, e.g. with auto-scaling, that takes only the visible data points into account. And the draw() method can do that now. The default capacity value of nil uses heuristics based on widget's _private.width to guess some capacity that would tolerate some dynamic widget resizing. If the user finds the heuristic failing them, they can set the capacity property to the desired maximum number of values that will be kept in the _private.values array (in each data group). commit 08d4a80686f7fe363551c35589a460839f60853e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 03:27:18 2021 +0700 Extract values size computation into compute_drawn_values_num() This is the first step of making add_values() not concern itself (directly) with widget dimensions. The user can monkey-patch the method to override how many values are (logically) drawn, because it affects graph auto-scaling and can be useful for rendering of step_shape-s that paint outside of their step_width. I didn't make it a full-blown function-valued property, because it seems a fringe functionality, which I'm too lazy to document. commit 3c5617c98ab1147618ed22e8414a77fc56b45f6e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 02:32:29 2021 +0700 Copy graph widget prototype methods over in bulk commit 169538837ece41a03d02ba8fbc0e4097062b67a3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun Apr 25 05:58:01 2021 +0700 Remove graph.baseline_y property After giving it some thought I've concluded, that it is unlikely to be useful as it is now. I'd better reserve the name for some more sensible functionality, like fixing the baseline_value defined axis at the given widget coordinate, adjusting the scaling accordingly, so min_value/max_value won't generally correspond to widget top and bottom anymore. commit 202ffaa9a0b820c7c471e1121d01b91f8f76cd38 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 12:06:55 2021 +0700 Add baseline_y and baseline_value properties to graph widget Now values in graph have a *baseline*, tunable with baseline_y and baseline_value properties. It is the (for now invisible) horizontal line from which all bars in the graph grow. I call it a "baseline" instead of "axis", because it doesn't actually have to correspond to the 0 value. But it can, and actually does now by default. baseline_y and baseline_value properties determine the vertical placement of the baseline in the widget. baseline_y is the position of the baseline in normalized widget coordinates, e.g. 0 corresponds to widget's top, 1 - to widget's bottom, and 0.5 - to widget's middle. So if e.g. one sets baseline_y = 0, then no matter how the widget is resized and how its values are scaled, bars in it will be drawn starting from the top. baseline_value is the position of the baseline in value coordinates. So if one sets baseline_value = 10, then no matter what happens, bars for values greater than 10 will be drawn as growing up from this line, and bars for values smaller as growing down. Where and if it will be seen in the widget, depends on the scaling. baseline_value = 0 is the familiar zero axis. baseline_y is unset by default and has priority, i.e. if it's set, then baseline_value is ignored. baseline_value defaults to 0. Prior to this commit, bars were always drawn from the bottom edge of the widget, or, in the new terms, the widget had baseline_y = 1. But it was indistinguishable from baseline_value = 0, because the widget clamped all negative values to 0, so the difference between the two could never be observed. baseline_value = 0 is clearly more useful and familiar to people, so now it is in force by default. commit df7e19abaebf0a871b5ca217b4cc3365533267a6 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 11:18:12 2021 +0700 Move value clamping from graph:add_value() into draw() Previously all values were forcibly clamped between 0 (not tunable at all) and max_value (1 by default). Thus graphs with negative values were not an option. NaN values were conflated with zeros, even though it may be useful to maintain the distinction and render them differently. And if user happened to dynamically change the max_value property, they discovered, that their data got truncated. Preserving user data as it was given is more flexible and makes things easier to debug. This commit does exactly that. So what happens now to the newly allowed values during draw()? There are 3 new cases to consider: 1) NaNs 2) Negatives 3) Values outside of min_value..max_value range NaNs are presently simply not drawn. An empty place is reserved where the value should've been, but nothing else is done. This is distinct from how a zero is handled, e.g. if step_shape is set, it is called with the height parameter = 0, which can still end up drawing something, depending on the shape. One could also consider addition of nan_color/nan_shape properties which could be drawn to alert user to a gap in their data. Negative values necessitate introduction of the concept of the zero axis, from which they will be drawn DOWN, in contrast to positive values. Where should this axis be drawn? How should step_shape-s with inherent direction, e.g. arrows, be drawn for negatives? This will be addressed in the next commit. Values outside of min/max_value range can imply shapes that go beyond widget dimensions. An arrow shape that goes beyond the roof is visibly different from an arrow that saturates and merely touches the widget top, and the user may reasonably desire either behavior. Therefore this commit introduces a boolean clamp_bars property, which defaults to true for reasons of robustness and backward compatibility with the behaviour prior to this commit. The property of course affects only the display of the values, the actual user data stays intact. commit c9c24fb1ab741cd6bb8074971d568e0646c51532 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:29:01 2021 +0700 Use rectangles for drawing graph bars of all sizes I have benchmarked various bar drawing strategies and graph sizes and found out that drawing graph bars with rectangles is as fast or even marginally faster than using fat lines. Thus the code that tries to special-case 1px bars as lines seems to be not worth the additional complexity it introduces, and this commit removes it. Incidentally, I've also established, that rectangles are more robust. If one draws graph bars and feeds cairo very large coordinates (~2^62), then corresponding rectangles fail to render, but all the other rectangles in the same series get rendered successfully, whereas a single large coordinate in a move_to/line_to() sequence is enough to cause all lines in the batch to fail to render. commit bc7f4be5e06405b5aa7fb443a5ace2b9b9575b1f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:01:42 2021 +0700 Cache cairo methods used in the inner loop of graph:draw() I have benchmarked the draw() method, both on cairo xcb and on argb32 image surfaces, and have discovered that cairo context method calls are unnaturally slow, to the point, that almost nothing else in the function matters. For example, drawing bars with vertical lines made the whole draw() function almost 2 times slower than doing the same with rectangles, merely because the former requires two method calls (move_to/line_to), and the latter only one. Simply caching hotpath cairo methods in local variables, reduced the difference between the drawing methods to a negligible one and made the whole function 4 times faster on a typical 100-bar graph, (1.4ms to 0.33ms on my laptop) thus bringing closer my secret dream of drawing live audio data with awesome graphs (kidding). I guess, LGI does something not very optimal there, like gobject-introspecting the method every time, or something. There might be some untapped optimization potential, possibly in upstream, like a method cache, from which everything in awesome could benefit, even though I imagine not many things are so call-intensive as the graph widget, to really feel the difference. commit 538df25bdf02b53d0bbe81809eef2e6fdb05ff6f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 07:32:37 2021 +0700 Use 1-based index in the inner graph draw loop It's used there only once for a calculation and then several times to index some tables, and index 0 is bad for lua tables performance-wise. commit 7412546916cd0a0a490130a120255ffb719398b8 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:55:12 2021 +0700 Leave graph.max_value uninitialized at construction This is a user-visible change, but the previous behavior was probably not what user expected anyway, namely with graph.scale set the graph mysteriously couldn't zoom in onto values that are smaller than 1. commit eb81d7929a161e79c13262cfa73ac2e6a820e8ed Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:48:29 2021 +0700 Allow graph.max_value be nil This makes its behave symmetrically to min_value. Now it defaults to 1, when graph.scale is not set, and to the maximum value in the graph data, when graph.scale is set. commit f622a8430cbfde7007b58d7f4c9c76b08c91e997 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 12:01:13 2021 +0700 Calculate max values num in graph:add_value() more precisely At most so many values were kept in the widget values array, as there are pixels of usable widget width (i.e. sans border). This is exactly right for the surely most common case of graphs with 1px-wide bars, but is more than needed for graphs with step_width+step_spacing>1, and not enough for graphs with subpixel bars. Moreover this made graph autoscaling behave weirdly in graphs with thicker bars, because old values survived longer in the array and kept affecting min_value/max_value calculations even after they were shifted off-screen. The maximum number of kept values is now calculated exactly as the number of values that can be displayed at once, taking step_width and step_spacing into account. It might be worth adding a max_values_num property to the graph to let user override this calculation, in case they draw shapes in step_shape() outside of its supposed rectangle, so our assumptions of visibility are wrong, or the widget often gets resized, resulting in the annoyance of visibly truncated series. commit 0aa63249a18641251a1bf77423739164f0eab39f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 02:30:13 2021 +0700 Fix border_color/border_width bugs in graph widget add_value() wrongly assumed that border_width is always set, when border_color is, so if user set only the latter, an error occurred. add_value() assumed that border_width is always < width which led to awesome hanging up in an infinite loop, should that fail to be the case. The graph border_width property was driven by the border_color property, instead of the other way round. If border_color wasn't set, the border wasn't drawn all, even if user sets non-zero border_width, which is a confusing behavior, in contrast to all other widgets, which simply use a plethora of fallback colors to draw the border, when needed. This also meant that, despite being documented, the beautiful.graph_border_color fallback color could not and was not ever used. If the border_color was set though, an unset border_width was immediately assumed to be 1 instead of 0, also contrary to what other widgets do. Theme colors shouldn't influence widget layout, so this commit fixes this behavior to: 1) border_width always defaults to 0, if unset. 2) border_color falls back to beautiful.graph_border_color or white. Maybe one should also add a beautiful.graph_border_width theme variable, like some widgets do. commit dad4a3253b8fef971fb82720d2a407fcc0a879ad Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 03:11:27 2021 +0700 Implement multiple data series drawing for non-stacked graph The widget can already hold and draw multiple data groups, so it makes little sense to limit drawing only to the first group when graph.stack = false, probably leading to user's confusion. Now user can call add_value(v, group) with group > 1 and the data series will be drawn (over groups with lesser id-s) with graph.stack_colors[group] color, if stack_colors is set, or with graph.color, if stack_colors isn't set. Thus nothing visibly changes for all current users of non-stacked graphs with only single data series, but the new functionality is now there for those who need it. One can't fully emulate this functionality, e.g. with multiple graphs widgets in a wibox.layout.stack, because data series in a single widget can enjoy a common auto-scaling. commit 3e645c976184ab5f50d625271c25062db9ab7ed9 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:59:12 2021 +0700 Don't draw a stacked graph data group if there's no color for it If stack_colors[idx] is nil for the group `idx`, don't draw it at all (as opposed, e.g. to the effect of drawing it with a transparent color, or as though it had all values == 0, which both produce different results). This code is still subtly different from prior behavior, because now user can have a non-contiguous stack_colors table and turn the display of any data group on and off at will, whereas the code two commits earlier would just stop rendering as soon as ipairs(stack_colors) does, i.e. no data series were drawn past the first nil hole. This commit only looks large, but is mostly a whitespace change due to nesting from a pair of wrapping ifs. commit 8c0273786de1ade66bdd977699f98b588da9f487 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:32:42 2021 +0700 Unify stacked and non-stacked graph rendering Now stacked graphs support everything the nonstacked do, like step_width ~= 1 and custom step_shape-s. Technical notes: This code no longer calls cr:stroke() for every single value in a stacked graph, but rather paints a whole series and then cr:stroke/fill()-s it at all once with its color, improving performance considerably. Additionally the graph no longer relies on the presence of the stack_colors property to render series. It draws all data that it has, and, if there's no corresponding color in stack_colors, falls back to graph.color. This will be changed in the next commit to the prior behavior of only drawing those series for which there's a color in the stack_colors table, but that would've made this commit's diff much larger due to required nesting ifs over large code chunks. This commit also takes care to properly handle NaNs and negative values. Those are currently (almost) guaranteed to not occur in the values array due to clipping in graph:add_value(), but I intend to eventually drop the clipping, because it does nothing that draw() really relies upon, and allowing unclipped and NaN values is more flexible and less surprising, e.g. when user dynamically toggles graph.scale. Eventually I intend to implement drawing negative values in non-stacked graphs and possibly even in the stacked ones. commit 38c6589c51b2aa057d7c2f40182ecf32b5b2ff5e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 10:04:15 2021 +0700 Convert graph:draw() to use `self` commit f21f1b2550426df1c4ea8abde079b1ccef84b394 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 12:27:26 2021 +0700 Fix scaling for stacked graphs Stacked graphs have cumulative heights, values from all groups that end up in the same graph bar are effectively summed. Previous code didn't take that into account when calculating min/max_value for the purposes of rescaling, but simply examined every value separately. This code adds up all values up front and finds min/max among those, to determine proper scaling that would fit the graph exactly. commit 1ce4c7bf63bdd6c99c195d668347bec49355e921 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 09:49:35 2021 +0700 Draw shaped graph bars only up to the baseline Prior to this commit, every step_shape bar was, for some reason, drawn with height = full widget's height, regardless of the value it represents. So for the most bars their lower end was far outside the box and therefore clipped off. So e.g. one couldn't see the lower round edges of rounded rectangles. This commit fixes that by appropriately varying the height that is passed into step_shape(), so that lower edges of the bars always exactly touch the lower edge of the graph. If the user wants to restore the prior behavior, they can simply ignore the passed-in height in step_shape() and just always draw their shape with height = full widget height. But they couldn't replicate the effect of this commit in their rc.lua without essentially writing their own graph widget. commit e25185b08da23745b5815f85f6e9e43222ed2294 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 08:42:54 2021 +0700 Avoid incremental transform shifting during graph bar rendering Instead of letting addition errors and cognitive load accumulate simply use the same offset calculation that is used for lines and reset the transform back to the top-left corner after every drawn bar. It's conceptually simpler, and will be useful later for stacked graphs and more. It also fixes a discrepancy between plain and step_shape rendering. Prior to this commit, bar coloring occurred with a disturbed transform, which was centered at the last drawn bar, wherever it happened to be. This meant that nontrivial colors, like gradients and images, were arbitrarily offset when step_shape was used. commit f3e98866d8583c35d0b557c09f529299edc9ba2c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:45:29 2021 +0700 Refactor out graph bar height expression into a variable commit f54528d6892f5aaa4595917853c9e2130f743fdb Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:20:25 2021 +0700 Draw graph bars with lines only when step_width == 1 Before this commit graph bars of step_width < 1 were drawn with lines of width 1, but that's a bit unfortunate, because it led to much overpainting, while still ending up with a graphically incorrect result. Let's draw such bars with rectangles and let cairo do things with subpixels, getting more graphically accurate result, while still keeping the common case of step_width == 1 (supposedly) fast-drawn with lines. commit b6515dca67c18b78eb5657378131a62ab43d4cfc Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:54:07 2021 +0700 Fix unsharp edges when drawing graph bars with rectangles The 0.5 offset is only helpful for 1-width strokes, not for fills. commit 7f3db8f07e82f821bcd1dc418d66564f537609d6 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:21:01 2021 +0700 Do coordinate transformations even if the value isn't drawn The graph presently doesn't draw values < 0, but there was a discrepancy between drawing with and without step_shape. The default line/rectangle drawing reserves an empty place where the skipped value should've been, but no such empty place is reserved, when step_shape is used. This commit fixes that. Skipped values always have their empty space now. commit 7b731ebfe96af7a9dba9c57371da6761cf22c00c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:03:30 2021 +0700 Fix graph bar scaling when min_value is not 0 Previous calculation lead to underscaling of values so that even value = max_value wasn't drawn to full height. commit dca59fd8b7fff0e7e646f2dfc6bd92047028e5a8 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:18:40 2021 +0700 Calculate the horizontal graph bar offset only when needed commit 940ddf4b9825e9c3a91d276208c089d8c74e49db Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:17:23 2021 +0700 Call cr:move_to() only when drawing the graph with lines It's redundant otherwise and might even interfere with user's drawing in step_shape(). commit b5d5cd10ca29fb29cd751faaa95137c2fe89ff63 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:04:06 2021 +0700 Fix wrong horizontal offset calculations in widget.graph Indices in the loop are zero-based, so things like i-1 and i>1 aren't necessary and actually wouldn't have been correct even in a 1-based loop. step_shape = gears.shape.rectangle and step_shape = nil, which is supposed to default to rectangle according to docs, were drawn differently due to mismatched offset calculations, which led to problems especially noticeable when step_spacing is not zero, e.g: 1) whole graph shifted left too much, drawn partially off-screen. 2) whole graph shifted right too much, and the first bar placed closer to the rest than others. commit dc369fa198281e000afc1bd17326f3a264adf7d1 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 02:09:21 2021 +0700 Fix the "border_width affects graph bar width" bug Graph bars were drawn with lines of border_width thickness instead of 1, i.e. possibly too fat to be readable. commit 1ba73378cf29ed27a6ecd18716394779ceb543d9 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 00:13:03 2021 +0700 Simplify internal values representation in graph even more New values in graph._private.values were pushed to the end, and old values were removed from the beginning of the table. In draw() however newer values are drawn first, at the left edge of the graph, necessitating annoying idx = #values - idx conversions everywhere. In particular in stacked graph that means that values that are drawn together may have different indices. One has to shift the values array in add_value() either way, so one might as well insert new values at the beginning of the array and simplify the rest of the code. This doesn't seem like much yet, but it will prove to be much more convenient and less error-prone in what I'm going to do later. commit f70523ee68dbd6b67050ace4b82903a9b22440c7 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 13:35:10 2021 +0700 Simplify internal values representation in graph widget graph._private.values was either a table of values or a table of tables of values, depending on whether "stack" property is enabled, ... or a mix of both if user forgot to pass the second "group" parameter to graph:add_value(). This resulted in pretty fragile and confusing behavior, e.g. leaving user to wonder, why they see nothing more after setting stack=true for a graph in a previously working rc.lua, and demanding a very careful use of add_value() from the user, or else some values or even entire stacks would mysteriously vanish and errors in draw() get triggered, sometimes after a period of seemingly normal functioning. Now `values` is always a table of tables and the group parameter in add_value() defaults to 1, thus making the non-stacked graph a trivial variant of the stacked graph.
2021-05-20 17:14:03 +02:00
-- @DOC_wibox_widget_graph_nan_color_EXAMPLE@
--
-- @property nan_indication
-- @tparam[opt=true] boolean nan_indication
graph: complete widget overhaul and bug fixes Squashed commit of the following: commit 69821f51fe1e8652715658543e50631ece495090 Author: Alex Belykh <albel727@ngs.ru> Date: Mon May 10 19:17:55 2021 +0700 Refactor property handling in wibox.widget.graph In addition, baseline_value, step_width and step_spacing properties are all set to their default non-nil numeric values during instance creation now. commit 842eb429bf5df4b2ba46b2e6f7646afe89b1a3c3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 9 06:18:45 2021 +0700 Make graph:draw_values() private There's no documentation nor confidence that it's a good API, so I'm hiding it for now. commit 720746780574e4bad14a71fc4174955139c2eb50 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 04:04:13 2021 +0700 Document graph_should_draw_data_group() for posterity commit 7c73f3754ce6ba86b118f1170a6f9e406ab4841d Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:59:21 2021 +0700 Make graph:should_draw_data_group() private There's no confidence whether it's a good API, so I'm hiding it for now. commit b3539e20adb6423118af726ae4f8751ef864b5d2 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:36:44 2021 +0700 Make graph:preprocess_values() private There's no confidence whether it's a good API, so I'm hiding it for now. commit 2df4400e780a6260f8d0ab1c60734fbb11bf3c4f Author: Alex Belykh <albel727@ngs.ru> Date: Thu May 6 10:05:23 2021 +0700 Add spec/wibox/widget/graph_spec.lua commit ce01a9771ab439b8df841f1565aa4aa8a44a5e0d Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 08:16:18 2021 +0700 Improve graph.capacity documentation commit e28fade8f655802f5bf89154169f68d256b8e332 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 07:36:01 2021 +0700 Document graph:compute_drawn_values_num() commit dd68332292465422d08b48e9f0229f143beccbc6 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 05:24:23 2021 +0700 Document graph:pick_data_group_color() method commit ca6dc55f77aca88a6e9cc90bb6fd0045739f41f5 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 04:13:58 2021 +0700 Guard against setting data groups sparsely in graph:add_value() commit cdea5f126f7984e72924f6dffbd312c4fe3b032f Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 02:36:12 2021 +0700 Refactor scaling and baseline choice out of graph:draw() commit 10135deafe8a9390ba66ab657ebb1fb41d549e8c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 21:34:48 2021 +0700 Document graph.nan_color and graph.stack properties commit 0d73bb84ac66b87e16aee472bd1f299549f66a1c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 20:49:24 2021 +0700 Use a better example in docs for the graph.stack property commit c27d160a0826d033d49d6141608c40aa8a0642fa Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 16:05:38 2021 +0700 Document graph.baseline_value property commit 459bc8176c13d4ea32af0dcdc2399d3b929b1c6a Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:53:47 2021 +0700 Document graph.clamp_bars property commit e732ee0b3b007d213e689b98cc00ae7cb141ae08 Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:34 2021 +0700 Improve graph:add_value() and :clear() documentation commit ae73e3aa9f8d60409cbe4cb4da9612f31af4f38e Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:11 2021 +0700 Improve graph min_value/max_value/scale documentation commit a7350996a9def773b91b1631e6c0507e33d8f93b Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:27:36 2021 +0700 Cleanup graph examples to show only relevant things in docs commit 30740c26f4f41c16f4c8a3c339bcb7bd299e14cd Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 14:04:36 2021 +0700 Rename graph.stack_colors property to `group_colors` The colors are used for stacked and non-stacked graphs alike, so the name didn't make sense. Also document and clarify some color stuff. commit 1ea01bfd410971cf0d0c25fd74cd764d5eb7279d Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 21:25:41 2021 +0700 Add an example file for code coverage of some fringe graph cases commit eae68e39553f88a6275a0c65bf040b5d4c262b54 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 20:23:32 2021 +0700 Add step_shape to the graph/nan_color example for code coverage commit 8027147bc2eab34a97b49a428fb8cef27f122bed Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:55:18 2021 +0700 Add example files for graph.nan_color/nan_indication properties commit 89a25a165c52b2c15b6c4d95ecb777a9067e66f9 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:33:07 2021 +0700 Make widget graph:add_value(v) default v to NaN 0 as a default makes no sense and silences the error that is failing to specify a value, to which the user should rather be alerted. The graph widget has sane NaN handling now and will do just that. commit 2835d552440be48d356a7f7b1d64963696dd550b Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:20:48 2021 +0700 Implement graph.nan_color and nan_indication properties Now, whenever there's a NaN among values and graph.nan_indication is set, the corresponding area, where the value bar should have been drawn, is filled with the nan_color from top to bottom. The painting is done after all data is rendered, to make sure that it won't be overpainted and go unnoticed. NaNs among graph's values are inevitably a sign of some error and it is arguably not a sane default behavior to simply not render them at all. The user should take immediate note of any errors by default, instead of wondering at a mysteriously empty graph widget, which is why nan_indication is set to true by default. If the user wants to silence NaNs, they can always simply set it to false. nan_color if unset, defaults to a noticeable yellow-black pattern. As a direct and planned consequence, stacked graphs with negative values in them trigger the NaN handling too. But silencing NaN handling for those graphs is also possible and semi-sane, because then NaNs and negatives are ignored and the graph behaves (almost) like they were zeros. commit 9c1f8f08b400e01e19c534f3810c9c9ab485760c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 26 08:10:19 2021 +0700 Refactor out graph_map_value_to_widget_coordinates() function commit 1905991ddecabdae3e15ffb8ee5b18a1bee1a711 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 28 13:39:29 2021 +0700 Add an example file for graph.baseline_value property commit b31b39d66f4c037a9484b71a3f910703ae01fac0 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 07:03:49 2021 +0700 Add an example file for graph.clamp_bars=false/true comparison commit a1fc850e3047f9ee2c9ddeffacfbabf3ded34171 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 04:06:45 2021 +0700 Add an example file for graphs with negative values and shapes commit 2f012f8ff1bb5094c14e724a61e59a2a3a296bc8 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 01:06:05 2021 +0700 Add an example file for a graph displaying negative values commit 7b14fbd59a4c4c51962fd3110a1a900dd5f4eb98 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 11:18:26 2021 +0700 Add an example file for graphs with step_width < 1 commit dfd9a54544b637e2fbe3d1325f9cc24b3f4d2313 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 10:17:38 2021 +0700 Add an example file for graph.stack=false/true comparison Should also demonstrate that multiple data groups are possible for non-stacked graphs too. commit 5b532106c3fd255f9b66a66cfb88d977b489ab5d Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 09:34:14 2021 +0700 Add an example file for disabling graph data groups with nil colors commit ac6f1083c02fcb428f089a616f9b0ebaa5496b6c Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:57:25 2021 +0700 Add an example file for step_shape/width/spacing on stacked graphs commit b3703db6a5139e73e72c1cfd198ece2cd9be7df2 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:43:56 2021 +0700 Add an example file for scale=false/true on stacked graph commit b000aed4a12cb44fb501e1c8432a71a14b2f8f3f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 20 08:22:19 2021 +0700 Document deprecation of graph's width and height commit cb0ad617bd6c83a499896797318e3b0b588c3566 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 07:53:37 2021 +0700 Deprecate width and height properties of wibox.widget.graph commit 8c64e14ad91a074b784166a7054e53444cccb9d2 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 04:37:59 2021 +0700 Refactor graph:draw() to separate concerns Firstly, summation of group values for stacked graphs is extracted into graph:preprocess_values(). This makes graph:draw() smaller and exposes the fact, that stacked graphs are almost entirely just a form of a more general concept: pre-render data manipulation. It's something a user might want to customize, and now they can do it by monkey-patching the method. Secondly, the logic for deciding, if and with which color a data group should be drawn, is extracted into graph:should_draw_data_group() and get_data_group_color() methods respectively. This makes it customizable too and makes more obvious the fact, that it is a general thing that has nothing to do with stacked graphs in particular. With this there are only 5 lines remaining in graph:draw(), that could be said to be specific to stacked graphs, all pertaining to the `prev_y` variable, which merely preserves the bar coordinates from a previous data group. commit 2a32b6305d893f413303728a0a3da1dcd9a5591e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 22:52:37 2021 +0700 Pointlessly optimize graph:add_value() 1) Purge old values first, then insert the new one, if needed. Since Lua grows arrays in power of two steps, and our guess_capacity() also takes care to return even integers, it would be a shame to overstep the capacity by one by inserting first and thus getting the table size doubled only to never use the most of it. 2) Use a removal procedure that doesn't call the slow length operator at all. Previous code called it twice (once in table.remove) for every removed value. commit 680ea8f5d81e9040afb4662b3229e937e4b89e02 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:23:38 2021 +0700 Set graph data series color before the painting loop commit b970f0f221ae76250f2a94433ff79d653caa6e21 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:05:37 2021 +0700 Drop a large no longer necessary `if` in graph:draw_values() This commit is almost purely a whitespace change. commit 6b4f0541c70ec99d7f6e276bae64939f337120d6 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:00:50 2021 +0700 Extract values rendering code into graph:draw_values() method This is to avoid ungodly if nesting levels, which are needed to bail out of drawing values early and skip to drawing the border. The next commit will get rid of the if. commit e0950e4f99d1ea1c4b8cea2cc67629fa40030c2a Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 14:50:20 2021 +0700 Track graph draw() usage to guess necessary array capacity The last step of divorcing add_values() from width calculations, while ensuring that user doesn't observe a lack of data to draw. The widget now tracks an approximate number of values that it has been asked to draw() in _private.last_drawn_values_num, and truncates its values array based on that (if not overridden by the capacity property). commit 2e3d542282f35e7c0fed454da0f2eada0fcf3b78 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 13:35:12 2021 +0700 Add `capacity` property to graph widget This is the second step of divorcing add_values() from calculations involving widths. How many values are stored and how many values are drawn are distinct concepts. There isn't even a single definitive "how many values are drawn" because the widget could be rendered in several places with distinct widths simultaneously, and in every such place the data must be drawn appropriately, e.g. with auto-scaling, that takes only the visible data points into account. And the draw() method can do that now. The default capacity value of nil uses heuristics based on widget's _private.width to guess some capacity that would tolerate some dynamic widget resizing. If the user finds the heuristic failing them, they can set the capacity property to the desired maximum number of values that will be kept in the _private.values array (in each data group). commit 08d4a80686f7fe363551c35589a460839f60853e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 03:27:18 2021 +0700 Extract values size computation into compute_drawn_values_num() This is the first step of making add_values() not concern itself (directly) with widget dimensions. The user can monkey-patch the method to override how many values are (logically) drawn, because it affects graph auto-scaling and can be useful for rendering of step_shape-s that paint outside of their step_width. I didn't make it a full-blown function-valued property, because it seems a fringe functionality, which I'm too lazy to document. commit 3c5617c98ab1147618ed22e8414a77fc56b45f6e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 02:32:29 2021 +0700 Copy graph widget prototype methods over in bulk commit 169538837ece41a03d02ba8fbc0e4097062b67a3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun Apr 25 05:58:01 2021 +0700 Remove graph.baseline_y property After giving it some thought I've concluded, that it is unlikely to be useful as it is now. I'd better reserve the name for some more sensible functionality, like fixing the baseline_value defined axis at the given widget coordinate, adjusting the scaling accordingly, so min_value/max_value won't generally correspond to widget top and bottom anymore. commit 202ffaa9a0b820c7c471e1121d01b91f8f76cd38 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 12:06:55 2021 +0700 Add baseline_y and baseline_value properties to graph widget Now values in graph have a *baseline*, tunable with baseline_y and baseline_value properties. It is the (for now invisible) horizontal line from which all bars in the graph grow. I call it a "baseline" instead of "axis", because it doesn't actually have to correspond to the 0 value. But it can, and actually does now by default. baseline_y and baseline_value properties determine the vertical placement of the baseline in the widget. baseline_y is the position of the baseline in normalized widget coordinates, e.g. 0 corresponds to widget's top, 1 - to widget's bottom, and 0.5 - to widget's middle. So if e.g. one sets baseline_y = 0, then no matter how the widget is resized and how its values are scaled, bars in it will be drawn starting from the top. baseline_value is the position of the baseline in value coordinates. So if one sets baseline_value = 10, then no matter what happens, bars for values greater than 10 will be drawn as growing up from this line, and bars for values smaller as growing down. Where and if it will be seen in the widget, depends on the scaling. baseline_value = 0 is the familiar zero axis. baseline_y is unset by default and has priority, i.e. if it's set, then baseline_value is ignored. baseline_value defaults to 0. Prior to this commit, bars were always drawn from the bottom edge of the widget, or, in the new terms, the widget had baseline_y = 1. But it was indistinguishable from baseline_value = 0, because the widget clamped all negative values to 0, so the difference between the two could never be observed. baseline_value = 0 is clearly more useful and familiar to people, so now it is in force by default. commit df7e19abaebf0a871b5ca217b4cc3365533267a6 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 11:18:12 2021 +0700 Move value clamping from graph:add_value() into draw() Previously all values were forcibly clamped between 0 (not tunable at all) and max_value (1 by default). Thus graphs with negative values were not an option. NaN values were conflated with zeros, even though it may be useful to maintain the distinction and render them differently. And if user happened to dynamically change the max_value property, they discovered, that their data got truncated. Preserving user data as it was given is more flexible and makes things easier to debug. This commit does exactly that. So what happens now to the newly allowed values during draw()? There are 3 new cases to consider: 1) NaNs 2) Negatives 3) Values outside of min_value..max_value range NaNs are presently simply not drawn. An empty place is reserved where the value should've been, but nothing else is done. This is distinct from how a zero is handled, e.g. if step_shape is set, it is called with the height parameter = 0, which can still end up drawing something, depending on the shape. One could also consider addition of nan_color/nan_shape properties which could be drawn to alert user to a gap in their data. Negative values necessitate introduction of the concept of the zero axis, from which they will be drawn DOWN, in contrast to positive values. Where should this axis be drawn? How should step_shape-s with inherent direction, e.g. arrows, be drawn for negatives? This will be addressed in the next commit. Values outside of min/max_value range can imply shapes that go beyond widget dimensions. An arrow shape that goes beyond the roof is visibly different from an arrow that saturates and merely touches the widget top, and the user may reasonably desire either behavior. Therefore this commit introduces a boolean clamp_bars property, which defaults to true for reasons of robustness and backward compatibility with the behaviour prior to this commit. The property of course affects only the display of the values, the actual user data stays intact. commit c9c24fb1ab741cd6bb8074971d568e0646c51532 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:29:01 2021 +0700 Use rectangles for drawing graph bars of all sizes I have benchmarked various bar drawing strategies and graph sizes and found out that drawing graph bars with rectangles is as fast or even marginally faster than using fat lines. Thus the code that tries to special-case 1px bars as lines seems to be not worth the additional complexity it introduces, and this commit removes it. Incidentally, I've also established, that rectangles are more robust. If one draws graph bars and feeds cairo very large coordinates (~2^62), then corresponding rectangles fail to render, but all the other rectangles in the same series get rendered successfully, whereas a single large coordinate in a move_to/line_to() sequence is enough to cause all lines in the batch to fail to render. commit bc7f4be5e06405b5aa7fb443a5ace2b9b9575b1f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:01:42 2021 +0700 Cache cairo methods used in the inner loop of graph:draw() I have benchmarked the draw() method, both on cairo xcb and on argb32 image surfaces, and have discovered that cairo context method calls are unnaturally slow, to the point, that almost nothing else in the function matters. For example, drawing bars with vertical lines made the whole draw() function almost 2 times slower than doing the same with rectangles, merely because the former requires two method calls (move_to/line_to), and the latter only one. Simply caching hotpath cairo methods in local variables, reduced the difference between the drawing methods to a negligible one and made the whole function 4 times faster on a typical 100-bar graph, (1.4ms to 0.33ms on my laptop) thus bringing closer my secret dream of drawing live audio data with awesome graphs (kidding). I guess, LGI does something not very optimal there, like gobject-introspecting the method every time, or something. There might be some untapped optimization potential, possibly in upstream, like a method cache, from which everything in awesome could benefit, even though I imagine not many things are so call-intensive as the graph widget, to really feel the difference. commit 538df25bdf02b53d0bbe81809eef2e6fdb05ff6f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 07:32:37 2021 +0700 Use 1-based index in the inner graph draw loop It's used there only once for a calculation and then several times to index some tables, and index 0 is bad for lua tables performance-wise. commit 7412546916cd0a0a490130a120255ffb719398b8 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:55:12 2021 +0700 Leave graph.max_value uninitialized at construction This is a user-visible change, but the previous behavior was probably not what user expected anyway, namely with graph.scale set the graph mysteriously couldn't zoom in onto values that are smaller than 1. commit eb81d7929a161e79c13262cfa73ac2e6a820e8ed Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:48:29 2021 +0700 Allow graph.max_value be nil This makes its behave symmetrically to min_value. Now it defaults to 1, when graph.scale is not set, and to the maximum value in the graph data, when graph.scale is set. commit f622a8430cbfde7007b58d7f4c9c76b08c91e997 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 12:01:13 2021 +0700 Calculate max values num in graph:add_value() more precisely At most so many values were kept in the widget values array, as there are pixels of usable widget width (i.e. sans border). This is exactly right for the surely most common case of graphs with 1px-wide bars, but is more than needed for graphs with step_width+step_spacing>1, and not enough for graphs with subpixel bars. Moreover this made graph autoscaling behave weirdly in graphs with thicker bars, because old values survived longer in the array and kept affecting min_value/max_value calculations even after they were shifted off-screen. The maximum number of kept values is now calculated exactly as the number of values that can be displayed at once, taking step_width and step_spacing into account. It might be worth adding a max_values_num property to the graph to let user override this calculation, in case they draw shapes in step_shape() outside of its supposed rectangle, so our assumptions of visibility are wrong, or the widget often gets resized, resulting in the annoyance of visibly truncated series. commit 0aa63249a18641251a1bf77423739164f0eab39f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 02:30:13 2021 +0700 Fix border_color/border_width bugs in graph widget add_value() wrongly assumed that border_width is always set, when border_color is, so if user set only the latter, an error occurred. add_value() assumed that border_width is always < width which led to awesome hanging up in an infinite loop, should that fail to be the case. The graph border_width property was driven by the border_color property, instead of the other way round. If border_color wasn't set, the border wasn't drawn all, even if user sets non-zero border_width, which is a confusing behavior, in contrast to all other widgets, which simply use a plethora of fallback colors to draw the border, when needed. This also meant that, despite being documented, the beautiful.graph_border_color fallback color could not and was not ever used. If the border_color was set though, an unset border_width was immediately assumed to be 1 instead of 0, also contrary to what other widgets do. Theme colors shouldn't influence widget layout, so this commit fixes this behavior to: 1) border_width always defaults to 0, if unset. 2) border_color falls back to beautiful.graph_border_color or white. Maybe one should also add a beautiful.graph_border_width theme variable, like some widgets do. commit dad4a3253b8fef971fb82720d2a407fcc0a879ad Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 03:11:27 2021 +0700 Implement multiple data series drawing for non-stacked graph The widget can already hold and draw multiple data groups, so it makes little sense to limit drawing only to the first group when graph.stack = false, probably leading to user's confusion. Now user can call add_value(v, group) with group > 1 and the data series will be drawn (over groups with lesser id-s) with graph.stack_colors[group] color, if stack_colors is set, or with graph.color, if stack_colors isn't set. Thus nothing visibly changes for all current users of non-stacked graphs with only single data series, but the new functionality is now there for those who need it. One can't fully emulate this functionality, e.g. with multiple graphs widgets in a wibox.layout.stack, because data series in a single widget can enjoy a common auto-scaling. commit 3e645c976184ab5f50d625271c25062db9ab7ed9 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:59:12 2021 +0700 Don't draw a stacked graph data group if there's no color for it If stack_colors[idx] is nil for the group `idx`, don't draw it at all (as opposed, e.g. to the effect of drawing it with a transparent color, or as though it had all values == 0, which both produce different results). This code is still subtly different from prior behavior, because now user can have a non-contiguous stack_colors table and turn the display of any data group on and off at will, whereas the code two commits earlier would just stop rendering as soon as ipairs(stack_colors) does, i.e. no data series were drawn past the first nil hole. This commit only looks large, but is mostly a whitespace change due to nesting from a pair of wrapping ifs. commit 8c0273786de1ade66bdd977699f98b588da9f487 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:32:42 2021 +0700 Unify stacked and non-stacked graph rendering Now stacked graphs support everything the nonstacked do, like step_width ~= 1 and custom step_shape-s. Technical notes: This code no longer calls cr:stroke() for every single value in a stacked graph, but rather paints a whole series and then cr:stroke/fill()-s it at all once with its color, improving performance considerably. Additionally the graph no longer relies on the presence of the stack_colors property to render series. It draws all data that it has, and, if there's no corresponding color in stack_colors, falls back to graph.color. This will be changed in the next commit to the prior behavior of only drawing those series for which there's a color in the stack_colors table, but that would've made this commit's diff much larger due to required nesting ifs over large code chunks. This commit also takes care to properly handle NaNs and negative values. Those are currently (almost) guaranteed to not occur in the values array due to clipping in graph:add_value(), but I intend to eventually drop the clipping, because it does nothing that draw() really relies upon, and allowing unclipped and NaN values is more flexible and less surprising, e.g. when user dynamically toggles graph.scale. Eventually I intend to implement drawing negative values in non-stacked graphs and possibly even in the stacked ones. commit 38c6589c51b2aa057d7c2f40182ecf32b5b2ff5e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 10:04:15 2021 +0700 Convert graph:draw() to use `self` commit f21f1b2550426df1c4ea8abde079b1ccef84b394 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 12:27:26 2021 +0700 Fix scaling for stacked graphs Stacked graphs have cumulative heights, values from all groups that end up in the same graph bar are effectively summed. Previous code didn't take that into account when calculating min/max_value for the purposes of rescaling, but simply examined every value separately. This code adds up all values up front and finds min/max among those, to determine proper scaling that would fit the graph exactly. commit 1ce4c7bf63bdd6c99c195d668347bec49355e921 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 09:49:35 2021 +0700 Draw shaped graph bars only up to the baseline Prior to this commit, every step_shape bar was, for some reason, drawn with height = full widget's height, regardless of the value it represents. So for the most bars their lower end was far outside the box and therefore clipped off. So e.g. one couldn't see the lower round edges of rounded rectangles. This commit fixes that by appropriately varying the height that is passed into step_shape(), so that lower edges of the bars always exactly touch the lower edge of the graph. If the user wants to restore the prior behavior, they can simply ignore the passed-in height in step_shape() and just always draw their shape with height = full widget height. But they couldn't replicate the effect of this commit in their rc.lua without essentially writing their own graph widget. commit e25185b08da23745b5815f85f6e9e43222ed2294 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 08:42:54 2021 +0700 Avoid incremental transform shifting during graph bar rendering Instead of letting addition errors and cognitive load accumulate simply use the same offset calculation that is used for lines and reset the transform back to the top-left corner after every drawn bar. It's conceptually simpler, and will be useful later for stacked graphs and more. It also fixes a discrepancy between plain and step_shape rendering. Prior to this commit, bar coloring occurred with a disturbed transform, which was centered at the last drawn bar, wherever it happened to be. This meant that nontrivial colors, like gradients and images, were arbitrarily offset when step_shape was used. commit f3e98866d8583c35d0b557c09f529299edc9ba2c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:45:29 2021 +0700 Refactor out graph bar height expression into a variable commit f54528d6892f5aaa4595917853c9e2130f743fdb Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:20:25 2021 +0700 Draw graph bars with lines only when step_width == 1 Before this commit graph bars of step_width < 1 were drawn with lines of width 1, but that's a bit unfortunate, because it led to much overpainting, while still ending up with a graphically incorrect result. Let's draw such bars with rectangles and let cairo do things with subpixels, getting more graphically accurate result, while still keeping the common case of step_width == 1 (supposedly) fast-drawn with lines. commit b6515dca67c18b78eb5657378131a62ab43d4cfc Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:54:07 2021 +0700 Fix unsharp edges when drawing graph bars with rectangles The 0.5 offset is only helpful for 1-width strokes, not for fills. commit 7f3db8f07e82f821bcd1dc418d66564f537609d6 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:21:01 2021 +0700 Do coordinate transformations even if the value isn't drawn The graph presently doesn't draw values < 0, but there was a discrepancy between drawing with and without step_shape. The default line/rectangle drawing reserves an empty place where the skipped value should've been, but no such empty place is reserved, when step_shape is used. This commit fixes that. Skipped values always have their empty space now. commit 7b731ebfe96af7a9dba9c57371da6761cf22c00c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:03:30 2021 +0700 Fix graph bar scaling when min_value is not 0 Previous calculation lead to underscaling of values so that even value = max_value wasn't drawn to full height. commit dca59fd8b7fff0e7e646f2dfc6bd92047028e5a8 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:18:40 2021 +0700 Calculate the horizontal graph bar offset only when needed commit 940ddf4b9825e9c3a91d276208c089d8c74e49db Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:17:23 2021 +0700 Call cr:move_to() only when drawing the graph with lines It's redundant otherwise and might even interfere with user's drawing in step_shape(). commit b5d5cd10ca29fb29cd751faaa95137c2fe89ff63 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:04:06 2021 +0700 Fix wrong horizontal offset calculations in widget.graph Indices in the loop are zero-based, so things like i-1 and i>1 aren't necessary and actually wouldn't have been correct even in a 1-based loop. step_shape = gears.shape.rectangle and step_shape = nil, which is supposed to default to rectangle according to docs, were drawn differently due to mismatched offset calculations, which led to problems especially noticeable when step_spacing is not zero, e.g: 1) whole graph shifted left too much, drawn partially off-screen. 2) whole graph shifted right too much, and the first bar placed closer to the rest than others. commit dc369fa198281e000afc1bd17326f3a264adf7d1 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 02:09:21 2021 +0700 Fix the "border_width affects graph bar width" bug Graph bars were drawn with lines of border_width thickness instead of 1, i.e. possibly too fat to be readable. commit 1ba73378cf29ed27a6ecd18716394779ceb543d9 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 00:13:03 2021 +0700 Simplify internal values representation in graph even more New values in graph._private.values were pushed to the end, and old values were removed from the beginning of the table. In draw() however newer values are drawn first, at the left edge of the graph, necessitating annoying idx = #values - idx conversions everywhere. In particular in stacked graph that means that values that are drawn together may have different indices. One has to shift the values array in add_value() either way, so one might as well insert new values at the beginning of the array and simplify the rest of the code. This doesn't seem like much yet, but it will prove to be much more convenient and less error-prone in what I'm going to do later. commit f70523ee68dbd6b67050ace4b82903a9b22440c7 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 13:35:10 2021 +0700 Simplify internal values representation in graph widget graph._private.values was either a table of values or a table of tables of values, depending on whether "stack" property is enabled, ... or a mix of both if user forgot to pass the second "group" parameter to graph:add_value(). This resulted in pretty fragile and confusing behavior, e.g. leaving user to wonder, why they see nothing more after setting stack=true for a graph in a previously working rc.lua, and demanding a very careful use of add_value() from the user, or else some values or even entire stacks would mysteriously vanish and errors in draw() get triggered, sometimes after a period of seemingly normal functioning. Now `values` is always a table of tables and the group parameter in add_value() defaults to 1, thus making the non-stacked graph a trivial variant of the stacked graph.
2021-05-20 17:14:03 +02:00
-- @propemits true false
2016-05-23 07:30:44 +02:00
graph: complete widget overhaul and bug fixes Squashed commit of the following: commit 69821f51fe1e8652715658543e50631ece495090 Author: Alex Belykh <albel727@ngs.ru> Date: Mon May 10 19:17:55 2021 +0700 Refactor property handling in wibox.widget.graph In addition, baseline_value, step_width and step_spacing properties are all set to their default non-nil numeric values during instance creation now. commit 842eb429bf5df4b2ba46b2e6f7646afe89b1a3c3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 9 06:18:45 2021 +0700 Make graph:draw_values() private There's no documentation nor confidence that it's a good API, so I'm hiding it for now. commit 720746780574e4bad14a71fc4174955139c2eb50 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 04:04:13 2021 +0700 Document graph_should_draw_data_group() for posterity commit 7c73f3754ce6ba86b118f1170a6f9e406ab4841d Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:59:21 2021 +0700 Make graph:should_draw_data_group() private There's no confidence whether it's a good API, so I'm hiding it for now. commit b3539e20adb6423118af726ae4f8751ef864b5d2 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:36:44 2021 +0700 Make graph:preprocess_values() private There's no confidence whether it's a good API, so I'm hiding it for now. commit 2df4400e780a6260f8d0ab1c60734fbb11bf3c4f Author: Alex Belykh <albel727@ngs.ru> Date: Thu May 6 10:05:23 2021 +0700 Add spec/wibox/widget/graph_spec.lua commit ce01a9771ab439b8df841f1565aa4aa8a44a5e0d Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 08:16:18 2021 +0700 Improve graph.capacity documentation commit e28fade8f655802f5bf89154169f68d256b8e332 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 07:36:01 2021 +0700 Document graph:compute_drawn_values_num() commit dd68332292465422d08b48e9f0229f143beccbc6 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 05:24:23 2021 +0700 Document graph:pick_data_group_color() method commit ca6dc55f77aca88a6e9cc90bb6fd0045739f41f5 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 04:13:58 2021 +0700 Guard against setting data groups sparsely in graph:add_value() commit cdea5f126f7984e72924f6dffbd312c4fe3b032f Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 02:36:12 2021 +0700 Refactor scaling and baseline choice out of graph:draw() commit 10135deafe8a9390ba66ab657ebb1fb41d549e8c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 21:34:48 2021 +0700 Document graph.nan_color and graph.stack properties commit 0d73bb84ac66b87e16aee472bd1f299549f66a1c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 20:49:24 2021 +0700 Use a better example in docs for the graph.stack property commit c27d160a0826d033d49d6141608c40aa8a0642fa Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 16:05:38 2021 +0700 Document graph.baseline_value property commit 459bc8176c13d4ea32af0dcdc2399d3b929b1c6a Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:53:47 2021 +0700 Document graph.clamp_bars property commit e732ee0b3b007d213e689b98cc00ae7cb141ae08 Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:34 2021 +0700 Improve graph:add_value() and :clear() documentation commit ae73e3aa9f8d60409cbe4cb4da9612f31af4f38e Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:11 2021 +0700 Improve graph min_value/max_value/scale documentation commit a7350996a9def773b91b1631e6c0507e33d8f93b Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:27:36 2021 +0700 Cleanup graph examples to show only relevant things in docs commit 30740c26f4f41c16f4c8a3c339bcb7bd299e14cd Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 14:04:36 2021 +0700 Rename graph.stack_colors property to `group_colors` The colors are used for stacked and non-stacked graphs alike, so the name didn't make sense. Also document and clarify some color stuff. commit 1ea01bfd410971cf0d0c25fd74cd764d5eb7279d Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 21:25:41 2021 +0700 Add an example file for code coverage of some fringe graph cases commit eae68e39553f88a6275a0c65bf040b5d4c262b54 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 20:23:32 2021 +0700 Add step_shape to the graph/nan_color example for code coverage commit 8027147bc2eab34a97b49a428fb8cef27f122bed Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:55:18 2021 +0700 Add example files for graph.nan_color/nan_indication properties commit 89a25a165c52b2c15b6c4d95ecb777a9067e66f9 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:33:07 2021 +0700 Make widget graph:add_value(v) default v to NaN 0 as a default makes no sense and silences the error that is failing to specify a value, to which the user should rather be alerted. The graph widget has sane NaN handling now and will do just that. commit 2835d552440be48d356a7f7b1d64963696dd550b Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:20:48 2021 +0700 Implement graph.nan_color and nan_indication properties Now, whenever there's a NaN among values and graph.nan_indication is set, the corresponding area, where the value bar should have been drawn, is filled with the nan_color from top to bottom. The painting is done after all data is rendered, to make sure that it won't be overpainted and go unnoticed. NaNs among graph's values are inevitably a sign of some error and it is arguably not a sane default behavior to simply not render them at all. The user should take immediate note of any errors by default, instead of wondering at a mysteriously empty graph widget, which is why nan_indication is set to true by default. If the user wants to silence NaNs, they can always simply set it to false. nan_color if unset, defaults to a noticeable yellow-black pattern. As a direct and planned consequence, stacked graphs with negative values in them trigger the NaN handling too. But silencing NaN handling for those graphs is also possible and semi-sane, because then NaNs and negatives are ignored and the graph behaves (almost) like they were zeros. commit 9c1f8f08b400e01e19c534f3810c9c9ab485760c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 26 08:10:19 2021 +0700 Refactor out graph_map_value_to_widget_coordinates() function commit 1905991ddecabdae3e15ffb8ee5b18a1bee1a711 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 28 13:39:29 2021 +0700 Add an example file for graph.baseline_value property commit b31b39d66f4c037a9484b71a3f910703ae01fac0 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 07:03:49 2021 +0700 Add an example file for graph.clamp_bars=false/true comparison commit a1fc850e3047f9ee2c9ddeffacfbabf3ded34171 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 04:06:45 2021 +0700 Add an example file for graphs with negative values and shapes commit 2f012f8ff1bb5094c14e724a61e59a2a3a296bc8 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 01:06:05 2021 +0700 Add an example file for a graph displaying negative values commit 7b14fbd59a4c4c51962fd3110a1a900dd5f4eb98 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 11:18:26 2021 +0700 Add an example file for graphs with step_width < 1 commit dfd9a54544b637e2fbe3d1325f9cc24b3f4d2313 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 10:17:38 2021 +0700 Add an example file for graph.stack=false/true comparison Should also demonstrate that multiple data groups are possible for non-stacked graphs too. commit 5b532106c3fd255f9b66a66cfb88d977b489ab5d Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 09:34:14 2021 +0700 Add an example file for disabling graph data groups with nil colors commit ac6f1083c02fcb428f089a616f9b0ebaa5496b6c Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:57:25 2021 +0700 Add an example file for step_shape/width/spacing on stacked graphs commit b3703db6a5139e73e72c1cfd198ece2cd9be7df2 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:43:56 2021 +0700 Add an example file for scale=false/true on stacked graph commit b000aed4a12cb44fb501e1c8432a71a14b2f8f3f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 20 08:22:19 2021 +0700 Document deprecation of graph's width and height commit cb0ad617bd6c83a499896797318e3b0b588c3566 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 07:53:37 2021 +0700 Deprecate width and height properties of wibox.widget.graph commit 8c64e14ad91a074b784166a7054e53444cccb9d2 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 04:37:59 2021 +0700 Refactor graph:draw() to separate concerns Firstly, summation of group values for stacked graphs is extracted into graph:preprocess_values(). This makes graph:draw() smaller and exposes the fact, that stacked graphs are almost entirely just a form of a more general concept: pre-render data manipulation. It's something a user might want to customize, and now they can do it by monkey-patching the method. Secondly, the logic for deciding, if and with which color a data group should be drawn, is extracted into graph:should_draw_data_group() and get_data_group_color() methods respectively. This makes it customizable too and makes more obvious the fact, that it is a general thing that has nothing to do with stacked graphs in particular. With this there are only 5 lines remaining in graph:draw(), that could be said to be specific to stacked graphs, all pertaining to the `prev_y` variable, which merely preserves the bar coordinates from a previous data group. commit 2a32b6305d893f413303728a0a3da1dcd9a5591e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 22:52:37 2021 +0700 Pointlessly optimize graph:add_value() 1) Purge old values first, then insert the new one, if needed. Since Lua grows arrays in power of two steps, and our guess_capacity() also takes care to return even integers, it would be a shame to overstep the capacity by one by inserting first and thus getting the table size doubled only to never use the most of it. 2) Use a removal procedure that doesn't call the slow length operator at all. Previous code called it twice (once in table.remove) for every removed value. commit 680ea8f5d81e9040afb4662b3229e937e4b89e02 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:23:38 2021 +0700 Set graph data series color before the painting loop commit b970f0f221ae76250f2a94433ff79d653caa6e21 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:05:37 2021 +0700 Drop a large no longer necessary `if` in graph:draw_values() This commit is almost purely a whitespace change. commit 6b4f0541c70ec99d7f6e276bae64939f337120d6 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:00:50 2021 +0700 Extract values rendering code into graph:draw_values() method This is to avoid ungodly if nesting levels, which are needed to bail out of drawing values early and skip to drawing the border. The next commit will get rid of the if. commit e0950e4f99d1ea1c4b8cea2cc67629fa40030c2a Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 14:50:20 2021 +0700 Track graph draw() usage to guess necessary array capacity The last step of divorcing add_values() from width calculations, while ensuring that user doesn't observe a lack of data to draw. The widget now tracks an approximate number of values that it has been asked to draw() in _private.last_drawn_values_num, and truncates its values array based on that (if not overridden by the capacity property). commit 2e3d542282f35e7c0fed454da0f2eada0fcf3b78 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 13:35:12 2021 +0700 Add `capacity` property to graph widget This is the second step of divorcing add_values() from calculations involving widths. How many values are stored and how many values are drawn are distinct concepts. There isn't even a single definitive "how many values are drawn" because the widget could be rendered in several places with distinct widths simultaneously, and in every such place the data must be drawn appropriately, e.g. with auto-scaling, that takes only the visible data points into account. And the draw() method can do that now. The default capacity value of nil uses heuristics based on widget's _private.width to guess some capacity that would tolerate some dynamic widget resizing. If the user finds the heuristic failing them, they can set the capacity property to the desired maximum number of values that will be kept in the _private.values array (in each data group). commit 08d4a80686f7fe363551c35589a460839f60853e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 03:27:18 2021 +0700 Extract values size computation into compute_drawn_values_num() This is the first step of making add_values() not concern itself (directly) with widget dimensions. The user can monkey-patch the method to override how many values are (logically) drawn, because it affects graph auto-scaling and can be useful for rendering of step_shape-s that paint outside of their step_width. I didn't make it a full-blown function-valued property, because it seems a fringe functionality, which I'm too lazy to document. commit 3c5617c98ab1147618ed22e8414a77fc56b45f6e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 02:32:29 2021 +0700 Copy graph widget prototype methods over in bulk commit 169538837ece41a03d02ba8fbc0e4097062b67a3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun Apr 25 05:58:01 2021 +0700 Remove graph.baseline_y property After giving it some thought I've concluded, that it is unlikely to be useful as it is now. I'd better reserve the name for some more sensible functionality, like fixing the baseline_value defined axis at the given widget coordinate, adjusting the scaling accordingly, so min_value/max_value won't generally correspond to widget top and bottom anymore. commit 202ffaa9a0b820c7c471e1121d01b91f8f76cd38 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 12:06:55 2021 +0700 Add baseline_y and baseline_value properties to graph widget Now values in graph have a *baseline*, tunable with baseline_y and baseline_value properties. It is the (for now invisible) horizontal line from which all bars in the graph grow. I call it a "baseline" instead of "axis", because it doesn't actually have to correspond to the 0 value. But it can, and actually does now by default. baseline_y and baseline_value properties determine the vertical placement of the baseline in the widget. baseline_y is the position of the baseline in normalized widget coordinates, e.g. 0 corresponds to widget's top, 1 - to widget's bottom, and 0.5 - to widget's middle. So if e.g. one sets baseline_y = 0, then no matter how the widget is resized and how its values are scaled, bars in it will be drawn starting from the top. baseline_value is the position of the baseline in value coordinates. So if one sets baseline_value = 10, then no matter what happens, bars for values greater than 10 will be drawn as growing up from this line, and bars for values smaller as growing down. Where and if it will be seen in the widget, depends on the scaling. baseline_value = 0 is the familiar zero axis. baseline_y is unset by default and has priority, i.e. if it's set, then baseline_value is ignored. baseline_value defaults to 0. Prior to this commit, bars were always drawn from the bottom edge of the widget, or, in the new terms, the widget had baseline_y = 1. But it was indistinguishable from baseline_value = 0, because the widget clamped all negative values to 0, so the difference between the two could never be observed. baseline_value = 0 is clearly more useful and familiar to people, so now it is in force by default. commit df7e19abaebf0a871b5ca217b4cc3365533267a6 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 11:18:12 2021 +0700 Move value clamping from graph:add_value() into draw() Previously all values were forcibly clamped between 0 (not tunable at all) and max_value (1 by default). Thus graphs with negative values were not an option. NaN values were conflated with zeros, even though it may be useful to maintain the distinction and render them differently. And if user happened to dynamically change the max_value property, they discovered, that their data got truncated. Preserving user data as it was given is more flexible and makes things easier to debug. This commit does exactly that. So what happens now to the newly allowed values during draw()? There are 3 new cases to consider: 1) NaNs 2) Negatives 3) Values outside of min_value..max_value range NaNs are presently simply not drawn. An empty place is reserved where the value should've been, but nothing else is done. This is distinct from how a zero is handled, e.g. if step_shape is set, it is called with the height parameter = 0, which can still end up drawing something, depending on the shape. One could also consider addition of nan_color/nan_shape properties which could be drawn to alert user to a gap in their data. Negative values necessitate introduction of the concept of the zero axis, from which they will be drawn DOWN, in contrast to positive values. Where should this axis be drawn? How should step_shape-s with inherent direction, e.g. arrows, be drawn for negatives? This will be addressed in the next commit. Values outside of min/max_value range can imply shapes that go beyond widget dimensions. An arrow shape that goes beyond the roof is visibly different from an arrow that saturates and merely touches the widget top, and the user may reasonably desire either behavior. Therefore this commit introduces a boolean clamp_bars property, which defaults to true for reasons of robustness and backward compatibility with the behaviour prior to this commit. The property of course affects only the display of the values, the actual user data stays intact. commit c9c24fb1ab741cd6bb8074971d568e0646c51532 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:29:01 2021 +0700 Use rectangles for drawing graph bars of all sizes I have benchmarked various bar drawing strategies and graph sizes and found out that drawing graph bars with rectangles is as fast or even marginally faster than using fat lines. Thus the code that tries to special-case 1px bars as lines seems to be not worth the additional complexity it introduces, and this commit removes it. Incidentally, I've also established, that rectangles are more robust. If one draws graph bars and feeds cairo very large coordinates (~2^62), then corresponding rectangles fail to render, but all the other rectangles in the same series get rendered successfully, whereas a single large coordinate in a move_to/line_to() sequence is enough to cause all lines in the batch to fail to render. commit bc7f4be5e06405b5aa7fb443a5ace2b9b9575b1f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:01:42 2021 +0700 Cache cairo methods used in the inner loop of graph:draw() I have benchmarked the draw() method, both on cairo xcb and on argb32 image surfaces, and have discovered that cairo context method calls are unnaturally slow, to the point, that almost nothing else in the function matters. For example, drawing bars with vertical lines made the whole draw() function almost 2 times slower than doing the same with rectangles, merely because the former requires two method calls (move_to/line_to), and the latter only one. Simply caching hotpath cairo methods in local variables, reduced the difference between the drawing methods to a negligible one and made the whole function 4 times faster on a typical 100-bar graph, (1.4ms to 0.33ms on my laptop) thus bringing closer my secret dream of drawing live audio data with awesome graphs (kidding). I guess, LGI does something not very optimal there, like gobject-introspecting the method every time, or something. There might be some untapped optimization potential, possibly in upstream, like a method cache, from which everything in awesome could benefit, even though I imagine not many things are so call-intensive as the graph widget, to really feel the difference. commit 538df25bdf02b53d0bbe81809eef2e6fdb05ff6f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 07:32:37 2021 +0700 Use 1-based index in the inner graph draw loop It's used there only once for a calculation and then several times to index some tables, and index 0 is bad for lua tables performance-wise. commit 7412546916cd0a0a490130a120255ffb719398b8 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:55:12 2021 +0700 Leave graph.max_value uninitialized at construction This is a user-visible change, but the previous behavior was probably not what user expected anyway, namely with graph.scale set the graph mysteriously couldn't zoom in onto values that are smaller than 1. commit eb81d7929a161e79c13262cfa73ac2e6a820e8ed Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:48:29 2021 +0700 Allow graph.max_value be nil This makes its behave symmetrically to min_value. Now it defaults to 1, when graph.scale is not set, and to the maximum value in the graph data, when graph.scale is set. commit f622a8430cbfde7007b58d7f4c9c76b08c91e997 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 12:01:13 2021 +0700 Calculate max values num in graph:add_value() more precisely At most so many values were kept in the widget values array, as there are pixels of usable widget width (i.e. sans border). This is exactly right for the surely most common case of graphs with 1px-wide bars, but is more than needed for graphs with step_width+step_spacing>1, and not enough for graphs with subpixel bars. Moreover this made graph autoscaling behave weirdly in graphs with thicker bars, because old values survived longer in the array and kept affecting min_value/max_value calculations even after they were shifted off-screen. The maximum number of kept values is now calculated exactly as the number of values that can be displayed at once, taking step_width and step_spacing into account. It might be worth adding a max_values_num property to the graph to let user override this calculation, in case they draw shapes in step_shape() outside of its supposed rectangle, so our assumptions of visibility are wrong, or the widget often gets resized, resulting in the annoyance of visibly truncated series. commit 0aa63249a18641251a1bf77423739164f0eab39f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 02:30:13 2021 +0700 Fix border_color/border_width bugs in graph widget add_value() wrongly assumed that border_width is always set, when border_color is, so if user set only the latter, an error occurred. add_value() assumed that border_width is always < width which led to awesome hanging up in an infinite loop, should that fail to be the case. The graph border_width property was driven by the border_color property, instead of the other way round. If border_color wasn't set, the border wasn't drawn all, even if user sets non-zero border_width, which is a confusing behavior, in contrast to all other widgets, which simply use a plethora of fallback colors to draw the border, when needed. This also meant that, despite being documented, the beautiful.graph_border_color fallback color could not and was not ever used. If the border_color was set though, an unset border_width was immediately assumed to be 1 instead of 0, also contrary to what other widgets do. Theme colors shouldn't influence widget layout, so this commit fixes this behavior to: 1) border_width always defaults to 0, if unset. 2) border_color falls back to beautiful.graph_border_color or white. Maybe one should also add a beautiful.graph_border_width theme variable, like some widgets do. commit dad4a3253b8fef971fb82720d2a407fcc0a879ad Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 03:11:27 2021 +0700 Implement multiple data series drawing for non-stacked graph The widget can already hold and draw multiple data groups, so it makes little sense to limit drawing only to the first group when graph.stack = false, probably leading to user's confusion. Now user can call add_value(v, group) with group > 1 and the data series will be drawn (over groups with lesser id-s) with graph.stack_colors[group] color, if stack_colors is set, or with graph.color, if stack_colors isn't set. Thus nothing visibly changes for all current users of non-stacked graphs with only single data series, but the new functionality is now there for those who need it. One can't fully emulate this functionality, e.g. with multiple graphs widgets in a wibox.layout.stack, because data series in a single widget can enjoy a common auto-scaling. commit 3e645c976184ab5f50d625271c25062db9ab7ed9 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:59:12 2021 +0700 Don't draw a stacked graph data group if there's no color for it If stack_colors[idx] is nil for the group `idx`, don't draw it at all (as opposed, e.g. to the effect of drawing it with a transparent color, or as though it had all values == 0, which both produce different results). This code is still subtly different from prior behavior, because now user can have a non-contiguous stack_colors table and turn the display of any data group on and off at will, whereas the code two commits earlier would just stop rendering as soon as ipairs(stack_colors) does, i.e. no data series were drawn past the first nil hole. This commit only looks large, but is mostly a whitespace change due to nesting from a pair of wrapping ifs. commit 8c0273786de1ade66bdd977699f98b588da9f487 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:32:42 2021 +0700 Unify stacked and non-stacked graph rendering Now stacked graphs support everything the nonstacked do, like step_width ~= 1 and custom step_shape-s. Technical notes: This code no longer calls cr:stroke() for every single value in a stacked graph, but rather paints a whole series and then cr:stroke/fill()-s it at all once with its color, improving performance considerably. Additionally the graph no longer relies on the presence of the stack_colors property to render series. It draws all data that it has, and, if there's no corresponding color in stack_colors, falls back to graph.color. This will be changed in the next commit to the prior behavior of only drawing those series for which there's a color in the stack_colors table, but that would've made this commit's diff much larger due to required nesting ifs over large code chunks. This commit also takes care to properly handle NaNs and negative values. Those are currently (almost) guaranteed to not occur in the values array due to clipping in graph:add_value(), but I intend to eventually drop the clipping, because it does nothing that draw() really relies upon, and allowing unclipped and NaN values is more flexible and less surprising, e.g. when user dynamically toggles graph.scale. Eventually I intend to implement drawing negative values in non-stacked graphs and possibly even in the stacked ones. commit 38c6589c51b2aa057d7c2f40182ecf32b5b2ff5e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 10:04:15 2021 +0700 Convert graph:draw() to use `self` commit f21f1b2550426df1c4ea8abde079b1ccef84b394 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 12:27:26 2021 +0700 Fix scaling for stacked graphs Stacked graphs have cumulative heights, values from all groups that end up in the same graph bar are effectively summed. Previous code didn't take that into account when calculating min/max_value for the purposes of rescaling, but simply examined every value separately. This code adds up all values up front and finds min/max among those, to determine proper scaling that would fit the graph exactly. commit 1ce4c7bf63bdd6c99c195d668347bec49355e921 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 09:49:35 2021 +0700 Draw shaped graph bars only up to the baseline Prior to this commit, every step_shape bar was, for some reason, drawn with height = full widget's height, regardless of the value it represents. So for the most bars their lower end was far outside the box and therefore clipped off. So e.g. one couldn't see the lower round edges of rounded rectangles. This commit fixes that by appropriately varying the height that is passed into step_shape(), so that lower edges of the bars always exactly touch the lower edge of the graph. If the user wants to restore the prior behavior, they can simply ignore the passed-in height in step_shape() and just always draw their shape with height = full widget height. But they couldn't replicate the effect of this commit in their rc.lua without essentially writing their own graph widget. commit e25185b08da23745b5815f85f6e9e43222ed2294 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 08:42:54 2021 +0700 Avoid incremental transform shifting during graph bar rendering Instead of letting addition errors and cognitive load accumulate simply use the same offset calculation that is used for lines and reset the transform back to the top-left corner after every drawn bar. It's conceptually simpler, and will be useful later for stacked graphs and more. It also fixes a discrepancy between plain and step_shape rendering. Prior to this commit, bar coloring occurred with a disturbed transform, which was centered at the last drawn bar, wherever it happened to be. This meant that nontrivial colors, like gradients and images, were arbitrarily offset when step_shape was used. commit f3e98866d8583c35d0b557c09f529299edc9ba2c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:45:29 2021 +0700 Refactor out graph bar height expression into a variable commit f54528d6892f5aaa4595917853c9e2130f743fdb Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:20:25 2021 +0700 Draw graph bars with lines only when step_width == 1 Before this commit graph bars of step_width < 1 were drawn with lines of width 1, but that's a bit unfortunate, because it led to much overpainting, while still ending up with a graphically incorrect result. Let's draw such bars with rectangles and let cairo do things with subpixels, getting more graphically accurate result, while still keeping the common case of step_width == 1 (supposedly) fast-drawn with lines. commit b6515dca67c18b78eb5657378131a62ab43d4cfc Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:54:07 2021 +0700 Fix unsharp edges when drawing graph bars with rectangles The 0.5 offset is only helpful for 1-width strokes, not for fills. commit 7f3db8f07e82f821bcd1dc418d66564f537609d6 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:21:01 2021 +0700 Do coordinate transformations even if the value isn't drawn The graph presently doesn't draw values < 0, but there was a discrepancy between drawing with and without step_shape. The default line/rectangle drawing reserves an empty place where the skipped value should've been, but no such empty place is reserved, when step_shape is used. This commit fixes that. Skipped values always have their empty space now. commit 7b731ebfe96af7a9dba9c57371da6761cf22c00c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:03:30 2021 +0700 Fix graph bar scaling when min_value is not 0 Previous calculation lead to underscaling of values so that even value = max_value wasn't drawn to full height. commit dca59fd8b7fff0e7e646f2dfc6bd92047028e5a8 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:18:40 2021 +0700 Calculate the horizontal graph bar offset only when needed commit 940ddf4b9825e9c3a91d276208c089d8c74e49db Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:17:23 2021 +0700 Call cr:move_to() only when drawing the graph with lines It's redundant otherwise and might even interfere with user's drawing in step_shape(). commit b5d5cd10ca29fb29cd751faaa95137c2fe89ff63 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:04:06 2021 +0700 Fix wrong horizontal offset calculations in widget.graph Indices in the loop are zero-based, so things like i-1 and i>1 aren't necessary and actually wouldn't have been correct even in a 1-based loop. step_shape = gears.shape.rectangle and step_shape = nil, which is supposed to default to rectangle according to docs, were drawn differently due to mismatched offset calculations, which led to problems especially noticeable when step_spacing is not zero, e.g: 1) whole graph shifted left too much, drawn partially off-screen. 2) whole graph shifted right too much, and the first bar placed closer to the rest than others. commit dc369fa198281e000afc1bd17326f3a264adf7d1 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 02:09:21 2021 +0700 Fix the "border_width affects graph bar width" bug Graph bars were drawn with lines of border_width thickness instead of 1, i.e. possibly too fat to be readable. commit 1ba73378cf29ed27a6ecd18716394779ceb543d9 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 00:13:03 2021 +0700 Simplify internal values representation in graph even more New values in graph._private.values were pushed to the end, and old values were removed from the beginning of the table. In draw() however newer values are drawn first, at the left edge of the graph, necessitating annoying idx = #values - idx conversions everywhere. In particular in stacked graph that means that values that are drawn together may have different indices. One has to shift the values array in add_value() either way, so one might as well insert new values at the beginning of the array and simplify the rest of the code. This doesn't seem like much yet, but it will prove to be much more convenient and less error-prone in what I'm going to do later. commit f70523ee68dbd6b67050ace4b82903a9b22440c7 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 13:35:10 2021 +0700 Simplify internal values representation in graph widget graph._private.values was either a table of values or a table of tables of values, depending on whether "stack" property is enabled, ... or a mix of both if user forgot to pass the second "group" parameter to graph:add_value(). This resulted in pretty fragile and confusing behavior, e.g. leaving user to wonder, why they see nothing more after setting stack=true for a graph in a previously working rc.lua, and demanding a very careful use of add_value() from the user, or else some values or even entire stacks would mysteriously vanish and errors in draw() get triggered, sometimes after a period of seemingly normal functioning. Now `values` is always a table of tables and the group parameter in add_value() defaults to 1, thus making the non-stacked graph a trivial variant of the stacked graph.
2021-05-20 17:14:03 +02:00
--- The color of NaN indication.
--
graph: complete widget overhaul and bug fixes Squashed commit of the following: commit 69821f51fe1e8652715658543e50631ece495090 Author: Alex Belykh <albel727@ngs.ru> Date: Mon May 10 19:17:55 2021 +0700 Refactor property handling in wibox.widget.graph In addition, baseline_value, step_width and step_spacing properties are all set to their default non-nil numeric values during instance creation now. commit 842eb429bf5df4b2ba46b2e6f7646afe89b1a3c3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 9 06:18:45 2021 +0700 Make graph:draw_values() private There's no documentation nor confidence that it's a good API, so I'm hiding it for now. commit 720746780574e4bad14a71fc4174955139c2eb50 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 04:04:13 2021 +0700 Document graph_should_draw_data_group() for posterity commit 7c73f3754ce6ba86b118f1170a6f9e406ab4841d Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:59:21 2021 +0700 Make graph:should_draw_data_group() private There's no confidence whether it's a good API, so I'm hiding it for now. commit b3539e20adb6423118af726ae4f8751ef864b5d2 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:36:44 2021 +0700 Make graph:preprocess_values() private There's no confidence whether it's a good API, so I'm hiding it for now. commit 2df4400e780a6260f8d0ab1c60734fbb11bf3c4f Author: Alex Belykh <albel727@ngs.ru> Date: Thu May 6 10:05:23 2021 +0700 Add spec/wibox/widget/graph_spec.lua commit ce01a9771ab439b8df841f1565aa4aa8a44a5e0d Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 08:16:18 2021 +0700 Improve graph.capacity documentation commit e28fade8f655802f5bf89154169f68d256b8e332 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 07:36:01 2021 +0700 Document graph:compute_drawn_values_num() commit dd68332292465422d08b48e9f0229f143beccbc6 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 05:24:23 2021 +0700 Document graph:pick_data_group_color() method commit ca6dc55f77aca88a6e9cc90bb6fd0045739f41f5 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 04:13:58 2021 +0700 Guard against setting data groups sparsely in graph:add_value() commit cdea5f126f7984e72924f6dffbd312c4fe3b032f Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 02:36:12 2021 +0700 Refactor scaling and baseline choice out of graph:draw() commit 10135deafe8a9390ba66ab657ebb1fb41d549e8c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 21:34:48 2021 +0700 Document graph.nan_color and graph.stack properties commit 0d73bb84ac66b87e16aee472bd1f299549f66a1c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 20:49:24 2021 +0700 Use a better example in docs for the graph.stack property commit c27d160a0826d033d49d6141608c40aa8a0642fa Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 16:05:38 2021 +0700 Document graph.baseline_value property commit 459bc8176c13d4ea32af0dcdc2399d3b929b1c6a Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:53:47 2021 +0700 Document graph.clamp_bars property commit e732ee0b3b007d213e689b98cc00ae7cb141ae08 Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:34 2021 +0700 Improve graph:add_value() and :clear() documentation commit ae73e3aa9f8d60409cbe4cb4da9612f31af4f38e Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:11 2021 +0700 Improve graph min_value/max_value/scale documentation commit a7350996a9def773b91b1631e6c0507e33d8f93b Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:27:36 2021 +0700 Cleanup graph examples to show only relevant things in docs commit 30740c26f4f41c16f4c8a3c339bcb7bd299e14cd Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 14:04:36 2021 +0700 Rename graph.stack_colors property to `group_colors` The colors are used for stacked and non-stacked graphs alike, so the name didn't make sense. Also document and clarify some color stuff. commit 1ea01bfd410971cf0d0c25fd74cd764d5eb7279d Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 21:25:41 2021 +0700 Add an example file for code coverage of some fringe graph cases commit eae68e39553f88a6275a0c65bf040b5d4c262b54 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 20:23:32 2021 +0700 Add step_shape to the graph/nan_color example for code coverage commit 8027147bc2eab34a97b49a428fb8cef27f122bed Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:55:18 2021 +0700 Add example files for graph.nan_color/nan_indication properties commit 89a25a165c52b2c15b6c4d95ecb777a9067e66f9 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:33:07 2021 +0700 Make widget graph:add_value(v) default v to NaN 0 as a default makes no sense and silences the error that is failing to specify a value, to which the user should rather be alerted. The graph widget has sane NaN handling now and will do just that. commit 2835d552440be48d356a7f7b1d64963696dd550b Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:20:48 2021 +0700 Implement graph.nan_color and nan_indication properties Now, whenever there's a NaN among values and graph.nan_indication is set, the corresponding area, where the value bar should have been drawn, is filled with the nan_color from top to bottom. The painting is done after all data is rendered, to make sure that it won't be overpainted and go unnoticed. NaNs among graph's values are inevitably a sign of some error and it is arguably not a sane default behavior to simply not render them at all. The user should take immediate note of any errors by default, instead of wondering at a mysteriously empty graph widget, which is why nan_indication is set to true by default. If the user wants to silence NaNs, they can always simply set it to false. nan_color if unset, defaults to a noticeable yellow-black pattern. As a direct and planned consequence, stacked graphs with negative values in them trigger the NaN handling too. But silencing NaN handling for those graphs is also possible and semi-sane, because then NaNs and negatives are ignored and the graph behaves (almost) like they were zeros. commit 9c1f8f08b400e01e19c534f3810c9c9ab485760c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 26 08:10:19 2021 +0700 Refactor out graph_map_value_to_widget_coordinates() function commit 1905991ddecabdae3e15ffb8ee5b18a1bee1a711 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 28 13:39:29 2021 +0700 Add an example file for graph.baseline_value property commit b31b39d66f4c037a9484b71a3f910703ae01fac0 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 07:03:49 2021 +0700 Add an example file for graph.clamp_bars=false/true comparison commit a1fc850e3047f9ee2c9ddeffacfbabf3ded34171 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 04:06:45 2021 +0700 Add an example file for graphs with negative values and shapes commit 2f012f8ff1bb5094c14e724a61e59a2a3a296bc8 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 01:06:05 2021 +0700 Add an example file for a graph displaying negative values commit 7b14fbd59a4c4c51962fd3110a1a900dd5f4eb98 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 11:18:26 2021 +0700 Add an example file for graphs with step_width < 1 commit dfd9a54544b637e2fbe3d1325f9cc24b3f4d2313 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 10:17:38 2021 +0700 Add an example file for graph.stack=false/true comparison Should also demonstrate that multiple data groups are possible for non-stacked graphs too. commit 5b532106c3fd255f9b66a66cfb88d977b489ab5d Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 09:34:14 2021 +0700 Add an example file for disabling graph data groups with nil colors commit ac6f1083c02fcb428f089a616f9b0ebaa5496b6c Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:57:25 2021 +0700 Add an example file for step_shape/width/spacing on stacked graphs commit b3703db6a5139e73e72c1cfd198ece2cd9be7df2 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:43:56 2021 +0700 Add an example file for scale=false/true on stacked graph commit b000aed4a12cb44fb501e1c8432a71a14b2f8f3f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 20 08:22:19 2021 +0700 Document deprecation of graph's width and height commit cb0ad617bd6c83a499896797318e3b0b588c3566 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 07:53:37 2021 +0700 Deprecate width and height properties of wibox.widget.graph commit 8c64e14ad91a074b784166a7054e53444cccb9d2 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 04:37:59 2021 +0700 Refactor graph:draw() to separate concerns Firstly, summation of group values for stacked graphs is extracted into graph:preprocess_values(). This makes graph:draw() smaller and exposes the fact, that stacked graphs are almost entirely just a form of a more general concept: pre-render data manipulation. It's something a user might want to customize, and now they can do it by monkey-patching the method. Secondly, the logic for deciding, if and with which color a data group should be drawn, is extracted into graph:should_draw_data_group() and get_data_group_color() methods respectively. This makes it customizable too and makes more obvious the fact, that it is a general thing that has nothing to do with stacked graphs in particular. With this there are only 5 lines remaining in graph:draw(), that could be said to be specific to stacked graphs, all pertaining to the `prev_y` variable, which merely preserves the bar coordinates from a previous data group. commit 2a32b6305d893f413303728a0a3da1dcd9a5591e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 22:52:37 2021 +0700 Pointlessly optimize graph:add_value() 1) Purge old values first, then insert the new one, if needed. Since Lua grows arrays in power of two steps, and our guess_capacity() also takes care to return even integers, it would be a shame to overstep the capacity by one by inserting first and thus getting the table size doubled only to never use the most of it. 2) Use a removal procedure that doesn't call the slow length operator at all. Previous code called it twice (once in table.remove) for every removed value. commit 680ea8f5d81e9040afb4662b3229e937e4b89e02 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:23:38 2021 +0700 Set graph data series color before the painting loop commit b970f0f221ae76250f2a94433ff79d653caa6e21 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:05:37 2021 +0700 Drop a large no longer necessary `if` in graph:draw_values() This commit is almost purely a whitespace change. commit 6b4f0541c70ec99d7f6e276bae64939f337120d6 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:00:50 2021 +0700 Extract values rendering code into graph:draw_values() method This is to avoid ungodly if nesting levels, which are needed to bail out of drawing values early and skip to drawing the border. The next commit will get rid of the if. commit e0950e4f99d1ea1c4b8cea2cc67629fa40030c2a Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 14:50:20 2021 +0700 Track graph draw() usage to guess necessary array capacity The last step of divorcing add_values() from width calculations, while ensuring that user doesn't observe a lack of data to draw. The widget now tracks an approximate number of values that it has been asked to draw() in _private.last_drawn_values_num, and truncates its values array based on that (if not overridden by the capacity property). commit 2e3d542282f35e7c0fed454da0f2eada0fcf3b78 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 13:35:12 2021 +0700 Add `capacity` property to graph widget This is the second step of divorcing add_values() from calculations involving widths. How many values are stored and how many values are drawn are distinct concepts. There isn't even a single definitive "how many values are drawn" because the widget could be rendered in several places with distinct widths simultaneously, and in every such place the data must be drawn appropriately, e.g. with auto-scaling, that takes only the visible data points into account. And the draw() method can do that now. The default capacity value of nil uses heuristics based on widget's _private.width to guess some capacity that would tolerate some dynamic widget resizing. If the user finds the heuristic failing them, they can set the capacity property to the desired maximum number of values that will be kept in the _private.values array (in each data group). commit 08d4a80686f7fe363551c35589a460839f60853e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 03:27:18 2021 +0700 Extract values size computation into compute_drawn_values_num() This is the first step of making add_values() not concern itself (directly) with widget dimensions. The user can monkey-patch the method to override how many values are (logically) drawn, because it affects graph auto-scaling and can be useful for rendering of step_shape-s that paint outside of their step_width. I didn't make it a full-blown function-valued property, because it seems a fringe functionality, which I'm too lazy to document. commit 3c5617c98ab1147618ed22e8414a77fc56b45f6e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 02:32:29 2021 +0700 Copy graph widget prototype methods over in bulk commit 169538837ece41a03d02ba8fbc0e4097062b67a3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun Apr 25 05:58:01 2021 +0700 Remove graph.baseline_y property After giving it some thought I've concluded, that it is unlikely to be useful as it is now. I'd better reserve the name for some more sensible functionality, like fixing the baseline_value defined axis at the given widget coordinate, adjusting the scaling accordingly, so min_value/max_value won't generally correspond to widget top and bottom anymore. commit 202ffaa9a0b820c7c471e1121d01b91f8f76cd38 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 12:06:55 2021 +0700 Add baseline_y and baseline_value properties to graph widget Now values in graph have a *baseline*, tunable with baseline_y and baseline_value properties. It is the (for now invisible) horizontal line from which all bars in the graph grow. I call it a "baseline" instead of "axis", because it doesn't actually have to correspond to the 0 value. But it can, and actually does now by default. baseline_y and baseline_value properties determine the vertical placement of the baseline in the widget. baseline_y is the position of the baseline in normalized widget coordinates, e.g. 0 corresponds to widget's top, 1 - to widget's bottom, and 0.5 - to widget's middle. So if e.g. one sets baseline_y = 0, then no matter how the widget is resized and how its values are scaled, bars in it will be drawn starting from the top. baseline_value is the position of the baseline in value coordinates. So if one sets baseline_value = 10, then no matter what happens, bars for values greater than 10 will be drawn as growing up from this line, and bars for values smaller as growing down. Where and if it will be seen in the widget, depends on the scaling. baseline_value = 0 is the familiar zero axis. baseline_y is unset by default and has priority, i.e. if it's set, then baseline_value is ignored. baseline_value defaults to 0. Prior to this commit, bars were always drawn from the bottom edge of the widget, or, in the new terms, the widget had baseline_y = 1. But it was indistinguishable from baseline_value = 0, because the widget clamped all negative values to 0, so the difference between the two could never be observed. baseline_value = 0 is clearly more useful and familiar to people, so now it is in force by default. commit df7e19abaebf0a871b5ca217b4cc3365533267a6 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 11:18:12 2021 +0700 Move value clamping from graph:add_value() into draw() Previously all values were forcibly clamped between 0 (not tunable at all) and max_value (1 by default). Thus graphs with negative values were not an option. NaN values were conflated with zeros, even though it may be useful to maintain the distinction and render them differently. And if user happened to dynamically change the max_value property, they discovered, that their data got truncated. Preserving user data as it was given is more flexible and makes things easier to debug. This commit does exactly that. So what happens now to the newly allowed values during draw()? There are 3 new cases to consider: 1) NaNs 2) Negatives 3) Values outside of min_value..max_value range NaNs are presently simply not drawn. An empty place is reserved where the value should've been, but nothing else is done. This is distinct from how a zero is handled, e.g. if step_shape is set, it is called with the height parameter = 0, which can still end up drawing something, depending on the shape. One could also consider addition of nan_color/nan_shape properties which could be drawn to alert user to a gap in their data. Negative values necessitate introduction of the concept of the zero axis, from which they will be drawn DOWN, in contrast to positive values. Where should this axis be drawn? How should step_shape-s with inherent direction, e.g. arrows, be drawn for negatives? This will be addressed in the next commit. Values outside of min/max_value range can imply shapes that go beyond widget dimensions. An arrow shape that goes beyond the roof is visibly different from an arrow that saturates and merely touches the widget top, and the user may reasonably desire either behavior. Therefore this commit introduces a boolean clamp_bars property, which defaults to true for reasons of robustness and backward compatibility with the behaviour prior to this commit. The property of course affects only the display of the values, the actual user data stays intact. commit c9c24fb1ab741cd6bb8074971d568e0646c51532 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:29:01 2021 +0700 Use rectangles for drawing graph bars of all sizes I have benchmarked various bar drawing strategies and graph sizes and found out that drawing graph bars with rectangles is as fast or even marginally faster than using fat lines. Thus the code that tries to special-case 1px bars as lines seems to be not worth the additional complexity it introduces, and this commit removes it. Incidentally, I've also established, that rectangles are more robust. If one draws graph bars and feeds cairo very large coordinates (~2^62), then corresponding rectangles fail to render, but all the other rectangles in the same series get rendered successfully, whereas a single large coordinate in a move_to/line_to() sequence is enough to cause all lines in the batch to fail to render. commit bc7f4be5e06405b5aa7fb443a5ace2b9b9575b1f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:01:42 2021 +0700 Cache cairo methods used in the inner loop of graph:draw() I have benchmarked the draw() method, both on cairo xcb and on argb32 image surfaces, and have discovered that cairo context method calls are unnaturally slow, to the point, that almost nothing else in the function matters. For example, drawing bars with vertical lines made the whole draw() function almost 2 times slower than doing the same with rectangles, merely because the former requires two method calls (move_to/line_to), and the latter only one. Simply caching hotpath cairo methods in local variables, reduced the difference between the drawing methods to a negligible one and made the whole function 4 times faster on a typical 100-bar graph, (1.4ms to 0.33ms on my laptop) thus bringing closer my secret dream of drawing live audio data with awesome graphs (kidding). I guess, LGI does something not very optimal there, like gobject-introspecting the method every time, or something. There might be some untapped optimization potential, possibly in upstream, like a method cache, from which everything in awesome could benefit, even though I imagine not many things are so call-intensive as the graph widget, to really feel the difference. commit 538df25bdf02b53d0bbe81809eef2e6fdb05ff6f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 07:32:37 2021 +0700 Use 1-based index in the inner graph draw loop It's used there only once for a calculation and then several times to index some tables, and index 0 is bad for lua tables performance-wise. commit 7412546916cd0a0a490130a120255ffb719398b8 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:55:12 2021 +0700 Leave graph.max_value uninitialized at construction This is a user-visible change, but the previous behavior was probably not what user expected anyway, namely with graph.scale set the graph mysteriously couldn't zoom in onto values that are smaller than 1. commit eb81d7929a161e79c13262cfa73ac2e6a820e8ed Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:48:29 2021 +0700 Allow graph.max_value be nil This makes its behave symmetrically to min_value. Now it defaults to 1, when graph.scale is not set, and to the maximum value in the graph data, when graph.scale is set. commit f622a8430cbfde7007b58d7f4c9c76b08c91e997 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 12:01:13 2021 +0700 Calculate max values num in graph:add_value() more precisely At most so many values were kept in the widget values array, as there are pixels of usable widget width (i.e. sans border). This is exactly right for the surely most common case of graphs with 1px-wide bars, but is more than needed for graphs with step_width+step_spacing>1, and not enough for graphs with subpixel bars. Moreover this made graph autoscaling behave weirdly in graphs with thicker bars, because old values survived longer in the array and kept affecting min_value/max_value calculations even after they were shifted off-screen. The maximum number of kept values is now calculated exactly as the number of values that can be displayed at once, taking step_width and step_spacing into account. It might be worth adding a max_values_num property to the graph to let user override this calculation, in case they draw shapes in step_shape() outside of its supposed rectangle, so our assumptions of visibility are wrong, or the widget often gets resized, resulting in the annoyance of visibly truncated series. commit 0aa63249a18641251a1bf77423739164f0eab39f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 02:30:13 2021 +0700 Fix border_color/border_width bugs in graph widget add_value() wrongly assumed that border_width is always set, when border_color is, so if user set only the latter, an error occurred. add_value() assumed that border_width is always < width which led to awesome hanging up in an infinite loop, should that fail to be the case. The graph border_width property was driven by the border_color property, instead of the other way round. If border_color wasn't set, the border wasn't drawn all, even if user sets non-zero border_width, which is a confusing behavior, in contrast to all other widgets, which simply use a plethora of fallback colors to draw the border, when needed. This also meant that, despite being documented, the beautiful.graph_border_color fallback color could not and was not ever used. If the border_color was set though, an unset border_width was immediately assumed to be 1 instead of 0, also contrary to what other widgets do. Theme colors shouldn't influence widget layout, so this commit fixes this behavior to: 1) border_width always defaults to 0, if unset. 2) border_color falls back to beautiful.graph_border_color or white. Maybe one should also add a beautiful.graph_border_width theme variable, like some widgets do. commit dad4a3253b8fef971fb82720d2a407fcc0a879ad Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 03:11:27 2021 +0700 Implement multiple data series drawing for non-stacked graph The widget can already hold and draw multiple data groups, so it makes little sense to limit drawing only to the first group when graph.stack = false, probably leading to user's confusion. Now user can call add_value(v, group) with group > 1 and the data series will be drawn (over groups with lesser id-s) with graph.stack_colors[group] color, if stack_colors is set, or with graph.color, if stack_colors isn't set. Thus nothing visibly changes for all current users of non-stacked graphs with only single data series, but the new functionality is now there for those who need it. One can't fully emulate this functionality, e.g. with multiple graphs widgets in a wibox.layout.stack, because data series in a single widget can enjoy a common auto-scaling. commit 3e645c976184ab5f50d625271c25062db9ab7ed9 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:59:12 2021 +0700 Don't draw a stacked graph data group if there's no color for it If stack_colors[idx] is nil for the group `idx`, don't draw it at all (as opposed, e.g. to the effect of drawing it with a transparent color, or as though it had all values == 0, which both produce different results). This code is still subtly different from prior behavior, because now user can have a non-contiguous stack_colors table and turn the display of any data group on and off at will, whereas the code two commits earlier would just stop rendering as soon as ipairs(stack_colors) does, i.e. no data series were drawn past the first nil hole. This commit only looks large, but is mostly a whitespace change due to nesting from a pair of wrapping ifs. commit 8c0273786de1ade66bdd977699f98b588da9f487 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:32:42 2021 +0700 Unify stacked and non-stacked graph rendering Now stacked graphs support everything the nonstacked do, like step_width ~= 1 and custom step_shape-s. Technical notes: This code no longer calls cr:stroke() for every single value in a stacked graph, but rather paints a whole series and then cr:stroke/fill()-s it at all once with its color, improving performance considerably. Additionally the graph no longer relies on the presence of the stack_colors property to render series. It draws all data that it has, and, if there's no corresponding color in stack_colors, falls back to graph.color. This will be changed in the next commit to the prior behavior of only drawing those series for which there's a color in the stack_colors table, but that would've made this commit's diff much larger due to required nesting ifs over large code chunks. This commit also takes care to properly handle NaNs and negative values. Those are currently (almost) guaranteed to not occur in the values array due to clipping in graph:add_value(), but I intend to eventually drop the clipping, because it does nothing that draw() really relies upon, and allowing unclipped and NaN values is more flexible and less surprising, e.g. when user dynamically toggles graph.scale. Eventually I intend to implement drawing negative values in non-stacked graphs and possibly even in the stacked ones. commit 38c6589c51b2aa057d7c2f40182ecf32b5b2ff5e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 10:04:15 2021 +0700 Convert graph:draw() to use `self` commit f21f1b2550426df1c4ea8abde079b1ccef84b394 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 12:27:26 2021 +0700 Fix scaling for stacked graphs Stacked graphs have cumulative heights, values from all groups that end up in the same graph bar are effectively summed. Previous code didn't take that into account when calculating min/max_value for the purposes of rescaling, but simply examined every value separately. This code adds up all values up front and finds min/max among those, to determine proper scaling that would fit the graph exactly. commit 1ce4c7bf63bdd6c99c195d668347bec49355e921 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 09:49:35 2021 +0700 Draw shaped graph bars only up to the baseline Prior to this commit, every step_shape bar was, for some reason, drawn with height = full widget's height, regardless of the value it represents. So for the most bars their lower end was far outside the box and therefore clipped off. So e.g. one couldn't see the lower round edges of rounded rectangles. This commit fixes that by appropriately varying the height that is passed into step_shape(), so that lower edges of the bars always exactly touch the lower edge of the graph. If the user wants to restore the prior behavior, they can simply ignore the passed-in height in step_shape() and just always draw their shape with height = full widget height. But they couldn't replicate the effect of this commit in their rc.lua without essentially writing their own graph widget. commit e25185b08da23745b5815f85f6e9e43222ed2294 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 08:42:54 2021 +0700 Avoid incremental transform shifting during graph bar rendering Instead of letting addition errors and cognitive load accumulate simply use the same offset calculation that is used for lines and reset the transform back to the top-left corner after every drawn bar. It's conceptually simpler, and will be useful later for stacked graphs and more. It also fixes a discrepancy between plain and step_shape rendering. Prior to this commit, bar coloring occurred with a disturbed transform, which was centered at the last drawn bar, wherever it happened to be. This meant that nontrivial colors, like gradients and images, were arbitrarily offset when step_shape was used. commit f3e98866d8583c35d0b557c09f529299edc9ba2c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:45:29 2021 +0700 Refactor out graph bar height expression into a variable commit f54528d6892f5aaa4595917853c9e2130f743fdb Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:20:25 2021 +0700 Draw graph bars with lines only when step_width == 1 Before this commit graph bars of step_width < 1 were drawn with lines of width 1, but that's a bit unfortunate, because it led to much overpainting, while still ending up with a graphically incorrect result. Let's draw such bars with rectangles and let cairo do things with subpixels, getting more graphically accurate result, while still keeping the common case of step_width == 1 (supposedly) fast-drawn with lines. commit b6515dca67c18b78eb5657378131a62ab43d4cfc Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:54:07 2021 +0700 Fix unsharp edges when drawing graph bars with rectangles The 0.5 offset is only helpful for 1-width strokes, not for fills. commit 7f3db8f07e82f821bcd1dc418d66564f537609d6 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:21:01 2021 +0700 Do coordinate transformations even if the value isn't drawn The graph presently doesn't draw values < 0, but there was a discrepancy between drawing with and without step_shape. The default line/rectangle drawing reserves an empty place where the skipped value should've been, but no such empty place is reserved, when step_shape is used. This commit fixes that. Skipped values always have their empty space now. commit 7b731ebfe96af7a9dba9c57371da6761cf22c00c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:03:30 2021 +0700 Fix graph bar scaling when min_value is not 0 Previous calculation lead to underscaling of values so that even value = max_value wasn't drawn to full height. commit dca59fd8b7fff0e7e646f2dfc6bd92047028e5a8 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:18:40 2021 +0700 Calculate the horizontal graph bar offset only when needed commit 940ddf4b9825e9c3a91d276208c089d8c74e49db Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:17:23 2021 +0700 Call cr:move_to() only when drawing the graph with lines It's redundant otherwise and might even interfere with user's drawing in step_shape(). commit b5d5cd10ca29fb29cd751faaa95137c2fe89ff63 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:04:06 2021 +0700 Fix wrong horizontal offset calculations in widget.graph Indices in the loop are zero-based, so things like i-1 and i>1 aren't necessary and actually wouldn't have been correct even in a 1-based loop. step_shape = gears.shape.rectangle and step_shape = nil, which is supposed to default to rectangle according to docs, were drawn differently due to mismatched offset calculations, which led to problems especially noticeable when step_spacing is not zero, e.g: 1) whole graph shifted left too much, drawn partially off-screen. 2) whole graph shifted right too much, and the first bar placed closer to the rest than others. commit dc369fa198281e000afc1bd17326f3a264adf7d1 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 02:09:21 2021 +0700 Fix the "border_width affects graph bar width" bug Graph bars were drawn with lines of border_width thickness instead of 1, i.e. possibly too fat to be readable. commit 1ba73378cf29ed27a6ecd18716394779ceb543d9 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 00:13:03 2021 +0700 Simplify internal values representation in graph even more New values in graph._private.values were pushed to the end, and old values were removed from the beginning of the table. In draw() however newer values are drawn first, at the left edge of the graph, necessitating annoying idx = #values - idx conversions everywhere. In particular in stacked graph that means that values that are drawn together may have different indices. One has to shift the values array in add_value() either way, so one might as well insert new values at the beginning of the array and simplify the rest of the code. This doesn't seem like much yet, but it will prove to be much more convenient and less error-prone in what I'm going to do later. commit f70523ee68dbd6b67050ace4b82903a9b22440c7 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 13:35:10 2021 +0700 Simplify internal values representation in graph widget graph._private.values was either a table of values or a table of tables of values, depending on whether "stack" property is enabled, ... or a mix of both if user forgot to pass the second "group" parameter to graph:add_value(). This resulted in pretty fragile and confusing behavior, e.g. leaving user to wonder, why they see nothing more after setting stack=true for a graph in a previously working rc.lua, and demanding a very careful use of add_value() from the user, or else some values or even entire stacks would mysteriously vanish and errors in draw() get triggered, sometimes after a period of seemingly normal functioning. Now `values` is always a table of tables and the group parameter in add_value() defaults to 1, thus making the non-stacked graph a trivial variant of the stacked graph.
2021-05-20 17:14:03 +02:00
-- The color used when `nan_indication` is set.
-- Defaults to a yellow-black diagonal stripes pattern.
--
-- @DOC_wibox_widget_graph_stacked_nan_EXAMPLE@
--
-- @property nan_color
-- @tparam[opt="#ffff00"] color nan_color The color of NaN indication.
graph: complete widget overhaul and bug fixes Squashed commit of the following: commit 69821f51fe1e8652715658543e50631ece495090 Author: Alex Belykh <albel727@ngs.ru> Date: Mon May 10 19:17:55 2021 +0700 Refactor property handling in wibox.widget.graph In addition, baseline_value, step_width and step_spacing properties are all set to their default non-nil numeric values during instance creation now. commit 842eb429bf5df4b2ba46b2e6f7646afe89b1a3c3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 9 06:18:45 2021 +0700 Make graph:draw_values() private There's no documentation nor confidence that it's a good API, so I'm hiding it for now. commit 720746780574e4bad14a71fc4174955139c2eb50 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 04:04:13 2021 +0700 Document graph_should_draw_data_group() for posterity commit 7c73f3754ce6ba86b118f1170a6f9e406ab4841d Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:59:21 2021 +0700 Make graph:should_draw_data_group() private There's no confidence whether it's a good API, so I'm hiding it for now. commit b3539e20adb6423118af726ae4f8751ef864b5d2 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:36:44 2021 +0700 Make graph:preprocess_values() private There's no confidence whether it's a good API, so I'm hiding it for now. commit 2df4400e780a6260f8d0ab1c60734fbb11bf3c4f Author: Alex Belykh <albel727@ngs.ru> Date: Thu May 6 10:05:23 2021 +0700 Add spec/wibox/widget/graph_spec.lua commit ce01a9771ab439b8df841f1565aa4aa8a44a5e0d Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 08:16:18 2021 +0700 Improve graph.capacity documentation commit e28fade8f655802f5bf89154169f68d256b8e332 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 07:36:01 2021 +0700 Document graph:compute_drawn_values_num() commit dd68332292465422d08b48e9f0229f143beccbc6 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 05:24:23 2021 +0700 Document graph:pick_data_group_color() method commit ca6dc55f77aca88a6e9cc90bb6fd0045739f41f5 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 04:13:58 2021 +0700 Guard against setting data groups sparsely in graph:add_value() commit cdea5f126f7984e72924f6dffbd312c4fe3b032f Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 02:36:12 2021 +0700 Refactor scaling and baseline choice out of graph:draw() commit 10135deafe8a9390ba66ab657ebb1fb41d549e8c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 21:34:48 2021 +0700 Document graph.nan_color and graph.stack properties commit 0d73bb84ac66b87e16aee472bd1f299549f66a1c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 20:49:24 2021 +0700 Use a better example in docs for the graph.stack property commit c27d160a0826d033d49d6141608c40aa8a0642fa Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 16:05:38 2021 +0700 Document graph.baseline_value property commit 459bc8176c13d4ea32af0dcdc2399d3b929b1c6a Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:53:47 2021 +0700 Document graph.clamp_bars property commit e732ee0b3b007d213e689b98cc00ae7cb141ae08 Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:34 2021 +0700 Improve graph:add_value() and :clear() documentation commit ae73e3aa9f8d60409cbe4cb4da9612f31af4f38e Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:11 2021 +0700 Improve graph min_value/max_value/scale documentation commit a7350996a9def773b91b1631e6c0507e33d8f93b Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:27:36 2021 +0700 Cleanup graph examples to show only relevant things in docs commit 30740c26f4f41c16f4c8a3c339bcb7bd299e14cd Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 14:04:36 2021 +0700 Rename graph.stack_colors property to `group_colors` The colors are used for stacked and non-stacked graphs alike, so the name didn't make sense. Also document and clarify some color stuff. commit 1ea01bfd410971cf0d0c25fd74cd764d5eb7279d Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 21:25:41 2021 +0700 Add an example file for code coverage of some fringe graph cases commit eae68e39553f88a6275a0c65bf040b5d4c262b54 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 20:23:32 2021 +0700 Add step_shape to the graph/nan_color example for code coverage commit 8027147bc2eab34a97b49a428fb8cef27f122bed Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:55:18 2021 +0700 Add example files for graph.nan_color/nan_indication properties commit 89a25a165c52b2c15b6c4d95ecb777a9067e66f9 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:33:07 2021 +0700 Make widget graph:add_value(v) default v to NaN 0 as a default makes no sense and silences the error that is failing to specify a value, to which the user should rather be alerted. The graph widget has sane NaN handling now and will do just that. commit 2835d552440be48d356a7f7b1d64963696dd550b Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:20:48 2021 +0700 Implement graph.nan_color and nan_indication properties Now, whenever there's a NaN among values and graph.nan_indication is set, the corresponding area, where the value bar should have been drawn, is filled with the nan_color from top to bottom. The painting is done after all data is rendered, to make sure that it won't be overpainted and go unnoticed. NaNs among graph's values are inevitably a sign of some error and it is arguably not a sane default behavior to simply not render them at all. The user should take immediate note of any errors by default, instead of wondering at a mysteriously empty graph widget, which is why nan_indication is set to true by default. If the user wants to silence NaNs, they can always simply set it to false. nan_color if unset, defaults to a noticeable yellow-black pattern. As a direct and planned consequence, stacked graphs with negative values in them trigger the NaN handling too. But silencing NaN handling for those graphs is also possible and semi-sane, because then NaNs and negatives are ignored and the graph behaves (almost) like they were zeros. commit 9c1f8f08b400e01e19c534f3810c9c9ab485760c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 26 08:10:19 2021 +0700 Refactor out graph_map_value_to_widget_coordinates() function commit 1905991ddecabdae3e15ffb8ee5b18a1bee1a711 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 28 13:39:29 2021 +0700 Add an example file for graph.baseline_value property commit b31b39d66f4c037a9484b71a3f910703ae01fac0 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 07:03:49 2021 +0700 Add an example file for graph.clamp_bars=false/true comparison commit a1fc850e3047f9ee2c9ddeffacfbabf3ded34171 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 04:06:45 2021 +0700 Add an example file for graphs with negative values and shapes commit 2f012f8ff1bb5094c14e724a61e59a2a3a296bc8 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 01:06:05 2021 +0700 Add an example file for a graph displaying negative values commit 7b14fbd59a4c4c51962fd3110a1a900dd5f4eb98 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 11:18:26 2021 +0700 Add an example file for graphs with step_width < 1 commit dfd9a54544b637e2fbe3d1325f9cc24b3f4d2313 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 10:17:38 2021 +0700 Add an example file for graph.stack=false/true comparison Should also demonstrate that multiple data groups are possible for non-stacked graphs too. commit 5b532106c3fd255f9b66a66cfb88d977b489ab5d Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 09:34:14 2021 +0700 Add an example file for disabling graph data groups with nil colors commit ac6f1083c02fcb428f089a616f9b0ebaa5496b6c Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:57:25 2021 +0700 Add an example file for step_shape/width/spacing on stacked graphs commit b3703db6a5139e73e72c1cfd198ece2cd9be7df2 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:43:56 2021 +0700 Add an example file for scale=false/true on stacked graph commit b000aed4a12cb44fb501e1c8432a71a14b2f8f3f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 20 08:22:19 2021 +0700 Document deprecation of graph's width and height commit cb0ad617bd6c83a499896797318e3b0b588c3566 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 07:53:37 2021 +0700 Deprecate width and height properties of wibox.widget.graph commit 8c64e14ad91a074b784166a7054e53444cccb9d2 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 04:37:59 2021 +0700 Refactor graph:draw() to separate concerns Firstly, summation of group values for stacked graphs is extracted into graph:preprocess_values(). This makes graph:draw() smaller and exposes the fact, that stacked graphs are almost entirely just a form of a more general concept: pre-render data manipulation. It's something a user might want to customize, and now they can do it by monkey-patching the method. Secondly, the logic for deciding, if and with which color a data group should be drawn, is extracted into graph:should_draw_data_group() and get_data_group_color() methods respectively. This makes it customizable too and makes more obvious the fact, that it is a general thing that has nothing to do with stacked graphs in particular. With this there are only 5 lines remaining in graph:draw(), that could be said to be specific to stacked graphs, all pertaining to the `prev_y` variable, which merely preserves the bar coordinates from a previous data group. commit 2a32b6305d893f413303728a0a3da1dcd9a5591e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 22:52:37 2021 +0700 Pointlessly optimize graph:add_value() 1) Purge old values first, then insert the new one, if needed. Since Lua grows arrays in power of two steps, and our guess_capacity() also takes care to return even integers, it would be a shame to overstep the capacity by one by inserting first and thus getting the table size doubled only to never use the most of it. 2) Use a removal procedure that doesn't call the slow length operator at all. Previous code called it twice (once in table.remove) for every removed value. commit 680ea8f5d81e9040afb4662b3229e937e4b89e02 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:23:38 2021 +0700 Set graph data series color before the painting loop commit b970f0f221ae76250f2a94433ff79d653caa6e21 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:05:37 2021 +0700 Drop a large no longer necessary `if` in graph:draw_values() This commit is almost purely a whitespace change. commit 6b4f0541c70ec99d7f6e276bae64939f337120d6 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:00:50 2021 +0700 Extract values rendering code into graph:draw_values() method This is to avoid ungodly if nesting levels, which are needed to bail out of drawing values early and skip to drawing the border. The next commit will get rid of the if. commit e0950e4f99d1ea1c4b8cea2cc67629fa40030c2a Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 14:50:20 2021 +0700 Track graph draw() usage to guess necessary array capacity The last step of divorcing add_values() from width calculations, while ensuring that user doesn't observe a lack of data to draw. The widget now tracks an approximate number of values that it has been asked to draw() in _private.last_drawn_values_num, and truncates its values array based on that (if not overridden by the capacity property). commit 2e3d542282f35e7c0fed454da0f2eada0fcf3b78 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 13:35:12 2021 +0700 Add `capacity` property to graph widget This is the second step of divorcing add_values() from calculations involving widths. How many values are stored and how many values are drawn are distinct concepts. There isn't even a single definitive "how many values are drawn" because the widget could be rendered in several places with distinct widths simultaneously, and in every such place the data must be drawn appropriately, e.g. with auto-scaling, that takes only the visible data points into account. And the draw() method can do that now. The default capacity value of nil uses heuristics based on widget's _private.width to guess some capacity that would tolerate some dynamic widget resizing. If the user finds the heuristic failing them, they can set the capacity property to the desired maximum number of values that will be kept in the _private.values array (in each data group). commit 08d4a80686f7fe363551c35589a460839f60853e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 03:27:18 2021 +0700 Extract values size computation into compute_drawn_values_num() This is the first step of making add_values() not concern itself (directly) with widget dimensions. The user can monkey-patch the method to override how many values are (logically) drawn, because it affects graph auto-scaling and can be useful for rendering of step_shape-s that paint outside of their step_width. I didn't make it a full-blown function-valued property, because it seems a fringe functionality, which I'm too lazy to document. commit 3c5617c98ab1147618ed22e8414a77fc56b45f6e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 02:32:29 2021 +0700 Copy graph widget prototype methods over in bulk commit 169538837ece41a03d02ba8fbc0e4097062b67a3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun Apr 25 05:58:01 2021 +0700 Remove graph.baseline_y property After giving it some thought I've concluded, that it is unlikely to be useful as it is now. I'd better reserve the name for some more sensible functionality, like fixing the baseline_value defined axis at the given widget coordinate, adjusting the scaling accordingly, so min_value/max_value won't generally correspond to widget top and bottom anymore. commit 202ffaa9a0b820c7c471e1121d01b91f8f76cd38 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 12:06:55 2021 +0700 Add baseline_y and baseline_value properties to graph widget Now values in graph have a *baseline*, tunable with baseline_y and baseline_value properties. It is the (for now invisible) horizontal line from which all bars in the graph grow. I call it a "baseline" instead of "axis", because it doesn't actually have to correspond to the 0 value. But it can, and actually does now by default. baseline_y and baseline_value properties determine the vertical placement of the baseline in the widget. baseline_y is the position of the baseline in normalized widget coordinates, e.g. 0 corresponds to widget's top, 1 - to widget's bottom, and 0.5 - to widget's middle. So if e.g. one sets baseline_y = 0, then no matter how the widget is resized and how its values are scaled, bars in it will be drawn starting from the top. baseline_value is the position of the baseline in value coordinates. So if one sets baseline_value = 10, then no matter what happens, bars for values greater than 10 will be drawn as growing up from this line, and bars for values smaller as growing down. Where and if it will be seen in the widget, depends on the scaling. baseline_value = 0 is the familiar zero axis. baseline_y is unset by default and has priority, i.e. if it's set, then baseline_value is ignored. baseline_value defaults to 0. Prior to this commit, bars were always drawn from the bottom edge of the widget, or, in the new terms, the widget had baseline_y = 1. But it was indistinguishable from baseline_value = 0, because the widget clamped all negative values to 0, so the difference between the two could never be observed. baseline_value = 0 is clearly more useful and familiar to people, so now it is in force by default. commit df7e19abaebf0a871b5ca217b4cc3365533267a6 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 11:18:12 2021 +0700 Move value clamping from graph:add_value() into draw() Previously all values were forcibly clamped between 0 (not tunable at all) and max_value (1 by default). Thus graphs with negative values were not an option. NaN values were conflated with zeros, even though it may be useful to maintain the distinction and render them differently. And if user happened to dynamically change the max_value property, they discovered, that their data got truncated. Preserving user data as it was given is more flexible and makes things easier to debug. This commit does exactly that. So what happens now to the newly allowed values during draw()? There are 3 new cases to consider: 1) NaNs 2) Negatives 3) Values outside of min_value..max_value range NaNs are presently simply not drawn. An empty place is reserved where the value should've been, but nothing else is done. This is distinct from how a zero is handled, e.g. if step_shape is set, it is called with the height parameter = 0, which can still end up drawing something, depending on the shape. One could also consider addition of nan_color/nan_shape properties which could be drawn to alert user to a gap in their data. Negative values necessitate introduction of the concept of the zero axis, from which they will be drawn DOWN, in contrast to positive values. Where should this axis be drawn? How should step_shape-s with inherent direction, e.g. arrows, be drawn for negatives? This will be addressed in the next commit. Values outside of min/max_value range can imply shapes that go beyond widget dimensions. An arrow shape that goes beyond the roof is visibly different from an arrow that saturates and merely touches the widget top, and the user may reasonably desire either behavior. Therefore this commit introduces a boolean clamp_bars property, which defaults to true for reasons of robustness and backward compatibility with the behaviour prior to this commit. The property of course affects only the display of the values, the actual user data stays intact. commit c9c24fb1ab741cd6bb8074971d568e0646c51532 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:29:01 2021 +0700 Use rectangles for drawing graph bars of all sizes I have benchmarked various bar drawing strategies and graph sizes and found out that drawing graph bars with rectangles is as fast or even marginally faster than using fat lines. Thus the code that tries to special-case 1px bars as lines seems to be not worth the additional complexity it introduces, and this commit removes it. Incidentally, I've also established, that rectangles are more robust. If one draws graph bars and feeds cairo very large coordinates (~2^62), then corresponding rectangles fail to render, but all the other rectangles in the same series get rendered successfully, whereas a single large coordinate in a move_to/line_to() sequence is enough to cause all lines in the batch to fail to render. commit bc7f4be5e06405b5aa7fb443a5ace2b9b9575b1f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:01:42 2021 +0700 Cache cairo methods used in the inner loop of graph:draw() I have benchmarked the draw() method, both on cairo xcb and on argb32 image surfaces, and have discovered that cairo context method calls are unnaturally slow, to the point, that almost nothing else in the function matters. For example, drawing bars with vertical lines made the whole draw() function almost 2 times slower than doing the same with rectangles, merely because the former requires two method calls (move_to/line_to), and the latter only one. Simply caching hotpath cairo methods in local variables, reduced the difference between the drawing methods to a negligible one and made the whole function 4 times faster on a typical 100-bar graph, (1.4ms to 0.33ms on my laptop) thus bringing closer my secret dream of drawing live audio data with awesome graphs (kidding). I guess, LGI does something not very optimal there, like gobject-introspecting the method every time, or something. There might be some untapped optimization potential, possibly in upstream, like a method cache, from which everything in awesome could benefit, even though I imagine not many things are so call-intensive as the graph widget, to really feel the difference. commit 538df25bdf02b53d0bbe81809eef2e6fdb05ff6f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 07:32:37 2021 +0700 Use 1-based index in the inner graph draw loop It's used there only once for a calculation and then several times to index some tables, and index 0 is bad for lua tables performance-wise. commit 7412546916cd0a0a490130a120255ffb719398b8 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:55:12 2021 +0700 Leave graph.max_value uninitialized at construction This is a user-visible change, but the previous behavior was probably not what user expected anyway, namely with graph.scale set the graph mysteriously couldn't zoom in onto values that are smaller than 1. commit eb81d7929a161e79c13262cfa73ac2e6a820e8ed Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:48:29 2021 +0700 Allow graph.max_value be nil This makes its behave symmetrically to min_value. Now it defaults to 1, when graph.scale is not set, and to the maximum value in the graph data, when graph.scale is set. commit f622a8430cbfde7007b58d7f4c9c76b08c91e997 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 12:01:13 2021 +0700 Calculate max values num in graph:add_value() more precisely At most so many values were kept in the widget values array, as there are pixels of usable widget width (i.e. sans border). This is exactly right for the surely most common case of graphs with 1px-wide bars, but is more than needed for graphs with step_width+step_spacing>1, and not enough for graphs with subpixel bars. Moreover this made graph autoscaling behave weirdly in graphs with thicker bars, because old values survived longer in the array and kept affecting min_value/max_value calculations even after they were shifted off-screen. The maximum number of kept values is now calculated exactly as the number of values that can be displayed at once, taking step_width and step_spacing into account. It might be worth adding a max_values_num property to the graph to let user override this calculation, in case they draw shapes in step_shape() outside of its supposed rectangle, so our assumptions of visibility are wrong, or the widget often gets resized, resulting in the annoyance of visibly truncated series. commit 0aa63249a18641251a1bf77423739164f0eab39f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 02:30:13 2021 +0700 Fix border_color/border_width bugs in graph widget add_value() wrongly assumed that border_width is always set, when border_color is, so if user set only the latter, an error occurred. add_value() assumed that border_width is always < width which led to awesome hanging up in an infinite loop, should that fail to be the case. The graph border_width property was driven by the border_color property, instead of the other way round. If border_color wasn't set, the border wasn't drawn all, even if user sets non-zero border_width, which is a confusing behavior, in contrast to all other widgets, which simply use a plethora of fallback colors to draw the border, when needed. This also meant that, despite being documented, the beautiful.graph_border_color fallback color could not and was not ever used. If the border_color was set though, an unset border_width was immediately assumed to be 1 instead of 0, also contrary to what other widgets do. Theme colors shouldn't influence widget layout, so this commit fixes this behavior to: 1) border_width always defaults to 0, if unset. 2) border_color falls back to beautiful.graph_border_color or white. Maybe one should also add a beautiful.graph_border_width theme variable, like some widgets do. commit dad4a3253b8fef971fb82720d2a407fcc0a879ad Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 03:11:27 2021 +0700 Implement multiple data series drawing for non-stacked graph The widget can already hold and draw multiple data groups, so it makes little sense to limit drawing only to the first group when graph.stack = false, probably leading to user's confusion. Now user can call add_value(v, group) with group > 1 and the data series will be drawn (over groups with lesser id-s) with graph.stack_colors[group] color, if stack_colors is set, or with graph.color, if stack_colors isn't set. Thus nothing visibly changes for all current users of non-stacked graphs with only single data series, but the new functionality is now there for those who need it. One can't fully emulate this functionality, e.g. with multiple graphs widgets in a wibox.layout.stack, because data series in a single widget can enjoy a common auto-scaling. commit 3e645c976184ab5f50d625271c25062db9ab7ed9 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:59:12 2021 +0700 Don't draw a stacked graph data group if there's no color for it If stack_colors[idx] is nil for the group `idx`, don't draw it at all (as opposed, e.g. to the effect of drawing it with a transparent color, or as though it had all values == 0, which both produce different results). This code is still subtly different from prior behavior, because now user can have a non-contiguous stack_colors table and turn the display of any data group on and off at will, whereas the code two commits earlier would just stop rendering as soon as ipairs(stack_colors) does, i.e. no data series were drawn past the first nil hole. This commit only looks large, but is mostly a whitespace change due to nesting from a pair of wrapping ifs. commit 8c0273786de1ade66bdd977699f98b588da9f487 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:32:42 2021 +0700 Unify stacked and non-stacked graph rendering Now stacked graphs support everything the nonstacked do, like step_width ~= 1 and custom step_shape-s. Technical notes: This code no longer calls cr:stroke() for every single value in a stacked graph, but rather paints a whole series and then cr:stroke/fill()-s it at all once with its color, improving performance considerably. Additionally the graph no longer relies on the presence of the stack_colors property to render series. It draws all data that it has, and, if there's no corresponding color in stack_colors, falls back to graph.color. This will be changed in the next commit to the prior behavior of only drawing those series for which there's a color in the stack_colors table, but that would've made this commit's diff much larger due to required nesting ifs over large code chunks. This commit also takes care to properly handle NaNs and negative values. Those are currently (almost) guaranteed to not occur in the values array due to clipping in graph:add_value(), but I intend to eventually drop the clipping, because it does nothing that draw() really relies upon, and allowing unclipped and NaN values is more flexible and less surprising, e.g. when user dynamically toggles graph.scale. Eventually I intend to implement drawing negative values in non-stacked graphs and possibly even in the stacked ones. commit 38c6589c51b2aa057d7c2f40182ecf32b5b2ff5e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 10:04:15 2021 +0700 Convert graph:draw() to use `self` commit f21f1b2550426df1c4ea8abde079b1ccef84b394 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 12:27:26 2021 +0700 Fix scaling for stacked graphs Stacked graphs have cumulative heights, values from all groups that end up in the same graph bar are effectively summed. Previous code didn't take that into account when calculating min/max_value for the purposes of rescaling, but simply examined every value separately. This code adds up all values up front and finds min/max among those, to determine proper scaling that would fit the graph exactly. commit 1ce4c7bf63bdd6c99c195d668347bec49355e921 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 09:49:35 2021 +0700 Draw shaped graph bars only up to the baseline Prior to this commit, every step_shape bar was, for some reason, drawn with height = full widget's height, regardless of the value it represents. So for the most bars their lower end was far outside the box and therefore clipped off. So e.g. one couldn't see the lower round edges of rounded rectangles. This commit fixes that by appropriately varying the height that is passed into step_shape(), so that lower edges of the bars always exactly touch the lower edge of the graph. If the user wants to restore the prior behavior, they can simply ignore the passed-in height in step_shape() and just always draw their shape with height = full widget height. But they couldn't replicate the effect of this commit in their rc.lua without essentially writing their own graph widget. commit e25185b08da23745b5815f85f6e9e43222ed2294 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 08:42:54 2021 +0700 Avoid incremental transform shifting during graph bar rendering Instead of letting addition errors and cognitive load accumulate simply use the same offset calculation that is used for lines and reset the transform back to the top-left corner after every drawn bar. It's conceptually simpler, and will be useful later for stacked graphs and more. It also fixes a discrepancy between plain and step_shape rendering. Prior to this commit, bar coloring occurred with a disturbed transform, which was centered at the last drawn bar, wherever it happened to be. This meant that nontrivial colors, like gradients and images, were arbitrarily offset when step_shape was used. commit f3e98866d8583c35d0b557c09f529299edc9ba2c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:45:29 2021 +0700 Refactor out graph bar height expression into a variable commit f54528d6892f5aaa4595917853c9e2130f743fdb Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:20:25 2021 +0700 Draw graph bars with lines only when step_width == 1 Before this commit graph bars of step_width < 1 were drawn with lines of width 1, but that's a bit unfortunate, because it led to much overpainting, while still ending up with a graphically incorrect result. Let's draw such bars with rectangles and let cairo do things with subpixels, getting more graphically accurate result, while still keeping the common case of step_width == 1 (supposedly) fast-drawn with lines. commit b6515dca67c18b78eb5657378131a62ab43d4cfc Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:54:07 2021 +0700 Fix unsharp edges when drawing graph bars with rectangles The 0.5 offset is only helpful for 1-width strokes, not for fills. commit 7f3db8f07e82f821bcd1dc418d66564f537609d6 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:21:01 2021 +0700 Do coordinate transformations even if the value isn't drawn The graph presently doesn't draw values < 0, but there was a discrepancy between drawing with and without step_shape. The default line/rectangle drawing reserves an empty place where the skipped value should've been, but no such empty place is reserved, when step_shape is used. This commit fixes that. Skipped values always have their empty space now. commit 7b731ebfe96af7a9dba9c57371da6761cf22c00c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:03:30 2021 +0700 Fix graph bar scaling when min_value is not 0 Previous calculation lead to underscaling of values so that even value = max_value wasn't drawn to full height. commit dca59fd8b7fff0e7e646f2dfc6bd92047028e5a8 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:18:40 2021 +0700 Calculate the horizontal graph bar offset only when needed commit 940ddf4b9825e9c3a91d276208c089d8c74e49db Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:17:23 2021 +0700 Call cr:move_to() only when drawing the graph with lines It's redundant otherwise and might even interfere with user's drawing in step_shape(). commit b5d5cd10ca29fb29cd751faaa95137c2fe89ff63 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:04:06 2021 +0700 Fix wrong horizontal offset calculations in widget.graph Indices in the loop are zero-based, so things like i-1 and i>1 aren't necessary and actually wouldn't have been correct even in a 1-based loop. step_shape = gears.shape.rectangle and step_shape = nil, which is supposed to default to rectangle according to docs, were drawn differently due to mismatched offset calculations, which led to problems especially noticeable when step_spacing is not zero, e.g: 1) whole graph shifted left too much, drawn partially off-screen. 2) whole graph shifted right too much, and the first bar placed closer to the rest than others. commit dc369fa198281e000afc1bd17326f3a264adf7d1 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 02:09:21 2021 +0700 Fix the "border_width affects graph bar width" bug Graph bars were drawn with lines of border_width thickness instead of 1, i.e. possibly too fat to be readable. commit 1ba73378cf29ed27a6ecd18716394779ceb543d9 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 00:13:03 2021 +0700 Simplify internal values representation in graph even more New values in graph._private.values were pushed to the end, and old values were removed from the beginning of the table. In draw() however newer values are drawn first, at the left edge of the graph, necessitating annoying idx = #values - idx conversions everywhere. In particular in stacked graph that means that values that are drawn together may have different indices. One has to shift the values array in add_value() either way, so one might as well insert new values at the beginning of the array and simplify the rest of the code. This doesn't seem like much yet, but it will prove to be much more convenient and less error-prone in what I'm going to do later. commit f70523ee68dbd6b67050ace4b82903a9b22440c7 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 13:35:10 2021 +0700 Simplify internal values representation in graph widget graph._private.values was either a table of values or a table of tables of values, depending on whether "stack" property is enabled, ... or a mix of both if user forgot to pass the second "group" parameter to graph:add_value(). This resulted in pretty fragile and confusing behavior, e.g. leaving user to wonder, why they see nothing more after setting stack=true for a graph in a previously working rc.lua, and demanding a very careful use of add_value() from the user, or else some values or even entire stacks would mysteriously vanish and errors in draw() get triggered, sometimes after a period of seemingly normal functioning. Now `values` is always a table of tables and the group parameter in add_value() defaults to 1, thus making the non-stacked graph a trivial variant of the stacked graph.
2021-05-20 17:14:03 +02:00
-- @propemits true false
-- @see gears.color
2016-05-27 07:23:12 +02:00
graph: complete widget overhaul and bug fixes Squashed commit of the following: commit 69821f51fe1e8652715658543e50631ece495090 Author: Alex Belykh <albel727@ngs.ru> Date: Mon May 10 19:17:55 2021 +0700 Refactor property handling in wibox.widget.graph In addition, baseline_value, step_width and step_spacing properties are all set to their default non-nil numeric values during instance creation now. commit 842eb429bf5df4b2ba46b2e6f7646afe89b1a3c3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 9 06:18:45 2021 +0700 Make graph:draw_values() private There's no documentation nor confidence that it's a good API, so I'm hiding it for now. commit 720746780574e4bad14a71fc4174955139c2eb50 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 04:04:13 2021 +0700 Document graph_should_draw_data_group() for posterity commit 7c73f3754ce6ba86b118f1170a6f9e406ab4841d Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:59:21 2021 +0700 Make graph:should_draw_data_group() private There's no confidence whether it's a good API, so I'm hiding it for now. commit b3539e20adb6423118af726ae4f8751ef864b5d2 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:36:44 2021 +0700 Make graph:preprocess_values() private There's no confidence whether it's a good API, so I'm hiding it for now. commit 2df4400e780a6260f8d0ab1c60734fbb11bf3c4f Author: Alex Belykh <albel727@ngs.ru> Date: Thu May 6 10:05:23 2021 +0700 Add spec/wibox/widget/graph_spec.lua commit ce01a9771ab439b8df841f1565aa4aa8a44a5e0d Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 08:16:18 2021 +0700 Improve graph.capacity documentation commit e28fade8f655802f5bf89154169f68d256b8e332 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 07:36:01 2021 +0700 Document graph:compute_drawn_values_num() commit dd68332292465422d08b48e9f0229f143beccbc6 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 05:24:23 2021 +0700 Document graph:pick_data_group_color() method commit ca6dc55f77aca88a6e9cc90bb6fd0045739f41f5 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 04:13:58 2021 +0700 Guard against setting data groups sparsely in graph:add_value() commit cdea5f126f7984e72924f6dffbd312c4fe3b032f Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 02:36:12 2021 +0700 Refactor scaling and baseline choice out of graph:draw() commit 10135deafe8a9390ba66ab657ebb1fb41d549e8c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 21:34:48 2021 +0700 Document graph.nan_color and graph.stack properties commit 0d73bb84ac66b87e16aee472bd1f299549f66a1c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 20:49:24 2021 +0700 Use a better example in docs for the graph.stack property commit c27d160a0826d033d49d6141608c40aa8a0642fa Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 16:05:38 2021 +0700 Document graph.baseline_value property commit 459bc8176c13d4ea32af0dcdc2399d3b929b1c6a Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:53:47 2021 +0700 Document graph.clamp_bars property commit e732ee0b3b007d213e689b98cc00ae7cb141ae08 Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:34 2021 +0700 Improve graph:add_value() and :clear() documentation commit ae73e3aa9f8d60409cbe4cb4da9612f31af4f38e Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:11 2021 +0700 Improve graph min_value/max_value/scale documentation commit a7350996a9def773b91b1631e6c0507e33d8f93b Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:27:36 2021 +0700 Cleanup graph examples to show only relevant things in docs commit 30740c26f4f41c16f4c8a3c339bcb7bd299e14cd Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 14:04:36 2021 +0700 Rename graph.stack_colors property to `group_colors` The colors are used for stacked and non-stacked graphs alike, so the name didn't make sense. Also document and clarify some color stuff. commit 1ea01bfd410971cf0d0c25fd74cd764d5eb7279d Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 21:25:41 2021 +0700 Add an example file for code coverage of some fringe graph cases commit eae68e39553f88a6275a0c65bf040b5d4c262b54 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 20:23:32 2021 +0700 Add step_shape to the graph/nan_color example for code coverage commit 8027147bc2eab34a97b49a428fb8cef27f122bed Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:55:18 2021 +0700 Add example files for graph.nan_color/nan_indication properties commit 89a25a165c52b2c15b6c4d95ecb777a9067e66f9 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:33:07 2021 +0700 Make widget graph:add_value(v) default v to NaN 0 as a default makes no sense and silences the error that is failing to specify a value, to which the user should rather be alerted. The graph widget has sane NaN handling now and will do just that. commit 2835d552440be48d356a7f7b1d64963696dd550b Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:20:48 2021 +0700 Implement graph.nan_color and nan_indication properties Now, whenever there's a NaN among values and graph.nan_indication is set, the corresponding area, where the value bar should have been drawn, is filled with the nan_color from top to bottom. The painting is done after all data is rendered, to make sure that it won't be overpainted and go unnoticed. NaNs among graph's values are inevitably a sign of some error and it is arguably not a sane default behavior to simply not render them at all. The user should take immediate note of any errors by default, instead of wondering at a mysteriously empty graph widget, which is why nan_indication is set to true by default. If the user wants to silence NaNs, they can always simply set it to false. nan_color if unset, defaults to a noticeable yellow-black pattern. As a direct and planned consequence, stacked graphs with negative values in them trigger the NaN handling too. But silencing NaN handling for those graphs is also possible and semi-sane, because then NaNs and negatives are ignored and the graph behaves (almost) like they were zeros. commit 9c1f8f08b400e01e19c534f3810c9c9ab485760c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 26 08:10:19 2021 +0700 Refactor out graph_map_value_to_widget_coordinates() function commit 1905991ddecabdae3e15ffb8ee5b18a1bee1a711 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 28 13:39:29 2021 +0700 Add an example file for graph.baseline_value property commit b31b39d66f4c037a9484b71a3f910703ae01fac0 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 07:03:49 2021 +0700 Add an example file for graph.clamp_bars=false/true comparison commit a1fc850e3047f9ee2c9ddeffacfbabf3ded34171 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 04:06:45 2021 +0700 Add an example file for graphs with negative values and shapes commit 2f012f8ff1bb5094c14e724a61e59a2a3a296bc8 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 01:06:05 2021 +0700 Add an example file for a graph displaying negative values commit 7b14fbd59a4c4c51962fd3110a1a900dd5f4eb98 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 11:18:26 2021 +0700 Add an example file for graphs with step_width < 1 commit dfd9a54544b637e2fbe3d1325f9cc24b3f4d2313 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 10:17:38 2021 +0700 Add an example file for graph.stack=false/true comparison Should also demonstrate that multiple data groups are possible for non-stacked graphs too. commit 5b532106c3fd255f9b66a66cfb88d977b489ab5d Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 09:34:14 2021 +0700 Add an example file for disabling graph data groups with nil colors commit ac6f1083c02fcb428f089a616f9b0ebaa5496b6c Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:57:25 2021 +0700 Add an example file for step_shape/width/spacing on stacked graphs commit b3703db6a5139e73e72c1cfd198ece2cd9be7df2 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:43:56 2021 +0700 Add an example file for scale=false/true on stacked graph commit b000aed4a12cb44fb501e1c8432a71a14b2f8f3f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 20 08:22:19 2021 +0700 Document deprecation of graph's width and height commit cb0ad617bd6c83a499896797318e3b0b588c3566 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 07:53:37 2021 +0700 Deprecate width and height properties of wibox.widget.graph commit 8c64e14ad91a074b784166a7054e53444cccb9d2 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 04:37:59 2021 +0700 Refactor graph:draw() to separate concerns Firstly, summation of group values for stacked graphs is extracted into graph:preprocess_values(). This makes graph:draw() smaller and exposes the fact, that stacked graphs are almost entirely just a form of a more general concept: pre-render data manipulation. It's something a user might want to customize, and now they can do it by monkey-patching the method. Secondly, the logic for deciding, if and with which color a data group should be drawn, is extracted into graph:should_draw_data_group() and get_data_group_color() methods respectively. This makes it customizable too and makes more obvious the fact, that it is a general thing that has nothing to do with stacked graphs in particular. With this there are only 5 lines remaining in graph:draw(), that could be said to be specific to stacked graphs, all pertaining to the `prev_y` variable, which merely preserves the bar coordinates from a previous data group. commit 2a32b6305d893f413303728a0a3da1dcd9a5591e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 22:52:37 2021 +0700 Pointlessly optimize graph:add_value() 1) Purge old values first, then insert the new one, if needed. Since Lua grows arrays in power of two steps, and our guess_capacity() also takes care to return even integers, it would be a shame to overstep the capacity by one by inserting first and thus getting the table size doubled only to never use the most of it. 2) Use a removal procedure that doesn't call the slow length operator at all. Previous code called it twice (once in table.remove) for every removed value. commit 680ea8f5d81e9040afb4662b3229e937e4b89e02 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:23:38 2021 +0700 Set graph data series color before the painting loop commit b970f0f221ae76250f2a94433ff79d653caa6e21 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:05:37 2021 +0700 Drop a large no longer necessary `if` in graph:draw_values() This commit is almost purely a whitespace change. commit 6b4f0541c70ec99d7f6e276bae64939f337120d6 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:00:50 2021 +0700 Extract values rendering code into graph:draw_values() method This is to avoid ungodly if nesting levels, which are needed to bail out of drawing values early and skip to drawing the border. The next commit will get rid of the if. commit e0950e4f99d1ea1c4b8cea2cc67629fa40030c2a Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 14:50:20 2021 +0700 Track graph draw() usage to guess necessary array capacity The last step of divorcing add_values() from width calculations, while ensuring that user doesn't observe a lack of data to draw. The widget now tracks an approximate number of values that it has been asked to draw() in _private.last_drawn_values_num, and truncates its values array based on that (if not overridden by the capacity property). commit 2e3d542282f35e7c0fed454da0f2eada0fcf3b78 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 13:35:12 2021 +0700 Add `capacity` property to graph widget This is the second step of divorcing add_values() from calculations involving widths. How many values are stored and how many values are drawn are distinct concepts. There isn't even a single definitive "how many values are drawn" because the widget could be rendered in several places with distinct widths simultaneously, and in every such place the data must be drawn appropriately, e.g. with auto-scaling, that takes only the visible data points into account. And the draw() method can do that now. The default capacity value of nil uses heuristics based on widget's _private.width to guess some capacity that would tolerate some dynamic widget resizing. If the user finds the heuristic failing them, they can set the capacity property to the desired maximum number of values that will be kept in the _private.values array (in each data group). commit 08d4a80686f7fe363551c35589a460839f60853e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 03:27:18 2021 +0700 Extract values size computation into compute_drawn_values_num() This is the first step of making add_values() not concern itself (directly) with widget dimensions. The user can monkey-patch the method to override how many values are (logically) drawn, because it affects graph auto-scaling and can be useful for rendering of step_shape-s that paint outside of their step_width. I didn't make it a full-blown function-valued property, because it seems a fringe functionality, which I'm too lazy to document. commit 3c5617c98ab1147618ed22e8414a77fc56b45f6e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 02:32:29 2021 +0700 Copy graph widget prototype methods over in bulk commit 169538837ece41a03d02ba8fbc0e4097062b67a3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun Apr 25 05:58:01 2021 +0700 Remove graph.baseline_y property After giving it some thought I've concluded, that it is unlikely to be useful as it is now. I'd better reserve the name for some more sensible functionality, like fixing the baseline_value defined axis at the given widget coordinate, adjusting the scaling accordingly, so min_value/max_value won't generally correspond to widget top and bottom anymore. commit 202ffaa9a0b820c7c471e1121d01b91f8f76cd38 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 12:06:55 2021 +0700 Add baseline_y and baseline_value properties to graph widget Now values in graph have a *baseline*, tunable with baseline_y and baseline_value properties. It is the (for now invisible) horizontal line from which all bars in the graph grow. I call it a "baseline" instead of "axis", because it doesn't actually have to correspond to the 0 value. But it can, and actually does now by default. baseline_y and baseline_value properties determine the vertical placement of the baseline in the widget. baseline_y is the position of the baseline in normalized widget coordinates, e.g. 0 corresponds to widget's top, 1 - to widget's bottom, and 0.5 - to widget's middle. So if e.g. one sets baseline_y = 0, then no matter how the widget is resized and how its values are scaled, bars in it will be drawn starting from the top. baseline_value is the position of the baseline in value coordinates. So if one sets baseline_value = 10, then no matter what happens, bars for values greater than 10 will be drawn as growing up from this line, and bars for values smaller as growing down. Where and if it will be seen in the widget, depends on the scaling. baseline_value = 0 is the familiar zero axis. baseline_y is unset by default and has priority, i.e. if it's set, then baseline_value is ignored. baseline_value defaults to 0. Prior to this commit, bars were always drawn from the bottom edge of the widget, or, in the new terms, the widget had baseline_y = 1. But it was indistinguishable from baseline_value = 0, because the widget clamped all negative values to 0, so the difference between the two could never be observed. baseline_value = 0 is clearly more useful and familiar to people, so now it is in force by default. commit df7e19abaebf0a871b5ca217b4cc3365533267a6 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 11:18:12 2021 +0700 Move value clamping from graph:add_value() into draw() Previously all values were forcibly clamped between 0 (not tunable at all) and max_value (1 by default). Thus graphs with negative values were not an option. NaN values were conflated with zeros, even though it may be useful to maintain the distinction and render them differently. And if user happened to dynamically change the max_value property, they discovered, that their data got truncated. Preserving user data as it was given is more flexible and makes things easier to debug. This commit does exactly that. So what happens now to the newly allowed values during draw()? There are 3 new cases to consider: 1) NaNs 2) Negatives 3) Values outside of min_value..max_value range NaNs are presently simply not drawn. An empty place is reserved where the value should've been, but nothing else is done. This is distinct from how a zero is handled, e.g. if step_shape is set, it is called with the height parameter = 0, which can still end up drawing something, depending on the shape. One could also consider addition of nan_color/nan_shape properties which could be drawn to alert user to a gap in their data. Negative values necessitate introduction of the concept of the zero axis, from which they will be drawn DOWN, in contrast to positive values. Where should this axis be drawn? How should step_shape-s with inherent direction, e.g. arrows, be drawn for negatives? This will be addressed in the next commit. Values outside of min/max_value range can imply shapes that go beyond widget dimensions. An arrow shape that goes beyond the roof is visibly different from an arrow that saturates and merely touches the widget top, and the user may reasonably desire either behavior. Therefore this commit introduces a boolean clamp_bars property, which defaults to true for reasons of robustness and backward compatibility with the behaviour prior to this commit. The property of course affects only the display of the values, the actual user data stays intact. commit c9c24fb1ab741cd6bb8074971d568e0646c51532 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:29:01 2021 +0700 Use rectangles for drawing graph bars of all sizes I have benchmarked various bar drawing strategies and graph sizes and found out that drawing graph bars with rectangles is as fast or even marginally faster than using fat lines. Thus the code that tries to special-case 1px bars as lines seems to be not worth the additional complexity it introduces, and this commit removes it. Incidentally, I've also established, that rectangles are more robust. If one draws graph bars and feeds cairo very large coordinates (~2^62), then corresponding rectangles fail to render, but all the other rectangles in the same series get rendered successfully, whereas a single large coordinate in a move_to/line_to() sequence is enough to cause all lines in the batch to fail to render. commit bc7f4be5e06405b5aa7fb443a5ace2b9b9575b1f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:01:42 2021 +0700 Cache cairo methods used in the inner loop of graph:draw() I have benchmarked the draw() method, both on cairo xcb and on argb32 image surfaces, and have discovered that cairo context method calls are unnaturally slow, to the point, that almost nothing else in the function matters. For example, drawing bars with vertical lines made the whole draw() function almost 2 times slower than doing the same with rectangles, merely because the former requires two method calls (move_to/line_to), and the latter only one. Simply caching hotpath cairo methods in local variables, reduced the difference between the drawing methods to a negligible one and made the whole function 4 times faster on a typical 100-bar graph, (1.4ms to 0.33ms on my laptop) thus bringing closer my secret dream of drawing live audio data with awesome graphs (kidding). I guess, LGI does something not very optimal there, like gobject-introspecting the method every time, or something. There might be some untapped optimization potential, possibly in upstream, like a method cache, from which everything in awesome could benefit, even though I imagine not many things are so call-intensive as the graph widget, to really feel the difference. commit 538df25bdf02b53d0bbe81809eef2e6fdb05ff6f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 07:32:37 2021 +0700 Use 1-based index in the inner graph draw loop It's used there only once for a calculation and then several times to index some tables, and index 0 is bad for lua tables performance-wise. commit 7412546916cd0a0a490130a120255ffb719398b8 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:55:12 2021 +0700 Leave graph.max_value uninitialized at construction This is a user-visible change, but the previous behavior was probably not what user expected anyway, namely with graph.scale set the graph mysteriously couldn't zoom in onto values that are smaller than 1. commit eb81d7929a161e79c13262cfa73ac2e6a820e8ed Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:48:29 2021 +0700 Allow graph.max_value be nil This makes its behave symmetrically to min_value. Now it defaults to 1, when graph.scale is not set, and to the maximum value in the graph data, when graph.scale is set. commit f622a8430cbfde7007b58d7f4c9c76b08c91e997 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 12:01:13 2021 +0700 Calculate max values num in graph:add_value() more precisely At most so many values were kept in the widget values array, as there are pixels of usable widget width (i.e. sans border). This is exactly right for the surely most common case of graphs with 1px-wide bars, but is more than needed for graphs with step_width+step_spacing>1, and not enough for graphs with subpixel bars. Moreover this made graph autoscaling behave weirdly in graphs with thicker bars, because old values survived longer in the array and kept affecting min_value/max_value calculations even after they were shifted off-screen. The maximum number of kept values is now calculated exactly as the number of values that can be displayed at once, taking step_width and step_spacing into account. It might be worth adding a max_values_num property to the graph to let user override this calculation, in case they draw shapes in step_shape() outside of its supposed rectangle, so our assumptions of visibility are wrong, or the widget often gets resized, resulting in the annoyance of visibly truncated series. commit 0aa63249a18641251a1bf77423739164f0eab39f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 02:30:13 2021 +0700 Fix border_color/border_width bugs in graph widget add_value() wrongly assumed that border_width is always set, when border_color is, so if user set only the latter, an error occurred. add_value() assumed that border_width is always < width which led to awesome hanging up in an infinite loop, should that fail to be the case. The graph border_width property was driven by the border_color property, instead of the other way round. If border_color wasn't set, the border wasn't drawn all, even if user sets non-zero border_width, which is a confusing behavior, in contrast to all other widgets, which simply use a plethora of fallback colors to draw the border, when needed. This also meant that, despite being documented, the beautiful.graph_border_color fallback color could not and was not ever used. If the border_color was set though, an unset border_width was immediately assumed to be 1 instead of 0, also contrary to what other widgets do. Theme colors shouldn't influence widget layout, so this commit fixes this behavior to: 1) border_width always defaults to 0, if unset. 2) border_color falls back to beautiful.graph_border_color or white. Maybe one should also add a beautiful.graph_border_width theme variable, like some widgets do. commit dad4a3253b8fef971fb82720d2a407fcc0a879ad Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 03:11:27 2021 +0700 Implement multiple data series drawing for non-stacked graph The widget can already hold and draw multiple data groups, so it makes little sense to limit drawing only to the first group when graph.stack = false, probably leading to user's confusion. Now user can call add_value(v, group) with group > 1 and the data series will be drawn (over groups with lesser id-s) with graph.stack_colors[group] color, if stack_colors is set, or with graph.color, if stack_colors isn't set. Thus nothing visibly changes for all current users of non-stacked graphs with only single data series, but the new functionality is now there for those who need it. One can't fully emulate this functionality, e.g. with multiple graphs widgets in a wibox.layout.stack, because data series in a single widget can enjoy a common auto-scaling. commit 3e645c976184ab5f50d625271c25062db9ab7ed9 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:59:12 2021 +0700 Don't draw a stacked graph data group if there's no color for it If stack_colors[idx] is nil for the group `idx`, don't draw it at all (as opposed, e.g. to the effect of drawing it with a transparent color, or as though it had all values == 0, which both produce different results). This code is still subtly different from prior behavior, because now user can have a non-contiguous stack_colors table and turn the display of any data group on and off at will, whereas the code two commits earlier would just stop rendering as soon as ipairs(stack_colors) does, i.e. no data series were drawn past the first nil hole. This commit only looks large, but is mostly a whitespace change due to nesting from a pair of wrapping ifs. commit 8c0273786de1ade66bdd977699f98b588da9f487 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:32:42 2021 +0700 Unify stacked and non-stacked graph rendering Now stacked graphs support everything the nonstacked do, like step_width ~= 1 and custom step_shape-s. Technical notes: This code no longer calls cr:stroke() for every single value in a stacked graph, but rather paints a whole series and then cr:stroke/fill()-s it at all once with its color, improving performance considerably. Additionally the graph no longer relies on the presence of the stack_colors property to render series. It draws all data that it has, and, if there's no corresponding color in stack_colors, falls back to graph.color. This will be changed in the next commit to the prior behavior of only drawing those series for which there's a color in the stack_colors table, but that would've made this commit's diff much larger due to required nesting ifs over large code chunks. This commit also takes care to properly handle NaNs and negative values. Those are currently (almost) guaranteed to not occur in the values array due to clipping in graph:add_value(), but I intend to eventually drop the clipping, because it does nothing that draw() really relies upon, and allowing unclipped and NaN values is more flexible and less surprising, e.g. when user dynamically toggles graph.scale. Eventually I intend to implement drawing negative values in non-stacked graphs and possibly even in the stacked ones. commit 38c6589c51b2aa057d7c2f40182ecf32b5b2ff5e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 10:04:15 2021 +0700 Convert graph:draw() to use `self` commit f21f1b2550426df1c4ea8abde079b1ccef84b394 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 12:27:26 2021 +0700 Fix scaling for stacked graphs Stacked graphs have cumulative heights, values from all groups that end up in the same graph bar are effectively summed. Previous code didn't take that into account when calculating min/max_value for the purposes of rescaling, but simply examined every value separately. This code adds up all values up front and finds min/max among those, to determine proper scaling that would fit the graph exactly. commit 1ce4c7bf63bdd6c99c195d668347bec49355e921 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 09:49:35 2021 +0700 Draw shaped graph bars only up to the baseline Prior to this commit, every step_shape bar was, for some reason, drawn with height = full widget's height, regardless of the value it represents. So for the most bars their lower end was far outside the box and therefore clipped off. So e.g. one couldn't see the lower round edges of rounded rectangles. This commit fixes that by appropriately varying the height that is passed into step_shape(), so that lower edges of the bars always exactly touch the lower edge of the graph. If the user wants to restore the prior behavior, they can simply ignore the passed-in height in step_shape() and just always draw their shape with height = full widget height. But they couldn't replicate the effect of this commit in their rc.lua without essentially writing their own graph widget. commit e25185b08da23745b5815f85f6e9e43222ed2294 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 08:42:54 2021 +0700 Avoid incremental transform shifting during graph bar rendering Instead of letting addition errors and cognitive load accumulate simply use the same offset calculation that is used for lines and reset the transform back to the top-left corner after every drawn bar. It's conceptually simpler, and will be useful later for stacked graphs and more. It also fixes a discrepancy between plain and step_shape rendering. Prior to this commit, bar coloring occurred with a disturbed transform, which was centered at the last drawn bar, wherever it happened to be. This meant that nontrivial colors, like gradients and images, were arbitrarily offset when step_shape was used. commit f3e98866d8583c35d0b557c09f529299edc9ba2c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:45:29 2021 +0700 Refactor out graph bar height expression into a variable commit f54528d6892f5aaa4595917853c9e2130f743fdb Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:20:25 2021 +0700 Draw graph bars with lines only when step_width == 1 Before this commit graph bars of step_width < 1 were drawn with lines of width 1, but that's a bit unfortunate, because it led to much overpainting, while still ending up with a graphically incorrect result. Let's draw such bars with rectangles and let cairo do things with subpixels, getting more graphically accurate result, while still keeping the common case of step_width == 1 (supposedly) fast-drawn with lines. commit b6515dca67c18b78eb5657378131a62ab43d4cfc Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:54:07 2021 +0700 Fix unsharp edges when drawing graph bars with rectangles The 0.5 offset is only helpful for 1-width strokes, not for fills. commit 7f3db8f07e82f821bcd1dc418d66564f537609d6 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:21:01 2021 +0700 Do coordinate transformations even if the value isn't drawn The graph presently doesn't draw values < 0, but there was a discrepancy between drawing with and without step_shape. The default line/rectangle drawing reserves an empty place where the skipped value should've been, but no such empty place is reserved, when step_shape is used. This commit fixes that. Skipped values always have their empty space now. commit 7b731ebfe96af7a9dba9c57371da6761cf22c00c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:03:30 2021 +0700 Fix graph bar scaling when min_value is not 0 Previous calculation lead to underscaling of values so that even value = max_value wasn't drawn to full height. commit dca59fd8b7fff0e7e646f2dfc6bd92047028e5a8 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:18:40 2021 +0700 Calculate the horizontal graph bar offset only when needed commit 940ddf4b9825e9c3a91d276208c089d8c74e49db Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:17:23 2021 +0700 Call cr:move_to() only when drawing the graph with lines It's redundant otherwise and might even interfere with user's drawing in step_shape(). commit b5d5cd10ca29fb29cd751faaa95137c2fe89ff63 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:04:06 2021 +0700 Fix wrong horizontal offset calculations in widget.graph Indices in the loop are zero-based, so things like i-1 and i>1 aren't necessary and actually wouldn't have been correct even in a 1-based loop. step_shape = gears.shape.rectangle and step_shape = nil, which is supposed to default to rectangle according to docs, were drawn differently due to mismatched offset calculations, which led to problems especially noticeable when step_spacing is not zero, e.g: 1) whole graph shifted left too much, drawn partially off-screen. 2) whole graph shifted right too much, and the first bar placed closer to the rest than others. commit dc369fa198281e000afc1bd17326f3a264adf7d1 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 02:09:21 2021 +0700 Fix the "border_width affects graph bar width" bug Graph bars were drawn with lines of border_width thickness instead of 1, i.e. possibly too fat to be readable. commit 1ba73378cf29ed27a6ecd18716394779ceb543d9 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 00:13:03 2021 +0700 Simplify internal values representation in graph even more New values in graph._private.values were pushed to the end, and old values were removed from the beginning of the table. In draw() however newer values are drawn first, at the left edge of the graph, necessitating annoying idx = #values - idx conversions everywhere. In particular in stacked graph that means that values that are drawn together may have different indices. One has to shift the values array in add_value() either way, so one might as well insert new values at the beginning of the array and simplify the rest of the code. This doesn't seem like much yet, but it will prove to be much more convenient and less error-prone in what I'm going to do later. commit f70523ee68dbd6b67050ace4b82903a9b22440c7 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 13:35:10 2021 +0700 Simplify internal values representation in graph widget graph._private.values was either a table of values or a table of tables of values, depending on whether "stack" property is enabled, ... or a mix of both if user forgot to pass the second "group" parameter to graph:add_value(). This resulted in pretty fragile and confusing behavior, e.g. leaving user to wonder, why they see nothing more after setting stack=true for a graph in a previously working rc.lua, and demanding a very careful use of add_value() from the user, or else some values or even entire stacks would mysteriously vanish and errors in draw() get triggered, sometimes after a period of seemingly normal functioning. Now `values` is always a table of tables and the group parameter in add_value() defaults to 1, thus making the non-stacked graph a trivial variant of the stacked graph.
2021-05-20 17:14:03 +02:00
--- The graph foreground color
-- Used, when the `color` property isn't set.
--
2016-05-27 07:23:12 +02:00
-- @beautiful beautiful.graph_fg
-- @param color
2016-05-27 07:23:12 +02:00
graph: complete widget overhaul and bug fixes Squashed commit of the following: commit 69821f51fe1e8652715658543e50631ece495090 Author: Alex Belykh <albel727@ngs.ru> Date: Mon May 10 19:17:55 2021 +0700 Refactor property handling in wibox.widget.graph In addition, baseline_value, step_width and step_spacing properties are all set to their default non-nil numeric values during instance creation now. commit 842eb429bf5df4b2ba46b2e6f7646afe89b1a3c3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 9 06:18:45 2021 +0700 Make graph:draw_values() private There's no documentation nor confidence that it's a good API, so I'm hiding it for now. commit 720746780574e4bad14a71fc4174955139c2eb50 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 04:04:13 2021 +0700 Document graph_should_draw_data_group() for posterity commit 7c73f3754ce6ba86b118f1170a6f9e406ab4841d Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:59:21 2021 +0700 Make graph:should_draw_data_group() private There's no confidence whether it's a good API, so I'm hiding it for now. commit b3539e20adb6423118af726ae4f8751ef864b5d2 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:36:44 2021 +0700 Make graph:preprocess_values() private There's no confidence whether it's a good API, so I'm hiding it for now. commit 2df4400e780a6260f8d0ab1c60734fbb11bf3c4f Author: Alex Belykh <albel727@ngs.ru> Date: Thu May 6 10:05:23 2021 +0700 Add spec/wibox/widget/graph_spec.lua commit ce01a9771ab439b8df841f1565aa4aa8a44a5e0d Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 08:16:18 2021 +0700 Improve graph.capacity documentation commit e28fade8f655802f5bf89154169f68d256b8e332 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 07:36:01 2021 +0700 Document graph:compute_drawn_values_num() commit dd68332292465422d08b48e9f0229f143beccbc6 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 05:24:23 2021 +0700 Document graph:pick_data_group_color() method commit ca6dc55f77aca88a6e9cc90bb6fd0045739f41f5 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 04:13:58 2021 +0700 Guard against setting data groups sparsely in graph:add_value() commit cdea5f126f7984e72924f6dffbd312c4fe3b032f Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 02:36:12 2021 +0700 Refactor scaling and baseline choice out of graph:draw() commit 10135deafe8a9390ba66ab657ebb1fb41d549e8c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 21:34:48 2021 +0700 Document graph.nan_color and graph.stack properties commit 0d73bb84ac66b87e16aee472bd1f299549f66a1c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 20:49:24 2021 +0700 Use a better example in docs for the graph.stack property commit c27d160a0826d033d49d6141608c40aa8a0642fa Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 16:05:38 2021 +0700 Document graph.baseline_value property commit 459bc8176c13d4ea32af0dcdc2399d3b929b1c6a Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:53:47 2021 +0700 Document graph.clamp_bars property commit e732ee0b3b007d213e689b98cc00ae7cb141ae08 Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:34 2021 +0700 Improve graph:add_value() and :clear() documentation commit ae73e3aa9f8d60409cbe4cb4da9612f31af4f38e Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:11 2021 +0700 Improve graph min_value/max_value/scale documentation commit a7350996a9def773b91b1631e6c0507e33d8f93b Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:27:36 2021 +0700 Cleanup graph examples to show only relevant things in docs commit 30740c26f4f41c16f4c8a3c339bcb7bd299e14cd Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 14:04:36 2021 +0700 Rename graph.stack_colors property to `group_colors` The colors are used for stacked and non-stacked graphs alike, so the name didn't make sense. Also document and clarify some color stuff. commit 1ea01bfd410971cf0d0c25fd74cd764d5eb7279d Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 21:25:41 2021 +0700 Add an example file for code coverage of some fringe graph cases commit eae68e39553f88a6275a0c65bf040b5d4c262b54 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 20:23:32 2021 +0700 Add step_shape to the graph/nan_color example for code coverage commit 8027147bc2eab34a97b49a428fb8cef27f122bed Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:55:18 2021 +0700 Add example files for graph.nan_color/nan_indication properties commit 89a25a165c52b2c15b6c4d95ecb777a9067e66f9 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:33:07 2021 +0700 Make widget graph:add_value(v) default v to NaN 0 as a default makes no sense and silences the error that is failing to specify a value, to which the user should rather be alerted. The graph widget has sane NaN handling now and will do just that. commit 2835d552440be48d356a7f7b1d64963696dd550b Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:20:48 2021 +0700 Implement graph.nan_color and nan_indication properties Now, whenever there's a NaN among values and graph.nan_indication is set, the corresponding area, where the value bar should have been drawn, is filled with the nan_color from top to bottom. The painting is done after all data is rendered, to make sure that it won't be overpainted and go unnoticed. NaNs among graph's values are inevitably a sign of some error and it is arguably not a sane default behavior to simply not render them at all. The user should take immediate note of any errors by default, instead of wondering at a mysteriously empty graph widget, which is why nan_indication is set to true by default. If the user wants to silence NaNs, they can always simply set it to false. nan_color if unset, defaults to a noticeable yellow-black pattern. As a direct and planned consequence, stacked graphs with negative values in them trigger the NaN handling too. But silencing NaN handling for those graphs is also possible and semi-sane, because then NaNs and negatives are ignored and the graph behaves (almost) like they were zeros. commit 9c1f8f08b400e01e19c534f3810c9c9ab485760c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 26 08:10:19 2021 +0700 Refactor out graph_map_value_to_widget_coordinates() function commit 1905991ddecabdae3e15ffb8ee5b18a1bee1a711 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 28 13:39:29 2021 +0700 Add an example file for graph.baseline_value property commit b31b39d66f4c037a9484b71a3f910703ae01fac0 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 07:03:49 2021 +0700 Add an example file for graph.clamp_bars=false/true comparison commit a1fc850e3047f9ee2c9ddeffacfbabf3ded34171 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 04:06:45 2021 +0700 Add an example file for graphs with negative values and shapes commit 2f012f8ff1bb5094c14e724a61e59a2a3a296bc8 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 01:06:05 2021 +0700 Add an example file for a graph displaying negative values commit 7b14fbd59a4c4c51962fd3110a1a900dd5f4eb98 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 11:18:26 2021 +0700 Add an example file for graphs with step_width < 1 commit dfd9a54544b637e2fbe3d1325f9cc24b3f4d2313 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 10:17:38 2021 +0700 Add an example file for graph.stack=false/true comparison Should also demonstrate that multiple data groups are possible for non-stacked graphs too. commit 5b532106c3fd255f9b66a66cfb88d977b489ab5d Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 09:34:14 2021 +0700 Add an example file for disabling graph data groups with nil colors commit ac6f1083c02fcb428f089a616f9b0ebaa5496b6c Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:57:25 2021 +0700 Add an example file for step_shape/width/spacing on stacked graphs commit b3703db6a5139e73e72c1cfd198ece2cd9be7df2 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:43:56 2021 +0700 Add an example file for scale=false/true on stacked graph commit b000aed4a12cb44fb501e1c8432a71a14b2f8f3f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 20 08:22:19 2021 +0700 Document deprecation of graph's width and height commit cb0ad617bd6c83a499896797318e3b0b588c3566 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 07:53:37 2021 +0700 Deprecate width and height properties of wibox.widget.graph commit 8c64e14ad91a074b784166a7054e53444cccb9d2 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 04:37:59 2021 +0700 Refactor graph:draw() to separate concerns Firstly, summation of group values for stacked graphs is extracted into graph:preprocess_values(). This makes graph:draw() smaller and exposes the fact, that stacked graphs are almost entirely just a form of a more general concept: pre-render data manipulation. It's something a user might want to customize, and now they can do it by monkey-patching the method. Secondly, the logic for deciding, if and with which color a data group should be drawn, is extracted into graph:should_draw_data_group() and get_data_group_color() methods respectively. This makes it customizable too and makes more obvious the fact, that it is a general thing that has nothing to do with stacked graphs in particular. With this there are only 5 lines remaining in graph:draw(), that could be said to be specific to stacked graphs, all pertaining to the `prev_y` variable, which merely preserves the bar coordinates from a previous data group. commit 2a32b6305d893f413303728a0a3da1dcd9a5591e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 22:52:37 2021 +0700 Pointlessly optimize graph:add_value() 1) Purge old values first, then insert the new one, if needed. Since Lua grows arrays in power of two steps, and our guess_capacity() also takes care to return even integers, it would be a shame to overstep the capacity by one by inserting first and thus getting the table size doubled only to never use the most of it. 2) Use a removal procedure that doesn't call the slow length operator at all. Previous code called it twice (once in table.remove) for every removed value. commit 680ea8f5d81e9040afb4662b3229e937e4b89e02 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:23:38 2021 +0700 Set graph data series color before the painting loop commit b970f0f221ae76250f2a94433ff79d653caa6e21 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:05:37 2021 +0700 Drop a large no longer necessary `if` in graph:draw_values() This commit is almost purely a whitespace change. commit 6b4f0541c70ec99d7f6e276bae64939f337120d6 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:00:50 2021 +0700 Extract values rendering code into graph:draw_values() method This is to avoid ungodly if nesting levels, which are needed to bail out of drawing values early and skip to drawing the border. The next commit will get rid of the if. commit e0950e4f99d1ea1c4b8cea2cc67629fa40030c2a Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 14:50:20 2021 +0700 Track graph draw() usage to guess necessary array capacity The last step of divorcing add_values() from width calculations, while ensuring that user doesn't observe a lack of data to draw. The widget now tracks an approximate number of values that it has been asked to draw() in _private.last_drawn_values_num, and truncates its values array based on that (if not overridden by the capacity property). commit 2e3d542282f35e7c0fed454da0f2eada0fcf3b78 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 13:35:12 2021 +0700 Add `capacity` property to graph widget This is the second step of divorcing add_values() from calculations involving widths. How many values are stored and how many values are drawn are distinct concepts. There isn't even a single definitive "how many values are drawn" because the widget could be rendered in several places with distinct widths simultaneously, and in every such place the data must be drawn appropriately, e.g. with auto-scaling, that takes only the visible data points into account. And the draw() method can do that now. The default capacity value of nil uses heuristics based on widget's _private.width to guess some capacity that would tolerate some dynamic widget resizing. If the user finds the heuristic failing them, they can set the capacity property to the desired maximum number of values that will be kept in the _private.values array (in each data group). commit 08d4a80686f7fe363551c35589a460839f60853e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 03:27:18 2021 +0700 Extract values size computation into compute_drawn_values_num() This is the first step of making add_values() not concern itself (directly) with widget dimensions. The user can monkey-patch the method to override how many values are (logically) drawn, because it affects graph auto-scaling and can be useful for rendering of step_shape-s that paint outside of their step_width. I didn't make it a full-blown function-valued property, because it seems a fringe functionality, which I'm too lazy to document. commit 3c5617c98ab1147618ed22e8414a77fc56b45f6e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 02:32:29 2021 +0700 Copy graph widget prototype methods over in bulk commit 169538837ece41a03d02ba8fbc0e4097062b67a3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun Apr 25 05:58:01 2021 +0700 Remove graph.baseline_y property After giving it some thought I've concluded, that it is unlikely to be useful as it is now. I'd better reserve the name for some more sensible functionality, like fixing the baseline_value defined axis at the given widget coordinate, adjusting the scaling accordingly, so min_value/max_value won't generally correspond to widget top and bottom anymore. commit 202ffaa9a0b820c7c471e1121d01b91f8f76cd38 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 12:06:55 2021 +0700 Add baseline_y and baseline_value properties to graph widget Now values in graph have a *baseline*, tunable with baseline_y and baseline_value properties. It is the (for now invisible) horizontal line from which all bars in the graph grow. I call it a "baseline" instead of "axis", because it doesn't actually have to correspond to the 0 value. But it can, and actually does now by default. baseline_y and baseline_value properties determine the vertical placement of the baseline in the widget. baseline_y is the position of the baseline in normalized widget coordinates, e.g. 0 corresponds to widget's top, 1 - to widget's bottom, and 0.5 - to widget's middle. So if e.g. one sets baseline_y = 0, then no matter how the widget is resized and how its values are scaled, bars in it will be drawn starting from the top. baseline_value is the position of the baseline in value coordinates. So if one sets baseline_value = 10, then no matter what happens, bars for values greater than 10 will be drawn as growing up from this line, and bars for values smaller as growing down. Where and if it will be seen in the widget, depends on the scaling. baseline_value = 0 is the familiar zero axis. baseline_y is unset by default and has priority, i.e. if it's set, then baseline_value is ignored. baseline_value defaults to 0. Prior to this commit, bars were always drawn from the bottom edge of the widget, or, in the new terms, the widget had baseline_y = 1. But it was indistinguishable from baseline_value = 0, because the widget clamped all negative values to 0, so the difference between the two could never be observed. baseline_value = 0 is clearly more useful and familiar to people, so now it is in force by default. commit df7e19abaebf0a871b5ca217b4cc3365533267a6 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 11:18:12 2021 +0700 Move value clamping from graph:add_value() into draw() Previously all values were forcibly clamped between 0 (not tunable at all) and max_value (1 by default). Thus graphs with negative values were not an option. NaN values were conflated with zeros, even though it may be useful to maintain the distinction and render them differently. And if user happened to dynamically change the max_value property, they discovered, that their data got truncated. Preserving user data as it was given is more flexible and makes things easier to debug. This commit does exactly that. So what happens now to the newly allowed values during draw()? There are 3 new cases to consider: 1) NaNs 2) Negatives 3) Values outside of min_value..max_value range NaNs are presently simply not drawn. An empty place is reserved where the value should've been, but nothing else is done. This is distinct from how a zero is handled, e.g. if step_shape is set, it is called with the height parameter = 0, which can still end up drawing something, depending on the shape. One could also consider addition of nan_color/nan_shape properties which could be drawn to alert user to a gap in their data. Negative values necessitate introduction of the concept of the zero axis, from which they will be drawn DOWN, in contrast to positive values. Where should this axis be drawn? How should step_shape-s with inherent direction, e.g. arrows, be drawn for negatives? This will be addressed in the next commit. Values outside of min/max_value range can imply shapes that go beyond widget dimensions. An arrow shape that goes beyond the roof is visibly different from an arrow that saturates and merely touches the widget top, and the user may reasonably desire either behavior. Therefore this commit introduces a boolean clamp_bars property, which defaults to true for reasons of robustness and backward compatibility with the behaviour prior to this commit. The property of course affects only the display of the values, the actual user data stays intact. commit c9c24fb1ab741cd6bb8074971d568e0646c51532 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:29:01 2021 +0700 Use rectangles for drawing graph bars of all sizes I have benchmarked various bar drawing strategies and graph sizes and found out that drawing graph bars with rectangles is as fast or even marginally faster than using fat lines. Thus the code that tries to special-case 1px bars as lines seems to be not worth the additional complexity it introduces, and this commit removes it. Incidentally, I've also established, that rectangles are more robust. If one draws graph bars and feeds cairo very large coordinates (~2^62), then corresponding rectangles fail to render, but all the other rectangles in the same series get rendered successfully, whereas a single large coordinate in a move_to/line_to() sequence is enough to cause all lines in the batch to fail to render. commit bc7f4be5e06405b5aa7fb443a5ace2b9b9575b1f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:01:42 2021 +0700 Cache cairo methods used in the inner loop of graph:draw() I have benchmarked the draw() method, both on cairo xcb and on argb32 image surfaces, and have discovered that cairo context method calls are unnaturally slow, to the point, that almost nothing else in the function matters. For example, drawing bars with vertical lines made the whole draw() function almost 2 times slower than doing the same with rectangles, merely because the former requires two method calls (move_to/line_to), and the latter only one. Simply caching hotpath cairo methods in local variables, reduced the difference between the drawing methods to a negligible one and made the whole function 4 times faster on a typical 100-bar graph, (1.4ms to 0.33ms on my laptop) thus bringing closer my secret dream of drawing live audio data with awesome graphs (kidding). I guess, LGI does something not very optimal there, like gobject-introspecting the method every time, or something. There might be some untapped optimization potential, possibly in upstream, like a method cache, from which everything in awesome could benefit, even though I imagine not many things are so call-intensive as the graph widget, to really feel the difference. commit 538df25bdf02b53d0bbe81809eef2e6fdb05ff6f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 07:32:37 2021 +0700 Use 1-based index in the inner graph draw loop It's used there only once for a calculation and then several times to index some tables, and index 0 is bad for lua tables performance-wise. commit 7412546916cd0a0a490130a120255ffb719398b8 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:55:12 2021 +0700 Leave graph.max_value uninitialized at construction This is a user-visible change, but the previous behavior was probably not what user expected anyway, namely with graph.scale set the graph mysteriously couldn't zoom in onto values that are smaller than 1. commit eb81d7929a161e79c13262cfa73ac2e6a820e8ed Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:48:29 2021 +0700 Allow graph.max_value be nil This makes its behave symmetrically to min_value. Now it defaults to 1, when graph.scale is not set, and to the maximum value in the graph data, when graph.scale is set. commit f622a8430cbfde7007b58d7f4c9c76b08c91e997 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 12:01:13 2021 +0700 Calculate max values num in graph:add_value() more precisely At most so many values were kept in the widget values array, as there are pixels of usable widget width (i.e. sans border). This is exactly right for the surely most common case of graphs with 1px-wide bars, but is more than needed for graphs with step_width+step_spacing>1, and not enough for graphs with subpixel bars. Moreover this made graph autoscaling behave weirdly in graphs with thicker bars, because old values survived longer in the array and kept affecting min_value/max_value calculations even after they were shifted off-screen. The maximum number of kept values is now calculated exactly as the number of values that can be displayed at once, taking step_width and step_spacing into account. It might be worth adding a max_values_num property to the graph to let user override this calculation, in case they draw shapes in step_shape() outside of its supposed rectangle, so our assumptions of visibility are wrong, or the widget often gets resized, resulting in the annoyance of visibly truncated series. commit 0aa63249a18641251a1bf77423739164f0eab39f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 02:30:13 2021 +0700 Fix border_color/border_width bugs in graph widget add_value() wrongly assumed that border_width is always set, when border_color is, so if user set only the latter, an error occurred. add_value() assumed that border_width is always < width which led to awesome hanging up in an infinite loop, should that fail to be the case. The graph border_width property was driven by the border_color property, instead of the other way round. If border_color wasn't set, the border wasn't drawn all, even if user sets non-zero border_width, which is a confusing behavior, in contrast to all other widgets, which simply use a plethora of fallback colors to draw the border, when needed. This also meant that, despite being documented, the beautiful.graph_border_color fallback color could not and was not ever used. If the border_color was set though, an unset border_width was immediately assumed to be 1 instead of 0, also contrary to what other widgets do. Theme colors shouldn't influence widget layout, so this commit fixes this behavior to: 1) border_width always defaults to 0, if unset. 2) border_color falls back to beautiful.graph_border_color or white. Maybe one should also add a beautiful.graph_border_width theme variable, like some widgets do. commit dad4a3253b8fef971fb82720d2a407fcc0a879ad Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 03:11:27 2021 +0700 Implement multiple data series drawing for non-stacked graph The widget can already hold and draw multiple data groups, so it makes little sense to limit drawing only to the first group when graph.stack = false, probably leading to user's confusion. Now user can call add_value(v, group) with group > 1 and the data series will be drawn (over groups with lesser id-s) with graph.stack_colors[group] color, if stack_colors is set, or with graph.color, if stack_colors isn't set. Thus nothing visibly changes for all current users of non-stacked graphs with only single data series, but the new functionality is now there for those who need it. One can't fully emulate this functionality, e.g. with multiple graphs widgets in a wibox.layout.stack, because data series in a single widget can enjoy a common auto-scaling. commit 3e645c976184ab5f50d625271c25062db9ab7ed9 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:59:12 2021 +0700 Don't draw a stacked graph data group if there's no color for it If stack_colors[idx] is nil for the group `idx`, don't draw it at all (as opposed, e.g. to the effect of drawing it with a transparent color, or as though it had all values == 0, which both produce different results). This code is still subtly different from prior behavior, because now user can have a non-contiguous stack_colors table and turn the display of any data group on and off at will, whereas the code two commits earlier would just stop rendering as soon as ipairs(stack_colors) does, i.e. no data series were drawn past the first nil hole. This commit only looks large, but is mostly a whitespace change due to nesting from a pair of wrapping ifs. commit 8c0273786de1ade66bdd977699f98b588da9f487 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:32:42 2021 +0700 Unify stacked and non-stacked graph rendering Now stacked graphs support everything the nonstacked do, like step_width ~= 1 and custom step_shape-s. Technical notes: This code no longer calls cr:stroke() for every single value in a stacked graph, but rather paints a whole series and then cr:stroke/fill()-s it at all once with its color, improving performance considerably. Additionally the graph no longer relies on the presence of the stack_colors property to render series. It draws all data that it has, and, if there's no corresponding color in stack_colors, falls back to graph.color. This will be changed in the next commit to the prior behavior of only drawing those series for which there's a color in the stack_colors table, but that would've made this commit's diff much larger due to required nesting ifs over large code chunks. This commit also takes care to properly handle NaNs and negative values. Those are currently (almost) guaranteed to not occur in the values array due to clipping in graph:add_value(), but I intend to eventually drop the clipping, because it does nothing that draw() really relies upon, and allowing unclipped and NaN values is more flexible and less surprising, e.g. when user dynamically toggles graph.scale. Eventually I intend to implement drawing negative values in non-stacked graphs and possibly even in the stacked ones. commit 38c6589c51b2aa057d7c2f40182ecf32b5b2ff5e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 10:04:15 2021 +0700 Convert graph:draw() to use `self` commit f21f1b2550426df1c4ea8abde079b1ccef84b394 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 12:27:26 2021 +0700 Fix scaling for stacked graphs Stacked graphs have cumulative heights, values from all groups that end up in the same graph bar are effectively summed. Previous code didn't take that into account when calculating min/max_value for the purposes of rescaling, but simply examined every value separately. This code adds up all values up front and finds min/max among those, to determine proper scaling that would fit the graph exactly. commit 1ce4c7bf63bdd6c99c195d668347bec49355e921 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 09:49:35 2021 +0700 Draw shaped graph bars only up to the baseline Prior to this commit, every step_shape bar was, for some reason, drawn with height = full widget's height, regardless of the value it represents. So for the most bars their lower end was far outside the box and therefore clipped off. So e.g. one couldn't see the lower round edges of rounded rectangles. This commit fixes that by appropriately varying the height that is passed into step_shape(), so that lower edges of the bars always exactly touch the lower edge of the graph. If the user wants to restore the prior behavior, they can simply ignore the passed-in height in step_shape() and just always draw their shape with height = full widget height. But they couldn't replicate the effect of this commit in their rc.lua without essentially writing their own graph widget. commit e25185b08da23745b5815f85f6e9e43222ed2294 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 08:42:54 2021 +0700 Avoid incremental transform shifting during graph bar rendering Instead of letting addition errors and cognitive load accumulate simply use the same offset calculation that is used for lines and reset the transform back to the top-left corner after every drawn bar. It's conceptually simpler, and will be useful later for stacked graphs and more. It also fixes a discrepancy between plain and step_shape rendering. Prior to this commit, bar coloring occurred with a disturbed transform, which was centered at the last drawn bar, wherever it happened to be. This meant that nontrivial colors, like gradients and images, were arbitrarily offset when step_shape was used. commit f3e98866d8583c35d0b557c09f529299edc9ba2c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:45:29 2021 +0700 Refactor out graph bar height expression into a variable commit f54528d6892f5aaa4595917853c9e2130f743fdb Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:20:25 2021 +0700 Draw graph bars with lines only when step_width == 1 Before this commit graph bars of step_width < 1 were drawn with lines of width 1, but that's a bit unfortunate, because it led to much overpainting, while still ending up with a graphically incorrect result. Let's draw such bars with rectangles and let cairo do things with subpixels, getting more graphically accurate result, while still keeping the common case of step_width == 1 (supposedly) fast-drawn with lines. commit b6515dca67c18b78eb5657378131a62ab43d4cfc Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:54:07 2021 +0700 Fix unsharp edges when drawing graph bars with rectangles The 0.5 offset is only helpful for 1-width strokes, not for fills. commit 7f3db8f07e82f821bcd1dc418d66564f537609d6 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:21:01 2021 +0700 Do coordinate transformations even if the value isn't drawn The graph presently doesn't draw values < 0, but there was a discrepancy between drawing with and without step_shape. The default line/rectangle drawing reserves an empty place where the skipped value should've been, but no such empty place is reserved, when step_shape is used. This commit fixes that. Skipped values always have their empty space now. commit 7b731ebfe96af7a9dba9c57371da6761cf22c00c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:03:30 2021 +0700 Fix graph bar scaling when min_value is not 0 Previous calculation lead to underscaling of values so that even value = max_value wasn't drawn to full height. commit dca59fd8b7fff0e7e646f2dfc6bd92047028e5a8 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:18:40 2021 +0700 Calculate the horizontal graph bar offset only when needed commit 940ddf4b9825e9c3a91d276208c089d8c74e49db Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:17:23 2021 +0700 Call cr:move_to() only when drawing the graph with lines It's redundant otherwise and might even interfere with user's drawing in step_shape(). commit b5d5cd10ca29fb29cd751faaa95137c2fe89ff63 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:04:06 2021 +0700 Fix wrong horizontal offset calculations in widget.graph Indices in the loop are zero-based, so things like i-1 and i>1 aren't necessary and actually wouldn't have been correct even in a 1-based loop. step_shape = gears.shape.rectangle and step_shape = nil, which is supposed to default to rectangle according to docs, were drawn differently due to mismatched offset calculations, which led to problems especially noticeable when step_spacing is not zero, e.g: 1) whole graph shifted left too much, drawn partially off-screen. 2) whole graph shifted right too much, and the first bar placed closer to the rest than others. commit dc369fa198281e000afc1bd17326f3a264adf7d1 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 02:09:21 2021 +0700 Fix the "border_width affects graph bar width" bug Graph bars were drawn with lines of border_width thickness instead of 1, i.e. possibly too fat to be readable. commit 1ba73378cf29ed27a6ecd18716394779ceb543d9 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 00:13:03 2021 +0700 Simplify internal values representation in graph even more New values in graph._private.values were pushed to the end, and old values were removed from the beginning of the table. In draw() however newer values are drawn first, at the left edge of the graph, necessitating annoying idx = #values - idx conversions everywhere. In particular in stacked graph that means that values that are drawn together may have different indices. One has to shift the values array in add_value() either way, so one might as well insert new values at the beginning of the array and simplify the rest of the code. This doesn't seem like much yet, but it will prove to be much more convenient and less error-prone in what I'm going to do later. commit f70523ee68dbd6b67050ace4b82903a9b22440c7 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 13:35:10 2021 +0700 Simplify internal values representation in graph widget graph._private.values was either a table of values or a table of tables of values, depending on whether "stack" property is enabled, ... or a mix of both if user forgot to pass the second "group" parameter to graph:add_value(). This resulted in pretty fragile and confusing behavior, e.g. leaving user to wonder, why they see nothing more after setting stack=true for a graph in a previously working rc.lua, and demanding a very careful use of add_value() from the user, or else some values or even entire stacks would mysteriously vanish and errors in draw() get triggered, sometimes after a period of seemingly normal functioning. Now `values` is always a table of tables and the group parameter in add_value() defaults to 1, thus making the non-stacked graph a trivial variant of the stacked graph.
2021-05-20 17:14:03 +02:00
--- The graph background color.
-- Used, when the `background_color` property isn't set.
--
-- @beautiful beautiful.graph_bg
-- @param color
2016-05-27 07:23:12 +02:00
--- The graph border color.
graph: complete widget overhaul and bug fixes Squashed commit of the following: commit 69821f51fe1e8652715658543e50631ece495090 Author: Alex Belykh <albel727@ngs.ru> Date: Mon May 10 19:17:55 2021 +0700 Refactor property handling in wibox.widget.graph In addition, baseline_value, step_width and step_spacing properties are all set to their default non-nil numeric values during instance creation now. commit 842eb429bf5df4b2ba46b2e6f7646afe89b1a3c3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 9 06:18:45 2021 +0700 Make graph:draw_values() private There's no documentation nor confidence that it's a good API, so I'm hiding it for now. commit 720746780574e4bad14a71fc4174955139c2eb50 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 04:04:13 2021 +0700 Document graph_should_draw_data_group() for posterity commit 7c73f3754ce6ba86b118f1170a6f9e406ab4841d Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:59:21 2021 +0700 Make graph:should_draw_data_group() private There's no confidence whether it's a good API, so I'm hiding it for now. commit b3539e20adb6423118af726ae4f8751ef864b5d2 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:36:44 2021 +0700 Make graph:preprocess_values() private There's no confidence whether it's a good API, so I'm hiding it for now. commit 2df4400e780a6260f8d0ab1c60734fbb11bf3c4f Author: Alex Belykh <albel727@ngs.ru> Date: Thu May 6 10:05:23 2021 +0700 Add spec/wibox/widget/graph_spec.lua commit ce01a9771ab439b8df841f1565aa4aa8a44a5e0d Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 08:16:18 2021 +0700 Improve graph.capacity documentation commit e28fade8f655802f5bf89154169f68d256b8e332 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 07:36:01 2021 +0700 Document graph:compute_drawn_values_num() commit dd68332292465422d08b48e9f0229f143beccbc6 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 05:24:23 2021 +0700 Document graph:pick_data_group_color() method commit ca6dc55f77aca88a6e9cc90bb6fd0045739f41f5 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 04:13:58 2021 +0700 Guard against setting data groups sparsely in graph:add_value() commit cdea5f126f7984e72924f6dffbd312c4fe3b032f Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 02:36:12 2021 +0700 Refactor scaling and baseline choice out of graph:draw() commit 10135deafe8a9390ba66ab657ebb1fb41d549e8c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 21:34:48 2021 +0700 Document graph.nan_color and graph.stack properties commit 0d73bb84ac66b87e16aee472bd1f299549f66a1c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 20:49:24 2021 +0700 Use a better example in docs for the graph.stack property commit c27d160a0826d033d49d6141608c40aa8a0642fa Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 16:05:38 2021 +0700 Document graph.baseline_value property commit 459bc8176c13d4ea32af0dcdc2399d3b929b1c6a Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:53:47 2021 +0700 Document graph.clamp_bars property commit e732ee0b3b007d213e689b98cc00ae7cb141ae08 Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:34 2021 +0700 Improve graph:add_value() and :clear() documentation commit ae73e3aa9f8d60409cbe4cb4da9612f31af4f38e Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:11 2021 +0700 Improve graph min_value/max_value/scale documentation commit a7350996a9def773b91b1631e6c0507e33d8f93b Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:27:36 2021 +0700 Cleanup graph examples to show only relevant things in docs commit 30740c26f4f41c16f4c8a3c339bcb7bd299e14cd Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 14:04:36 2021 +0700 Rename graph.stack_colors property to `group_colors` The colors are used for stacked and non-stacked graphs alike, so the name didn't make sense. Also document and clarify some color stuff. commit 1ea01bfd410971cf0d0c25fd74cd764d5eb7279d Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 21:25:41 2021 +0700 Add an example file for code coverage of some fringe graph cases commit eae68e39553f88a6275a0c65bf040b5d4c262b54 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 20:23:32 2021 +0700 Add step_shape to the graph/nan_color example for code coverage commit 8027147bc2eab34a97b49a428fb8cef27f122bed Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:55:18 2021 +0700 Add example files for graph.nan_color/nan_indication properties commit 89a25a165c52b2c15b6c4d95ecb777a9067e66f9 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:33:07 2021 +0700 Make widget graph:add_value(v) default v to NaN 0 as a default makes no sense and silences the error that is failing to specify a value, to which the user should rather be alerted. The graph widget has sane NaN handling now and will do just that. commit 2835d552440be48d356a7f7b1d64963696dd550b Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:20:48 2021 +0700 Implement graph.nan_color and nan_indication properties Now, whenever there's a NaN among values and graph.nan_indication is set, the corresponding area, where the value bar should have been drawn, is filled with the nan_color from top to bottom. The painting is done after all data is rendered, to make sure that it won't be overpainted and go unnoticed. NaNs among graph's values are inevitably a sign of some error and it is arguably not a sane default behavior to simply not render them at all. The user should take immediate note of any errors by default, instead of wondering at a mysteriously empty graph widget, which is why nan_indication is set to true by default. If the user wants to silence NaNs, they can always simply set it to false. nan_color if unset, defaults to a noticeable yellow-black pattern. As a direct and planned consequence, stacked graphs with negative values in them trigger the NaN handling too. But silencing NaN handling for those graphs is also possible and semi-sane, because then NaNs and negatives are ignored and the graph behaves (almost) like they were zeros. commit 9c1f8f08b400e01e19c534f3810c9c9ab485760c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 26 08:10:19 2021 +0700 Refactor out graph_map_value_to_widget_coordinates() function commit 1905991ddecabdae3e15ffb8ee5b18a1bee1a711 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 28 13:39:29 2021 +0700 Add an example file for graph.baseline_value property commit b31b39d66f4c037a9484b71a3f910703ae01fac0 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 07:03:49 2021 +0700 Add an example file for graph.clamp_bars=false/true comparison commit a1fc850e3047f9ee2c9ddeffacfbabf3ded34171 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 04:06:45 2021 +0700 Add an example file for graphs with negative values and shapes commit 2f012f8ff1bb5094c14e724a61e59a2a3a296bc8 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 01:06:05 2021 +0700 Add an example file for a graph displaying negative values commit 7b14fbd59a4c4c51962fd3110a1a900dd5f4eb98 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 11:18:26 2021 +0700 Add an example file for graphs with step_width < 1 commit dfd9a54544b637e2fbe3d1325f9cc24b3f4d2313 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 10:17:38 2021 +0700 Add an example file for graph.stack=false/true comparison Should also demonstrate that multiple data groups are possible for non-stacked graphs too. commit 5b532106c3fd255f9b66a66cfb88d977b489ab5d Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 09:34:14 2021 +0700 Add an example file for disabling graph data groups with nil colors commit ac6f1083c02fcb428f089a616f9b0ebaa5496b6c Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:57:25 2021 +0700 Add an example file for step_shape/width/spacing on stacked graphs commit b3703db6a5139e73e72c1cfd198ece2cd9be7df2 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:43:56 2021 +0700 Add an example file for scale=false/true on stacked graph commit b000aed4a12cb44fb501e1c8432a71a14b2f8f3f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 20 08:22:19 2021 +0700 Document deprecation of graph's width and height commit cb0ad617bd6c83a499896797318e3b0b588c3566 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 07:53:37 2021 +0700 Deprecate width and height properties of wibox.widget.graph commit 8c64e14ad91a074b784166a7054e53444cccb9d2 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 04:37:59 2021 +0700 Refactor graph:draw() to separate concerns Firstly, summation of group values for stacked graphs is extracted into graph:preprocess_values(). This makes graph:draw() smaller and exposes the fact, that stacked graphs are almost entirely just a form of a more general concept: pre-render data manipulation. It's something a user might want to customize, and now they can do it by monkey-patching the method. Secondly, the logic for deciding, if and with which color a data group should be drawn, is extracted into graph:should_draw_data_group() and get_data_group_color() methods respectively. This makes it customizable too and makes more obvious the fact, that it is a general thing that has nothing to do with stacked graphs in particular. With this there are only 5 lines remaining in graph:draw(), that could be said to be specific to stacked graphs, all pertaining to the `prev_y` variable, which merely preserves the bar coordinates from a previous data group. commit 2a32b6305d893f413303728a0a3da1dcd9a5591e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 22:52:37 2021 +0700 Pointlessly optimize graph:add_value() 1) Purge old values first, then insert the new one, if needed. Since Lua grows arrays in power of two steps, and our guess_capacity() also takes care to return even integers, it would be a shame to overstep the capacity by one by inserting first and thus getting the table size doubled only to never use the most of it. 2) Use a removal procedure that doesn't call the slow length operator at all. Previous code called it twice (once in table.remove) for every removed value. commit 680ea8f5d81e9040afb4662b3229e937e4b89e02 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:23:38 2021 +0700 Set graph data series color before the painting loop commit b970f0f221ae76250f2a94433ff79d653caa6e21 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:05:37 2021 +0700 Drop a large no longer necessary `if` in graph:draw_values() This commit is almost purely a whitespace change. commit 6b4f0541c70ec99d7f6e276bae64939f337120d6 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:00:50 2021 +0700 Extract values rendering code into graph:draw_values() method This is to avoid ungodly if nesting levels, which are needed to bail out of drawing values early and skip to drawing the border. The next commit will get rid of the if. commit e0950e4f99d1ea1c4b8cea2cc67629fa40030c2a Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 14:50:20 2021 +0700 Track graph draw() usage to guess necessary array capacity The last step of divorcing add_values() from width calculations, while ensuring that user doesn't observe a lack of data to draw. The widget now tracks an approximate number of values that it has been asked to draw() in _private.last_drawn_values_num, and truncates its values array based on that (if not overridden by the capacity property). commit 2e3d542282f35e7c0fed454da0f2eada0fcf3b78 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 13:35:12 2021 +0700 Add `capacity` property to graph widget This is the second step of divorcing add_values() from calculations involving widths. How many values are stored and how many values are drawn are distinct concepts. There isn't even a single definitive "how many values are drawn" because the widget could be rendered in several places with distinct widths simultaneously, and in every such place the data must be drawn appropriately, e.g. with auto-scaling, that takes only the visible data points into account. And the draw() method can do that now. The default capacity value of nil uses heuristics based on widget's _private.width to guess some capacity that would tolerate some dynamic widget resizing. If the user finds the heuristic failing them, they can set the capacity property to the desired maximum number of values that will be kept in the _private.values array (in each data group). commit 08d4a80686f7fe363551c35589a460839f60853e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 03:27:18 2021 +0700 Extract values size computation into compute_drawn_values_num() This is the first step of making add_values() not concern itself (directly) with widget dimensions. The user can monkey-patch the method to override how many values are (logically) drawn, because it affects graph auto-scaling and can be useful for rendering of step_shape-s that paint outside of their step_width. I didn't make it a full-blown function-valued property, because it seems a fringe functionality, which I'm too lazy to document. commit 3c5617c98ab1147618ed22e8414a77fc56b45f6e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 02:32:29 2021 +0700 Copy graph widget prototype methods over in bulk commit 169538837ece41a03d02ba8fbc0e4097062b67a3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun Apr 25 05:58:01 2021 +0700 Remove graph.baseline_y property After giving it some thought I've concluded, that it is unlikely to be useful as it is now. I'd better reserve the name for some more sensible functionality, like fixing the baseline_value defined axis at the given widget coordinate, adjusting the scaling accordingly, so min_value/max_value won't generally correspond to widget top and bottom anymore. commit 202ffaa9a0b820c7c471e1121d01b91f8f76cd38 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 12:06:55 2021 +0700 Add baseline_y and baseline_value properties to graph widget Now values in graph have a *baseline*, tunable with baseline_y and baseline_value properties. It is the (for now invisible) horizontal line from which all bars in the graph grow. I call it a "baseline" instead of "axis", because it doesn't actually have to correspond to the 0 value. But it can, and actually does now by default. baseline_y and baseline_value properties determine the vertical placement of the baseline in the widget. baseline_y is the position of the baseline in normalized widget coordinates, e.g. 0 corresponds to widget's top, 1 - to widget's bottom, and 0.5 - to widget's middle. So if e.g. one sets baseline_y = 0, then no matter how the widget is resized and how its values are scaled, bars in it will be drawn starting from the top. baseline_value is the position of the baseline in value coordinates. So if one sets baseline_value = 10, then no matter what happens, bars for values greater than 10 will be drawn as growing up from this line, and bars for values smaller as growing down. Where and if it will be seen in the widget, depends on the scaling. baseline_value = 0 is the familiar zero axis. baseline_y is unset by default and has priority, i.e. if it's set, then baseline_value is ignored. baseline_value defaults to 0. Prior to this commit, bars were always drawn from the bottom edge of the widget, or, in the new terms, the widget had baseline_y = 1. But it was indistinguishable from baseline_value = 0, because the widget clamped all negative values to 0, so the difference between the two could never be observed. baseline_value = 0 is clearly more useful and familiar to people, so now it is in force by default. commit df7e19abaebf0a871b5ca217b4cc3365533267a6 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 11:18:12 2021 +0700 Move value clamping from graph:add_value() into draw() Previously all values were forcibly clamped between 0 (not tunable at all) and max_value (1 by default). Thus graphs with negative values were not an option. NaN values were conflated with zeros, even though it may be useful to maintain the distinction and render them differently. And if user happened to dynamically change the max_value property, they discovered, that their data got truncated. Preserving user data as it was given is more flexible and makes things easier to debug. This commit does exactly that. So what happens now to the newly allowed values during draw()? There are 3 new cases to consider: 1) NaNs 2) Negatives 3) Values outside of min_value..max_value range NaNs are presently simply not drawn. An empty place is reserved where the value should've been, but nothing else is done. This is distinct from how a zero is handled, e.g. if step_shape is set, it is called with the height parameter = 0, which can still end up drawing something, depending on the shape. One could also consider addition of nan_color/nan_shape properties which could be drawn to alert user to a gap in their data. Negative values necessitate introduction of the concept of the zero axis, from which they will be drawn DOWN, in contrast to positive values. Where should this axis be drawn? How should step_shape-s with inherent direction, e.g. arrows, be drawn for negatives? This will be addressed in the next commit. Values outside of min/max_value range can imply shapes that go beyond widget dimensions. An arrow shape that goes beyond the roof is visibly different from an arrow that saturates and merely touches the widget top, and the user may reasonably desire either behavior. Therefore this commit introduces a boolean clamp_bars property, which defaults to true for reasons of robustness and backward compatibility with the behaviour prior to this commit. The property of course affects only the display of the values, the actual user data stays intact. commit c9c24fb1ab741cd6bb8074971d568e0646c51532 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:29:01 2021 +0700 Use rectangles for drawing graph bars of all sizes I have benchmarked various bar drawing strategies and graph sizes and found out that drawing graph bars with rectangles is as fast or even marginally faster than using fat lines. Thus the code that tries to special-case 1px bars as lines seems to be not worth the additional complexity it introduces, and this commit removes it. Incidentally, I've also established, that rectangles are more robust. If one draws graph bars and feeds cairo very large coordinates (~2^62), then corresponding rectangles fail to render, but all the other rectangles in the same series get rendered successfully, whereas a single large coordinate in a move_to/line_to() sequence is enough to cause all lines in the batch to fail to render. commit bc7f4be5e06405b5aa7fb443a5ace2b9b9575b1f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:01:42 2021 +0700 Cache cairo methods used in the inner loop of graph:draw() I have benchmarked the draw() method, both on cairo xcb and on argb32 image surfaces, and have discovered that cairo context method calls are unnaturally slow, to the point, that almost nothing else in the function matters. For example, drawing bars with vertical lines made the whole draw() function almost 2 times slower than doing the same with rectangles, merely because the former requires two method calls (move_to/line_to), and the latter only one. Simply caching hotpath cairo methods in local variables, reduced the difference between the drawing methods to a negligible one and made the whole function 4 times faster on a typical 100-bar graph, (1.4ms to 0.33ms on my laptop) thus bringing closer my secret dream of drawing live audio data with awesome graphs (kidding). I guess, LGI does something not very optimal there, like gobject-introspecting the method every time, or something. There might be some untapped optimization potential, possibly in upstream, like a method cache, from which everything in awesome could benefit, even though I imagine not many things are so call-intensive as the graph widget, to really feel the difference. commit 538df25bdf02b53d0bbe81809eef2e6fdb05ff6f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 07:32:37 2021 +0700 Use 1-based index in the inner graph draw loop It's used there only once for a calculation and then several times to index some tables, and index 0 is bad for lua tables performance-wise. commit 7412546916cd0a0a490130a120255ffb719398b8 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:55:12 2021 +0700 Leave graph.max_value uninitialized at construction This is a user-visible change, but the previous behavior was probably not what user expected anyway, namely with graph.scale set the graph mysteriously couldn't zoom in onto values that are smaller than 1. commit eb81d7929a161e79c13262cfa73ac2e6a820e8ed Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:48:29 2021 +0700 Allow graph.max_value be nil This makes its behave symmetrically to min_value. Now it defaults to 1, when graph.scale is not set, and to the maximum value in the graph data, when graph.scale is set. commit f622a8430cbfde7007b58d7f4c9c76b08c91e997 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 12:01:13 2021 +0700 Calculate max values num in graph:add_value() more precisely At most so many values were kept in the widget values array, as there are pixels of usable widget width (i.e. sans border). This is exactly right for the surely most common case of graphs with 1px-wide bars, but is more than needed for graphs with step_width+step_spacing>1, and not enough for graphs with subpixel bars. Moreover this made graph autoscaling behave weirdly in graphs with thicker bars, because old values survived longer in the array and kept affecting min_value/max_value calculations even after they were shifted off-screen. The maximum number of kept values is now calculated exactly as the number of values that can be displayed at once, taking step_width and step_spacing into account. It might be worth adding a max_values_num property to the graph to let user override this calculation, in case they draw shapes in step_shape() outside of its supposed rectangle, so our assumptions of visibility are wrong, or the widget often gets resized, resulting in the annoyance of visibly truncated series. commit 0aa63249a18641251a1bf77423739164f0eab39f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 02:30:13 2021 +0700 Fix border_color/border_width bugs in graph widget add_value() wrongly assumed that border_width is always set, when border_color is, so if user set only the latter, an error occurred. add_value() assumed that border_width is always < width which led to awesome hanging up in an infinite loop, should that fail to be the case. The graph border_width property was driven by the border_color property, instead of the other way round. If border_color wasn't set, the border wasn't drawn all, even if user sets non-zero border_width, which is a confusing behavior, in contrast to all other widgets, which simply use a plethora of fallback colors to draw the border, when needed. This also meant that, despite being documented, the beautiful.graph_border_color fallback color could not and was not ever used. If the border_color was set though, an unset border_width was immediately assumed to be 1 instead of 0, also contrary to what other widgets do. Theme colors shouldn't influence widget layout, so this commit fixes this behavior to: 1) border_width always defaults to 0, if unset. 2) border_color falls back to beautiful.graph_border_color or white. Maybe one should also add a beautiful.graph_border_width theme variable, like some widgets do. commit dad4a3253b8fef971fb82720d2a407fcc0a879ad Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 03:11:27 2021 +0700 Implement multiple data series drawing for non-stacked graph The widget can already hold and draw multiple data groups, so it makes little sense to limit drawing only to the first group when graph.stack = false, probably leading to user's confusion. Now user can call add_value(v, group) with group > 1 and the data series will be drawn (over groups with lesser id-s) with graph.stack_colors[group] color, if stack_colors is set, or with graph.color, if stack_colors isn't set. Thus nothing visibly changes for all current users of non-stacked graphs with only single data series, but the new functionality is now there for those who need it. One can't fully emulate this functionality, e.g. with multiple graphs widgets in a wibox.layout.stack, because data series in a single widget can enjoy a common auto-scaling. commit 3e645c976184ab5f50d625271c25062db9ab7ed9 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:59:12 2021 +0700 Don't draw a stacked graph data group if there's no color for it If stack_colors[idx] is nil for the group `idx`, don't draw it at all (as opposed, e.g. to the effect of drawing it with a transparent color, or as though it had all values == 0, which both produce different results). This code is still subtly different from prior behavior, because now user can have a non-contiguous stack_colors table and turn the display of any data group on and off at will, whereas the code two commits earlier would just stop rendering as soon as ipairs(stack_colors) does, i.e. no data series were drawn past the first nil hole. This commit only looks large, but is mostly a whitespace change due to nesting from a pair of wrapping ifs. commit 8c0273786de1ade66bdd977699f98b588da9f487 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:32:42 2021 +0700 Unify stacked and non-stacked graph rendering Now stacked graphs support everything the nonstacked do, like step_width ~= 1 and custom step_shape-s. Technical notes: This code no longer calls cr:stroke() for every single value in a stacked graph, but rather paints a whole series and then cr:stroke/fill()-s it at all once with its color, improving performance considerably. Additionally the graph no longer relies on the presence of the stack_colors property to render series. It draws all data that it has, and, if there's no corresponding color in stack_colors, falls back to graph.color. This will be changed in the next commit to the prior behavior of only drawing those series for which there's a color in the stack_colors table, but that would've made this commit's diff much larger due to required nesting ifs over large code chunks. This commit also takes care to properly handle NaNs and negative values. Those are currently (almost) guaranteed to not occur in the values array due to clipping in graph:add_value(), but I intend to eventually drop the clipping, because it does nothing that draw() really relies upon, and allowing unclipped and NaN values is more flexible and less surprising, e.g. when user dynamically toggles graph.scale. Eventually I intend to implement drawing negative values in non-stacked graphs and possibly even in the stacked ones. commit 38c6589c51b2aa057d7c2f40182ecf32b5b2ff5e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 10:04:15 2021 +0700 Convert graph:draw() to use `self` commit f21f1b2550426df1c4ea8abde079b1ccef84b394 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 12:27:26 2021 +0700 Fix scaling for stacked graphs Stacked graphs have cumulative heights, values from all groups that end up in the same graph bar are effectively summed. Previous code didn't take that into account when calculating min/max_value for the purposes of rescaling, but simply examined every value separately. This code adds up all values up front and finds min/max among those, to determine proper scaling that would fit the graph exactly. commit 1ce4c7bf63bdd6c99c195d668347bec49355e921 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 09:49:35 2021 +0700 Draw shaped graph bars only up to the baseline Prior to this commit, every step_shape bar was, for some reason, drawn with height = full widget's height, regardless of the value it represents. So for the most bars their lower end was far outside the box and therefore clipped off. So e.g. one couldn't see the lower round edges of rounded rectangles. This commit fixes that by appropriately varying the height that is passed into step_shape(), so that lower edges of the bars always exactly touch the lower edge of the graph. If the user wants to restore the prior behavior, they can simply ignore the passed-in height in step_shape() and just always draw their shape with height = full widget height. But they couldn't replicate the effect of this commit in their rc.lua without essentially writing their own graph widget. commit e25185b08da23745b5815f85f6e9e43222ed2294 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 08:42:54 2021 +0700 Avoid incremental transform shifting during graph bar rendering Instead of letting addition errors and cognitive load accumulate simply use the same offset calculation that is used for lines and reset the transform back to the top-left corner after every drawn bar. It's conceptually simpler, and will be useful later for stacked graphs and more. It also fixes a discrepancy between plain and step_shape rendering. Prior to this commit, bar coloring occurred with a disturbed transform, which was centered at the last drawn bar, wherever it happened to be. This meant that nontrivial colors, like gradients and images, were arbitrarily offset when step_shape was used. commit f3e98866d8583c35d0b557c09f529299edc9ba2c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:45:29 2021 +0700 Refactor out graph bar height expression into a variable commit f54528d6892f5aaa4595917853c9e2130f743fdb Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:20:25 2021 +0700 Draw graph bars with lines only when step_width == 1 Before this commit graph bars of step_width < 1 were drawn with lines of width 1, but that's a bit unfortunate, because it led to much overpainting, while still ending up with a graphically incorrect result. Let's draw such bars with rectangles and let cairo do things with subpixels, getting more graphically accurate result, while still keeping the common case of step_width == 1 (supposedly) fast-drawn with lines. commit b6515dca67c18b78eb5657378131a62ab43d4cfc Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:54:07 2021 +0700 Fix unsharp edges when drawing graph bars with rectangles The 0.5 offset is only helpful for 1-width strokes, not for fills. commit 7f3db8f07e82f821bcd1dc418d66564f537609d6 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:21:01 2021 +0700 Do coordinate transformations even if the value isn't drawn The graph presently doesn't draw values < 0, but there was a discrepancy between drawing with and without step_shape. The default line/rectangle drawing reserves an empty place where the skipped value should've been, but no such empty place is reserved, when step_shape is used. This commit fixes that. Skipped values always have their empty space now. commit 7b731ebfe96af7a9dba9c57371da6761cf22c00c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:03:30 2021 +0700 Fix graph bar scaling when min_value is not 0 Previous calculation lead to underscaling of values so that even value = max_value wasn't drawn to full height. commit dca59fd8b7fff0e7e646f2dfc6bd92047028e5a8 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:18:40 2021 +0700 Calculate the horizontal graph bar offset only when needed commit 940ddf4b9825e9c3a91d276208c089d8c74e49db Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:17:23 2021 +0700 Call cr:move_to() only when drawing the graph with lines It's redundant otherwise and might even interfere with user's drawing in step_shape(). commit b5d5cd10ca29fb29cd751faaa95137c2fe89ff63 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:04:06 2021 +0700 Fix wrong horizontal offset calculations in widget.graph Indices in the loop are zero-based, so things like i-1 and i>1 aren't necessary and actually wouldn't have been correct even in a 1-based loop. step_shape = gears.shape.rectangle and step_shape = nil, which is supposed to default to rectangle according to docs, were drawn differently due to mismatched offset calculations, which led to problems especially noticeable when step_spacing is not zero, e.g: 1) whole graph shifted left too much, drawn partially off-screen. 2) whole graph shifted right too much, and the first bar placed closer to the rest than others. commit dc369fa198281e000afc1bd17326f3a264adf7d1 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 02:09:21 2021 +0700 Fix the "border_width affects graph bar width" bug Graph bars were drawn with lines of border_width thickness instead of 1, i.e. possibly too fat to be readable. commit 1ba73378cf29ed27a6ecd18716394779ceb543d9 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 00:13:03 2021 +0700 Simplify internal values representation in graph even more New values in graph._private.values were pushed to the end, and old values were removed from the beginning of the table. In draw() however newer values are drawn first, at the left edge of the graph, necessitating annoying idx = #values - idx conversions everywhere. In particular in stacked graph that means that values that are drawn together may have different indices. One has to shift the values array in add_value() either way, so one might as well insert new values at the beginning of the array and simplify the rest of the code. This doesn't seem like much yet, but it will prove to be much more convenient and less error-prone in what I'm going to do later. commit f70523ee68dbd6b67050ace4b82903a9b22440c7 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 13:35:10 2021 +0700 Simplify internal values representation in graph widget graph._private.values was either a table of values or a table of tables of values, depending on whether "stack" property is enabled, ... or a mix of both if user forgot to pass the second "group" parameter to graph:add_value(). This resulted in pretty fragile and confusing behavior, e.g. leaving user to wonder, why they see nothing more after setting stack=true for a graph in a previously working rc.lua, and demanding a very careful use of add_value() from the user, or else some values or even entire stacks would mysteriously vanish and errors in draw() get triggered, sometimes after a period of seemingly normal functioning. Now `values` is always a table of tables and the group parameter in add_value() defaults to 1, thus making the non-stacked graph a trivial variant of the stacked graph.
2021-05-20 17:14:03 +02:00
-- Used, when the `border_color` property isn't set.
--
2016-05-27 07:23:12 +02:00
-- @beautiful beautiful.graph_border_color
-- @param color
2016-05-27 07:23:12 +02:00
2016-05-23 07:30:44 +02:00
local properties = { "width", "height", "border_color", "stack",
"stack_colors", "color", "background_color",
"max_value", "scale", "min_value", "step_shape",
graph: complete widget overhaul and bug fixes Squashed commit of the following: commit 69821f51fe1e8652715658543e50631ece495090 Author: Alex Belykh <albel727@ngs.ru> Date: Mon May 10 19:17:55 2021 +0700 Refactor property handling in wibox.widget.graph In addition, baseline_value, step_width and step_spacing properties are all set to their default non-nil numeric values during instance creation now. commit 842eb429bf5df4b2ba46b2e6f7646afe89b1a3c3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 9 06:18:45 2021 +0700 Make graph:draw_values() private There's no documentation nor confidence that it's a good API, so I'm hiding it for now. commit 720746780574e4bad14a71fc4174955139c2eb50 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 04:04:13 2021 +0700 Document graph_should_draw_data_group() for posterity commit 7c73f3754ce6ba86b118f1170a6f9e406ab4841d Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:59:21 2021 +0700 Make graph:should_draw_data_group() private There's no confidence whether it's a good API, so I'm hiding it for now. commit b3539e20adb6423118af726ae4f8751ef864b5d2 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:36:44 2021 +0700 Make graph:preprocess_values() private There's no confidence whether it's a good API, so I'm hiding it for now. commit 2df4400e780a6260f8d0ab1c60734fbb11bf3c4f Author: Alex Belykh <albel727@ngs.ru> Date: Thu May 6 10:05:23 2021 +0700 Add spec/wibox/widget/graph_spec.lua commit ce01a9771ab439b8df841f1565aa4aa8a44a5e0d Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 08:16:18 2021 +0700 Improve graph.capacity documentation commit e28fade8f655802f5bf89154169f68d256b8e332 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 07:36:01 2021 +0700 Document graph:compute_drawn_values_num() commit dd68332292465422d08b48e9f0229f143beccbc6 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 05:24:23 2021 +0700 Document graph:pick_data_group_color() method commit ca6dc55f77aca88a6e9cc90bb6fd0045739f41f5 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 04:13:58 2021 +0700 Guard against setting data groups sparsely in graph:add_value() commit cdea5f126f7984e72924f6dffbd312c4fe3b032f Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 02:36:12 2021 +0700 Refactor scaling and baseline choice out of graph:draw() commit 10135deafe8a9390ba66ab657ebb1fb41d549e8c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 21:34:48 2021 +0700 Document graph.nan_color and graph.stack properties commit 0d73bb84ac66b87e16aee472bd1f299549f66a1c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 20:49:24 2021 +0700 Use a better example in docs for the graph.stack property commit c27d160a0826d033d49d6141608c40aa8a0642fa Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 16:05:38 2021 +0700 Document graph.baseline_value property commit 459bc8176c13d4ea32af0dcdc2399d3b929b1c6a Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:53:47 2021 +0700 Document graph.clamp_bars property commit e732ee0b3b007d213e689b98cc00ae7cb141ae08 Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:34 2021 +0700 Improve graph:add_value() and :clear() documentation commit ae73e3aa9f8d60409cbe4cb4da9612f31af4f38e Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:11 2021 +0700 Improve graph min_value/max_value/scale documentation commit a7350996a9def773b91b1631e6c0507e33d8f93b Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:27:36 2021 +0700 Cleanup graph examples to show only relevant things in docs commit 30740c26f4f41c16f4c8a3c339bcb7bd299e14cd Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 14:04:36 2021 +0700 Rename graph.stack_colors property to `group_colors` The colors are used for stacked and non-stacked graphs alike, so the name didn't make sense. Also document and clarify some color stuff. commit 1ea01bfd410971cf0d0c25fd74cd764d5eb7279d Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 21:25:41 2021 +0700 Add an example file for code coverage of some fringe graph cases commit eae68e39553f88a6275a0c65bf040b5d4c262b54 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 20:23:32 2021 +0700 Add step_shape to the graph/nan_color example for code coverage commit 8027147bc2eab34a97b49a428fb8cef27f122bed Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:55:18 2021 +0700 Add example files for graph.nan_color/nan_indication properties commit 89a25a165c52b2c15b6c4d95ecb777a9067e66f9 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:33:07 2021 +0700 Make widget graph:add_value(v) default v to NaN 0 as a default makes no sense and silences the error that is failing to specify a value, to which the user should rather be alerted. The graph widget has sane NaN handling now and will do just that. commit 2835d552440be48d356a7f7b1d64963696dd550b Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:20:48 2021 +0700 Implement graph.nan_color and nan_indication properties Now, whenever there's a NaN among values and graph.nan_indication is set, the corresponding area, where the value bar should have been drawn, is filled with the nan_color from top to bottom. The painting is done after all data is rendered, to make sure that it won't be overpainted and go unnoticed. NaNs among graph's values are inevitably a sign of some error and it is arguably not a sane default behavior to simply not render them at all. The user should take immediate note of any errors by default, instead of wondering at a mysteriously empty graph widget, which is why nan_indication is set to true by default. If the user wants to silence NaNs, they can always simply set it to false. nan_color if unset, defaults to a noticeable yellow-black pattern. As a direct and planned consequence, stacked graphs with negative values in them trigger the NaN handling too. But silencing NaN handling for those graphs is also possible and semi-sane, because then NaNs and negatives are ignored and the graph behaves (almost) like they were zeros. commit 9c1f8f08b400e01e19c534f3810c9c9ab485760c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 26 08:10:19 2021 +0700 Refactor out graph_map_value_to_widget_coordinates() function commit 1905991ddecabdae3e15ffb8ee5b18a1bee1a711 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 28 13:39:29 2021 +0700 Add an example file for graph.baseline_value property commit b31b39d66f4c037a9484b71a3f910703ae01fac0 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 07:03:49 2021 +0700 Add an example file for graph.clamp_bars=false/true comparison commit a1fc850e3047f9ee2c9ddeffacfbabf3ded34171 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 04:06:45 2021 +0700 Add an example file for graphs with negative values and shapes commit 2f012f8ff1bb5094c14e724a61e59a2a3a296bc8 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 01:06:05 2021 +0700 Add an example file for a graph displaying negative values commit 7b14fbd59a4c4c51962fd3110a1a900dd5f4eb98 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 11:18:26 2021 +0700 Add an example file for graphs with step_width < 1 commit dfd9a54544b637e2fbe3d1325f9cc24b3f4d2313 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 10:17:38 2021 +0700 Add an example file for graph.stack=false/true comparison Should also demonstrate that multiple data groups are possible for non-stacked graphs too. commit 5b532106c3fd255f9b66a66cfb88d977b489ab5d Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 09:34:14 2021 +0700 Add an example file for disabling graph data groups with nil colors commit ac6f1083c02fcb428f089a616f9b0ebaa5496b6c Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:57:25 2021 +0700 Add an example file for step_shape/width/spacing on stacked graphs commit b3703db6a5139e73e72c1cfd198ece2cd9be7df2 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:43:56 2021 +0700 Add an example file for scale=false/true on stacked graph commit b000aed4a12cb44fb501e1c8432a71a14b2f8f3f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 20 08:22:19 2021 +0700 Document deprecation of graph's width and height commit cb0ad617bd6c83a499896797318e3b0b588c3566 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 07:53:37 2021 +0700 Deprecate width and height properties of wibox.widget.graph commit 8c64e14ad91a074b784166a7054e53444cccb9d2 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 04:37:59 2021 +0700 Refactor graph:draw() to separate concerns Firstly, summation of group values for stacked graphs is extracted into graph:preprocess_values(). This makes graph:draw() smaller and exposes the fact, that stacked graphs are almost entirely just a form of a more general concept: pre-render data manipulation. It's something a user might want to customize, and now they can do it by monkey-patching the method. Secondly, the logic for deciding, if and with which color a data group should be drawn, is extracted into graph:should_draw_data_group() and get_data_group_color() methods respectively. This makes it customizable too and makes more obvious the fact, that it is a general thing that has nothing to do with stacked graphs in particular. With this there are only 5 lines remaining in graph:draw(), that could be said to be specific to stacked graphs, all pertaining to the `prev_y` variable, which merely preserves the bar coordinates from a previous data group. commit 2a32b6305d893f413303728a0a3da1dcd9a5591e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 22:52:37 2021 +0700 Pointlessly optimize graph:add_value() 1) Purge old values first, then insert the new one, if needed. Since Lua grows arrays in power of two steps, and our guess_capacity() also takes care to return even integers, it would be a shame to overstep the capacity by one by inserting first and thus getting the table size doubled only to never use the most of it. 2) Use a removal procedure that doesn't call the slow length operator at all. Previous code called it twice (once in table.remove) for every removed value. commit 680ea8f5d81e9040afb4662b3229e937e4b89e02 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:23:38 2021 +0700 Set graph data series color before the painting loop commit b970f0f221ae76250f2a94433ff79d653caa6e21 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:05:37 2021 +0700 Drop a large no longer necessary `if` in graph:draw_values() This commit is almost purely a whitespace change. commit 6b4f0541c70ec99d7f6e276bae64939f337120d6 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:00:50 2021 +0700 Extract values rendering code into graph:draw_values() method This is to avoid ungodly if nesting levels, which are needed to bail out of drawing values early and skip to drawing the border. The next commit will get rid of the if. commit e0950e4f99d1ea1c4b8cea2cc67629fa40030c2a Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 14:50:20 2021 +0700 Track graph draw() usage to guess necessary array capacity The last step of divorcing add_values() from width calculations, while ensuring that user doesn't observe a lack of data to draw. The widget now tracks an approximate number of values that it has been asked to draw() in _private.last_drawn_values_num, and truncates its values array based on that (if not overridden by the capacity property). commit 2e3d542282f35e7c0fed454da0f2eada0fcf3b78 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 13:35:12 2021 +0700 Add `capacity` property to graph widget This is the second step of divorcing add_values() from calculations involving widths. How many values are stored and how many values are drawn are distinct concepts. There isn't even a single definitive "how many values are drawn" because the widget could be rendered in several places with distinct widths simultaneously, and in every such place the data must be drawn appropriately, e.g. with auto-scaling, that takes only the visible data points into account. And the draw() method can do that now. The default capacity value of nil uses heuristics based on widget's _private.width to guess some capacity that would tolerate some dynamic widget resizing. If the user finds the heuristic failing them, they can set the capacity property to the desired maximum number of values that will be kept in the _private.values array (in each data group). commit 08d4a80686f7fe363551c35589a460839f60853e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 03:27:18 2021 +0700 Extract values size computation into compute_drawn_values_num() This is the first step of making add_values() not concern itself (directly) with widget dimensions. The user can monkey-patch the method to override how many values are (logically) drawn, because it affects graph auto-scaling and can be useful for rendering of step_shape-s that paint outside of their step_width. I didn't make it a full-blown function-valued property, because it seems a fringe functionality, which I'm too lazy to document. commit 3c5617c98ab1147618ed22e8414a77fc56b45f6e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 02:32:29 2021 +0700 Copy graph widget prototype methods over in bulk commit 169538837ece41a03d02ba8fbc0e4097062b67a3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun Apr 25 05:58:01 2021 +0700 Remove graph.baseline_y property After giving it some thought I've concluded, that it is unlikely to be useful as it is now. I'd better reserve the name for some more sensible functionality, like fixing the baseline_value defined axis at the given widget coordinate, adjusting the scaling accordingly, so min_value/max_value won't generally correspond to widget top and bottom anymore. commit 202ffaa9a0b820c7c471e1121d01b91f8f76cd38 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 12:06:55 2021 +0700 Add baseline_y and baseline_value properties to graph widget Now values in graph have a *baseline*, tunable with baseline_y and baseline_value properties. It is the (for now invisible) horizontal line from which all bars in the graph grow. I call it a "baseline" instead of "axis", because it doesn't actually have to correspond to the 0 value. But it can, and actually does now by default. baseline_y and baseline_value properties determine the vertical placement of the baseline in the widget. baseline_y is the position of the baseline in normalized widget coordinates, e.g. 0 corresponds to widget's top, 1 - to widget's bottom, and 0.5 - to widget's middle. So if e.g. one sets baseline_y = 0, then no matter how the widget is resized and how its values are scaled, bars in it will be drawn starting from the top. baseline_value is the position of the baseline in value coordinates. So if one sets baseline_value = 10, then no matter what happens, bars for values greater than 10 will be drawn as growing up from this line, and bars for values smaller as growing down. Where and if it will be seen in the widget, depends on the scaling. baseline_value = 0 is the familiar zero axis. baseline_y is unset by default and has priority, i.e. if it's set, then baseline_value is ignored. baseline_value defaults to 0. Prior to this commit, bars were always drawn from the bottom edge of the widget, or, in the new terms, the widget had baseline_y = 1. But it was indistinguishable from baseline_value = 0, because the widget clamped all negative values to 0, so the difference between the two could never be observed. baseline_value = 0 is clearly more useful and familiar to people, so now it is in force by default. commit df7e19abaebf0a871b5ca217b4cc3365533267a6 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 11:18:12 2021 +0700 Move value clamping from graph:add_value() into draw() Previously all values were forcibly clamped between 0 (not tunable at all) and max_value (1 by default). Thus graphs with negative values were not an option. NaN values were conflated with zeros, even though it may be useful to maintain the distinction and render them differently. And if user happened to dynamically change the max_value property, they discovered, that their data got truncated. Preserving user data as it was given is more flexible and makes things easier to debug. This commit does exactly that. So what happens now to the newly allowed values during draw()? There are 3 new cases to consider: 1) NaNs 2) Negatives 3) Values outside of min_value..max_value range NaNs are presently simply not drawn. An empty place is reserved where the value should've been, but nothing else is done. This is distinct from how a zero is handled, e.g. if step_shape is set, it is called with the height parameter = 0, which can still end up drawing something, depending on the shape. One could also consider addition of nan_color/nan_shape properties which could be drawn to alert user to a gap in their data. Negative values necessitate introduction of the concept of the zero axis, from which they will be drawn DOWN, in contrast to positive values. Where should this axis be drawn? How should step_shape-s with inherent direction, e.g. arrows, be drawn for negatives? This will be addressed in the next commit. Values outside of min/max_value range can imply shapes that go beyond widget dimensions. An arrow shape that goes beyond the roof is visibly different from an arrow that saturates and merely touches the widget top, and the user may reasonably desire either behavior. Therefore this commit introduces a boolean clamp_bars property, which defaults to true for reasons of robustness and backward compatibility with the behaviour prior to this commit. The property of course affects only the display of the values, the actual user data stays intact. commit c9c24fb1ab741cd6bb8074971d568e0646c51532 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:29:01 2021 +0700 Use rectangles for drawing graph bars of all sizes I have benchmarked various bar drawing strategies and graph sizes and found out that drawing graph bars with rectangles is as fast or even marginally faster than using fat lines. Thus the code that tries to special-case 1px bars as lines seems to be not worth the additional complexity it introduces, and this commit removes it. Incidentally, I've also established, that rectangles are more robust. If one draws graph bars and feeds cairo very large coordinates (~2^62), then corresponding rectangles fail to render, but all the other rectangles in the same series get rendered successfully, whereas a single large coordinate in a move_to/line_to() sequence is enough to cause all lines in the batch to fail to render. commit bc7f4be5e06405b5aa7fb443a5ace2b9b9575b1f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:01:42 2021 +0700 Cache cairo methods used in the inner loop of graph:draw() I have benchmarked the draw() method, both on cairo xcb and on argb32 image surfaces, and have discovered that cairo context method calls are unnaturally slow, to the point, that almost nothing else in the function matters. For example, drawing bars with vertical lines made the whole draw() function almost 2 times slower than doing the same with rectangles, merely because the former requires two method calls (move_to/line_to), and the latter only one. Simply caching hotpath cairo methods in local variables, reduced the difference between the drawing methods to a negligible one and made the whole function 4 times faster on a typical 100-bar graph, (1.4ms to 0.33ms on my laptop) thus bringing closer my secret dream of drawing live audio data with awesome graphs (kidding). I guess, LGI does something not very optimal there, like gobject-introspecting the method every time, or something. There might be some untapped optimization potential, possibly in upstream, like a method cache, from which everything in awesome could benefit, even though I imagine not many things are so call-intensive as the graph widget, to really feel the difference. commit 538df25bdf02b53d0bbe81809eef2e6fdb05ff6f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 07:32:37 2021 +0700 Use 1-based index in the inner graph draw loop It's used there only once for a calculation and then several times to index some tables, and index 0 is bad for lua tables performance-wise. commit 7412546916cd0a0a490130a120255ffb719398b8 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:55:12 2021 +0700 Leave graph.max_value uninitialized at construction This is a user-visible change, but the previous behavior was probably not what user expected anyway, namely with graph.scale set the graph mysteriously couldn't zoom in onto values that are smaller than 1. commit eb81d7929a161e79c13262cfa73ac2e6a820e8ed Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:48:29 2021 +0700 Allow graph.max_value be nil This makes its behave symmetrically to min_value. Now it defaults to 1, when graph.scale is not set, and to the maximum value in the graph data, when graph.scale is set. commit f622a8430cbfde7007b58d7f4c9c76b08c91e997 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 12:01:13 2021 +0700 Calculate max values num in graph:add_value() more precisely At most so many values were kept in the widget values array, as there are pixels of usable widget width (i.e. sans border). This is exactly right for the surely most common case of graphs with 1px-wide bars, but is more than needed for graphs with step_width+step_spacing>1, and not enough for graphs with subpixel bars. Moreover this made graph autoscaling behave weirdly in graphs with thicker bars, because old values survived longer in the array and kept affecting min_value/max_value calculations even after they were shifted off-screen. The maximum number of kept values is now calculated exactly as the number of values that can be displayed at once, taking step_width and step_spacing into account. It might be worth adding a max_values_num property to the graph to let user override this calculation, in case they draw shapes in step_shape() outside of its supposed rectangle, so our assumptions of visibility are wrong, or the widget often gets resized, resulting in the annoyance of visibly truncated series. commit 0aa63249a18641251a1bf77423739164f0eab39f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 02:30:13 2021 +0700 Fix border_color/border_width bugs in graph widget add_value() wrongly assumed that border_width is always set, when border_color is, so if user set only the latter, an error occurred. add_value() assumed that border_width is always < width which led to awesome hanging up in an infinite loop, should that fail to be the case. The graph border_width property was driven by the border_color property, instead of the other way round. If border_color wasn't set, the border wasn't drawn all, even if user sets non-zero border_width, which is a confusing behavior, in contrast to all other widgets, which simply use a plethora of fallback colors to draw the border, when needed. This also meant that, despite being documented, the beautiful.graph_border_color fallback color could not and was not ever used. If the border_color was set though, an unset border_width was immediately assumed to be 1 instead of 0, also contrary to what other widgets do. Theme colors shouldn't influence widget layout, so this commit fixes this behavior to: 1) border_width always defaults to 0, if unset. 2) border_color falls back to beautiful.graph_border_color or white. Maybe one should also add a beautiful.graph_border_width theme variable, like some widgets do. commit dad4a3253b8fef971fb82720d2a407fcc0a879ad Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 03:11:27 2021 +0700 Implement multiple data series drawing for non-stacked graph The widget can already hold and draw multiple data groups, so it makes little sense to limit drawing only to the first group when graph.stack = false, probably leading to user's confusion. Now user can call add_value(v, group) with group > 1 and the data series will be drawn (over groups with lesser id-s) with graph.stack_colors[group] color, if stack_colors is set, or with graph.color, if stack_colors isn't set. Thus nothing visibly changes for all current users of non-stacked graphs with only single data series, but the new functionality is now there for those who need it. One can't fully emulate this functionality, e.g. with multiple graphs widgets in a wibox.layout.stack, because data series in a single widget can enjoy a common auto-scaling. commit 3e645c976184ab5f50d625271c25062db9ab7ed9 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:59:12 2021 +0700 Don't draw a stacked graph data group if there's no color for it If stack_colors[idx] is nil for the group `idx`, don't draw it at all (as opposed, e.g. to the effect of drawing it with a transparent color, or as though it had all values == 0, which both produce different results). This code is still subtly different from prior behavior, because now user can have a non-contiguous stack_colors table and turn the display of any data group on and off at will, whereas the code two commits earlier would just stop rendering as soon as ipairs(stack_colors) does, i.e. no data series were drawn past the first nil hole. This commit only looks large, but is mostly a whitespace change due to nesting from a pair of wrapping ifs. commit 8c0273786de1ade66bdd977699f98b588da9f487 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:32:42 2021 +0700 Unify stacked and non-stacked graph rendering Now stacked graphs support everything the nonstacked do, like step_width ~= 1 and custom step_shape-s. Technical notes: This code no longer calls cr:stroke() for every single value in a stacked graph, but rather paints a whole series and then cr:stroke/fill()-s it at all once with its color, improving performance considerably. Additionally the graph no longer relies on the presence of the stack_colors property to render series. It draws all data that it has, and, if there's no corresponding color in stack_colors, falls back to graph.color. This will be changed in the next commit to the prior behavior of only drawing those series for which there's a color in the stack_colors table, but that would've made this commit's diff much larger due to required nesting ifs over large code chunks. This commit also takes care to properly handle NaNs and negative values. Those are currently (almost) guaranteed to not occur in the values array due to clipping in graph:add_value(), but I intend to eventually drop the clipping, because it does nothing that draw() really relies upon, and allowing unclipped and NaN values is more flexible and less surprising, e.g. when user dynamically toggles graph.scale. Eventually I intend to implement drawing negative values in non-stacked graphs and possibly even in the stacked ones. commit 38c6589c51b2aa057d7c2f40182ecf32b5b2ff5e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 10:04:15 2021 +0700 Convert graph:draw() to use `self` commit f21f1b2550426df1c4ea8abde079b1ccef84b394 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 12:27:26 2021 +0700 Fix scaling for stacked graphs Stacked graphs have cumulative heights, values from all groups that end up in the same graph bar are effectively summed. Previous code didn't take that into account when calculating min/max_value for the purposes of rescaling, but simply examined every value separately. This code adds up all values up front and finds min/max among those, to determine proper scaling that would fit the graph exactly. commit 1ce4c7bf63bdd6c99c195d668347bec49355e921 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 09:49:35 2021 +0700 Draw shaped graph bars only up to the baseline Prior to this commit, every step_shape bar was, for some reason, drawn with height = full widget's height, regardless of the value it represents. So for the most bars their lower end was far outside the box and therefore clipped off. So e.g. one couldn't see the lower round edges of rounded rectangles. This commit fixes that by appropriately varying the height that is passed into step_shape(), so that lower edges of the bars always exactly touch the lower edge of the graph. If the user wants to restore the prior behavior, they can simply ignore the passed-in height in step_shape() and just always draw their shape with height = full widget height. But they couldn't replicate the effect of this commit in their rc.lua without essentially writing their own graph widget. commit e25185b08da23745b5815f85f6e9e43222ed2294 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 08:42:54 2021 +0700 Avoid incremental transform shifting during graph bar rendering Instead of letting addition errors and cognitive load accumulate simply use the same offset calculation that is used for lines and reset the transform back to the top-left corner after every drawn bar. It's conceptually simpler, and will be useful later for stacked graphs and more. It also fixes a discrepancy between plain and step_shape rendering. Prior to this commit, bar coloring occurred with a disturbed transform, which was centered at the last drawn bar, wherever it happened to be. This meant that nontrivial colors, like gradients and images, were arbitrarily offset when step_shape was used. commit f3e98866d8583c35d0b557c09f529299edc9ba2c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:45:29 2021 +0700 Refactor out graph bar height expression into a variable commit f54528d6892f5aaa4595917853c9e2130f743fdb Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:20:25 2021 +0700 Draw graph bars with lines only when step_width == 1 Before this commit graph bars of step_width < 1 were drawn with lines of width 1, but that's a bit unfortunate, because it led to much overpainting, while still ending up with a graphically incorrect result. Let's draw such bars with rectangles and let cairo do things with subpixels, getting more graphically accurate result, while still keeping the common case of step_width == 1 (supposedly) fast-drawn with lines. commit b6515dca67c18b78eb5657378131a62ab43d4cfc Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:54:07 2021 +0700 Fix unsharp edges when drawing graph bars with rectangles The 0.5 offset is only helpful for 1-width strokes, not for fills. commit 7f3db8f07e82f821bcd1dc418d66564f537609d6 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:21:01 2021 +0700 Do coordinate transformations even if the value isn't drawn The graph presently doesn't draw values < 0, but there was a discrepancy between drawing with and without step_shape. The default line/rectangle drawing reserves an empty place where the skipped value should've been, but no such empty place is reserved, when step_shape is used. This commit fixes that. Skipped values always have their empty space now. commit 7b731ebfe96af7a9dba9c57371da6761cf22c00c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:03:30 2021 +0700 Fix graph bar scaling when min_value is not 0 Previous calculation lead to underscaling of values so that even value = max_value wasn't drawn to full height. commit dca59fd8b7fff0e7e646f2dfc6bd92047028e5a8 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:18:40 2021 +0700 Calculate the horizontal graph bar offset only when needed commit 940ddf4b9825e9c3a91d276208c089d8c74e49db Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:17:23 2021 +0700 Call cr:move_to() only when drawing the graph with lines It's redundant otherwise and might even interfere with user's drawing in step_shape(). commit b5d5cd10ca29fb29cd751faaa95137c2fe89ff63 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:04:06 2021 +0700 Fix wrong horizontal offset calculations in widget.graph Indices in the loop are zero-based, so things like i-1 and i>1 aren't necessary and actually wouldn't have been correct even in a 1-based loop. step_shape = gears.shape.rectangle and step_shape = nil, which is supposed to default to rectangle according to docs, were drawn differently due to mismatched offset calculations, which led to problems especially noticeable when step_spacing is not zero, e.g: 1) whole graph shifted left too much, drawn partially off-screen. 2) whole graph shifted right too much, and the first bar placed closer to the rest than others. commit dc369fa198281e000afc1bd17326f3a264adf7d1 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 02:09:21 2021 +0700 Fix the "border_width affects graph bar width" bug Graph bars were drawn with lines of border_width thickness instead of 1, i.e. possibly too fat to be readable. commit 1ba73378cf29ed27a6ecd18716394779ceb543d9 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 00:13:03 2021 +0700 Simplify internal values representation in graph even more New values in graph._private.values were pushed to the end, and old values were removed from the beginning of the table. In draw() however newer values are drawn first, at the left edge of the graph, necessitating annoying idx = #values - idx conversions everywhere. In particular in stacked graph that means that values that are drawn together may have different indices. One has to shift the values array in add_value() either way, so one might as well insert new values at the beginning of the array and simplify the rest of the code. This doesn't seem like much yet, but it will prove to be much more convenient and less error-prone in what I'm going to do later. commit f70523ee68dbd6b67050ace4b82903a9b22440c7 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 13:35:10 2021 +0700 Simplify internal values representation in graph widget graph._private.values was either a table of values or a table of tables of values, depending on whether "stack" property is enabled, ... or a mix of both if user forgot to pass the second "group" parameter to graph:add_value(). This resulted in pretty fragile and confusing behavior, e.g. leaving user to wonder, why they see nothing more after setting stack=true for a graph in a previously working rc.lua, and demanding a very careful use of add_value() from the user, or else some values or even entire stacks would mysteriously vanish and errors in draw() get triggered, sometimes after a period of seemingly normal functioning. Now `values` is always a table of tables and the group parameter in add_value() defaults to 1, thus making the non-stacked graph a trivial variant of the stacked graph.
2021-05-20 17:14:03 +02:00
"step_spacing", "step_width", "border_width",
"clamp_bars", "baseline_value",
"capacity", "nan_color", "nan_indication",
"group_colors",
}
-- This is what the properties are set to on widget construction.
local prop_defaults = {
baseline_value = 0,
clamp_bars = true,
nan_indication = true,
step_width = 1,
step_spacing = 0,
-- These aren't very useful to set, and the docs don't distinguish between
-- "defaults to" (equals to in a fresh instance) and
-- "falls back to" (is assumed to be equal to, when nil) anyway.
-- scale = false,
-- stack = false,
}
-- This is what the properties are assumed to be in the code, when unset/falsy.
local prop_fallbacks = {
-- This one might become beautiful-themed in the future, so we can't set it
-- in the constructor.
border_width = 0,
-- These are better left unreplaced in code, because they're used only in one
-- place and the intent is more clear when the numbers are directly visible.
-- min_value = 0,
-- max_value = 1,
-- This one is set later. It's not in `prop_defaults`, because I don't
-- want to make it accessible through the getter, lest the user somehow mutates
-- the Cairo pattern and breaks NaN indication for all other graphs.
-- nan_color = make_fallback_nan_color()
}
-- All property defaults are also necessarily fallbacks.
gtable.crush(prop_fallbacks, prop_defaults, true)
-- These fallbacks are themed and can change on the fly.
setmetatable(prop_fallbacks, {
__index = function(_, key)
-- TODO: maybe theme graph.nan_color too?
if key == "background_color" then
return beautiful.graph_bg or "#000000aa"
elseif key == "border_color" then
return beautiful.graph_border_color or "#ffffff"
elseif key == "color" then
return beautiful.graph_fg or "#ff0000"
end
end
})
-- This function sets up default implementations for property getters/setters,
-- when none exist yet. It will be called at the end of this class module.
local function build_properties(prototype, prop_names)
for _, prop in ipairs(prop_names) do
if not prototype["set_" .. prop] then
prototype["set_" .. prop] = function(self, value)
if self._private[prop] ~= value then
self._private[prop] = value
self:emit_signal("widget::redraw_needed")
self:emit_signal("property::"..prop, value)
end
return self
end
end
if not prototype["get_" .. prop] then
prototype["get_" .. prop] = function(self)
return self._private[prop]
end
end
end
end
2016-05-23 07:30:44 +02:00
graph: complete widget overhaul and bug fixes Squashed commit of the following: commit 69821f51fe1e8652715658543e50631ece495090 Author: Alex Belykh <albel727@ngs.ru> Date: Mon May 10 19:17:55 2021 +0700 Refactor property handling in wibox.widget.graph In addition, baseline_value, step_width and step_spacing properties are all set to their default non-nil numeric values during instance creation now. commit 842eb429bf5df4b2ba46b2e6f7646afe89b1a3c3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 9 06:18:45 2021 +0700 Make graph:draw_values() private There's no documentation nor confidence that it's a good API, so I'm hiding it for now. commit 720746780574e4bad14a71fc4174955139c2eb50 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 04:04:13 2021 +0700 Document graph_should_draw_data_group() for posterity commit 7c73f3754ce6ba86b118f1170a6f9e406ab4841d Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:59:21 2021 +0700 Make graph:should_draw_data_group() private There's no confidence whether it's a good API, so I'm hiding it for now. commit b3539e20adb6423118af726ae4f8751ef864b5d2 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:36:44 2021 +0700 Make graph:preprocess_values() private There's no confidence whether it's a good API, so I'm hiding it for now. commit 2df4400e780a6260f8d0ab1c60734fbb11bf3c4f Author: Alex Belykh <albel727@ngs.ru> Date: Thu May 6 10:05:23 2021 +0700 Add spec/wibox/widget/graph_spec.lua commit ce01a9771ab439b8df841f1565aa4aa8a44a5e0d Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 08:16:18 2021 +0700 Improve graph.capacity documentation commit e28fade8f655802f5bf89154169f68d256b8e332 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 07:36:01 2021 +0700 Document graph:compute_drawn_values_num() commit dd68332292465422d08b48e9f0229f143beccbc6 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 05:24:23 2021 +0700 Document graph:pick_data_group_color() method commit ca6dc55f77aca88a6e9cc90bb6fd0045739f41f5 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 04:13:58 2021 +0700 Guard against setting data groups sparsely in graph:add_value() commit cdea5f126f7984e72924f6dffbd312c4fe3b032f Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 02:36:12 2021 +0700 Refactor scaling and baseline choice out of graph:draw() commit 10135deafe8a9390ba66ab657ebb1fb41d549e8c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 21:34:48 2021 +0700 Document graph.nan_color and graph.stack properties commit 0d73bb84ac66b87e16aee472bd1f299549f66a1c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 20:49:24 2021 +0700 Use a better example in docs for the graph.stack property commit c27d160a0826d033d49d6141608c40aa8a0642fa Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 16:05:38 2021 +0700 Document graph.baseline_value property commit 459bc8176c13d4ea32af0dcdc2399d3b929b1c6a Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:53:47 2021 +0700 Document graph.clamp_bars property commit e732ee0b3b007d213e689b98cc00ae7cb141ae08 Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:34 2021 +0700 Improve graph:add_value() and :clear() documentation commit ae73e3aa9f8d60409cbe4cb4da9612f31af4f38e Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:11 2021 +0700 Improve graph min_value/max_value/scale documentation commit a7350996a9def773b91b1631e6c0507e33d8f93b Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:27:36 2021 +0700 Cleanup graph examples to show only relevant things in docs commit 30740c26f4f41c16f4c8a3c339bcb7bd299e14cd Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 14:04:36 2021 +0700 Rename graph.stack_colors property to `group_colors` The colors are used for stacked and non-stacked graphs alike, so the name didn't make sense. Also document and clarify some color stuff. commit 1ea01bfd410971cf0d0c25fd74cd764d5eb7279d Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 21:25:41 2021 +0700 Add an example file for code coverage of some fringe graph cases commit eae68e39553f88a6275a0c65bf040b5d4c262b54 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 20:23:32 2021 +0700 Add step_shape to the graph/nan_color example for code coverage commit 8027147bc2eab34a97b49a428fb8cef27f122bed Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:55:18 2021 +0700 Add example files for graph.nan_color/nan_indication properties commit 89a25a165c52b2c15b6c4d95ecb777a9067e66f9 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:33:07 2021 +0700 Make widget graph:add_value(v) default v to NaN 0 as a default makes no sense and silences the error that is failing to specify a value, to which the user should rather be alerted. The graph widget has sane NaN handling now and will do just that. commit 2835d552440be48d356a7f7b1d64963696dd550b Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:20:48 2021 +0700 Implement graph.nan_color and nan_indication properties Now, whenever there's a NaN among values and graph.nan_indication is set, the corresponding area, where the value bar should have been drawn, is filled with the nan_color from top to bottom. The painting is done after all data is rendered, to make sure that it won't be overpainted and go unnoticed. NaNs among graph's values are inevitably a sign of some error and it is arguably not a sane default behavior to simply not render them at all. The user should take immediate note of any errors by default, instead of wondering at a mysteriously empty graph widget, which is why nan_indication is set to true by default. If the user wants to silence NaNs, they can always simply set it to false. nan_color if unset, defaults to a noticeable yellow-black pattern. As a direct and planned consequence, stacked graphs with negative values in them trigger the NaN handling too. But silencing NaN handling for those graphs is also possible and semi-sane, because then NaNs and negatives are ignored and the graph behaves (almost) like they were zeros. commit 9c1f8f08b400e01e19c534f3810c9c9ab485760c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 26 08:10:19 2021 +0700 Refactor out graph_map_value_to_widget_coordinates() function commit 1905991ddecabdae3e15ffb8ee5b18a1bee1a711 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 28 13:39:29 2021 +0700 Add an example file for graph.baseline_value property commit b31b39d66f4c037a9484b71a3f910703ae01fac0 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 07:03:49 2021 +0700 Add an example file for graph.clamp_bars=false/true comparison commit a1fc850e3047f9ee2c9ddeffacfbabf3ded34171 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 04:06:45 2021 +0700 Add an example file for graphs with negative values and shapes commit 2f012f8ff1bb5094c14e724a61e59a2a3a296bc8 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 01:06:05 2021 +0700 Add an example file for a graph displaying negative values commit 7b14fbd59a4c4c51962fd3110a1a900dd5f4eb98 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 11:18:26 2021 +0700 Add an example file for graphs with step_width < 1 commit dfd9a54544b637e2fbe3d1325f9cc24b3f4d2313 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 10:17:38 2021 +0700 Add an example file for graph.stack=false/true comparison Should also demonstrate that multiple data groups are possible for non-stacked graphs too. commit 5b532106c3fd255f9b66a66cfb88d977b489ab5d Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 09:34:14 2021 +0700 Add an example file for disabling graph data groups with nil colors commit ac6f1083c02fcb428f089a616f9b0ebaa5496b6c Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:57:25 2021 +0700 Add an example file for step_shape/width/spacing on stacked graphs commit b3703db6a5139e73e72c1cfd198ece2cd9be7df2 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:43:56 2021 +0700 Add an example file for scale=false/true on stacked graph commit b000aed4a12cb44fb501e1c8432a71a14b2f8f3f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 20 08:22:19 2021 +0700 Document deprecation of graph's width and height commit cb0ad617bd6c83a499896797318e3b0b588c3566 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 07:53:37 2021 +0700 Deprecate width and height properties of wibox.widget.graph commit 8c64e14ad91a074b784166a7054e53444cccb9d2 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 04:37:59 2021 +0700 Refactor graph:draw() to separate concerns Firstly, summation of group values for stacked graphs is extracted into graph:preprocess_values(). This makes graph:draw() smaller and exposes the fact, that stacked graphs are almost entirely just a form of a more general concept: pre-render data manipulation. It's something a user might want to customize, and now they can do it by monkey-patching the method. Secondly, the logic for deciding, if and with which color a data group should be drawn, is extracted into graph:should_draw_data_group() and get_data_group_color() methods respectively. This makes it customizable too and makes more obvious the fact, that it is a general thing that has nothing to do with stacked graphs in particular. With this there are only 5 lines remaining in graph:draw(), that could be said to be specific to stacked graphs, all pertaining to the `prev_y` variable, which merely preserves the bar coordinates from a previous data group. commit 2a32b6305d893f413303728a0a3da1dcd9a5591e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 22:52:37 2021 +0700 Pointlessly optimize graph:add_value() 1) Purge old values first, then insert the new one, if needed. Since Lua grows arrays in power of two steps, and our guess_capacity() also takes care to return even integers, it would be a shame to overstep the capacity by one by inserting first and thus getting the table size doubled only to never use the most of it. 2) Use a removal procedure that doesn't call the slow length operator at all. Previous code called it twice (once in table.remove) for every removed value. commit 680ea8f5d81e9040afb4662b3229e937e4b89e02 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:23:38 2021 +0700 Set graph data series color before the painting loop commit b970f0f221ae76250f2a94433ff79d653caa6e21 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:05:37 2021 +0700 Drop a large no longer necessary `if` in graph:draw_values() This commit is almost purely a whitespace change. commit 6b4f0541c70ec99d7f6e276bae64939f337120d6 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:00:50 2021 +0700 Extract values rendering code into graph:draw_values() method This is to avoid ungodly if nesting levels, which are needed to bail out of drawing values early and skip to drawing the border. The next commit will get rid of the if. commit e0950e4f99d1ea1c4b8cea2cc67629fa40030c2a Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 14:50:20 2021 +0700 Track graph draw() usage to guess necessary array capacity The last step of divorcing add_values() from width calculations, while ensuring that user doesn't observe a lack of data to draw. The widget now tracks an approximate number of values that it has been asked to draw() in _private.last_drawn_values_num, and truncates its values array based on that (if not overridden by the capacity property). commit 2e3d542282f35e7c0fed454da0f2eada0fcf3b78 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 13:35:12 2021 +0700 Add `capacity` property to graph widget This is the second step of divorcing add_values() from calculations involving widths. How many values are stored and how many values are drawn are distinct concepts. There isn't even a single definitive "how many values are drawn" because the widget could be rendered in several places with distinct widths simultaneously, and in every such place the data must be drawn appropriately, e.g. with auto-scaling, that takes only the visible data points into account. And the draw() method can do that now. The default capacity value of nil uses heuristics based on widget's _private.width to guess some capacity that would tolerate some dynamic widget resizing. If the user finds the heuristic failing them, they can set the capacity property to the desired maximum number of values that will be kept in the _private.values array (in each data group). commit 08d4a80686f7fe363551c35589a460839f60853e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 03:27:18 2021 +0700 Extract values size computation into compute_drawn_values_num() This is the first step of making add_values() not concern itself (directly) with widget dimensions. The user can monkey-patch the method to override how many values are (logically) drawn, because it affects graph auto-scaling and can be useful for rendering of step_shape-s that paint outside of their step_width. I didn't make it a full-blown function-valued property, because it seems a fringe functionality, which I'm too lazy to document. commit 3c5617c98ab1147618ed22e8414a77fc56b45f6e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 02:32:29 2021 +0700 Copy graph widget prototype methods over in bulk commit 169538837ece41a03d02ba8fbc0e4097062b67a3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun Apr 25 05:58:01 2021 +0700 Remove graph.baseline_y property After giving it some thought I've concluded, that it is unlikely to be useful as it is now. I'd better reserve the name for some more sensible functionality, like fixing the baseline_value defined axis at the given widget coordinate, adjusting the scaling accordingly, so min_value/max_value won't generally correspond to widget top and bottom anymore. commit 202ffaa9a0b820c7c471e1121d01b91f8f76cd38 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 12:06:55 2021 +0700 Add baseline_y and baseline_value properties to graph widget Now values in graph have a *baseline*, tunable with baseline_y and baseline_value properties. It is the (for now invisible) horizontal line from which all bars in the graph grow. I call it a "baseline" instead of "axis", because it doesn't actually have to correspond to the 0 value. But it can, and actually does now by default. baseline_y and baseline_value properties determine the vertical placement of the baseline in the widget. baseline_y is the position of the baseline in normalized widget coordinates, e.g. 0 corresponds to widget's top, 1 - to widget's bottom, and 0.5 - to widget's middle. So if e.g. one sets baseline_y = 0, then no matter how the widget is resized and how its values are scaled, bars in it will be drawn starting from the top. baseline_value is the position of the baseline in value coordinates. So if one sets baseline_value = 10, then no matter what happens, bars for values greater than 10 will be drawn as growing up from this line, and bars for values smaller as growing down. Where and if it will be seen in the widget, depends on the scaling. baseline_value = 0 is the familiar zero axis. baseline_y is unset by default and has priority, i.e. if it's set, then baseline_value is ignored. baseline_value defaults to 0. Prior to this commit, bars were always drawn from the bottom edge of the widget, or, in the new terms, the widget had baseline_y = 1. But it was indistinguishable from baseline_value = 0, because the widget clamped all negative values to 0, so the difference between the two could never be observed. baseline_value = 0 is clearly more useful and familiar to people, so now it is in force by default. commit df7e19abaebf0a871b5ca217b4cc3365533267a6 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 11:18:12 2021 +0700 Move value clamping from graph:add_value() into draw() Previously all values were forcibly clamped between 0 (not tunable at all) and max_value (1 by default). Thus graphs with negative values were not an option. NaN values were conflated with zeros, even though it may be useful to maintain the distinction and render them differently. And if user happened to dynamically change the max_value property, they discovered, that their data got truncated. Preserving user data as it was given is more flexible and makes things easier to debug. This commit does exactly that. So what happens now to the newly allowed values during draw()? There are 3 new cases to consider: 1) NaNs 2) Negatives 3) Values outside of min_value..max_value range NaNs are presently simply not drawn. An empty place is reserved where the value should've been, but nothing else is done. This is distinct from how a zero is handled, e.g. if step_shape is set, it is called with the height parameter = 0, which can still end up drawing something, depending on the shape. One could also consider addition of nan_color/nan_shape properties which could be drawn to alert user to a gap in their data. Negative values necessitate introduction of the concept of the zero axis, from which they will be drawn DOWN, in contrast to positive values. Where should this axis be drawn? How should step_shape-s with inherent direction, e.g. arrows, be drawn for negatives? This will be addressed in the next commit. Values outside of min/max_value range can imply shapes that go beyond widget dimensions. An arrow shape that goes beyond the roof is visibly different from an arrow that saturates and merely touches the widget top, and the user may reasonably desire either behavior. Therefore this commit introduces a boolean clamp_bars property, which defaults to true for reasons of robustness and backward compatibility with the behaviour prior to this commit. The property of course affects only the display of the values, the actual user data stays intact. commit c9c24fb1ab741cd6bb8074971d568e0646c51532 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:29:01 2021 +0700 Use rectangles for drawing graph bars of all sizes I have benchmarked various bar drawing strategies and graph sizes and found out that drawing graph bars with rectangles is as fast or even marginally faster than using fat lines. Thus the code that tries to special-case 1px bars as lines seems to be not worth the additional complexity it introduces, and this commit removes it. Incidentally, I've also established, that rectangles are more robust. If one draws graph bars and feeds cairo very large coordinates (~2^62), then corresponding rectangles fail to render, but all the other rectangles in the same series get rendered successfully, whereas a single large coordinate in a move_to/line_to() sequence is enough to cause all lines in the batch to fail to render. commit bc7f4be5e06405b5aa7fb443a5ace2b9b9575b1f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:01:42 2021 +0700 Cache cairo methods used in the inner loop of graph:draw() I have benchmarked the draw() method, both on cairo xcb and on argb32 image surfaces, and have discovered that cairo context method calls are unnaturally slow, to the point, that almost nothing else in the function matters. For example, drawing bars with vertical lines made the whole draw() function almost 2 times slower than doing the same with rectangles, merely because the former requires two method calls (move_to/line_to), and the latter only one. Simply caching hotpath cairo methods in local variables, reduced the difference between the drawing methods to a negligible one and made the whole function 4 times faster on a typical 100-bar graph, (1.4ms to 0.33ms on my laptop) thus bringing closer my secret dream of drawing live audio data with awesome graphs (kidding). I guess, LGI does something not very optimal there, like gobject-introspecting the method every time, or something. There might be some untapped optimization potential, possibly in upstream, like a method cache, from which everything in awesome could benefit, even though I imagine not many things are so call-intensive as the graph widget, to really feel the difference. commit 538df25bdf02b53d0bbe81809eef2e6fdb05ff6f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 07:32:37 2021 +0700 Use 1-based index in the inner graph draw loop It's used there only once for a calculation and then several times to index some tables, and index 0 is bad for lua tables performance-wise. commit 7412546916cd0a0a490130a120255ffb719398b8 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:55:12 2021 +0700 Leave graph.max_value uninitialized at construction This is a user-visible change, but the previous behavior was probably not what user expected anyway, namely with graph.scale set the graph mysteriously couldn't zoom in onto values that are smaller than 1. commit eb81d7929a161e79c13262cfa73ac2e6a820e8ed Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:48:29 2021 +0700 Allow graph.max_value be nil This makes its behave symmetrically to min_value. Now it defaults to 1, when graph.scale is not set, and to the maximum value in the graph data, when graph.scale is set. commit f622a8430cbfde7007b58d7f4c9c76b08c91e997 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 12:01:13 2021 +0700 Calculate max values num in graph:add_value() more precisely At most so many values were kept in the widget values array, as there are pixels of usable widget width (i.e. sans border). This is exactly right for the surely most common case of graphs with 1px-wide bars, but is more than needed for graphs with step_width+step_spacing>1, and not enough for graphs with subpixel bars. Moreover this made graph autoscaling behave weirdly in graphs with thicker bars, because old values survived longer in the array and kept affecting min_value/max_value calculations even after they were shifted off-screen. The maximum number of kept values is now calculated exactly as the number of values that can be displayed at once, taking step_width and step_spacing into account. It might be worth adding a max_values_num property to the graph to let user override this calculation, in case they draw shapes in step_shape() outside of its supposed rectangle, so our assumptions of visibility are wrong, or the widget often gets resized, resulting in the annoyance of visibly truncated series. commit 0aa63249a18641251a1bf77423739164f0eab39f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 02:30:13 2021 +0700 Fix border_color/border_width bugs in graph widget add_value() wrongly assumed that border_width is always set, when border_color is, so if user set only the latter, an error occurred. add_value() assumed that border_width is always < width which led to awesome hanging up in an infinite loop, should that fail to be the case. The graph border_width property was driven by the border_color property, instead of the other way round. If border_color wasn't set, the border wasn't drawn all, even if user sets non-zero border_width, which is a confusing behavior, in contrast to all other widgets, which simply use a plethora of fallback colors to draw the border, when needed. This also meant that, despite being documented, the beautiful.graph_border_color fallback color could not and was not ever used. If the border_color was set though, an unset border_width was immediately assumed to be 1 instead of 0, also contrary to what other widgets do. Theme colors shouldn't influence widget layout, so this commit fixes this behavior to: 1) border_width always defaults to 0, if unset. 2) border_color falls back to beautiful.graph_border_color or white. Maybe one should also add a beautiful.graph_border_width theme variable, like some widgets do. commit dad4a3253b8fef971fb82720d2a407fcc0a879ad Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 03:11:27 2021 +0700 Implement multiple data series drawing for non-stacked graph The widget can already hold and draw multiple data groups, so it makes little sense to limit drawing only to the first group when graph.stack = false, probably leading to user's confusion. Now user can call add_value(v, group) with group > 1 and the data series will be drawn (over groups with lesser id-s) with graph.stack_colors[group] color, if stack_colors is set, or with graph.color, if stack_colors isn't set. Thus nothing visibly changes for all current users of non-stacked graphs with only single data series, but the new functionality is now there for those who need it. One can't fully emulate this functionality, e.g. with multiple graphs widgets in a wibox.layout.stack, because data series in a single widget can enjoy a common auto-scaling. commit 3e645c976184ab5f50d625271c25062db9ab7ed9 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:59:12 2021 +0700 Don't draw a stacked graph data group if there's no color for it If stack_colors[idx] is nil for the group `idx`, don't draw it at all (as opposed, e.g. to the effect of drawing it with a transparent color, or as though it had all values == 0, which both produce different results). This code is still subtly different from prior behavior, because now user can have a non-contiguous stack_colors table and turn the display of any data group on and off at will, whereas the code two commits earlier would just stop rendering as soon as ipairs(stack_colors) does, i.e. no data series were drawn past the first nil hole. This commit only looks large, but is mostly a whitespace change due to nesting from a pair of wrapping ifs. commit 8c0273786de1ade66bdd977699f98b588da9f487 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:32:42 2021 +0700 Unify stacked and non-stacked graph rendering Now stacked graphs support everything the nonstacked do, like step_width ~= 1 and custom step_shape-s. Technical notes: This code no longer calls cr:stroke() for every single value in a stacked graph, but rather paints a whole series and then cr:stroke/fill()-s it at all once with its color, improving performance considerably. Additionally the graph no longer relies on the presence of the stack_colors property to render series. It draws all data that it has, and, if there's no corresponding color in stack_colors, falls back to graph.color. This will be changed in the next commit to the prior behavior of only drawing those series for which there's a color in the stack_colors table, but that would've made this commit's diff much larger due to required nesting ifs over large code chunks. This commit also takes care to properly handle NaNs and negative values. Those are currently (almost) guaranteed to not occur in the values array due to clipping in graph:add_value(), but I intend to eventually drop the clipping, because it does nothing that draw() really relies upon, and allowing unclipped and NaN values is more flexible and less surprising, e.g. when user dynamically toggles graph.scale. Eventually I intend to implement drawing negative values in non-stacked graphs and possibly even in the stacked ones. commit 38c6589c51b2aa057d7c2f40182ecf32b5b2ff5e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 10:04:15 2021 +0700 Convert graph:draw() to use `self` commit f21f1b2550426df1c4ea8abde079b1ccef84b394 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 12:27:26 2021 +0700 Fix scaling for stacked graphs Stacked graphs have cumulative heights, values from all groups that end up in the same graph bar are effectively summed. Previous code didn't take that into account when calculating min/max_value for the purposes of rescaling, but simply examined every value separately. This code adds up all values up front and finds min/max among those, to determine proper scaling that would fit the graph exactly. commit 1ce4c7bf63bdd6c99c195d668347bec49355e921 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 09:49:35 2021 +0700 Draw shaped graph bars only up to the baseline Prior to this commit, every step_shape bar was, for some reason, drawn with height = full widget's height, regardless of the value it represents. So for the most bars their lower end was far outside the box and therefore clipped off. So e.g. one couldn't see the lower round edges of rounded rectangles. This commit fixes that by appropriately varying the height that is passed into step_shape(), so that lower edges of the bars always exactly touch the lower edge of the graph. If the user wants to restore the prior behavior, they can simply ignore the passed-in height in step_shape() and just always draw their shape with height = full widget height. But they couldn't replicate the effect of this commit in their rc.lua without essentially writing their own graph widget. commit e25185b08da23745b5815f85f6e9e43222ed2294 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 08:42:54 2021 +0700 Avoid incremental transform shifting during graph bar rendering Instead of letting addition errors and cognitive load accumulate simply use the same offset calculation that is used for lines and reset the transform back to the top-left corner after every drawn bar. It's conceptually simpler, and will be useful later for stacked graphs and more. It also fixes a discrepancy between plain and step_shape rendering. Prior to this commit, bar coloring occurred with a disturbed transform, which was centered at the last drawn bar, wherever it happened to be. This meant that nontrivial colors, like gradients and images, were arbitrarily offset when step_shape was used. commit f3e98866d8583c35d0b557c09f529299edc9ba2c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:45:29 2021 +0700 Refactor out graph bar height expression into a variable commit f54528d6892f5aaa4595917853c9e2130f743fdb Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:20:25 2021 +0700 Draw graph bars with lines only when step_width == 1 Before this commit graph bars of step_width < 1 were drawn with lines of width 1, but that's a bit unfortunate, because it led to much overpainting, while still ending up with a graphically incorrect result. Let's draw such bars with rectangles and let cairo do things with subpixels, getting more graphically accurate result, while still keeping the common case of step_width == 1 (supposedly) fast-drawn with lines. commit b6515dca67c18b78eb5657378131a62ab43d4cfc Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:54:07 2021 +0700 Fix unsharp edges when drawing graph bars with rectangles The 0.5 offset is only helpful for 1-width strokes, not for fills. commit 7f3db8f07e82f821bcd1dc418d66564f537609d6 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:21:01 2021 +0700 Do coordinate transformations even if the value isn't drawn The graph presently doesn't draw values < 0, but there was a discrepancy between drawing with and without step_shape. The default line/rectangle drawing reserves an empty place where the skipped value should've been, but no such empty place is reserved, when step_shape is used. This commit fixes that. Skipped values always have their empty space now. commit 7b731ebfe96af7a9dba9c57371da6761cf22c00c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:03:30 2021 +0700 Fix graph bar scaling when min_value is not 0 Previous calculation lead to underscaling of values so that even value = max_value wasn't drawn to full height. commit dca59fd8b7fff0e7e646f2dfc6bd92047028e5a8 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:18:40 2021 +0700 Calculate the horizontal graph bar offset only when needed commit 940ddf4b9825e9c3a91d276208c089d8c74e49db Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:17:23 2021 +0700 Call cr:move_to() only when drawing the graph with lines It's redundant otherwise and might even interfere with user's drawing in step_shape(). commit b5d5cd10ca29fb29cd751faaa95137c2fe89ff63 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:04:06 2021 +0700 Fix wrong horizontal offset calculations in widget.graph Indices in the loop are zero-based, so things like i-1 and i>1 aren't necessary and actually wouldn't have been correct even in a 1-based loop. step_shape = gears.shape.rectangle and step_shape = nil, which is supposed to default to rectangle according to docs, were drawn differently due to mismatched offset calculations, which led to problems especially noticeable when step_spacing is not zero, e.g: 1) whole graph shifted left too much, drawn partially off-screen. 2) whole graph shifted right too much, and the first bar placed closer to the rest than others. commit dc369fa198281e000afc1bd17326f3a264adf7d1 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 02:09:21 2021 +0700 Fix the "border_width affects graph bar width" bug Graph bars were drawn with lines of border_width thickness instead of 1, i.e. possibly too fat to be readable. commit 1ba73378cf29ed27a6ecd18716394779ceb543d9 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 00:13:03 2021 +0700 Simplify internal values representation in graph even more New values in graph._private.values were pushed to the end, and old values were removed from the beginning of the table. In draw() however newer values are drawn first, at the left edge of the graph, necessitating annoying idx = #values - idx conversions everywhere. In particular in stacked graph that means that values that are drawn together may have different indices. One has to shift the values array in add_value() either way, so one might as well insert new values at the beginning of the array and simplify the rest of the code. This doesn't seem like much yet, but it will prove to be much more convenient and less error-prone in what I'm going to do later. commit f70523ee68dbd6b67050ace4b82903a9b22440c7 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 13:35:10 2021 +0700 Simplify internal values representation in graph widget graph._private.values was either a table of values or a table of tables of values, depending on whether "stack" property is enabled, ... or a mix of both if user forgot to pass the second "group" parameter to graph:add_value(). This resulted in pretty fragile and confusing behavior, e.g. leaving user to wonder, why they see nothing more after setting stack=true for a graph in a previously working rc.lua, and demanding a very careful use of add_value() from the user, or else some values or even entire stacks would mysteriously vanish and errors in draw() get triggered, sometimes after a period of seemingly normal functioning. Now `values` is always a table of tables and the group parameter in add_value() defaults to 1, thus making the non-stacked graph a trivial variant of the stacked graph.
2021-05-20 17:14:03 +02:00
-- Creates a yellow-black danger stripe @{gears.color} for NaN indication.
local function build_fallback_nan_color()
local clr = color.create_pattern_uncached({
["type"] = "linear",
from = {0, 0}, to = {4, 4},
stops={
{0, "#000000"},
{0.25, "#000000"}, {0.25, "#ffff00"},
{0.50, "#ffff00"}, {0.50, "#000000"},
{0.75, "#000000"}, {0.75, "#ffff00"},
{1, "#ffff00"},
},
})
clr:set_extend("REPEAT")
return clr
end
2016-05-23 07:30:44 +02:00
graph: complete widget overhaul and bug fixes Squashed commit of the following: commit 69821f51fe1e8652715658543e50631ece495090 Author: Alex Belykh <albel727@ngs.ru> Date: Mon May 10 19:17:55 2021 +0700 Refactor property handling in wibox.widget.graph In addition, baseline_value, step_width and step_spacing properties are all set to their default non-nil numeric values during instance creation now. commit 842eb429bf5df4b2ba46b2e6f7646afe89b1a3c3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 9 06:18:45 2021 +0700 Make graph:draw_values() private There's no documentation nor confidence that it's a good API, so I'm hiding it for now. commit 720746780574e4bad14a71fc4174955139c2eb50 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 04:04:13 2021 +0700 Document graph_should_draw_data_group() for posterity commit 7c73f3754ce6ba86b118f1170a6f9e406ab4841d Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:59:21 2021 +0700 Make graph:should_draw_data_group() private There's no confidence whether it's a good API, so I'm hiding it for now. commit b3539e20adb6423118af726ae4f8751ef864b5d2 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:36:44 2021 +0700 Make graph:preprocess_values() private There's no confidence whether it's a good API, so I'm hiding it for now. commit 2df4400e780a6260f8d0ab1c60734fbb11bf3c4f Author: Alex Belykh <albel727@ngs.ru> Date: Thu May 6 10:05:23 2021 +0700 Add spec/wibox/widget/graph_spec.lua commit ce01a9771ab439b8df841f1565aa4aa8a44a5e0d Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 08:16:18 2021 +0700 Improve graph.capacity documentation commit e28fade8f655802f5bf89154169f68d256b8e332 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 07:36:01 2021 +0700 Document graph:compute_drawn_values_num() commit dd68332292465422d08b48e9f0229f143beccbc6 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 05:24:23 2021 +0700 Document graph:pick_data_group_color() method commit ca6dc55f77aca88a6e9cc90bb6fd0045739f41f5 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 04:13:58 2021 +0700 Guard against setting data groups sparsely in graph:add_value() commit cdea5f126f7984e72924f6dffbd312c4fe3b032f Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 02:36:12 2021 +0700 Refactor scaling and baseline choice out of graph:draw() commit 10135deafe8a9390ba66ab657ebb1fb41d549e8c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 21:34:48 2021 +0700 Document graph.nan_color and graph.stack properties commit 0d73bb84ac66b87e16aee472bd1f299549f66a1c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 20:49:24 2021 +0700 Use a better example in docs for the graph.stack property commit c27d160a0826d033d49d6141608c40aa8a0642fa Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 16:05:38 2021 +0700 Document graph.baseline_value property commit 459bc8176c13d4ea32af0dcdc2399d3b929b1c6a Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:53:47 2021 +0700 Document graph.clamp_bars property commit e732ee0b3b007d213e689b98cc00ae7cb141ae08 Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:34 2021 +0700 Improve graph:add_value() and :clear() documentation commit ae73e3aa9f8d60409cbe4cb4da9612f31af4f38e Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:11 2021 +0700 Improve graph min_value/max_value/scale documentation commit a7350996a9def773b91b1631e6c0507e33d8f93b Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:27:36 2021 +0700 Cleanup graph examples to show only relevant things in docs commit 30740c26f4f41c16f4c8a3c339bcb7bd299e14cd Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 14:04:36 2021 +0700 Rename graph.stack_colors property to `group_colors` The colors are used for stacked and non-stacked graphs alike, so the name didn't make sense. Also document and clarify some color stuff. commit 1ea01bfd410971cf0d0c25fd74cd764d5eb7279d Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 21:25:41 2021 +0700 Add an example file for code coverage of some fringe graph cases commit eae68e39553f88a6275a0c65bf040b5d4c262b54 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 20:23:32 2021 +0700 Add step_shape to the graph/nan_color example for code coverage commit 8027147bc2eab34a97b49a428fb8cef27f122bed Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:55:18 2021 +0700 Add example files for graph.nan_color/nan_indication properties commit 89a25a165c52b2c15b6c4d95ecb777a9067e66f9 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:33:07 2021 +0700 Make widget graph:add_value(v) default v to NaN 0 as a default makes no sense and silences the error that is failing to specify a value, to which the user should rather be alerted. The graph widget has sane NaN handling now and will do just that. commit 2835d552440be48d356a7f7b1d64963696dd550b Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:20:48 2021 +0700 Implement graph.nan_color and nan_indication properties Now, whenever there's a NaN among values and graph.nan_indication is set, the corresponding area, where the value bar should have been drawn, is filled with the nan_color from top to bottom. The painting is done after all data is rendered, to make sure that it won't be overpainted and go unnoticed. NaNs among graph's values are inevitably a sign of some error and it is arguably not a sane default behavior to simply not render them at all. The user should take immediate note of any errors by default, instead of wondering at a mysteriously empty graph widget, which is why nan_indication is set to true by default. If the user wants to silence NaNs, they can always simply set it to false. nan_color if unset, defaults to a noticeable yellow-black pattern. As a direct and planned consequence, stacked graphs with negative values in them trigger the NaN handling too. But silencing NaN handling for those graphs is also possible and semi-sane, because then NaNs and negatives are ignored and the graph behaves (almost) like they were zeros. commit 9c1f8f08b400e01e19c534f3810c9c9ab485760c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 26 08:10:19 2021 +0700 Refactor out graph_map_value_to_widget_coordinates() function commit 1905991ddecabdae3e15ffb8ee5b18a1bee1a711 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 28 13:39:29 2021 +0700 Add an example file for graph.baseline_value property commit b31b39d66f4c037a9484b71a3f910703ae01fac0 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 07:03:49 2021 +0700 Add an example file for graph.clamp_bars=false/true comparison commit a1fc850e3047f9ee2c9ddeffacfbabf3ded34171 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 04:06:45 2021 +0700 Add an example file for graphs with negative values and shapes commit 2f012f8ff1bb5094c14e724a61e59a2a3a296bc8 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 01:06:05 2021 +0700 Add an example file for a graph displaying negative values commit 7b14fbd59a4c4c51962fd3110a1a900dd5f4eb98 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 11:18:26 2021 +0700 Add an example file for graphs with step_width < 1 commit dfd9a54544b637e2fbe3d1325f9cc24b3f4d2313 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 10:17:38 2021 +0700 Add an example file for graph.stack=false/true comparison Should also demonstrate that multiple data groups are possible for non-stacked graphs too. commit 5b532106c3fd255f9b66a66cfb88d977b489ab5d Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 09:34:14 2021 +0700 Add an example file for disabling graph data groups with nil colors commit ac6f1083c02fcb428f089a616f9b0ebaa5496b6c Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:57:25 2021 +0700 Add an example file for step_shape/width/spacing on stacked graphs commit b3703db6a5139e73e72c1cfd198ece2cd9be7df2 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:43:56 2021 +0700 Add an example file for scale=false/true on stacked graph commit b000aed4a12cb44fb501e1c8432a71a14b2f8f3f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 20 08:22:19 2021 +0700 Document deprecation of graph's width and height commit cb0ad617bd6c83a499896797318e3b0b588c3566 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 07:53:37 2021 +0700 Deprecate width and height properties of wibox.widget.graph commit 8c64e14ad91a074b784166a7054e53444cccb9d2 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 04:37:59 2021 +0700 Refactor graph:draw() to separate concerns Firstly, summation of group values for stacked graphs is extracted into graph:preprocess_values(). This makes graph:draw() smaller and exposes the fact, that stacked graphs are almost entirely just a form of a more general concept: pre-render data manipulation. It's something a user might want to customize, and now they can do it by monkey-patching the method. Secondly, the logic for deciding, if and with which color a data group should be drawn, is extracted into graph:should_draw_data_group() and get_data_group_color() methods respectively. This makes it customizable too and makes more obvious the fact, that it is a general thing that has nothing to do with stacked graphs in particular. With this there are only 5 lines remaining in graph:draw(), that could be said to be specific to stacked graphs, all pertaining to the `prev_y` variable, which merely preserves the bar coordinates from a previous data group. commit 2a32b6305d893f413303728a0a3da1dcd9a5591e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 22:52:37 2021 +0700 Pointlessly optimize graph:add_value() 1) Purge old values first, then insert the new one, if needed. Since Lua grows arrays in power of two steps, and our guess_capacity() also takes care to return even integers, it would be a shame to overstep the capacity by one by inserting first and thus getting the table size doubled only to never use the most of it. 2) Use a removal procedure that doesn't call the slow length operator at all. Previous code called it twice (once in table.remove) for every removed value. commit 680ea8f5d81e9040afb4662b3229e937e4b89e02 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:23:38 2021 +0700 Set graph data series color before the painting loop commit b970f0f221ae76250f2a94433ff79d653caa6e21 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:05:37 2021 +0700 Drop a large no longer necessary `if` in graph:draw_values() This commit is almost purely a whitespace change. commit 6b4f0541c70ec99d7f6e276bae64939f337120d6 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:00:50 2021 +0700 Extract values rendering code into graph:draw_values() method This is to avoid ungodly if nesting levels, which are needed to bail out of drawing values early and skip to drawing the border. The next commit will get rid of the if. commit e0950e4f99d1ea1c4b8cea2cc67629fa40030c2a Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 14:50:20 2021 +0700 Track graph draw() usage to guess necessary array capacity The last step of divorcing add_values() from width calculations, while ensuring that user doesn't observe a lack of data to draw. The widget now tracks an approximate number of values that it has been asked to draw() in _private.last_drawn_values_num, and truncates its values array based on that (if not overridden by the capacity property). commit 2e3d542282f35e7c0fed454da0f2eada0fcf3b78 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 13:35:12 2021 +0700 Add `capacity` property to graph widget This is the second step of divorcing add_values() from calculations involving widths. How many values are stored and how many values are drawn are distinct concepts. There isn't even a single definitive "how many values are drawn" because the widget could be rendered in several places with distinct widths simultaneously, and in every such place the data must be drawn appropriately, e.g. with auto-scaling, that takes only the visible data points into account. And the draw() method can do that now. The default capacity value of nil uses heuristics based on widget's _private.width to guess some capacity that would tolerate some dynamic widget resizing. If the user finds the heuristic failing them, they can set the capacity property to the desired maximum number of values that will be kept in the _private.values array (in each data group). commit 08d4a80686f7fe363551c35589a460839f60853e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 03:27:18 2021 +0700 Extract values size computation into compute_drawn_values_num() This is the first step of making add_values() not concern itself (directly) with widget dimensions. The user can monkey-patch the method to override how many values are (logically) drawn, because it affects graph auto-scaling and can be useful for rendering of step_shape-s that paint outside of their step_width. I didn't make it a full-blown function-valued property, because it seems a fringe functionality, which I'm too lazy to document. commit 3c5617c98ab1147618ed22e8414a77fc56b45f6e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 02:32:29 2021 +0700 Copy graph widget prototype methods over in bulk commit 169538837ece41a03d02ba8fbc0e4097062b67a3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun Apr 25 05:58:01 2021 +0700 Remove graph.baseline_y property After giving it some thought I've concluded, that it is unlikely to be useful as it is now. I'd better reserve the name for some more sensible functionality, like fixing the baseline_value defined axis at the given widget coordinate, adjusting the scaling accordingly, so min_value/max_value won't generally correspond to widget top and bottom anymore. commit 202ffaa9a0b820c7c471e1121d01b91f8f76cd38 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 12:06:55 2021 +0700 Add baseline_y and baseline_value properties to graph widget Now values in graph have a *baseline*, tunable with baseline_y and baseline_value properties. It is the (for now invisible) horizontal line from which all bars in the graph grow. I call it a "baseline" instead of "axis", because it doesn't actually have to correspond to the 0 value. But it can, and actually does now by default. baseline_y and baseline_value properties determine the vertical placement of the baseline in the widget. baseline_y is the position of the baseline in normalized widget coordinates, e.g. 0 corresponds to widget's top, 1 - to widget's bottom, and 0.5 - to widget's middle. So if e.g. one sets baseline_y = 0, then no matter how the widget is resized and how its values are scaled, bars in it will be drawn starting from the top. baseline_value is the position of the baseline in value coordinates. So if one sets baseline_value = 10, then no matter what happens, bars for values greater than 10 will be drawn as growing up from this line, and bars for values smaller as growing down. Where and if it will be seen in the widget, depends on the scaling. baseline_value = 0 is the familiar zero axis. baseline_y is unset by default and has priority, i.e. if it's set, then baseline_value is ignored. baseline_value defaults to 0. Prior to this commit, bars were always drawn from the bottom edge of the widget, or, in the new terms, the widget had baseline_y = 1. But it was indistinguishable from baseline_value = 0, because the widget clamped all negative values to 0, so the difference between the two could never be observed. baseline_value = 0 is clearly more useful and familiar to people, so now it is in force by default. commit df7e19abaebf0a871b5ca217b4cc3365533267a6 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 11:18:12 2021 +0700 Move value clamping from graph:add_value() into draw() Previously all values were forcibly clamped between 0 (not tunable at all) and max_value (1 by default). Thus graphs with negative values were not an option. NaN values were conflated with zeros, even though it may be useful to maintain the distinction and render them differently. And if user happened to dynamically change the max_value property, they discovered, that their data got truncated. Preserving user data as it was given is more flexible and makes things easier to debug. This commit does exactly that. So what happens now to the newly allowed values during draw()? There are 3 new cases to consider: 1) NaNs 2) Negatives 3) Values outside of min_value..max_value range NaNs are presently simply not drawn. An empty place is reserved where the value should've been, but nothing else is done. This is distinct from how a zero is handled, e.g. if step_shape is set, it is called with the height parameter = 0, which can still end up drawing something, depending on the shape. One could also consider addition of nan_color/nan_shape properties which could be drawn to alert user to a gap in their data. Negative values necessitate introduction of the concept of the zero axis, from which they will be drawn DOWN, in contrast to positive values. Where should this axis be drawn? How should step_shape-s with inherent direction, e.g. arrows, be drawn for negatives? This will be addressed in the next commit. Values outside of min/max_value range can imply shapes that go beyond widget dimensions. An arrow shape that goes beyond the roof is visibly different from an arrow that saturates and merely touches the widget top, and the user may reasonably desire either behavior. Therefore this commit introduces a boolean clamp_bars property, which defaults to true for reasons of robustness and backward compatibility with the behaviour prior to this commit. The property of course affects only the display of the values, the actual user data stays intact. commit c9c24fb1ab741cd6bb8074971d568e0646c51532 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:29:01 2021 +0700 Use rectangles for drawing graph bars of all sizes I have benchmarked various bar drawing strategies and graph sizes and found out that drawing graph bars with rectangles is as fast or even marginally faster than using fat lines. Thus the code that tries to special-case 1px bars as lines seems to be not worth the additional complexity it introduces, and this commit removes it. Incidentally, I've also established, that rectangles are more robust. If one draws graph bars and feeds cairo very large coordinates (~2^62), then corresponding rectangles fail to render, but all the other rectangles in the same series get rendered successfully, whereas a single large coordinate in a move_to/line_to() sequence is enough to cause all lines in the batch to fail to render. commit bc7f4be5e06405b5aa7fb443a5ace2b9b9575b1f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:01:42 2021 +0700 Cache cairo methods used in the inner loop of graph:draw() I have benchmarked the draw() method, both on cairo xcb and on argb32 image surfaces, and have discovered that cairo context method calls are unnaturally slow, to the point, that almost nothing else in the function matters. For example, drawing bars with vertical lines made the whole draw() function almost 2 times slower than doing the same with rectangles, merely because the former requires two method calls (move_to/line_to), and the latter only one. Simply caching hotpath cairo methods in local variables, reduced the difference between the drawing methods to a negligible one and made the whole function 4 times faster on a typical 100-bar graph, (1.4ms to 0.33ms on my laptop) thus bringing closer my secret dream of drawing live audio data with awesome graphs (kidding). I guess, LGI does something not very optimal there, like gobject-introspecting the method every time, or something. There might be some untapped optimization potential, possibly in upstream, like a method cache, from which everything in awesome could benefit, even though I imagine not many things are so call-intensive as the graph widget, to really feel the difference. commit 538df25bdf02b53d0bbe81809eef2e6fdb05ff6f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 07:32:37 2021 +0700 Use 1-based index in the inner graph draw loop It's used there only once for a calculation and then several times to index some tables, and index 0 is bad for lua tables performance-wise. commit 7412546916cd0a0a490130a120255ffb719398b8 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:55:12 2021 +0700 Leave graph.max_value uninitialized at construction This is a user-visible change, but the previous behavior was probably not what user expected anyway, namely with graph.scale set the graph mysteriously couldn't zoom in onto values that are smaller than 1. commit eb81d7929a161e79c13262cfa73ac2e6a820e8ed Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:48:29 2021 +0700 Allow graph.max_value be nil This makes its behave symmetrically to min_value. Now it defaults to 1, when graph.scale is not set, and to the maximum value in the graph data, when graph.scale is set. commit f622a8430cbfde7007b58d7f4c9c76b08c91e997 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 12:01:13 2021 +0700 Calculate max values num in graph:add_value() more precisely At most so many values were kept in the widget values array, as there are pixels of usable widget width (i.e. sans border). This is exactly right for the surely most common case of graphs with 1px-wide bars, but is more than needed for graphs with step_width+step_spacing>1, and not enough for graphs with subpixel bars. Moreover this made graph autoscaling behave weirdly in graphs with thicker bars, because old values survived longer in the array and kept affecting min_value/max_value calculations even after they were shifted off-screen. The maximum number of kept values is now calculated exactly as the number of values that can be displayed at once, taking step_width and step_spacing into account. It might be worth adding a max_values_num property to the graph to let user override this calculation, in case they draw shapes in step_shape() outside of its supposed rectangle, so our assumptions of visibility are wrong, or the widget often gets resized, resulting in the annoyance of visibly truncated series. commit 0aa63249a18641251a1bf77423739164f0eab39f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 02:30:13 2021 +0700 Fix border_color/border_width bugs in graph widget add_value() wrongly assumed that border_width is always set, when border_color is, so if user set only the latter, an error occurred. add_value() assumed that border_width is always < width which led to awesome hanging up in an infinite loop, should that fail to be the case. The graph border_width property was driven by the border_color property, instead of the other way round. If border_color wasn't set, the border wasn't drawn all, even if user sets non-zero border_width, which is a confusing behavior, in contrast to all other widgets, which simply use a plethora of fallback colors to draw the border, when needed. This also meant that, despite being documented, the beautiful.graph_border_color fallback color could not and was not ever used. If the border_color was set though, an unset border_width was immediately assumed to be 1 instead of 0, also contrary to what other widgets do. Theme colors shouldn't influence widget layout, so this commit fixes this behavior to: 1) border_width always defaults to 0, if unset. 2) border_color falls back to beautiful.graph_border_color or white. Maybe one should also add a beautiful.graph_border_width theme variable, like some widgets do. commit dad4a3253b8fef971fb82720d2a407fcc0a879ad Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 03:11:27 2021 +0700 Implement multiple data series drawing for non-stacked graph The widget can already hold and draw multiple data groups, so it makes little sense to limit drawing only to the first group when graph.stack = false, probably leading to user's confusion. Now user can call add_value(v, group) with group > 1 and the data series will be drawn (over groups with lesser id-s) with graph.stack_colors[group] color, if stack_colors is set, or with graph.color, if stack_colors isn't set. Thus nothing visibly changes for all current users of non-stacked graphs with only single data series, but the new functionality is now there for those who need it. One can't fully emulate this functionality, e.g. with multiple graphs widgets in a wibox.layout.stack, because data series in a single widget can enjoy a common auto-scaling. commit 3e645c976184ab5f50d625271c25062db9ab7ed9 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:59:12 2021 +0700 Don't draw a stacked graph data group if there's no color for it If stack_colors[idx] is nil for the group `idx`, don't draw it at all (as opposed, e.g. to the effect of drawing it with a transparent color, or as though it had all values == 0, which both produce different results). This code is still subtly different from prior behavior, because now user can have a non-contiguous stack_colors table and turn the display of any data group on and off at will, whereas the code two commits earlier would just stop rendering as soon as ipairs(stack_colors) does, i.e. no data series were drawn past the first nil hole. This commit only looks large, but is mostly a whitespace change due to nesting from a pair of wrapping ifs. commit 8c0273786de1ade66bdd977699f98b588da9f487 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:32:42 2021 +0700 Unify stacked and non-stacked graph rendering Now stacked graphs support everything the nonstacked do, like step_width ~= 1 and custom step_shape-s. Technical notes: This code no longer calls cr:stroke() for every single value in a stacked graph, but rather paints a whole series and then cr:stroke/fill()-s it at all once with its color, improving performance considerably. Additionally the graph no longer relies on the presence of the stack_colors property to render series. It draws all data that it has, and, if there's no corresponding color in stack_colors, falls back to graph.color. This will be changed in the next commit to the prior behavior of only drawing those series for which there's a color in the stack_colors table, but that would've made this commit's diff much larger due to required nesting ifs over large code chunks. This commit also takes care to properly handle NaNs and negative values. Those are currently (almost) guaranteed to not occur in the values array due to clipping in graph:add_value(), but I intend to eventually drop the clipping, because it does nothing that draw() really relies upon, and allowing unclipped and NaN values is more flexible and less surprising, e.g. when user dynamically toggles graph.scale. Eventually I intend to implement drawing negative values in non-stacked graphs and possibly even in the stacked ones. commit 38c6589c51b2aa057d7c2f40182ecf32b5b2ff5e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 10:04:15 2021 +0700 Convert graph:draw() to use `self` commit f21f1b2550426df1c4ea8abde079b1ccef84b394 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 12:27:26 2021 +0700 Fix scaling for stacked graphs Stacked graphs have cumulative heights, values from all groups that end up in the same graph bar are effectively summed. Previous code didn't take that into account when calculating min/max_value for the purposes of rescaling, but simply examined every value separately. This code adds up all values up front and finds min/max among those, to determine proper scaling that would fit the graph exactly. commit 1ce4c7bf63bdd6c99c195d668347bec49355e921 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 09:49:35 2021 +0700 Draw shaped graph bars only up to the baseline Prior to this commit, every step_shape bar was, for some reason, drawn with height = full widget's height, regardless of the value it represents. So for the most bars their lower end was far outside the box and therefore clipped off. So e.g. one couldn't see the lower round edges of rounded rectangles. This commit fixes that by appropriately varying the height that is passed into step_shape(), so that lower edges of the bars always exactly touch the lower edge of the graph. If the user wants to restore the prior behavior, they can simply ignore the passed-in height in step_shape() and just always draw their shape with height = full widget height. But they couldn't replicate the effect of this commit in their rc.lua without essentially writing their own graph widget. commit e25185b08da23745b5815f85f6e9e43222ed2294 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 08:42:54 2021 +0700 Avoid incremental transform shifting during graph bar rendering Instead of letting addition errors and cognitive load accumulate simply use the same offset calculation that is used for lines and reset the transform back to the top-left corner after every drawn bar. It's conceptually simpler, and will be useful later for stacked graphs and more. It also fixes a discrepancy between plain and step_shape rendering. Prior to this commit, bar coloring occurred with a disturbed transform, which was centered at the last drawn bar, wherever it happened to be. This meant that nontrivial colors, like gradients and images, were arbitrarily offset when step_shape was used. commit f3e98866d8583c35d0b557c09f529299edc9ba2c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:45:29 2021 +0700 Refactor out graph bar height expression into a variable commit f54528d6892f5aaa4595917853c9e2130f743fdb Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:20:25 2021 +0700 Draw graph bars with lines only when step_width == 1 Before this commit graph bars of step_width < 1 were drawn with lines of width 1, but that's a bit unfortunate, because it led to much overpainting, while still ending up with a graphically incorrect result. Let's draw such bars with rectangles and let cairo do things with subpixels, getting more graphically accurate result, while still keeping the common case of step_width == 1 (supposedly) fast-drawn with lines. commit b6515dca67c18b78eb5657378131a62ab43d4cfc Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:54:07 2021 +0700 Fix unsharp edges when drawing graph bars with rectangles The 0.5 offset is only helpful for 1-width strokes, not for fills. commit 7f3db8f07e82f821bcd1dc418d66564f537609d6 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:21:01 2021 +0700 Do coordinate transformations even if the value isn't drawn The graph presently doesn't draw values < 0, but there was a discrepancy between drawing with and without step_shape. The default line/rectangle drawing reserves an empty place where the skipped value should've been, but no such empty place is reserved, when step_shape is used. This commit fixes that. Skipped values always have their empty space now. commit 7b731ebfe96af7a9dba9c57371da6761cf22c00c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:03:30 2021 +0700 Fix graph bar scaling when min_value is not 0 Previous calculation lead to underscaling of values so that even value = max_value wasn't drawn to full height. commit dca59fd8b7fff0e7e646f2dfc6bd92047028e5a8 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:18:40 2021 +0700 Calculate the horizontal graph bar offset only when needed commit 940ddf4b9825e9c3a91d276208c089d8c74e49db Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:17:23 2021 +0700 Call cr:move_to() only when drawing the graph with lines It's redundant otherwise and might even interfere with user's drawing in step_shape(). commit b5d5cd10ca29fb29cd751faaa95137c2fe89ff63 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:04:06 2021 +0700 Fix wrong horizontal offset calculations in widget.graph Indices in the loop are zero-based, so things like i-1 and i>1 aren't necessary and actually wouldn't have been correct even in a 1-based loop. step_shape = gears.shape.rectangle and step_shape = nil, which is supposed to default to rectangle according to docs, were drawn differently due to mismatched offset calculations, which led to problems especially noticeable when step_spacing is not zero, e.g: 1) whole graph shifted left too much, drawn partially off-screen. 2) whole graph shifted right too much, and the first bar placed closer to the rest than others. commit dc369fa198281e000afc1bd17326f3a264adf7d1 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 02:09:21 2021 +0700 Fix the "border_width affects graph bar width" bug Graph bars were drawn with lines of border_width thickness instead of 1, i.e. possibly too fat to be readable. commit 1ba73378cf29ed27a6ecd18716394779ceb543d9 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 00:13:03 2021 +0700 Simplify internal values representation in graph even more New values in graph._private.values were pushed to the end, and old values were removed from the beginning of the table. In draw() however newer values are drawn first, at the left edge of the graph, necessitating annoying idx = #values - idx conversions everywhere. In particular in stacked graph that means that values that are drawn together may have different indices. One has to shift the values array in add_value() either way, so one might as well insert new values at the beginning of the array and simplify the rest of the code. This doesn't seem like much yet, but it will prove to be much more convenient and less error-prone in what I'm going to do later. commit f70523ee68dbd6b67050ace4b82903a9b22440c7 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 13:35:10 2021 +0700 Simplify internal values representation in graph widget graph._private.values was either a table of values or a table of tables of values, depending on whether "stack" property is enabled, ... or a mix of both if user forgot to pass the second "group" parameter to graph:add_value(). This resulted in pretty fragile and confusing behavior, e.g. leaving user to wonder, why they see nothing more after setting stack=true for a graph in a previously working rc.lua, and demanding a very careful use of add_value() from the user, or else some values or even entire stacks would mysteriously vanish and errors in draw() get triggered, sometimes after a period of seemingly normal functioning. Now `values` is always a table of tables and the group parameter in add_value() defaults to 1, thus making the non-stacked graph a trivial variant of the stacked graph.
2021-05-20 17:14:03 +02:00
-- Set up the nan_color fallback.
prop_fallbacks.nan_color = build_fallback_nan_color()
graph: complete widget overhaul and bug fixes Squashed commit of the following: commit 69821f51fe1e8652715658543e50631ece495090 Author: Alex Belykh <albel727@ngs.ru> Date: Mon May 10 19:17:55 2021 +0700 Refactor property handling in wibox.widget.graph In addition, baseline_value, step_width and step_spacing properties are all set to their default non-nil numeric values during instance creation now. commit 842eb429bf5df4b2ba46b2e6f7646afe89b1a3c3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 9 06:18:45 2021 +0700 Make graph:draw_values() private There's no documentation nor confidence that it's a good API, so I'm hiding it for now. commit 720746780574e4bad14a71fc4174955139c2eb50 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 04:04:13 2021 +0700 Document graph_should_draw_data_group() for posterity commit 7c73f3754ce6ba86b118f1170a6f9e406ab4841d Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:59:21 2021 +0700 Make graph:should_draw_data_group() private There's no confidence whether it's a good API, so I'm hiding it for now. commit b3539e20adb6423118af726ae4f8751ef864b5d2 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:36:44 2021 +0700 Make graph:preprocess_values() private There's no confidence whether it's a good API, so I'm hiding it for now. commit 2df4400e780a6260f8d0ab1c60734fbb11bf3c4f Author: Alex Belykh <albel727@ngs.ru> Date: Thu May 6 10:05:23 2021 +0700 Add spec/wibox/widget/graph_spec.lua commit ce01a9771ab439b8df841f1565aa4aa8a44a5e0d Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 08:16:18 2021 +0700 Improve graph.capacity documentation commit e28fade8f655802f5bf89154169f68d256b8e332 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 07:36:01 2021 +0700 Document graph:compute_drawn_values_num() commit dd68332292465422d08b48e9f0229f143beccbc6 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 05:24:23 2021 +0700 Document graph:pick_data_group_color() method commit ca6dc55f77aca88a6e9cc90bb6fd0045739f41f5 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 04:13:58 2021 +0700 Guard against setting data groups sparsely in graph:add_value() commit cdea5f126f7984e72924f6dffbd312c4fe3b032f Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 02:36:12 2021 +0700 Refactor scaling and baseline choice out of graph:draw() commit 10135deafe8a9390ba66ab657ebb1fb41d549e8c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 21:34:48 2021 +0700 Document graph.nan_color and graph.stack properties commit 0d73bb84ac66b87e16aee472bd1f299549f66a1c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 20:49:24 2021 +0700 Use a better example in docs for the graph.stack property commit c27d160a0826d033d49d6141608c40aa8a0642fa Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 16:05:38 2021 +0700 Document graph.baseline_value property commit 459bc8176c13d4ea32af0dcdc2399d3b929b1c6a Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:53:47 2021 +0700 Document graph.clamp_bars property commit e732ee0b3b007d213e689b98cc00ae7cb141ae08 Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:34 2021 +0700 Improve graph:add_value() and :clear() documentation commit ae73e3aa9f8d60409cbe4cb4da9612f31af4f38e Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:11 2021 +0700 Improve graph min_value/max_value/scale documentation commit a7350996a9def773b91b1631e6c0507e33d8f93b Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:27:36 2021 +0700 Cleanup graph examples to show only relevant things in docs commit 30740c26f4f41c16f4c8a3c339bcb7bd299e14cd Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 14:04:36 2021 +0700 Rename graph.stack_colors property to `group_colors` The colors are used for stacked and non-stacked graphs alike, so the name didn't make sense. Also document and clarify some color stuff. commit 1ea01bfd410971cf0d0c25fd74cd764d5eb7279d Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 21:25:41 2021 +0700 Add an example file for code coverage of some fringe graph cases commit eae68e39553f88a6275a0c65bf040b5d4c262b54 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 20:23:32 2021 +0700 Add step_shape to the graph/nan_color example for code coverage commit 8027147bc2eab34a97b49a428fb8cef27f122bed Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:55:18 2021 +0700 Add example files for graph.nan_color/nan_indication properties commit 89a25a165c52b2c15b6c4d95ecb777a9067e66f9 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:33:07 2021 +0700 Make widget graph:add_value(v) default v to NaN 0 as a default makes no sense and silences the error that is failing to specify a value, to which the user should rather be alerted. The graph widget has sane NaN handling now and will do just that. commit 2835d552440be48d356a7f7b1d64963696dd550b Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:20:48 2021 +0700 Implement graph.nan_color and nan_indication properties Now, whenever there's a NaN among values and graph.nan_indication is set, the corresponding area, where the value bar should have been drawn, is filled with the nan_color from top to bottom. The painting is done after all data is rendered, to make sure that it won't be overpainted and go unnoticed. NaNs among graph's values are inevitably a sign of some error and it is arguably not a sane default behavior to simply not render them at all. The user should take immediate note of any errors by default, instead of wondering at a mysteriously empty graph widget, which is why nan_indication is set to true by default. If the user wants to silence NaNs, they can always simply set it to false. nan_color if unset, defaults to a noticeable yellow-black pattern. As a direct and planned consequence, stacked graphs with negative values in them trigger the NaN handling too. But silencing NaN handling for those graphs is also possible and semi-sane, because then NaNs and negatives are ignored and the graph behaves (almost) like they were zeros. commit 9c1f8f08b400e01e19c534f3810c9c9ab485760c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 26 08:10:19 2021 +0700 Refactor out graph_map_value_to_widget_coordinates() function commit 1905991ddecabdae3e15ffb8ee5b18a1bee1a711 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 28 13:39:29 2021 +0700 Add an example file for graph.baseline_value property commit b31b39d66f4c037a9484b71a3f910703ae01fac0 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 07:03:49 2021 +0700 Add an example file for graph.clamp_bars=false/true comparison commit a1fc850e3047f9ee2c9ddeffacfbabf3ded34171 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 04:06:45 2021 +0700 Add an example file for graphs with negative values and shapes commit 2f012f8ff1bb5094c14e724a61e59a2a3a296bc8 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 01:06:05 2021 +0700 Add an example file for a graph displaying negative values commit 7b14fbd59a4c4c51962fd3110a1a900dd5f4eb98 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 11:18:26 2021 +0700 Add an example file for graphs with step_width < 1 commit dfd9a54544b637e2fbe3d1325f9cc24b3f4d2313 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 10:17:38 2021 +0700 Add an example file for graph.stack=false/true comparison Should also demonstrate that multiple data groups are possible for non-stacked graphs too. commit 5b532106c3fd255f9b66a66cfb88d977b489ab5d Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 09:34:14 2021 +0700 Add an example file for disabling graph data groups with nil colors commit ac6f1083c02fcb428f089a616f9b0ebaa5496b6c Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:57:25 2021 +0700 Add an example file for step_shape/width/spacing on stacked graphs commit b3703db6a5139e73e72c1cfd198ece2cd9be7df2 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:43:56 2021 +0700 Add an example file for scale=false/true on stacked graph commit b000aed4a12cb44fb501e1c8432a71a14b2f8f3f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 20 08:22:19 2021 +0700 Document deprecation of graph's width and height commit cb0ad617bd6c83a499896797318e3b0b588c3566 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 07:53:37 2021 +0700 Deprecate width and height properties of wibox.widget.graph commit 8c64e14ad91a074b784166a7054e53444cccb9d2 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 04:37:59 2021 +0700 Refactor graph:draw() to separate concerns Firstly, summation of group values for stacked graphs is extracted into graph:preprocess_values(). This makes graph:draw() smaller and exposes the fact, that stacked graphs are almost entirely just a form of a more general concept: pre-render data manipulation. It's something a user might want to customize, and now they can do it by monkey-patching the method. Secondly, the logic for deciding, if and with which color a data group should be drawn, is extracted into graph:should_draw_data_group() and get_data_group_color() methods respectively. This makes it customizable too and makes more obvious the fact, that it is a general thing that has nothing to do with stacked graphs in particular. With this there are only 5 lines remaining in graph:draw(), that could be said to be specific to stacked graphs, all pertaining to the `prev_y` variable, which merely preserves the bar coordinates from a previous data group. commit 2a32b6305d893f413303728a0a3da1dcd9a5591e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 22:52:37 2021 +0700 Pointlessly optimize graph:add_value() 1) Purge old values first, then insert the new one, if needed. Since Lua grows arrays in power of two steps, and our guess_capacity() also takes care to return even integers, it would be a shame to overstep the capacity by one by inserting first and thus getting the table size doubled only to never use the most of it. 2) Use a removal procedure that doesn't call the slow length operator at all. Previous code called it twice (once in table.remove) for every removed value. commit 680ea8f5d81e9040afb4662b3229e937e4b89e02 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:23:38 2021 +0700 Set graph data series color before the painting loop commit b970f0f221ae76250f2a94433ff79d653caa6e21 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:05:37 2021 +0700 Drop a large no longer necessary `if` in graph:draw_values() This commit is almost purely a whitespace change. commit 6b4f0541c70ec99d7f6e276bae64939f337120d6 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:00:50 2021 +0700 Extract values rendering code into graph:draw_values() method This is to avoid ungodly if nesting levels, which are needed to bail out of drawing values early and skip to drawing the border. The next commit will get rid of the if. commit e0950e4f99d1ea1c4b8cea2cc67629fa40030c2a Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 14:50:20 2021 +0700 Track graph draw() usage to guess necessary array capacity The last step of divorcing add_values() from width calculations, while ensuring that user doesn't observe a lack of data to draw. The widget now tracks an approximate number of values that it has been asked to draw() in _private.last_drawn_values_num, and truncates its values array based on that (if not overridden by the capacity property). commit 2e3d542282f35e7c0fed454da0f2eada0fcf3b78 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 13:35:12 2021 +0700 Add `capacity` property to graph widget This is the second step of divorcing add_values() from calculations involving widths. How many values are stored and how many values are drawn are distinct concepts. There isn't even a single definitive "how many values are drawn" because the widget could be rendered in several places with distinct widths simultaneously, and in every such place the data must be drawn appropriately, e.g. with auto-scaling, that takes only the visible data points into account. And the draw() method can do that now. The default capacity value of nil uses heuristics based on widget's _private.width to guess some capacity that would tolerate some dynamic widget resizing. If the user finds the heuristic failing them, they can set the capacity property to the desired maximum number of values that will be kept in the _private.values array (in each data group). commit 08d4a80686f7fe363551c35589a460839f60853e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 03:27:18 2021 +0700 Extract values size computation into compute_drawn_values_num() This is the first step of making add_values() not concern itself (directly) with widget dimensions. The user can monkey-patch the method to override how many values are (logically) drawn, because it affects graph auto-scaling and can be useful for rendering of step_shape-s that paint outside of their step_width. I didn't make it a full-blown function-valued property, because it seems a fringe functionality, which I'm too lazy to document. commit 3c5617c98ab1147618ed22e8414a77fc56b45f6e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 02:32:29 2021 +0700 Copy graph widget prototype methods over in bulk commit 169538837ece41a03d02ba8fbc0e4097062b67a3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun Apr 25 05:58:01 2021 +0700 Remove graph.baseline_y property After giving it some thought I've concluded, that it is unlikely to be useful as it is now. I'd better reserve the name for some more sensible functionality, like fixing the baseline_value defined axis at the given widget coordinate, adjusting the scaling accordingly, so min_value/max_value won't generally correspond to widget top and bottom anymore. commit 202ffaa9a0b820c7c471e1121d01b91f8f76cd38 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 12:06:55 2021 +0700 Add baseline_y and baseline_value properties to graph widget Now values in graph have a *baseline*, tunable with baseline_y and baseline_value properties. It is the (for now invisible) horizontal line from which all bars in the graph grow. I call it a "baseline" instead of "axis", because it doesn't actually have to correspond to the 0 value. But it can, and actually does now by default. baseline_y and baseline_value properties determine the vertical placement of the baseline in the widget. baseline_y is the position of the baseline in normalized widget coordinates, e.g. 0 corresponds to widget's top, 1 - to widget's bottom, and 0.5 - to widget's middle. So if e.g. one sets baseline_y = 0, then no matter how the widget is resized and how its values are scaled, bars in it will be drawn starting from the top. baseline_value is the position of the baseline in value coordinates. So if one sets baseline_value = 10, then no matter what happens, bars for values greater than 10 will be drawn as growing up from this line, and bars for values smaller as growing down. Where and if it will be seen in the widget, depends on the scaling. baseline_value = 0 is the familiar zero axis. baseline_y is unset by default and has priority, i.e. if it's set, then baseline_value is ignored. baseline_value defaults to 0. Prior to this commit, bars were always drawn from the bottom edge of the widget, or, in the new terms, the widget had baseline_y = 1. But it was indistinguishable from baseline_value = 0, because the widget clamped all negative values to 0, so the difference between the two could never be observed. baseline_value = 0 is clearly more useful and familiar to people, so now it is in force by default. commit df7e19abaebf0a871b5ca217b4cc3365533267a6 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 11:18:12 2021 +0700 Move value clamping from graph:add_value() into draw() Previously all values were forcibly clamped between 0 (not tunable at all) and max_value (1 by default). Thus graphs with negative values were not an option. NaN values were conflated with zeros, even though it may be useful to maintain the distinction and render them differently. And if user happened to dynamically change the max_value property, they discovered, that their data got truncated. Preserving user data as it was given is more flexible and makes things easier to debug. This commit does exactly that. So what happens now to the newly allowed values during draw()? There are 3 new cases to consider: 1) NaNs 2) Negatives 3) Values outside of min_value..max_value range NaNs are presently simply not drawn. An empty place is reserved where the value should've been, but nothing else is done. This is distinct from how a zero is handled, e.g. if step_shape is set, it is called with the height parameter = 0, which can still end up drawing something, depending on the shape. One could also consider addition of nan_color/nan_shape properties which could be drawn to alert user to a gap in their data. Negative values necessitate introduction of the concept of the zero axis, from which they will be drawn DOWN, in contrast to positive values. Where should this axis be drawn? How should step_shape-s with inherent direction, e.g. arrows, be drawn for negatives? This will be addressed in the next commit. Values outside of min/max_value range can imply shapes that go beyond widget dimensions. An arrow shape that goes beyond the roof is visibly different from an arrow that saturates and merely touches the widget top, and the user may reasonably desire either behavior. Therefore this commit introduces a boolean clamp_bars property, which defaults to true for reasons of robustness and backward compatibility with the behaviour prior to this commit. The property of course affects only the display of the values, the actual user data stays intact. commit c9c24fb1ab741cd6bb8074971d568e0646c51532 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:29:01 2021 +0700 Use rectangles for drawing graph bars of all sizes I have benchmarked various bar drawing strategies and graph sizes and found out that drawing graph bars with rectangles is as fast or even marginally faster than using fat lines. Thus the code that tries to special-case 1px bars as lines seems to be not worth the additional complexity it introduces, and this commit removes it. Incidentally, I've also established, that rectangles are more robust. If one draws graph bars and feeds cairo very large coordinates (~2^62), then corresponding rectangles fail to render, but all the other rectangles in the same series get rendered successfully, whereas a single large coordinate in a move_to/line_to() sequence is enough to cause all lines in the batch to fail to render. commit bc7f4be5e06405b5aa7fb443a5ace2b9b9575b1f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:01:42 2021 +0700 Cache cairo methods used in the inner loop of graph:draw() I have benchmarked the draw() method, both on cairo xcb and on argb32 image surfaces, and have discovered that cairo context method calls are unnaturally slow, to the point, that almost nothing else in the function matters. For example, drawing bars with vertical lines made the whole draw() function almost 2 times slower than doing the same with rectangles, merely because the former requires two method calls (move_to/line_to), and the latter only one. Simply caching hotpath cairo methods in local variables, reduced the difference between the drawing methods to a negligible one and made the whole function 4 times faster on a typical 100-bar graph, (1.4ms to 0.33ms on my laptop) thus bringing closer my secret dream of drawing live audio data with awesome graphs (kidding). I guess, LGI does something not very optimal there, like gobject-introspecting the method every time, or something. There might be some untapped optimization potential, possibly in upstream, like a method cache, from which everything in awesome could benefit, even though I imagine not many things are so call-intensive as the graph widget, to really feel the difference. commit 538df25bdf02b53d0bbe81809eef2e6fdb05ff6f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 07:32:37 2021 +0700 Use 1-based index in the inner graph draw loop It's used there only once for a calculation and then several times to index some tables, and index 0 is bad for lua tables performance-wise. commit 7412546916cd0a0a490130a120255ffb719398b8 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:55:12 2021 +0700 Leave graph.max_value uninitialized at construction This is a user-visible change, but the previous behavior was probably not what user expected anyway, namely with graph.scale set the graph mysteriously couldn't zoom in onto values that are smaller than 1. commit eb81d7929a161e79c13262cfa73ac2e6a820e8ed Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:48:29 2021 +0700 Allow graph.max_value be nil This makes its behave symmetrically to min_value. Now it defaults to 1, when graph.scale is not set, and to the maximum value in the graph data, when graph.scale is set. commit f622a8430cbfde7007b58d7f4c9c76b08c91e997 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 12:01:13 2021 +0700 Calculate max values num in graph:add_value() more precisely At most so many values were kept in the widget values array, as there are pixels of usable widget width (i.e. sans border). This is exactly right for the surely most common case of graphs with 1px-wide bars, but is more than needed for graphs with step_width+step_spacing>1, and not enough for graphs with subpixel bars. Moreover this made graph autoscaling behave weirdly in graphs with thicker bars, because old values survived longer in the array and kept affecting min_value/max_value calculations even after they were shifted off-screen. The maximum number of kept values is now calculated exactly as the number of values that can be displayed at once, taking step_width and step_spacing into account. It might be worth adding a max_values_num property to the graph to let user override this calculation, in case they draw shapes in step_shape() outside of its supposed rectangle, so our assumptions of visibility are wrong, or the widget often gets resized, resulting in the annoyance of visibly truncated series. commit 0aa63249a18641251a1bf77423739164f0eab39f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 02:30:13 2021 +0700 Fix border_color/border_width bugs in graph widget add_value() wrongly assumed that border_width is always set, when border_color is, so if user set only the latter, an error occurred. add_value() assumed that border_width is always < width which led to awesome hanging up in an infinite loop, should that fail to be the case. The graph border_width property was driven by the border_color property, instead of the other way round. If border_color wasn't set, the border wasn't drawn all, even if user sets non-zero border_width, which is a confusing behavior, in contrast to all other widgets, which simply use a plethora of fallback colors to draw the border, when needed. This also meant that, despite being documented, the beautiful.graph_border_color fallback color could not and was not ever used. If the border_color was set though, an unset border_width was immediately assumed to be 1 instead of 0, also contrary to what other widgets do. Theme colors shouldn't influence widget layout, so this commit fixes this behavior to: 1) border_width always defaults to 0, if unset. 2) border_color falls back to beautiful.graph_border_color or white. Maybe one should also add a beautiful.graph_border_width theme variable, like some widgets do. commit dad4a3253b8fef971fb82720d2a407fcc0a879ad Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 03:11:27 2021 +0700 Implement multiple data series drawing for non-stacked graph The widget can already hold and draw multiple data groups, so it makes little sense to limit drawing only to the first group when graph.stack = false, probably leading to user's confusion. Now user can call add_value(v, group) with group > 1 and the data series will be drawn (over groups with lesser id-s) with graph.stack_colors[group] color, if stack_colors is set, or with graph.color, if stack_colors isn't set. Thus nothing visibly changes for all current users of non-stacked graphs with only single data series, but the new functionality is now there for those who need it. One can't fully emulate this functionality, e.g. with multiple graphs widgets in a wibox.layout.stack, because data series in a single widget can enjoy a common auto-scaling. commit 3e645c976184ab5f50d625271c25062db9ab7ed9 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:59:12 2021 +0700 Don't draw a stacked graph data group if there's no color for it If stack_colors[idx] is nil for the group `idx`, don't draw it at all (as opposed, e.g. to the effect of drawing it with a transparent color, or as though it had all values == 0, which both produce different results). This code is still subtly different from prior behavior, because now user can have a non-contiguous stack_colors table and turn the display of any data group on and off at will, whereas the code two commits earlier would just stop rendering as soon as ipairs(stack_colors) does, i.e. no data series were drawn past the first nil hole. This commit only looks large, but is mostly a whitespace change due to nesting from a pair of wrapping ifs. commit 8c0273786de1ade66bdd977699f98b588da9f487 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:32:42 2021 +0700 Unify stacked and non-stacked graph rendering Now stacked graphs support everything the nonstacked do, like step_width ~= 1 and custom step_shape-s. Technical notes: This code no longer calls cr:stroke() for every single value in a stacked graph, but rather paints a whole series and then cr:stroke/fill()-s it at all once with its color, improving performance considerably. Additionally the graph no longer relies on the presence of the stack_colors property to render series. It draws all data that it has, and, if there's no corresponding color in stack_colors, falls back to graph.color. This will be changed in the next commit to the prior behavior of only drawing those series for which there's a color in the stack_colors table, but that would've made this commit's diff much larger due to required nesting ifs over large code chunks. This commit also takes care to properly handle NaNs and negative values. Those are currently (almost) guaranteed to not occur in the values array due to clipping in graph:add_value(), but I intend to eventually drop the clipping, because it does nothing that draw() really relies upon, and allowing unclipped and NaN values is more flexible and less surprising, e.g. when user dynamically toggles graph.scale. Eventually I intend to implement drawing negative values in non-stacked graphs and possibly even in the stacked ones. commit 38c6589c51b2aa057d7c2f40182ecf32b5b2ff5e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 10:04:15 2021 +0700 Convert graph:draw() to use `self` commit f21f1b2550426df1c4ea8abde079b1ccef84b394 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 12:27:26 2021 +0700 Fix scaling for stacked graphs Stacked graphs have cumulative heights, values from all groups that end up in the same graph bar are effectively summed. Previous code didn't take that into account when calculating min/max_value for the purposes of rescaling, but simply examined every value separately. This code adds up all values up front and finds min/max among those, to determine proper scaling that would fit the graph exactly. commit 1ce4c7bf63bdd6c99c195d668347bec49355e921 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 09:49:35 2021 +0700 Draw shaped graph bars only up to the baseline Prior to this commit, every step_shape bar was, for some reason, drawn with height = full widget's height, regardless of the value it represents. So for the most bars their lower end was far outside the box and therefore clipped off. So e.g. one couldn't see the lower round edges of rounded rectangles. This commit fixes that by appropriately varying the height that is passed into step_shape(), so that lower edges of the bars always exactly touch the lower edge of the graph. If the user wants to restore the prior behavior, they can simply ignore the passed-in height in step_shape() and just always draw their shape with height = full widget height. But they couldn't replicate the effect of this commit in their rc.lua without essentially writing their own graph widget. commit e25185b08da23745b5815f85f6e9e43222ed2294 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 08:42:54 2021 +0700 Avoid incremental transform shifting during graph bar rendering Instead of letting addition errors and cognitive load accumulate simply use the same offset calculation that is used for lines and reset the transform back to the top-left corner after every drawn bar. It's conceptually simpler, and will be useful later for stacked graphs and more. It also fixes a discrepancy between plain and step_shape rendering. Prior to this commit, bar coloring occurred with a disturbed transform, which was centered at the last drawn bar, wherever it happened to be. This meant that nontrivial colors, like gradients and images, were arbitrarily offset when step_shape was used. commit f3e98866d8583c35d0b557c09f529299edc9ba2c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:45:29 2021 +0700 Refactor out graph bar height expression into a variable commit f54528d6892f5aaa4595917853c9e2130f743fdb Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:20:25 2021 +0700 Draw graph bars with lines only when step_width == 1 Before this commit graph bars of step_width < 1 were drawn with lines of width 1, but that's a bit unfortunate, because it led to much overpainting, while still ending up with a graphically incorrect result. Let's draw such bars with rectangles and let cairo do things with subpixels, getting more graphically accurate result, while still keeping the common case of step_width == 1 (supposedly) fast-drawn with lines. commit b6515dca67c18b78eb5657378131a62ab43d4cfc Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:54:07 2021 +0700 Fix unsharp edges when drawing graph bars with rectangles The 0.5 offset is only helpful for 1-width strokes, not for fills. commit 7f3db8f07e82f821bcd1dc418d66564f537609d6 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:21:01 2021 +0700 Do coordinate transformations even if the value isn't drawn The graph presently doesn't draw values < 0, but there was a discrepancy between drawing with and without step_shape. The default line/rectangle drawing reserves an empty place where the skipped value should've been, but no such empty place is reserved, when step_shape is used. This commit fixes that. Skipped values always have their empty space now. commit 7b731ebfe96af7a9dba9c57371da6761cf22c00c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:03:30 2021 +0700 Fix graph bar scaling when min_value is not 0 Previous calculation lead to underscaling of values so that even value = max_value wasn't drawn to full height. commit dca59fd8b7fff0e7e646f2dfc6bd92047028e5a8 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:18:40 2021 +0700 Calculate the horizontal graph bar offset only when needed commit 940ddf4b9825e9c3a91d276208c089d8c74e49db Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:17:23 2021 +0700 Call cr:move_to() only when drawing the graph with lines It's redundant otherwise and might even interfere with user's drawing in step_shape(). commit b5d5cd10ca29fb29cd751faaa95137c2fe89ff63 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:04:06 2021 +0700 Fix wrong horizontal offset calculations in widget.graph Indices in the loop are zero-based, so things like i-1 and i>1 aren't necessary and actually wouldn't have been correct even in a 1-based loop. step_shape = gears.shape.rectangle and step_shape = nil, which is supposed to default to rectangle according to docs, were drawn differently due to mismatched offset calculations, which led to problems especially noticeable when step_spacing is not zero, e.g: 1) whole graph shifted left too much, drawn partially off-screen. 2) whole graph shifted right too much, and the first bar placed closer to the rest than others. commit dc369fa198281e000afc1bd17326f3a264adf7d1 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 02:09:21 2021 +0700 Fix the "border_width affects graph bar width" bug Graph bars were drawn with lines of border_width thickness instead of 1, i.e. possibly too fat to be readable. commit 1ba73378cf29ed27a6ecd18716394779ceb543d9 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 00:13:03 2021 +0700 Simplify internal values representation in graph even more New values in graph._private.values were pushed to the end, and old values were removed from the beginning of the table. In draw() however newer values are drawn first, at the left edge of the graph, necessitating annoying idx = #values - idx conversions everywhere. In particular in stacked graph that means that values that are drawn together may have different indices. One has to shift the values array in add_value() either way, so one might as well insert new values at the beginning of the array and simplify the rest of the code. This doesn't seem like much yet, but it will prove to be much more convenient and less error-prone in what I'm going to do later. commit f70523ee68dbd6b67050ace4b82903a9b22440c7 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 13:35:10 2021 +0700 Simplify internal values representation in graph widget graph._private.values was either a table of values or a table of tables of values, depending on whether "stack" property is enabled, ... or a mix of both if user forgot to pass the second "group" parameter to graph:add_value(). This resulted in pretty fragile and confusing behavior, e.g. leaving user to wonder, why they see nothing more after setting stack=true for a graph in a previously working rc.lua, and demanding a very careful use of add_value() from the user, or else some values or even entire stacks would mysteriously vanish and errors in draw() get triggered, sometimes after a period of seemingly normal functioning. Now `values` is always a table of tables and the group parameter in add_value() defaults to 1, thus making the non-stacked graph a trivial variant of the stacked graph.
2021-05-20 17:14:03 +02:00
--
-- Module and prototype methods.
--
2016-05-23 07:30:44 +02:00
graph: complete widget overhaul and bug fixes Squashed commit of the following: commit 69821f51fe1e8652715658543e50631ece495090 Author: Alex Belykh <albel727@ngs.ru> Date: Mon May 10 19:17:55 2021 +0700 Refactor property handling in wibox.widget.graph In addition, baseline_value, step_width and step_spacing properties are all set to their default non-nil numeric values during instance creation now. commit 842eb429bf5df4b2ba46b2e6f7646afe89b1a3c3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 9 06:18:45 2021 +0700 Make graph:draw_values() private There's no documentation nor confidence that it's a good API, so I'm hiding it for now. commit 720746780574e4bad14a71fc4174955139c2eb50 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 04:04:13 2021 +0700 Document graph_should_draw_data_group() for posterity commit 7c73f3754ce6ba86b118f1170a6f9e406ab4841d Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:59:21 2021 +0700 Make graph:should_draw_data_group() private There's no confidence whether it's a good API, so I'm hiding it for now. commit b3539e20adb6423118af726ae4f8751ef864b5d2 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:36:44 2021 +0700 Make graph:preprocess_values() private There's no confidence whether it's a good API, so I'm hiding it for now. commit 2df4400e780a6260f8d0ab1c60734fbb11bf3c4f Author: Alex Belykh <albel727@ngs.ru> Date: Thu May 6 10:05:23 2021 +0700 Add spec/wibox/widget/graph_spec.lua commit ce01a9771ab439b8df841f1565aa4aa8a44a5e0d Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 08:16:18 2021 +0700 Improve graph.capacity documentation commit e28fade8f655802f5bf89154169f68d256b8e332 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 07:36:01 2021 +0700 Document graph:compute_drawn_values_num() commit dd68332292465422d08b48e9f0229f143beccbc6 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 05:24:23 2021 +0700 Document graph:pick_data_group_color() method commit ca6dc55f77aca88a6e9cc90bb6fd0045739f41f5 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 04:13:58 2021 +0700 Guard against setting data groups sparsely in graph:add_value() commit cdea5f126f7984e72924f6dffbd312c4fe3b032f Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 02:36:12 2021 +0700 Refactor scaling and baseline choice out of graph:draw() commit 10135deafe8a9390ba66ab657ebb1fb41d549e8c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 21:34:48 2021 +0700 Document graph.nan_color and graph.stack properties commit 0d73bb84ac66b87e16aee472bd1f299549f66a1c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 20:49:24 2021 +0700 Use a better example in docs for the graph.stack property commit c27d160a0826d033d49d6141608c40aa8a0642fa Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 16:05:38 2021 +0700 Document graph.baseline_value property commit 459bc8176c13d4ea32af0dcdc2399d3b929b1c6a Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:53:47 2021 +0700 Document graph.clamp_bars property commit e732ee0b3b007d213e689b98cc00ae7cb141ae08 Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:34 2021 +0700 Improve graph:add_value() and :clear() documentation commit ae73e3aa9f8d60409cbe4cb4da9612f31af4f38e Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:11 2021 +0700 Improve graph min_value/max_value/scale documentation commit a7350996a9def773b91b1631e6c0507e33d8f93b Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:27:36 2021 +0700 Cleanup graph examples to show only relevant things in docs commit 30740c26f4f41c16f4c8a3c339bcb7bd299e14cd Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 14:04:36 2021 +0700 Rename graph.stack_colors property to `group_colors` The colors are used for stacked and non-stacked graphs alike, so the name didn't make sense. Also document and clarify some color stuff. commit 1ea01bfd410971cf0d0c25fd74cd764d5eb7279d Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 21:25:41 2021 +0700 Add an example file for code coverage of some fringe graph cases commit eae68e39553f88a6275a0c65bf040b5d4c262b54 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 20:23:32 2021 +0700 Add step_shape to the graph/nan_color example for code coverage commit 8027147bc2eab34a97b49a428fb8cef27f122bed Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:55:18 2021 +0700 Add example files for graph.nan_color/nan_indication properties commit 89a25a165c52b2c15b6c4d95ecb777a9067e66f9 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:33:07 2021 +0700 Make widget graph:add_value(v) default v to NaN 0 as a default makes no sense and silences the error that is failing to specify a value, to which the user should rather be alerted. The graph widget has sane NaN handling now and will do just that. commit 2835d552440be48d356a7f7b1d64963696dd550b Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:20:48 2021 +0700 Implement graph.nan_color and nan_indication properties Now, whenever there's a NaN among values and graph.nan_indication is set, the corresponding area, where the value bar should have been drawn, is filled with the nan_color from top to bottom. The painting is done after all data is rendered, to make sure that it won't be overpainted and go unnoticed. NaNs among graph's values are inevitably a sign of some error and it is arguably not a sane default behavior to simply not render them at all. The user should take immediate note of any errors by default, instead of wondering at a mysteriously empty graph widget, which is why nan_indication is set to true by default. If the user wants to silence NaNs, they can always simply set it to false. nan_color if unset, defaults to a noticeable yellow-black pattern. As a direct and planned consequence, stacked graphs with negative values in them trigger the NaN handling too. But silencing NaN handling for those graphs is also possible and semi-sane, because then NaNs and negatives are ignored and the graph behaves (almost) like they were zeros. commit 9c1f8f08b400e01e19c534f3810c9c9ab485760c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 26 08:10:19 2021 +0700 Refactor out graph_map_value_to_widget_coordinates() function commit 1905991ddecabdae3e15ffb8ee5b18a1bee1a711 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 28 13:39:29 2021 +0700 Add an example file for graph.baseline_value property commit b31b39d66f4c037a9484b71a3f910703ae01fac0 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 07:03:49 2021 +0700 Add an example file for graph.clamp_bars=false/true comparison commit a1fc850e3047f9ee2c9ddeffacfbabf3ded34171 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 04:06:45 2021 +0700 Add an example file for graphs with negative values and shapes commit 2f012f8ff1bb5094c14e724a61e59a2a3a296bc8 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 01:06:05 2021 +0700 Add an example file for a graph displaying negative values commit 7b14fbd59a4c4c51962fd3110a1a900dd5f4eb98 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 11:18:26 2021 +0700 Add an example file for graphs with step_width < 1 commit dfd9a54544b637e2fbe3d1325f9cc24b3f4d2313 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 10:17:38 2021 +0700 Add an example file for graph.stack=false/true comparison Should also demonstrate that multiple data groups are possible for non-stacked graphs too. commit 5b532106c3fd255f9b66a66cfb88d977b489ab5d Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 09:34:14 2021 +0700 Add an example file for disabling graph data groups with nil colors commit ac6f1083c02fcb428f089a616f9b0ebaa5496b6c Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:57:25 2021 +0700 Add an example file for step_shape/width/spacing on stacked graphs commit b3703db6a5139e73e72c1cfd198ece2cd9be7df2 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:43:56 2021 +0700 Add an example file for scale=false/true on stacked graph commit b000aed4a12cb44fb501e1c8432a71a14b2f8f3f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 20 08:22:19 2021 +0700 Document deprecation of graph's width and height commit cb0ad617bd6c83a499896797318e3b0b588c3566 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 07:53:37 2021 +0700 Deprecate width and height properties of wibox.widget.graph commit 8c64e14ad91a074b784166a7054e53444cccb9d2 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 04:37:59 2021 +0700 Refactor graph:draw() to separate concerns Firstly, summation of group values for stacked graphs is extracted into graph:preprocess_values(). This makes graph:draw() smaller and exposes the fact, that stacked graphs are almost entirely just a form of a more general concept: pre-render data manipulation. It's something a user might want to customize, and now they can do it by monkey-patching the method. Secondly, the logic for deciding, if and with which color a data group should be drawn, is extracted into graph:should_draw_data_group() and get_data_group_color() methods respectively. This makes it customizable too and makes more obvious the fact, that it is a general thing that has nothing to do with stacked graphs in particular. With this there are only 5 lines remaining in graph:draw(), that could be said to be specific to stacked graphs, all pertaining to the `prev_y` variable, which merely preserves the bar coordinates from a previous data group. commit 2a32b6305d893f413303728a0a3da1dcd9a5591e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 22:52:37 2021 +0700 Pointlessly optimize graph:add_value() 1) Purge old values first, then insert the new one, if needed. Since Lua grows arrays in power of two steps, and our guess_capacity() also takes care to return even integers, it would be a shame to overstep the capacity by one by inserting first and thus getting the table size doubled only to never use the most of it. 2) Use a removal procedure that doesn't call the slow length operator at all. Previous code called it twice (once in table.remove) for every removed value. commit 680ea8f5d81e9040afb4662b3229e937e4b89e02 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:23:38 2021 +0700 Set graph data series color before the painting loop commit b970f0f221ae76250f2a94433ff79d653caa6e21 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:05:37 2021 +0700 Drop a large no longer necessary `if` in graph:draw_values() This commit is almost purely a whitespace change. commit 6b4f0541c70ec99d7f6e276bae64939f337120d6 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:00:50 2021 +0700 Extract values rendering code into graph:draw_values() method This is to avoid ungodly if nesting levels, which are needed to bail out of drawing values early and skip to drawing the border. The next commit will get rid of the if. commit e0950e4f99d1ea1c4b8cea2cc67629fa40030c2a Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 14:50:20 2021 +0700 Track graph draw() usage to guess necessary array capacity The last step of divorcing add_values() from width calculations, while ensuring that user doesn't observe a lack of data to draw. The widget now tracks an approximate number of values that it has been asked to draw() in _private.last_drawn_values_num, and truncates its values array based on that (if not overridden by the capacity property). commit 2e3d542282f35e7c0fed454da0f2eada0fcf3b78 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 13:35:12 2021 +0700 Add `capacity` property to graph widget This is the second step of divorcing add_values() from calculations involving widths. How many values are stored and how many values are drawn are distinct concepts. There isn't even a single definitive "how many values are drawn" because the widget could be rendered in several places with distinct widths simultaneously, and in every such place the data must be drawn appropriately, e.g. with auto-scaling, that takes only the visible data points into account. And the draw() method can do that now. The default capacity value of nil uses heuristics based on widget's _private.width to guess some capacity that would tolerate some dynamic widget resizing. If the user finds the heuristic failing them, they can set the capacity property to the desired maximum number of values that will be kept in the _private.values array (in each data group). commit 08d4a80686f7fe363551c35589a460839f60853e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 03:27:18 2021 +0700 Extract values size computation into compute_drawn_values_num() This is the first step of making add_values() not concern itself (directly) with widget dimensions. The user can monkey-patch the method to override how many values are (logically) drawn, because it affects graph auto-scaling and can be useful for rendering of step_shape-s that paint outside of their step_width. I didn't make it a full-blown function-valued property, because it seems a fringe functionality, which I'm too lazy to document. commit 3c5617c98ab1147618ed22e8414a77fc56b45f6e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 02:32:29 2021 +0700 Copy graph widget prototype methods over in bulk commit 169538837ece41a03d02ba8fbc0e4097062b67a3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun Apr 25 05:58:01 2021 +0700 Remove graph.baseline_y property After giving it some thought I've concluded, that it is unlikely to be useful as it is now. I'd better reserve the name for some more sensible functionality, like fixing the baseline_value defined axis at the given widget coordinate, adjusting the scaling accordingly, so min_value/max_value won't generally correspond to widget top and bottom anymore. commit 202ffaa9a0b820c7c471e1121d01b91f8f76cd38 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 12:06:55 2021 +0700 Add baseline_y and baseline_value properties to graph widget Now values in graph have a *baseline*, tunable with baseline_y and baseline_value properties. It is the (for now invisible) horizontal line from which all bars in the graph grow. I call it a "baseline" instead of "axis", because it doesn't actually have to correspond to the 0 value. But it can, and actually does now by default. baseline_y and baseline_value properties determine the vertical placement of the baseline in the widget. baseline_y is the position of the baseline in normalized widget coordinates, e.g. 0 corresponds to widget's top, 1 - to widget's bottom, and 0.5 - to widget's middle. So if e.g. one sets baseline_y = 0, then no matter how the widget is resized and how its values are scaled, bars in it will be drawn starting from the top. baseline_value is the position of the baseline in value coordinates. So if one sets baseline_value = 10, then no matter what happens, bars for values greater than 10 will be drawn as growing up from this line, and bars for values smaller as growing down. Where and if it will be seen in the widget, depends on the scaling. baseline_value = 0 is the familiar zero axis. baseline_y is unset by default and has priority, i.e. if it's set, then baseline_value is ignored. baseline_value defaults to 0. Prior to this commit, bars were always drawn from the bottom edge of the widget, or, in the new terms, the widget had baseline_y = 1. But it was indistinguishable from baseline_value = 0, because the widget clamped all negative values to 0, so the difference between the two could never be observed. baseline_value = 0 is clearly more useful and familiar to people, so now it is in force by default. commit df7e19abaebf0a871b5ca217b4cc3365533267a6 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 11:18:12 2021 +0700 Move value clamping from graph:add_value() into draw() Previously all values were forcibly clamped between 0 (not tunable at all) and max_value (1 by default). Thus graphs with negative values were not an option. NaN values were conflated with zeros, even though it may be useful to maintain the distinction and render them differently. And if user happened to dynamically change the max_value property, they discovered, that their data got truncated. Preserving user data as it was given is more flexible and makes things easier to debug. This commit does exactly that. So what happens now to the newly allowed values during draw()? There are 3 new cases to consider: 1) NaNs 2) Negatives 3) Values outside of min_value..max_value range NaNs are presently simply not drawn. An empty place is reserved where the value should've been, but nothing else is done. This is distinct from how a zero is handled, e.g. if step_shape is set, it is called with the height parameter = 0, which can still end up drawing something, depending on the shape. One could also consider addition of nan_color/nan_shape properties which could be drawn to alert user to a gap in their data. Negative values necessitate introduction of the concept of the zero axis, from which they will be drawn DOWN, in contrast to positive values. Where should this axis be drawn? How should step_shape-s with inherent direction, e.g. arrows, be drawn for negatives? This will be addressed in the next commit. Values outside of min/max_value range can imply shapes that go beyond widget dimensions. An arrow shape that goes beyond the roof is visibly different from an arrow that saturates and merely touches the widget top, and the user may reasonably desire either behavior. Therefore this commit introduces a boolean clamp_bars property, which defaults to true for reasons of robustness and backward compatibility with the behaviour prior to this commit. The property of course affects only the display of the values, the actual user data stays intact. commit c9c24fb1ab741cd6bb8074971d568e0646c51532 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:29:01 2021 +0700 Use rectangles for drawing graph bars of all sizes I have benchmarked various bar drawing strategies and graph sizes and found out that drawing graph bars with rectangles is as fast or even marginally faster than using fat lines. Thus the code that tries to special-case 1px bars as lines seems to be not worth the additional complexity it introduces, and this commit removes it. Incidentally, I've also established, that rectangles are more robust. If one draws graph bars and feeds cairo very large coordinates (~2^62), then corresponding rectangles fail to render, but all the other rectangles in the same series get rendered successfully, whereas a single large coordinate in a move_to/line_to() sequence is enough to cause all lines in the batch to fail to render. commit bc7f4be5e06405b5aa7fb443a5ace2b9b9575b1f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:01:42 2021 +0700 Cache cairo methods used in the inner loop of graph:draw() I have benchmarked the draw() method, both on cairo xcb and on argb32 image surfaces, and have discovered that cairo context method calls are unnaturally slow, to the point, that almost nothing else in the function matters. For example, drawing bars with vertical lines made the whole draw() function almost 2 times slower than doing the same with rectangles, merely because the former requires two method calls (move_to/line_to), and the latter only one. Simply caching hotpath cairo methods in local variables, reduced the difference between the drawing methods to a negligible one and made the whole function 4 times faster on a typical 100-bar graph, (1.4ms to 0.33ms on my laptop) thus bringing closer my secret dream of drawing live audio data with awesome graphs (kidding). I guess, LGI does something not very optimal there, like gobject-introspecting the method every time, or something. There might be some untapped optimization potential, possibly in upstream, like a method cache, from which everything in awesome could benefit, even though I imagine not many things are so call-intensive as the graph widget, to really feel the difference. commit 538df25bdf02b53d0bbe81809eef2e6fdb05ff6f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 07:32:37 2021 +0700 Use 1-based index in the inner graph draw loop It's used there only once for a calculation and then several times to index some tables, and index 0 is bad for lua tables performance-wise. commit 7412546916cd0a0a490130a120255ffb719398b8 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:55:12 2021 +0700 Leave graph.max_value uninitialized at construction This is a user-visible change, but the previous behavior was probably not what user expected anyway, namely with graph.scale set the graph mysteriously couldn't zoom in onto values that are smaller than 1. commit eb81d7929a161e79c13262cfa73ac2e6a820e8ed Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:48:29 2021 +0700 Allow graph.max_value be nil This makes its behave symmetrically to min_value. Now it defaults to 1, when graph.scale is not set, and to the maximum value in the graph data, when graph.scale is set. commit f622a8430cbfde7007b58d7f4c9c76b08c91e997 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 12:01:13 2021 +0700 Calculate max values num in graph:add_value() more precisely At most so many values were kept in the widget values array, as there are pixels of usable widget width (i.e. sans border). This is exactly right for the surely most common case of graphs with 1px-wide bars, but is more than needed for graphs with step_width+step_spacing>1, and not enough for graphs with subpixel bars. Moreover this made graph autoscaling behave weirdly in graphs with thicker bars, because old values survived longer in the array and kept affecting min_value/max_value calculations even after they were shifted off-screen. The maximum number of kept values is now calculated exactly as the number of values that can be displayed at once, taking step_width and step_spacing into account. It might be worth adding a max_values_num property to the graph to let user override this calculation, in case they draw shapes in step_shape() outside of its supposed rectangle, so our assumptions of visibility are wrong, or the widget often gets resized, resulting in the annoyance of visibly truncated series. commit 0aa63249a18641251a1bf77423739164f0eab39f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 02:30:13 2021 +0700 Fix border_color/border_width bugs in graph widget add_value() wrongly assumed that border_width is always set, when border_color is, so if user set only the latter, an error occurred. add_value() assumed that border_width is always < width which led to awesome hanging up in an infinite loop, should that fail to be the case. The graph border_width property was driven by the border_color property, instead of the other way round. If border_color wasn't set, the border wasn't drawn all, even if user sets non-zero border_width, which is a confusing behavior, in contrast to all other widgets, which simply use a plethora of fallback colors to draw the border, when needed. This also meant that, despite being documented, the beautiful.graph_border_color fallback color could not and was not ever used. If the border_color was set though, an unset border_width was immediately assumed to be 1 instead of 0, also contrary to what other widgets do. Theme colors shouldn't influence widget layout, so this commit fixes this behavior to: 1) border_width always defaults to 0, if unset. 2) border_color falls back to beautiful.graph_border_color or white. Maybe one should also add a beautiful.graph_border_width theme variable, like some widgets do. commit dad4a3253b8fef971fb82720d2a407fcc0a879ad Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 03:11:27 2021 +0700 Implement multiple data series drawing for non-stacked graph The widget can already hold and draw multiple data groups, so it makes little sense to limit drawing only to the first group when graph.stack = false, probably leading to user's confusion. Now user can call add_value(v, group) with group > 1 and the data series will be drawn (over groups with lesser id-s) with graph.stack_colors[group] color, if stack_colors is set, or with graph.color, if stack_colors isn't set. Thus nothing visibly changes for all current users of non-stacked graphs with only single data series, but the new functionality is now there for those who need it. One can't fully emulate this functionality, e.g. with multiple graphs widgets in a wibox.layout.stack, because data series in a single widget can enjoy a common auto-scaling. commit 3e645c976184ab5f50d625271c25062db9ab7ed9 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:59:12 2021 +0700 Don't draw a stacked graph data group if there's no color for it If stack_colors[idx] is nil for the group `idx`, don't draw it at all (as opposed, e.g. to the effect of drawing it with a transparent color, or as though it had all values == 0, which both produce different results). This code is still subtly different from prior behavior, because now user can have a non-contiguous stack_colors table and turn the display of any data group on and off at will, whereas the code two commits earlier would just stop rendering as soon as ipairs(stack_colors) does, i.e. no data series were drawn past the first nil hole. This commit only looks large, but is mostly a whitespace change due to nesting from a pair of wrapping ifs. commit 8c0273786de1ade66bdd977699f98b588da9f487 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:32:42 2021 +0700 Unify stacked and non-stacked graph rendering Now stacked graphs support everything the nonstacked do, like step_width ~= 1 and custom step_shape-s. Technical notes: This code no longer calls cr:stroke() for every single value in a stacked graph, but rather paints a whole series and then cr:stroke/fill()-s it at all once with its color, improving performance considerably. Additionally the graph no longer relies on the presence of the stack_colors property to render series. It draws all data that it has, and, if there's no corresponding color in stack_colors, falls back to graph.color. This will be changed in the next commit to the prior behavior of only drawing those series for which there's a color in the stack_colors table, but that would've made this commit's diff much larger due to required nesting ifs over large code chunks. This commit also takes care to properly handle NaNs and negative values. Those are currently (almost) guaranteed to not occur in the values array due to clipping in graph:add_value(), but I intend to eventually drop the clipping, because it does nothing that draw() really relies upon, and allowing unclipped and NaN values is more flexible and less surprising, e.g. when user dynamically toggles graph.scale. Eventually I intend to implement drawing negative values in non-stacked graphs and possibly even in the stacked ones. commit 38c6589c51b2aa057d7c2f40182ecf32b5b2ff5e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 10:04:15 2021 +0700 Convert graph:draw() to use `self` commit f21f1b2550426df1c4ea8abde079b1ccef84b394 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 12:27:26 2021 +0700 Fix scaling for stacked graphs Stacked graphs have cumulative heights, values from all groups that end up in the same graph bar are effectively summed. Previous code didn't take that into account when calculating min/max_value for the purposes of rescaling, but simply examined every value separately. This code adds up all values up front and finds min/max among those, to determine proper scaling that would fit the graph exactly. commit 1ce4c7bf63bdd6c99c195d668347bec49355e921 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 09:49:35 2021 +0700 Draw shaped graph bars only up to the baseline Prior to this commit, every step_shape bar was, for some reason, drawn with height = full widget's height, regardless of the value it represents. So for the most bars their lower end was far outside the box and therefore clipped off. So e.g. one couldn't see the lower round edges of rounded rectangles. This commit fixes that by appropriately varying the height that is passed into step_shape(), so that lower edges of the bars always exactly touch the lower edge of the graph. If the user wants to restore the prior behavior, they can simply ignore the passed-in height in step_shape() and just always draw their shape with height = full widget height. But they couldn't replicate the effect of this commit in their rc.lua without essentially writing their own graph widget. commit e25185b08da23745b5815f85f6e9e43222ed2294 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 08:42:54 2021 +0700 Avoid incremental transform shifting during graph bar rendering Instead of letting addition errors and cognitive load accumulate simply use the same offset calculation that is used for lines and reset the transform back to the top-left corner after every drawn bar. It's conceptually simpler, and will be useful later for stacked graphs and more. It also fixes a discrepancy between plain and step_shape rendering. Prior to this commit, bar coloring occurred with a disturbed transform, which was centered at the last drawn bar, wherever it happened to be. This meant that nontrivial colors, like gradients and images, were arbitrarily offset when step_shape was used. commit f3e98866d8583c35d0b557c09f529299edc9ba2c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:45:29 2021 +0700 Refactor out graph bar height expression into a variable commit f54528d6892f5aaa4595917853c9e2130f743fdb Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:20:25 2021 +0700 Draw graph bars with lines only when step_width == 1 Before this commit graph bars of step_width < 1 were drawn with lines of width 1, but that's a bit unfortunate, because it led to much overpainting, while still ending up with a graphically incorrect result. Let's draw such bars with rectangles and let cairo do things with subpixels, getting more graphically accurate result, while still keeping the common case of step_width == 1 (supposedly) fast-drawn with lines. commit b6515dca67c18b78eb5657378131a62ab43d4cfc Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:54:07 2021 +0700 Fix unsharp edges when drawing graph bars with rectangles The 0.5 offset is only helpful for 1-width strokes, not for fills. commit 7f3db8f07e82f821bcd1dc418d66564f537609d6 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:21:01 2021 +0700 Do coordinate transformations even if the value isn't drawn The graph presently doesn't draw values < 0, but there was a discrepancy between drawing with and without step_shape. The default line/rectangle drawing reserves an empty place where the skipped value should've been, but no such empty place is reserved, when step_shape is used. This commit fixes that. Skipped values always have their empty space now. commit 7b731ebfe96af7a9dba9c57371da6761cf22c00c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:03:30 2021 +0700 Fix graph bar scaling when min_value is not 0 Previous calculation lead to underscaling of values so that even value = max_value wasn't drawn to full height. commit dca59fd8b7fff0e7e646f2dfc6bd92047028e5a8 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:18:40 2021 +0700 Calculate the horizontal graph bar offset only when needed commit 940ddf4b9825e9c3a91d276208c089d8c74e49db Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:17:23 2021 +0700 Call cr:move_to() only when drawing the graph with lines It's redundant otherwise and might even interfere with user's drawing in step_shape(). commit b5d5cd10ca29fb29cd751faaa95137c2fe89ff63 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:04:06 2021 +0700 Fix wrong horizontal offset calculations in widget.graph Indices in the loop are zero-based, so things like i-1 and i>1 aren't necessary and actually wouldn't have been correct even in a 1-based loop. step_shape = gears.shape.rectangle and step_shape = nil, which is supposed to default to rectangle according to docs, were drawn differently due to mismatched offset calculations, which led to problems especially noticeable when step_spacing is not zero, e.g: 1) whole graph shifted left too much, drawn partially off-screen. 2) whole graph shifted right too much, and the first bar placed closer to the rest than others. commit dc369fa198281e000afc1bd17326f3a264adf7d1 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 02:09:21 2021 +0700 Fix the "border_width affects graph bar width" bug Graph bars were drawn with lines of border_width thickness instead of 1, i.e. possibly too fat to be readable. commit 1ba73378cf29ed27a6ecd18716394779ceb543d9 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 00:13:03 2021 +0700 Simplify internal values representation in graph even more New values in graph._private.values were pushed to the end, and old values were removed from the beginning of the table. In draw() however newer values are drawn first, at the left edge of the graph, necessitating annoying idx = #values - idx conversions everywhere. In particular in stacked graph that means that values that are drawn together may have different indices. One has to shift the values array in add_value() either way, so one might as well insert new values at the beginning of the array and simplify the rest of the code. This doesn't seem like much yet, but it will prove to be much more convenient and less error-prone in what I'm going to do later. commit f70523ee68dbd6b67050ace4b82903a9b22440c7 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 13:35:10 2021 +0700 Simplify internal values representation in graph widget graph._private.values was either a table of values or a table of tables of values, depending on whether "stack" property is enabled, ... or a mix of both if user forgot to pass the second "group" parameter to graph:add_value(). This resulted in pretty fragile and confusing behavior, e.g. leaving user to wonder, why they see nothing more after setting stack=true for a graph in a previously working rc.lua, and demanding a very careful use of add_value() from the user, or else some values or even entire stacks would mysteriously vanish and errors in draw() get triggered, sometimes after a period of seemingly normal functioning. Now `values` is always a table of tables and the group parameter in add_value() defaults to 1, thus making the non-stacked graph a trivial variant of the stacked graph.
2021-05-20 17:14:03 +02:00
local function graph_gather_drawn_values_num_stats(self, new_value)
-- Because the graphs are at risk of dividing by zero, the value can
-- be `inf`, `-inf` and `-nan`. For example:
--
-- nan = 0/0
-- print(not (nan >= 0), nan < 0)
-- > true, false
--
-- Because if this, we need to ignore the luacheck rule which promotes
-- the removal of double negative and DeMorgan's law style optimizations.
-- Those only works on real numbers, which `NaN` and `Inf` are not part of.
--
-- The `luacheck` project decided that adding this check by default and
-- adding annotations where it doesn't work is the way to go
-- https://github.com/lunarmodules/luacheck/issues/43#issuecomment-1014949507
if not (new_value >= 0) then return end --luacheck: ignore 581
graph: complete widget overhaul and bug fixes Squashed commit of the following: commit 69821f51fe1e8652715658543e50631ece495090 Author: Alex Belykh <albel727@ngs.ru> Date: Mon May 10 19:17:55 2021 +0700 Refactor property handling in wibox.widget.graph In addition, baseline_value, step_width and step_spacing properties are all set to their default non-nil numeric values during instance creation now. commit 842eb429bf5df4b2ba46b2e6f7646afe89b1a3c3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 9 06:18:45 2021 +0700 Make graph:draw_values() private There's no documentation nor confidence that it's a good API, so I'm hiding it for now. commit 720746780574e4bad14a71fc4174955139c2eb50 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 04:04:13 2021 +0700 Document graph_should_draw_data_group() for posterity commit 7c73f3754ce6ba86b118f1170a6f9e406ab4841d Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:59:21 2021 +0700 Make graph:should_draw_data_group() private There's no confidence whether it's a good API, so I'm hiding it for now. commit b3539e20adb6423118af726ae4f8751ef864b5d2 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:36:44 2021 +0700 Make graph:preprocess_values() private There's no confidence whether it's a good API, so I'm hiding it for now. commit 2df4400e780a6260f8d0ab1c60734fbb11bf3c4f Author: Alex Belykh <albel727@ngs.ru> Date: Thu May 6 10:05:23 2021 +0700 Add spec/wibox/widget/graph_spec.lua commit ce01a9771ab439b8df841f1565aa4aa8a44a5e0d Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 08:16:18 2021 +0700 Improve graph.capacity documentation commit e28fade8f655802f5bf89154169f68d256b8e332 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 07:36:01 2021 +0700 Document graph:compute_drawn_values_num() commit dd68332292465422d08b48e9f0229f143beccbc6 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 05:24:23 2021 +0700 Document graph:pick_data_group_color() method commit ca6dc55f77aca88a6e9cc90bb6fd0045739f41f5 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 04:13:58 2021 +0700 Guard against setting data groups sparsely in graph:add_value() commit cdea5f126f7984e72924f6dffbd312c4fe3b032f Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 02:36:12 2021 +0700 Refactor scaling and baseline choice out of graph:draw() commit 10135deafe8a9390ba66ab657ebb1fb41d549e8c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 21:34:48 2021 +0700 Document graph.nan_color and graph.stack properties commit 0d73bb84ac66b87e16aee472bd1f299549f66a1c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 20:49:24 2021 +0700 Use a better example in docs for the graph.stack property commit c27d160a0826d033d49d6141608c40aa8a0642fa Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 16:05:38 2021 +0700 Document graph.baseline_value property commit 459bc8176c13d4ea32af0dcdc2399d3b929b1c6a Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:53:47 2021 +0700 Document graph.clamp_bars property commit e732ee0b3b007d213e689b98cc00ae7cb141ae08 Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:34 2021 +0700 Improve graph:add_value() and :clear() documentation commit ae73e3aa9f8d60409cbe4cb4da9612f31af4f38e Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:11 2021 +0700 Improve graph min_value/max_value/scale documentation commit a7350996a9def773b91b1631e6c0507e33d8f93b Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:27:36 2021 +0700 Cleanup graph examples to show only relevant things in docs commit 30740c26f4f41c16f4c8a3c339bcb7bd299e14cd Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 14:04:36 2021 +0700 Rename graph.stack_colors property to `group_colors` The colors are used for stacked and non-stacked graphs alike, so the name didn't make sense. Also document and clarify some color stuff. commit 1ea01bfd410971cf0d0c25fd74cd764d5eb7279d Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 21:25:41 2021 +0700 Add an example file for code coverage of some fringe graph cases commit eae68e39553f88a6275a0c65bf040b5d4c262b54 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 20:23:32 2021 +0700 Add step_shape to the graph/nan_color example for code coverage commit 8027147bc2eab34a97b49a428fb8cef27f122bed Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:55:18 2021 +0700 Add example files for graph.nan_color/nan_indication properties commit 89a25a165c52b2c15b6c4d95ecb777a9067e66f9 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:33:07 2021 +0700 Make widget graph:add_value(v) default v to NaN 0 as a default makes no sense and silences the error that is failing to specify a value, to which the user should rather be alerted. The graph widget has sane NaN handling now and will do just that. commit 2835d552440be48d356a7f7b1d64963696dd550b Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:20:48 2021 +0700 Implement graph.nan_color and nan_indication properties Now, whenever there's a NaN among values and graph.nan_indication is set, the corresponding area, where the value bar should have been drawn, is filled with the nan_color from top to bottom. The painting is done after all data is rendered, to make sure that it won't be overpainted and go unnoticed. NaNs among graph's values are inevitably a sign of some error and it is arguably not a sane default behavior to simply not render them at all. The user should take immediate note of any errors by default, instead of wondering at a mysteriously empty graph widget, which is why nan_indication is set to true by default. If the user wants to silence NaNs, they can always simply set it to false. nan_color if unset, defaults to a noticeable yellow-black pattern. As a direct and planned consequence, stacked graphs with negative values in them trigger the NaN handling too. But silencing NaN handling for those graphs is also possible and semi-sane, because then NaNs and negatives are ignored and the graph behaves (almost) like they were zeros. commit 9c1f8f08b400e01e19c534f3810c9c9ab485760c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 26 08:10:19 2021 +0700 Refactor out graph_map_value_to_widget_coordinates() function commit 1905991ddecabdae3e15ffb8ee5b18a1bee1a711 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 28 13:39:29 2021 +0700 Add an example file for graph.baseline_value property commit b31b39d66f4c037a9484b71a3f910703ae01fac0 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 07:03:49 2021 +0700 Add an example file for graph.clamp_bars=false/true comparison commit a1fc850e3047f9ee2c9ddeffacfbabf3ded34171 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 04:06:45 2021 +0700 Add an example file for graphs with negative values and shapes commit 2f012f8ff1bb5094c14e724a61e59a2a3a296bc8 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 01:06:05 2021 +0700 Add an example file for a graph displaying negative values commit 7b14fbd59a4c4c51962fd3110a1a900dd5f4eb98 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 11:18:26 2021 +0700 Add an example file for graphs with step_width < 1 commit dfd9a54544b637e2fbe3d1325f9cc24b3f4d2313 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 10:17:38 2021 +0700 Add an example file for graph.stack=false/true comparison Should also demonstrate that multiple data groups are possible for non-stacked graphs too. commit 5b532106c3fd255f9b66a66cfb88d977b489ab5d Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 09:34:14 2021 +0700 Add an example file for disabling graph data groups with nil colors commit ac6f1083c02fcb428f089a616f9b0ebaa5496b6c Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:57:25 2021 +0700 Add an example file for step_shape/width/spacing on stacked graphs commit b3703db6a5139e73e72c1cfd198ece2cd9be7df2 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:43:56 2021 +0700 Add an example file for scale=false/true on stacked graph commit b000aed4a12cb44fb501e1c8432a71a14b2f8f3f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 20 08:22:19 2021 +0700 Document deprecation of graph's width and height commit cb0ad617bd6c83a499896797318e3b0b588c3566 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 07:53:37 2021 +0700 Deprecate width and height properties of wibox.widget.graph commit 8c64e14ad91a074b784166a7054e53444cccb9d2 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 04:37:59 2021 +0700 Refactor graph:draw() to separate concerns Firstly, summation of group values for stacked graphs is extracted into graph:preprocess_values(). This makes graph:draw() smaller and exposes the fact, that stacked graphs are almost entirely just a form of a more general concept: pre-render data manipulation. It's something a user might want to customize, and now they can do it by monkey-patching the method. Secondly, the logic for deciding, if and with which color a data group should be drawn, is extracted into graph:should_draw_data_group() and get_data_group_color() methods respectively. This makes it customizable too and makes more obvious the fact, that it is a general thing that has nothing to do with stacked graphs in particular. With this there are only 5 lines remaining in graph:draw(), that could be said to be specific to stacked graphs, all pertaining to the `prev_y` variable, which merely preserves the bar coordinates from a previous data group. commit 2a32b6305d893f413303728a0a3da1dcd9a5591e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 22:52:37 2021 +0700 Pointlessly optimize graph:add_value() 1) Purge old values first, then insert the new one, if needed. Since Lua grows arrays in power of two steps, and our guess_capacity() also takes care to return even integers, it would be a shame to overstep the capacity by one by inserting first and thus getting the table size doubled only to never use the most of it. 2) Use a removal procedure that doesn't call the slow length operator at all. Previous code called it twice (once in table.remove) for every removed value. commit 680ea8f5d81e9040afb4662b3229e937e4b89e02 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:23:38 2021 +0700 Set graph data series color before the painting loop commit b970f0f221ae76250f2a94433ff79d653caa6e21 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:05:37 2021 +0700 Drop a large no longer necessary `if` in graph:draw_values() This commit is almost purely a whitespace change. commit 6b4f0541c70ec99d7f6e276bae64939f337120d6 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:00:50 2021 +0700 Extract values rendering code into graph:draw_values() method This is to avoid ungodly if nesting levels, which are needed to bail out of drawing values early and skip to drawing the border. The next commit will get rid of the if. commit e0950e4f99d1ea1c4b8cea2cc67629fa40030c2a Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 14:50:20 2021 +0700 Track graph draw() usage to guess necessary array capacity The last step of divorcing add_values() from width calculations, while ensuring that user doesn't observe a lack of data to draw. The widget now tracks an approximate number of values that it has been asked to draw() in _private.last_drawn_values_num, and truncates its values array based on that (if not overridden by the capacity property). commit 2e3d542282f35e7c0fed454da0f2eada0fcf3b78 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 13:35:12 2021 +0700 Add `capacity` property to graph widget This is the second step of divorcing add_values() from calculations involving widths. How many values are stored and how many values are drawn are distinct concepts. There isn't even a single definitive "how many values are drawn" because the widget could be rendered in several places with distinct widths simultaneously, and in every such place the data must be drawn appropriately, e.g. with auto-scaling, that takes only the visible data points into account. And the draw() method can do that now. The default capacity value of nil uses heuristics based on widget's _private.width to guess some capacity that would tolerate some dynamic widget resizing. If the user finds the heuristic failing them, they can set the capacity property to the desired maximum number of values that will be kept in the _private.values array (in each data group). commit 08d4a80686f7fe363551c35589a460839f60853e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 03:27:18 2021 +0700 Extract values size computation into compute_drawn_values_num() This is the first step of making add_values() not concern itself (directly) with widget dimensions. The user can monkey-patch the method to override how many values are (logically) drawn, because it affects graph auto-scaling and can be useful for rendering of step_shape-s that paint outside of their step_width. I didn't make it a full-blown function-valued property, because it seems a fringe functionality, which I'm too lazy to document. commit 3c5617c98ab1147618ed22e8414a77fc56b45f6e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 02:32:29 2021 +0700 Copy graph widget prototype methods over in bulk commit 169538837ece41a03d02ba8fbc0e4097062b67a3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun Apr 25 05:58:01 2021 +0700 Remove graph.baseline_y property After giving it some thought I've concluded, that it is unlikely to be useful as it is now. I'd better reserve the name for some more sensible functionality, like fixing the baseline_value defined axis at the given widget coordinate, adjusting the scaling accordingly, so min_value/max_value won't generally correspond to widget top and bottom anymore. commit 202ffaa9a0b820c7c471e1121d01b91f8f76cd38 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 12:06:55 2021 +0700 Add baseline_y and baseline_value properties to graph widget Now values in graph have a *baseline*, tunable with baseline_y and baseline_value properties. It is the (for now invisible) horizontal line from which all bars in the graph grow. I call it a "baseline" instead of "axis", because it doesn't actually have to correspond to the 0 value. But it can, and actually does now by default. baseline_y and baseline_value properties determine the vertical placement of the baseline in the widget. baseline_y is the position of the baseline in normalized widget coordinates, e.g. 0 corresponds to widget's top, 1 - to widget's bottom, and 0.5 - to widget's middle. So if e.g. one sets baseline_y = 0, then no matter how the widget is resized and how its values are scaled, bars in it will be drawn starting from the top. baseline_value is the position of the baseline in value coordinates. So if one sets baseline_value = 10, then no matter what happens, bars for values greater than 10 will be drawn as growing up from this line, and bars for values smaller as growing down. Where and if it will be seen in the widget, depends on the scaling. baseline_value = 0 is the familiar zero axis. baseline_y is unset by default and has priority, i.e. if it's set, then baseline_value is ignored. baseline_value defaults to 0. Prior to this commit, bars were always drawn from the bottom edge of the widget, or, in the new terms, the widget had baseline_y = 1. But it was indistinguishable from baseline_value = 0, because the widget clamped all negative values to 0, so the difference between the two could never be observed. baseline_value = 0 is clearly more useful and familiar to people, so now it is in force by default. commit df7e19abaebf0a871b5ca217b4cc3365533267a6 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 11:18:12 2021 +0700 Move value clamping from graph:add_value() into draw() Previously all values were forcibly clamped between 0 (not tunable at all) and max_value (1 by default). Thus graphs with negative values were not an option. NaN values were conflated with zeros, even though it may be useful to maintain the distinction and render them differently. And if user happened to dynamically change the max_value property, they discovered, that their data got truncated. Preserving user data as it was given is more flexible and makes things easier to debug. This commit does exactly that. So what happens now to the newly allowed values during draw()? There are 3 new cases to consider: 1) NaNs 2) Negatives 3) Values outside of min_value..max_value range NaNs are presently simply not drawn. An empty place is reserved where the value should've been, but nothing else is done. This is distinct from how a zero is handled, e.g. if step_shape is set, it is called with the height parameter = 0, which can still end up drawing something, depending on the shape. One could also consider addition of nan_color/nan_shape properties which could be drawn to alert user to a gap in their data. Negative values necessitate introduction of the concept of the zero axis, from which they will be drawn DOWN, in contrast to positive values. Where should this axis be drawn? How should step_shape-s with inherent direction, e.g. arrows, be drawn for negatives? This will be addressed in the next commit. Values outside of min/max_value range can imply shapes that go beyond widget dimensions. An arrow shape that goes beyond the roof is visibly different from an arrow that saturates and merely touches the widget top, and the user may reasonably desire either behavior. Therefore this commit introduces a boolean clamp_bars property, which defaults to true for reasons of robustness and backward compatibility with the behaviour prior to this commit. The property of course affects only the display of the values, the actual user data stays intact. commit c9c24fb1ab741cd6bb8074971d568e0646c51532 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:29:01 2021 +0700 Use rectangles for drawing graph bars of all sizes I have benchmarked various bar drawing strategies and graph sizes and found out that drawing graph bars with rectangles is as fast or even marginally faster than using fat lines. Thus the code that tries to special-case 1px bars as lines seems to be not worth the additional complexity it introduces, and this commit removes it. Incidentally, I've also established, that rectangles are more robust. If one draws graph bars and feeds cairo very large coordinates (~2^62), then corresponding rectangles fail to render, but all the other rectangles in the same series get rendered successfully, whereas a single large coordinate in a move_to/line_to() sequence is enough to cause all lines in the batch to fail to render. commit bc7f4be5e06405b5aa7fb443a5ace2b9b9575b1f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:01:42 2021 +0700 Cache cairo methods used in the inner loop of graph:draw() I have benchmarked the draw() method, both on cairo xcb and on argb32 image surfaces, and have discovered that cairo context method calls are unnaturally slow, to the point, that almost nothing else in the function matters. For example, drawing bars with vertical lines made the whole draw() function almost 2 times slower than doing the same with rectangles, merely because the former requires two method calls (move_to/line_to), and the latter only one. Simply caching hotpath cairo methods in local variables, reduced the difference between the drawing methods to a negligible one and made the whole function 4 times faster on a typical 100-bar graph, (1.4ms to 0.33ms on my laptop) thus bringing closer my secret dream of drawing live audio data with awesome graphs (kidding). I guess, LGI does something not very optimal there, like gobject-introspecting the method every time, or something. There might be some untapped optimization potential, possibly in upstream, like a method cache, from which everything in awesome could benefit, even though I imagine not many things are so call-intensive as the graph widget, to really feel the difference. commit 538df25bdf02b53d0bbe81809eef2e6fdb05ff6f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 07:32:37 2021 +0700 Use 1-based index in the inner graph draw loop It's used there only once for a calculation and then several times to index some tables, and index 0 is bad for lua tables performance-wise. commit 7412546916cd0a0a490130a120255ffb719398b8 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:55:12 2021 +0700 Leave graph.max_value uninitialized at construction This is a user-visible change, but the previous behavior was probably not what user expected anyway, namely with graph.scale set the graph mysteriously couldn't zoom in onto values that are smaller than 1. commit eb81d7929a161e79c13262cfa73ac2e6a820e8ed Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:48:29 2021 +0700 Allow graph.max_value be nil This makes its behave symmetrically to min_value. Now it defaults to 1, when graph.scale is not set, and to the maximum value in the graph data, when graph.scale is set. commit f622a8430cbfde7007b58d7f4c9c76b08c91e997 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 12:01:13 2021 +0700 Calculate max values num in graph:add_value() more precisely At most so many values were kept in the widget values array, as there are pixels of usable widget width (i.e. sans border). This is exactly right for the surely most common case of graphs with 1px-wide bars, but is more than needed for graphs with step_width+step_spacing>1, and not enough for graphs with subpixel bars. Moreover this made graph autoscaling behave weirdly in graphs with thicker bars, because old values survived longer in the array and kept affecting min_value/max_value calculations even after they were shifted off-screen. The maximum number of kept values is now calculated exactly as the number of values that can be displayed at once, taking step_width and step_spacing into account. It might be worth adding a max_values_num property to the graph to let user override this calculation, in case they draw shapes in step_shape() outside of its supposed rectangle, so our assumptions of visibility are wrong, or the widget often gets resized, resulting in the annoyance of visibly truncated series. commit 0aa63249a18641251a1bf77423739164f0eab39f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 02:30:13 2021 +0700 Fix border_color/border_width bugs in graph widget add_value() wrongly assumed that border_width is always set, when border_color is, so if user set only the latter, an error occurred. add_value() assumed that border_width is always < width which led to awesome hanging up in an infinite loop, should that fail to be the case. The graph border_width property was driven by the border_color property, instead of the other way round. If border_color wasn't set, the border wasn't drawn all, even if user sets non-zero border_width, which is a confusing behavior, in contrast to all other widgets, which simply use a plethora of fallback colors to draw the border, when needed. This also meant that, despite being documented, the beautiful.graph_border_color fallback color could not and was not ever used. If the border_color was set though, an unset border_width was immediately assumed to be 1 instead of 0, also contrary to what other widgets do. Theme colors shouldn't influence widget layout, so this commit fixes this behavior to: 1) border_width always defaults to 0, if unset. 2) border_color falls back to beautiful.graph_border_color or white. Maybe one should also add a beautiful.graph_border_width theme variable, like some widgets do. commit dad4a3253b8fef971fb82720d2a407fcc0a879ad Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 03:11:27 2021 +0700 Implement multiple data series drawing for non-stacked graph The widget can already hold and draw multiple data groups, so it makes little sense to limit drawing only to the first group when graph.stack = false, probably leading to user's confusion. Now user can call add_value(v, group) with group > 1 and the data series will be drawn (over groups with lesser id-s) with graph.stack_colors[group] color, if stack_colors is set, or with graph.color, if stack_colors isn't set. Thus nothing visibly changes for all current users of non-stacked graphs with only single data series, but the new functionality is now there for those who need it. One can't fully emulate this functionality, e.g. with multiple graphs widgets in a wibox.layout.stack, because data series in a single widget can enjoy a common auto-scaling. commit 3e645c976184ab5f50d625271c25062db9ab7ed9 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:59:12 2021 +0700 Don't draw a stacked graph data group if there's no color for it If stack_colors[idx] is nil for the group `idx`, don't draw it at all (as opposed, e.g. to the effect of drawing it with a transparent color, or as though it had all values == 0, which both produce different results). This code is still subtly different from prior behavior, because now user can have a non-contiguous stack_colors table and turn the display of any data group on and off at will, whereas the code two commits earlier would just stop rendering as soon as ipairs(stack_colors) does, i.e. no data series were drawn past the first nil hole. This commit only looks large, but is mostly a whitespace change due to nesting from a pair of wrapping ifs. commit 8c0273786de1ade66bdd977699f98b588da9f487 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:32:42 2021 +0700 Unify stacked and non-stacked graph rendering Now stacked graphs support everything the nonstacked do, like step_width ~= 1 and custom step_shape-s. Technical notes: This code no longer calls cr:stroke() for every single value in a stacked graph, but rather paints a whole series and then cr:stroke/fill()-s it at all once with its color, improving performance considerably. Additionally the graph no longer relies on the presence of the stack_colors property to render series. It draws all data that it has, and, if there's no corresponding color in stack_colors, falls back to graph.color. This will be changed in the next commit to the prior behavior of only drawing those series for which there's a color in the stack_colors table, but that would've made this commit's diff much larger due to required nesting ifs over large code chunks. This commit also takes care to properly handle NaNs and negative values. Those are currently (almost) guaranteed to not occur in the values array due to clipping in graph:add_value(), but I intend to eventually drop the clipping, because it does nothing that draw() really relies upon, and allowing unclipped and NaN values is more flexible and less surprising, e.g. when user dynamically toggles graph.scale. Eventually I intend to implement drawing negative values in non-stacked graphs and possibly even in the stacked ones. commit 38c6589c51b2aa057d7c2f40182ecf32b5b2ff5e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 10:04:15 2021 +0700 Convert graph:draw() to use `self` commit f21f1b2550426df1c4ea8abde079b1ccef84b394 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 12:27:26 2021 +0700 Fix scaling for stacked graphs Stacked graphs have cumulative heights, values from all groups that end up in the same graph bar are effectively summed. Previous code didn't take that into account when calculating min/max_value for the purposes of rescaling, but simply examined every value separately. This code adds up all values up front and finds min/max among those, to determine proper scaling that would fit the graph exactly. commit 1ce4c7bf63bdd6c99c195d668347bec49355e921 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 09:49:35 2021 +0700 Draw shaped graph bars only up to the baseline Prior to this commit, every step_shape bar was, for some reason, drawn with height = full widget's height, regardless of the value it represents. So for the most bars their lower end was far outside the box and therefore clipped off. So e.g. one couldn't see the lower round edges of rounded rectangles. This commit fixes that by appropriately varying the height that is passed into step_shape(), so that lower edges of the bars always exactly touch the lower edge of the graph. If the user wants to restore the prior behavior, they can simply ignore the passed-in height in step_shape() and just always draw their shape with height = full widget height. But they couldn't replicate the effect of this commit in their rc.lua without essentially writing their own graph widget. commit e25185b08da23745b5815f85f6e9e43222ed2294 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 08:42:54 2021 +0700 Avoid incremental transform shifting during graph bar rendering Instead of letting addition errors and cognitive load accumulate simply use the same offset calculation that is used for lines and reset the transform back to the top-left corner after every drawn bar. It's conceptually simpler, and will be useful later for stacked graphs and more. It also fixes a discrepancy between plain and step_shape rendering. Prior to this commit, bar coloring occurred with a disturbed transform, which was centered at the last drawn bar, wherever it happened to be. This meant that nontrivial colors, like gradients and images, were arbitrarily offset when step_shape was used. commit f3e98866d8583c35d0b557c09f529299edc9ba2c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:45:29 2021 +0700 Refactor out graph bar height expression into a variable commit f54528d6892f5aaa4595917853c9e2130f743fdb Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:20:25 2021 +0700 Draw graph bars with lines only when step_width == 1 Before this commit graph bars of step_width < 1 were drawn with lines of width 1, but that's a bit unfortunate, because it led to much overpainting, while still ending up with a graphically incorrect result. Let's draw such bars with rectangles and let cairo do things with subpixels, getting more graphically accurate result, while still keeping the common case of step_width == 1 (supposedly) fast-drawn with lines. commit b6515dca67c18b78eb5657378131a62ab43d4cfc Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:54:07 2021 +0700 Fix unsharp edges when drawing graph bars with rectangles The 0.5 offset is only helpful for 1-width strokes, not for fills. commit 7f3db8f07e82f821bcd1dc418d66564f537609d6 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:21:01 2021 +0700 Do coordinate transformations even if the value isn't drawn The graph presently doesn't draw values < 0, but there was a discrepancy between drawing with and without step_shape. The default line/rectangle drawing reserves an empty place where the skipped value should've been, but no such empty place is reserved, when step_shape is used. This commit fixes that. Skipped values always have their empty space now. commit 7b731ebfe96af7a9dba9c57371da6761cf22c00c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:03:30 2021 +0700 Fix graph bar scaling when min_value is not 0 Previous calculation lead to underscaling of values so that even value = max_value wasn't drawn to full height. commit dca59fd8b7fff0e7e646f2dfc6bd92047028e5a8 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:18:40 2021 +0700 Calculate the horizontal graph bar offset only when needed commit 940ddf4b9825e9c3a91d276208c089d8c74e49db Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:17:23 2021 +0700 Call cr:move_to() only when drawing the graph with lines It's redundant otherwise and might even interfere with user's drawing in step_shape(). commit b5d5cd10ca29fb29cd751faaa95137c2fe89ff63 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:04:06 2021 +0700 Fix wrong horizontal offset calculations in widget.graph Indices in the loop are zero-based, so things like i-1 and i>1 aren't necessary and actually wouldn't have been correct even in a 1-based loop. step_shape = gears.shape.rectangle and step_shape = nil, which is supposed to default to rectangle according to docs, were drawn differently due to mismatched offset calculations, which led to problems especially noticeable when step_spacing is not zero, e.g: 1) whole graph shifted left too much, drawn partially off-screen. 2) whole graph shifted right too much, and the first bar placed closer to the rest than others. commit dc369fa198281e000afc1bd17326f3a264adf7d1 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 02:09:21 2021 +0700 Fix the "border_width affects graph bar width" bug Graph bars were drawn with lines of border_width thickness instead of 1, i.e. possibly too fat to be readable. commit 1ba73378cf29ed27a6ecd18716394779ceb543d9 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 00:13:03 2021 +0700 Simplify internal values representation in graph even more New values in graph._private.values were pushed to the end, and old values were removed from the beginning of the table. In draw() however newer values are drawn first, at the left edge of the graph, necessitating annoying idx = #values - idx conversions everywhere. In particular in stacked graph that means that values that are drawn together may have different indices. One has to shift the values array in add_value() either way, so one might as well insert new values at the beginning of the array and simplify the rest of the code. This doesn't seem like much yet, but it will prove to be much more convenient and less error-prone in what I'm going to do later. commit f70523ee68dbd6b67050ace4b82903a9b22440c7 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 13:35:10 2021 +0700 Simplify internal values representation in graph widget graph._private.values was either a table of values or a table of tables of values, depending on whether "stack" property is enabled, ... or a mix of both if user forgot to pass the second "group" parameter to graph:add_value(). This resulted in pretty fragile and confusing behavior, e.g. leaving user to wonder, why they see nothing more after setting stack=true for a graph in a previously working rc.lua, and demanding a very careful use of add_value() from the user, or else some values or even entire stacks would mysteriously vanish and errors in draw() get triggered, sometimes after a period of seemingly normal functioning. Now `values` is always a table of tables and the group parameter in add_value() defaults to 1, thus making the non-stacked graph a trivial variant of the stacked graph.
2021-05-20 17:14:03 +02:00
local last_value = self._private.last_drawn_values_num or 0
-- Grow instantly and shrink slow
if new_value < last_value then
self._private.last_drawn_values_num = last_value - 1
else
self._private.last_drawn_values_num = new_value
end
end
2016-05-23 07:30:44 +02:00
graph: complete widget overhaul and bug fixes Squashed commit of the following: commit 69821f51fe1e8652715658543e50631ece495090 Author: Alex Belykh <albel727@ngs.ru> Date: Mon May 10 19:17:55 2021 +0700 Refactor property handling in wibox.widget.graph In addition, baseline_value, step_width and step_spacing properties are all set to their default non-nil numeric values during instance creation now. commit 842eb429bf5df4b2ba46b2e6f7646afe89b1a3c3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 9 06:18:45 2021 +0700 Make graph:draw_values() private There's no documentation nor confidence that it's a good API, so I'm hiding it for now. commit 720746780574e4bad14a71fc4174955139c2eb50 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 04:04:13 2021 +0700 Document graph_should_draw_data_group() for posterity commit 7c73f3754ce6ba86b118f1170a6f9e406ab4841d Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:59:21 2021 +0700 Make graph:should_draw_data_group() private There's no confidence whether it's a good API, so I'm hiding it for now. commit b3539e20adb6423118af726ae4f8751ef864b5d2 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:36:44 2021 +0700 Make graph:preprocess_values() private There's no confidence whether it's a good API, so I'm hiding it for now. commit 2df4400e780a6260f8d0ab1c60734fbb11bf3c4f Author: Alex Belykh <albel727@ngs.ru> Date: Thu May 6 10:05:23 2021 +0700 Add spec/wibox/widget/graph_spec.lua commit ce01a9771ab439b8df841f1565aa4aa8a44a5e0d Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 08:16:18 2021 +0700 Improve graph.capacity documentation commit e28fade8f655802f5bf89154169f68d256b8e332 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 07:36:01 2021 +0700 Document graph:compute_drawn_values_num() commit dd68332292465422d08b48e9f0229f143beccbc6 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 05:24:23 2021 +0700 Document graph:pick_data_group_color() method commit ca6dc55f77aca88a6e9cc90bb6fd0045739f41f5 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 04:13:58 2021 +0700 Guard against setting data groups sparsely in graph:add_value() commit cdea5f126f7984e72924f6dffbd312c4fe3b032f Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 02:36:12 2021 +0700 Refactor scaling and baseline choice out of graph:draw() commit 10135deafe8a9390ba66ab657ebb1fb41d549e8c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 21:34:48 2021 +0700 Document graph.nan_color and graph.stack properties commit 0d73bb84ac66b87e16aee472bd1f299549f66a1c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 20:49:24 2021 +0700 Use a better example in docs for the graph.stack property commit c27d160a0826d033d49d6141608c40aa8a0642fa Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 16:05:38 2021 +0700 Document graph.baseline_value property commit 459bc8176c13d4ea32af0dcdc2399d3b929b1c6a Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:53:47 2021 +0700 Document graph.clamp_bars property commit e732ee0b3b007d213e689b98cc00ae7cb141ae08 Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:34 2021 +0700 Improve graph:add_value() and :clear() documentation commit ae73e3aa9f8d60409cbe4cb4da9612f31af4f38e Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:11 2021 +0700 Improve graph min_value/max_value/scale documentation commit a7350996a9def773b91b1631e6c0507e33d8f93b Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:27:36 2021 +0700 Cleanup graph examples to show only relevant things in docs commit 30740c26f4f41c16f4c8a3c339bcb7bd299e14cd Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 14:04:36 2021 +0700 Rename graph.stack_colors property to `group_colors` The colors are used for stacked and non-stacked graphs alike, so the name didn't make sense. Also document and clarify some color stuff. commit 1ea01bfd410971cf0d0c25fd74cd764d5eb7279d Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 21:25:41 2021 +0700 Add an example file for code coverage of some fringe graph cases commit eae68e39553f88a6275a0c65bf040b5d4c262b54 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 20:23:32 2021 +0700 Add step_shape to the graph/nan_color example for code coverage commit 8027147bc2eab34a97b49a428fb8cef27f122bed Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:55:18 2021 +0700 Add example files for graph.nan_color/nan_indication properties commit 89a25a165c52b2c15b6c4d95ecb777a9067e66f9 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:33:07 2021 +0700 Make widget graph:add_value(v) default v to NaN 0 as a default makes no sense and silences the error that is failing to specify a value, to which the user should rather be alerted. The graph widget has sane NaN handling now and will do just that. commit 2835d552440be48d356a7f7b1d64963696dd550b Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:20:48 2021 +0700 Implement graph.nan_color and nan_indication properties Now, whenever there's a NaN among values and graph.nan_indication is set, the corresponding area, where the value bar should have been drawn, is filled with the nan_color from top to bottom. The painting is done after all data is rendered, to make sure that it won't be overpainted and go unnoticed. NaNs among graph's values are inevitably a sign of some error and it is arguably not a sane default behavior to simply not render them at all. The user should take immediate note of any errors by default, instead of wondering at a mysteriously empty graph widget, which is why nan_indication is set to true by default. If the user wants to silence NaNs, they can always simply set it to false. nan_color if unset, defaults to a noticeable yellow-black pattern. As a direct and planned consequence, stacked graphs with negative values in them trigger the NaN handling too. But silencing NaN handling for those graphs is also possible and semi-sane, because then NaNs and negatives are ignored and the graph behaves (almost) like they were zeros. commit 9c1f8f08b400e01e19c534f3810c9c9ab485760c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 26 08:10:19 2021 +0700 Refactor out graph_map_value_to_widget_coordinates() function commit 1905991ddecabdae3e15ffb8ee5b18a1bee1a711 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 28 13:39:29 2021 +0700 Add an example file for graph.baseline_value property commit b31b39d66f4c037a9484b71a3f910703ae01fac0 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 07:03:49 2021 +0700 Add an example file for graph.clamp_bars=false/true comparison commit a1fc850e3047f9ee2c9ddeffacfbabf3ded34171 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 04:06:45 2021 +0700 Add an example file for graphs with negative values and shapes commit 2f012f8ff1bb5094c14e724a61e59a2a3a296bc8 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 01:06:05 2021 +0700 Add an example file for a graph displaying negative values commit 7b14fbd59a4c4c51962fd3110a1a900dd5f4eb98 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 11:18:26 2021 +0700 Add an example file for graphs with step_width < 1 commit dfd9a54544b637e2fbe3d1325f9cc24b3f4d2313 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 10:17:38 2021 +0700 Add an example file for graph.stack=false/true comparison Should also demonstrate that multiple data groups are possible for non-stacked graphs too. commit 5b532106c3fd255f9b66a66cfb88d977b489ab5d Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 09:34:14 2021 +0700 Add an example file for disabling graph data groups with nil colors commit ac6f1083c02fcb428f089a616f9b0ebaa5496b6c Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:57:25 2021 +0700 Add an example file for step_shape/width/spacing on stacked graphs commit b3703db6a5139e73e72c1cfd198ece2cd9be7df2 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:43:56 2021 +0700 Add an example file for scale=false/true on stacked graph commit b000aed4a12cb44fb501e1c8432a71a14b2f8f3f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 20 08:22:19 2021 +0700 Document deprecation of graph's width and height commit cb0ad617bd6c83a499896797318e3b0b588c3566 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 07:53:37 2021 +0700 Deprecate width and height properties of wibox.widget.graph commit 8c64e14ad91a074b784166a7054e53444cccb9d2 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 04:37:59 2021 +0700 Refactor graph:draw() to separate concerns Firstly, summation of group values for stacked graphs is extracted into graph:preprocess_values(). This makes graph:draw() smaller and exposes the fact, that stacked graphs are almost entirely just a form of a more general concept: pre-render data manipulation. It's something a user might want to customize, and now they can do it by monkey-patching the method. Secondly, the logic for deciding, if and with which color a data group should be drawn, is extracted into graph:should_draw_data_group() and get_data_group_color() methods respectively. This makes it customizable too and makes more obvious the fact, that it is a general thing that has nothing to do with stacked graphs in particular. With this there are only 5 lines remaining in graph:draw(), that could be said to be specific to stacked graphs, all pertaining to the `prev_y` variable, which merely preserves the bar coordinates from a previous data group. commit 2a32b6305d893f413303728a0a3da1dcd9a5591e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 22:52:37 2021 +0700 Pointlessly optimize graph:add_value() 1) Purge old values first, then insert the new one, if needed. Since Lua grows arrays in power of two steps, and our guess_capacity() also takes care to return even integers, it would be a shame to overstep the capacity by one by inserting first and thus getting the table size doubled only to never use the most of it. 2) Use a removal procedure that doesn't call the slow length operator at all. Previous code called it twice (once in table.remove) for every removed value. commit 680ea8f5d81e9040afb4662b3229e937e4b89e02 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:23:38 2021 +0700 Set graph data series color before the painting loop commit b970f0f221ae76250f2a94433ff79d653caa6e21 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:05:37 2021 +0700 Drop a large no longer necessary `if` in graph:draw_values() This commit is almost purely a whitespace change. commit 6b4f0541c70ec99d7f6e276bae64939f337120d6 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:00:50 2021 +0700 Extract values rendering code into graph:draw_values() method This is to avoid ungodly if nesting levels, which are needed to bail out of drawing values early and skip to drawing the border. The next commit will get rid of the if. commit e0950e4f99d1ea1c4b8cea2cc67629fa40030c2a Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 14:50:20 2021 +0700 Track graph draw() usage to guess necessary array capacity The last step of divorcing add_values() from width calculations, while ensuring that user doesn't observe a lack of data to draw. The widget now tracks an approximate number of values that it has been asked to draw() in _private.last_drawn_values_num, and truncates its values array based on that (if not overridden by the capacity property). commit 2e3d542282f35e7c0fed454da0f2eada0fcf3b78 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 13:35:12 2021 +0700 Add `capacity` property to graph widget This is the second step of divorcing add_values() from calculations involving widths. How many values are stored and how many values are drawn are distinct concepts. There isn't even a single definitive "how many values are drawn" because the widget could be rendered in several places with distinct widths simultaneously, and in every such place the data must be drawn appropriately, e.g. with auto-scaling, that takes only the visible data points into account. And the draw() method can do that now. The default capacity value of nil uses heuristics based on widget's _private.width to guess some capacity that would tolerate some dynamic widget resizing. If the user finds the heuristic failing them, they can set the capacity property to the desired maximum number of values that will be kept in the _private.values array (in each data group). commit 08d4a80686f7fe363551c35589a460839f60853e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 03:27:18 2021 +0700 Extract values size computation into compute_drawn_values_num() This is the first step of making add_values() not concern itself (directly) with widget dimensions. The user can monkey-patch the method to override how many values are (logically) drawn, because it affects graph auto-scaling and can be useful for rendering of step_shape-s that paint outside of their step_width. I didn't make it a full-blown function-valued property, because it seems a fringe functionality, which I'm too lazy to document. commit 3c5617c98ab1147618ed22e8414a77fc56b45f6e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 02:32:29 2021 +0700 Copy graph widget prototype methods over in bulk commit 169538837ece41a03d02ba8fbc0e4097062b67a3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun Apr 25 05:58:01 2021 +0700 Remove graph.baseline_y property After giving it some thought I've concluded, that it is unlikely to be useful as it is now. I'd better reserve the name for some more sensible functionality, like fixing the baseline_value defined axis at the given widget coordinate, adjusting the scaling accordingly, so min_value/max_value won't generally correspond to widget top and bottom anymore. commit 202ffaa9a0b820c7c471e1121d01b91f8f76cd38 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 12:06:55 2021 +0700 Add baseline_y and baseline_value properties to graph widget Now values in graph have a *baseline*, tunable with baseline_y and baseline_value properties. It is the (for now invisible) horizontal line from which all bars in the graph grow. I call it a "baseline" instead of "axis", because it doesn't actually have to correspond to the 0 value. But it can, and actually does now by default. baseline_y and baseline_value properties determine the vertical placement of the baseline in the widget. baseline_y is the position of the baseline in normalized widget coordinates, e.g. 0 corresponds to widget's top, 1 - to widget's bottom, and 0.5 - to widget's middle. So if e.g. one sets baseline_y = 0, then no matter how the widget is resized and how its values are scaled, bars in it will be drawn starting from the top. baseline_value is the position of the baseline in value coordinates. So if one sets baseline_value = 10, then no matter what happens, bars for values greater than 10 will be drawn as growing up from this line, and bars for values smaller as growing down. Where and if it will be seen in the widget, depends on the scaling. baseline_value = 0 is the familiar zero axis. baseline_y is unset by default and has priority, i.e. if it's set, then baseline_value is ignored. baseline_value defaults to 0. Prior to this commit, bars were always drawn from the bottom edge of the widget, or, in the new terms, the widget had baseline_y = 1. But it was indistinguishable from baseline_value = 0, because the widget clamped all negative values to 0, so the difference between the two could never be observed. baseline_value = 0 is clearly more useful and familiar to people, so now it is in force by default. commit df7e19abaebf0a871b5ca217b4cc3365533267a6 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 11:18:12 2021 +0700 Move value clamping from graph:add_value() into draw() Previously all values were forcibly clamped between 0 (not tunable at all) and max_value (1 by default). Thus graphs with negative values were not an option. NaN values were conflated with zeros, even though it may be useful to maintain the distinction and render them differently. And if user happened to dynamically change the max_value property, they discovered, that their data got truncated. Preserving user data as it was given is more flexible and makes things easier to debug. This commit does exactly that. So what happens now to the newly allowed values during draw()? There are 3 new cases to consider: 1) NaNs 2) Negatives 3) Values outside of min_value..max_value range NaNs are presently simply not drawn. An empty place is reserved where the value should've been, but nothing else is done. This is distinct from how a zero is handled, e.g. if step_shape is set, it is called with the height parameter = 0, which can still end up drawing something, depending on the shape. One could also consider addition of nan_color/nan_shape properties which could be drawn to alert user to a gap in their data. Negative values necessitate introduction of the concept of the zero axis, from which they will be drawn DOWN, in contrast to positive values. Where should this axis be drawn? How should step_shape-s with inherent direction, e.g. arrows, be drawn for negatives? This will be addressed in the next commit. Values outside of min/max_value range can imply shapes that go beyond widget dimensions. An arrow shape that goes beyond the roof is visibly different from an arrow that saturates and merely touches the widget top, and the user may reasonably desire either behavior. Therefore this commit introduces a boolean clamp_bars property, which defaults to true for reasons of robustness and backward compatibility with the behaviour prior to this commit. The property of course affects only the display of the values, the actual user data stays intact. commit c9c24fb1ab741cd6bb8074971d568e0646c51532 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:29:01 2021 +0700 Use rectangles for drawing graph bars of all sizes I have benchmarked various bar drawing strategies and graph sizes and found out that drawing graph bars with rectangles is as fast or even marginally faster than using fat lines. Thus the code that tries to special-case 1px bars as lines seems to be not worth the additional complexity it introduces, and this commit removes it. Incidentally, I've also established, that rectangles are more robust. If one draws graph bars and feeds cairo very large coordinates (~2^62), then corresponding rectangles fail to render, but all the other rectangles in the same series get rendered successfully, whereas a single large coordinate in a move_to/line_to() sequence is enough to cause all lines in the batch to fail to render. commit bc7f4be5e06405b5aa7fb443a5ace2b9b9575b1f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:01:42 2021 +0700 Cache cairo methods used in the inner loop of graph:draw() I have benchmarked the draw() method, both on cairo xcb and on argb32 image surfaces, and have discovered that cairo context method calls are unnaturally slow, to the point, that almost nothing else in the function matters. For example, drawing bars with vertical lines made the whole draw() function almost 2 times slower than doing the same with rectangles, merely because the former requires two method calls (move_to/line_to), and the latter only one. Simply caching hotpath cairo methods in local variables, reduced the difference between the drawing methods to a negligible one and made the whole function 4 times faster on a typical 100-bar graph, (1.4ms to 0.33ms on my laptop) thus bringing closer my secret dream of drawing live audio data with awesome graphs (kidding). I guess, LGI does something not very optimal there, like gobject-introspecting the method every time, or something. There might be some untapped optimization potential, possibly in upstream, like a method cache, from which everything in awesome could benefit, even though I imagine not many things are so call-intensive as the graph widget, to really feel the difference. commit 538df25bdf02b53d0bbe81809eef2e6fdb05ff6f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 07:32:37 2021 +0700 Use 1-based index in the inner graph draw loop It's used there only once for a calculation and then several times to index some tables, and index 0 is bad for lua tables performance-wise. commit 7412546916cd0a0a490130a120255ffb719398b8 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:55:12 2021 +0700 Leave graph.max_value uninitialized at construction This is a user-visible change, but the previous behavior was probably not what user expected anyway, namely with graph.scale set the graph mysteriously couldn't zoom in onto values that are smaller than 1. commit eb81d7929a161e79c13262cfa73ac2e6a820e8ed Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:48:29 2021 +0700 Allow graph.max_value be nil This makes its behave symmetrically to min_value. Now it defaults to 1, when graph.scale is not set, and to the maximum value in the graph data, when graph.scale is set. commit f622a8430cbfde7007b58d7f4c9c76b08c91e997 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 12:01:13 2021 +0700 Calculate max values num in graph:add_value() more precisely At most so many values were kept in the widget values array, as there are pixels of usable widget width (i.e. sans border). This is exactly right for the surely most common case of graphs with 1px-wide bars, but is more than needed for graphs with step_width+step_spacing>1, and not enough for graphs with subpixel bars. Moreover this made graph autoscaling behave weirdly in graphs with thicker bars, because old values survived longer in the array and kept affecting min_value/max_value calculations even after they were shifted off-screen. The maximum number of kept values is now calculated exactly as the number of values that can be displayed at once, taking step_width and step_spacing into account. It might be worth adding a max_values_num property to the graph to let user override this calculation, in case they draw shapes in step_shape() outside of its supposed rectangle, so our assumptions of visibility are wrong, or the widget often gets resized, resulting in the annoyance of visibly truncated series. commit 0aa63249a18641251a1bf77423739164f0eab39f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 02:30:13 2021 +0700 Fix border_color/border_width bugs in graph widget add_value() wrongly assumed that border_width is always set, when border_color is, so if user set only the latter, an error occurred. add_value() assumed that border_width is always < width which led to awesome hanging up in an infinite loop, should that fail to be the case. The graph border_width property was driven by the border_color property, instead of the other way round. If border_color wasn't set, the border wasn't drawn all, even if user sets non-zero border_width, which is a confusing behavior, in contrast to all other widgets, which simply use a plethora of fallback colors to draw the border, when needed. This also meant that, despite being documented, the beautiful.graph_border_color fallback color could not and was not ever used. If the border_color was set though, an unset border_width was immediately assumed to be 1 instead of 0, also contrary to what other widgets do. Theme colors shouldn't influence widget layout, so this commit fixes this behavior to: 1) border_width always defaults to 0, if unset. 2) border_color falls back to beautiful.graph_border_color or white. Maybe one should also add a beautiful.graph_border_width theme variable, like some widgets do. commit dad4a3253b8fef971fb82720d2a407fcc0a879ad Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 03:11:27 2021 +0700 Implement multiple data series drawing for non-stacked graph The widget can already hold and draw multiple data groups, so it makes little sense to limit drawing only to the first group when graph.stack = false, probably leading to user's confusion. Now user can call add_value(v, group) with group > 1 and the data series will be drawn (over groups with lesser id-s) with graph.stack_colors[group] color, if stack_colors is set, or with graph.color, if stack_colors isn't set. Thus nothing visibly changes for all current users of non-stacked graphs with only single data series, but the new functionality is now there for those who need it. One can't fully emulate this functionality, e.g. with multiple graphs widgets in a wibox.layout.stack, because data series in a single widget can enjoy a common auto-scaling. commit 3e645c976184ab5f50d625271c25062db9ab7ed9 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:59:12 2021 +0700 Don't draw a stacked graph data group if there's no color for it If stack_colors[idx] is nil for the group `idx`, don't draw it at all (as opposed, e.g. to the effect of drawing it with a transparent color, or as though it had all values == 0, which both produce different results). This code is still subtly different from prior behavior, because now user can have a non-contiguous stack_colors table and turn the display of any data group on and off at will, whereas the code two commits earlier would just stop rendering as soon as ipairs(stack_colors) does, i.e. no data series were drawn past the first nil hole. This commit only looks large, but is mostly a whitespace change due to nesting from a pair of wrapping ifs. commit 8c0273786de1ade66bdd977699f98b588da9f487 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:32:42 2021 +0700 Unify stacked and non-stacked graph rendering Now stacked graphs support everything the nonstacked do, like step_width ~= 1 and custom step_shape-s. Technical notes: This code no longer calls cr:stroke() for every single value in a stacked graph, but rather paints a whole series and then cr:stroke/fill()-s it at all once with its color, improving performance considerably. Additionally the graph no longer relies on the presence of the stack_colors property to render series. It draws all data that it has, and, if there's no corresponding color in stack_colors, falls back to graph.color. This will be changed in the next commit to the prior behavior of only drawing those series for which there's a color in the stack_colors table, but that would've made this commit's diff much larger due to required nesting ifs over large code chunks. This commit also takes care to properly handle NaNs and negative values. Those are currently (almost) guaranteed to not occur in the values array due to clipping in graph:add_value(), but I intend to eventually drop the clipping, because it does nothing that draw() really relies upon, and allowing unclipped and NaN values is more flexible and less surprising, e.g. when user dynamically toggles graph.scale. Eventually I intend to implement drawing negative values in non-stacked graphs and possibly even in the stacked ones. commit 38c6589c51b2aa057d7c2f40182ecf32b5b2ff5e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 10:04:15 2021 +0700 Convert graph:draw() to use `self` commit f21f1b2550426df1c4ea8abde079b1ccef84b394 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 12:27:26 2021 +0700 Fix scaling for stacked graphs Stacked graphs have cumulative heights, values from all groups that end up in the same graph bar are effectively summed. Previous code didn't take that into account when calculating min/max_value for the purposes of rescaling, but simply examined every value separately. This code adds up all values up front and finds min/max among those, to determine proper scaling that would fit the graph exactly. commit 1ce4c7bf63bdd6c99c195d668347bec49355e921 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 09:49:35 2021 +0700 Draw shaped graph bars only up to the baseline Prior to this commit, every step_shape bar was, for some reason, drawn with height = full widget's height, regardless of the value it represents. So for the most bars their lower end was far outside the box and therefore clipped off. So e.g. one couldn't see the lower round edges of rounded rectangles. This commit fixes that by appropriately varying the height that is passed into step_shape(), so that lower edges of the bars always exactly touch the lower edge of the graph. If the user wants to restore the prior behavior, they can simply ignore the passed-in height in step_shape() and just always draw their shape with height = full widget height. But they couldn't replicate the effect of this commit in their rc.lua without essentially writing their own graph widget. commit e25185b08da23745b5815f85f6e9e43222ed2294 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 08:42:54 2021 +0700 Avoid incremental transform shifting during graph bar rendering Instead of letting addition errors and cognitive load accumulate simply use the same offset calculation that is used for lines and reset the transform back to the top-left corner after every drawn bar. It's conceptually simpler, and will be useful later for stacked graphs and more. It also fixes a discrepancy between plain and step_shape rendering. Prior to this commit, bar coloring occurred with a disturbed transform, which was centered at the last drawn bar, wherever it happened to be. This meant that nontrivial colors, like gradients and images, were arbitrarily offset when step_shape was used. commit f3e98866d8583c35d0b557c09f529299edc9ba2c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:45:29 2021 +0700 Refactor out graph bar height expression into a variable commit f54528d6892f5aaa4595917853c9e2130f743fdb Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:20:25 2021 +0700 Draw graph bars with lines only when step_width == 1 Before this commit graph bars of step_width < 1 were drawn with lines of width 1, but that's a bit unfortunate, because it led to much overpainting, while still ending up with a graphically incorrect result. Let's draw such bars with rectangles and let cairo do things with subpixels, getting more graphically accurate result, while still keeping the common case of step_width == 1 (supposedly) fast-drawn with lines. commit b6515dca67c18b78eb5657378131a62ab43d4cfc Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:54:07 2021 +0700 Fix unsharp edges when drawing graph bars with rectangles The 0.5 offset is only helpful for 1-width strokes, not for fills. commit 7f3db8f07e82f821bcd1dc418d66564f537609d6 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:21:01 2021 +0700 Do coordinate transformations even if the value isn't drawn The graph presently doesn't draw values < 0, but there was a discrepancy between drawing with and without step_shape. The default line/rectangle drawing reserves an empty place where the skipped value should've been, but no such empty place is reserved, when step_shape is used. This commit fixes that. Skipped values always have their empty space now. commit 7b731ebfe96af7a9dba9c57371da6761cf22c00c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:03:30 2021 +0700 Fix graph bar scaling when min_value is not 0 Previous calculation lead to underscaling of values so that even value = max_value wasn't drawn to full height. commit dca59fd8b7fff0e7e646f2dfc6bd92047028e5a8 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:18:40 2021 +0700 Calculate the horizontal graph bar offset only when needed commit 940ddf4b9825e9c3a91d276208c089d8c74e49db Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:17:23 2021 +0700 Call cr:move_to() only when drawing the graph with lines It's redundant otherwise and might even interfere with user's drawing in step_shape(). commit b5d5cd10ca29fb29cd751faaa95137c2fe89ff63 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:04:06 2021 +0700 Fix wrong horizontal offset calculations in widget.graph Indices in the loop are zero-based, so things like i-1 and i>1 aren't necessary and actually wouldn't have been correct even in a 1-based loop. step_shape = gears.shape.rectangle and step_shape = nil, which is supposed to default to rectangle according to docs, were drawn differently due to mismatched offset calculations, which led to problems especially noticeable when step_spacing is not zero, e.g: 1) whole graph shifted left too much, drawn partially off-screen. 2) whole graph shifted right too much, and the first bar placed closer to the rest than others. commit dc369fa198281e000afc1bd17326f3a264adf7d1 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 02:09:21 2021 +0700 Fix the "border_width affects graph bar width" bug Graph bars were drawn with lines of border_width thickness instead of 1, i.e. possibly too fat to be readable. commit 1ba73378cf29ed27a6ecd18716394779ceb543d9 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 00:13:03 2021 +0700 Simplify internal values representation in graph even more New values in graph._private.values were pushed to the end, and old values were removed from the beginning of the table. In draw() however newer values are drawn first, at the left edge of the graph, necessitating annoying idx = #values - idx conversions everywhere. In particular in stacked graph that means that values that are drawn together may have different indices. One has to shift the values array in add_value() either way, so one might as well insert new values at the beginning of the array and simplify the rest of the code. This doesn't seem like much yet, but it will prove to be much more convenient and less error-prone in what I'm going to do later. commit f70523ee68dbd6b67050ace4b82903a9b22440c7 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 13:35:10 2021 +0700 Simplify internal values representation in graph widget graph._private.values was either a table of values or a table of tables of values, depending on whether "stack" property is enabled, ... or a mix of both if user forgot to pass the second "group" parameter to graph:add_value(). This resulted in pretty fragile and confusing behavior, e.g. leaving user to wonder, why they see nothing more after setting stack=true for a graph in a previously working rc.lua, and demanding a very careful use of add_value() from the user, or else some values or even entire stacks would mysteriously vanish and errors in draw() get triggered, sometimes after a period of seemingly normal functioning. Now `values` is always a table of tables and the group parameter in add_value() defaults to 1, thus making the non-stacked graph a trivial variant of the stacked graph.
2021-05-20 17:14:03 +02:00
--- Determine the color to paint a data group with.
--
-- The graph uses this method to choose a color for a given data group.
-- The default implementation uses a color from the `group_colors` table,
-- if present, otherwise it falls back to `color`, then
-- `beautiful.graph_fg` and finally to color red (#ff0000).
--
-- @method pick_data_group_color
-- @tparam number group_idx The index of the data group.
-- @treturn color The color to paint the data group's values with.
graph: complete widget overhaul and bug fixes Squashed commit of the following: commit 69821f51fe1e8652715658543e50631ece495090 Author: Alex Belykh <albel727@ngs.ru> Date: Mon May 10 19:17:55 2021 +0700 Refactor property handling in wibox.widget.graph In addition, baseline_value, step_width and step_spacing properties are all set to their default non-nil numeric values during instance creation now. commit 842eb429bf5df4b2ba46b2e6f7646afe89b1a3c3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 9 06:18:45 2021 +0700 Make graph:draw_values() private There's no documentation nor confidence that it's a good API, so I'm hiding it for now. commit 720746780574e4bad14a71fc4174955139c2eb50 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 04:04:13 2021 +0700 Document graph_should_draw_data_group() for posterity commit 7c73f3754ce6ba86b118f1170a6f9e406ab4841d Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:59:21 2021 +0700 Make graph:should_draw_data_group() private There's no confidence whether it's a good API, so I'm hiding it for now. commit b3539e20adb6423118af726ae4f8751ef864b5d2 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:36:44 2021 +0700 Make graph:preprocess_values() private There's no confidence whether it's a good API, so I'm hiding it for now. commit 2df4400e780a6260f8d0ab1c60734fbb11bf3c4f Author: Alex Belykh <albel727@ngs.ru> Date: Thu May 6 10:05:23 2021 +0700 Add spec/wibox/widget/graph_spec.lua commit ce01a9771ab439b8df841f1565aa4aa8a44a5e0d Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 08:16:18 2021 +0700 Improve graph.capacity documentation commit e28fade8f655802f5bf89154169f68d256b8e332 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 07:36:01 2021 +0700 Document graph:compute_drawn_values_num() commit dd68332292465422d08b48e9f0229f143beccbc6 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 05:24:23 2021 +0700 Document graph:pick_data_group_color() method commit ca6dc55f77aca88a6e9cc90bb6fd0045739f41f5 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 04:13:58 2021 +0700 Guard against setting data groups sparsely in graph:add_value() commit cdea5f126f7984e72924f6dffbd312c4fe3b032f Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 02:36:12 2021 +0700 Refactor scaling and baseline choice out of graph:draw() commit 10135deafe8a9390ba66ab657ebb1fb41d549e8c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 21:34:48 2021 +0700 Document graph.nan_color and graph.stack properties commit 0d73bb84ac66b87e16aee472bd1f299549f66a1c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 20:49:24 2021 +0700 Use a better example in docs for the graph.stack property commit c27d160a0826d033d49d6141608c40aa8a0642fa Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 16:05:38 2021 +0700 Document graph.baseline_value property commit 459bc8176c13d4ea32af0dcdc2399d3b929b1c6a Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:53:47 2021 +0700 Document graph.clamp_bars property commit e732ee0b3b007d213e689b98cc00ae7cb141ae08 Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:34 2021 +0700 Improve graph:add_value() and :clear() documentation commit ae73e3aa9f8d60409cbe4cb4da9612f31af4f38e Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:11 2021 +0700 Improve graph min_value/max_value/scale documentation commit a7350996a9def773b91b1631e6c0507e33d8f93b Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:27:36 2021 +0700 Cleanup graph examples to show only relevant things in docs commit 30740c26f4f41c16f4c8a3c339bcb7bd299e14cd Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 14:04:36 2021 +0700 Rename graph.stack_colors property to `group_colors` The colors are used for stacked and non-stacked graphs alike, so the name didn't make sense. Also document and clarify some color stuff. commit 1ea01bfd410971cf0d0c25fd74cd764d5eb7279d Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 21:25:41 2021 +0700 Add an example file for code coverage of some fringe graph cases commit eae68e39553f88a6275a0c65bf040b5d4c262b54 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 20:23:32 2021 +0700 Add step_shape to the graph/nan_color example for code coverage commit 8027147bc2eab34a97b49a428fb8cef27f122bed Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:55:18 2021 +0700 Add example files for graph.nan_color/nan_indication properties commit 89a25a165c52b2c15b6c4d95ecb777a9067e66f9 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:33:07 2021 +0700 Make widget graph:add_value(v) default v to NaN 0 as a default makes no sense and silences the error that is failing to specify a value, to which the user should rather be alerted. The graph widget has sane NaN handling now and will do just that. commit 2835d552440be48d356a7f7b1d64963696dd550b Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:20:48 2021 +0700 Implement graph.nan_color and nan_indication properties Now, whenever there's a NaN among values and graph.nan_indication is set, the corresponding area, where the value bar should have been drawn, is filled with the nan_color from top to bottom. The painting is done after all data is rendered, to make sure that it won't be overpainted and go unnoticed. NaNs among graph's values are inevitably a sign of some error and it is arguably not a sane default behavior to simply not render them at all. The user should take immediate note of any errors by default, instead of wondering at a mysteriously empty graph widget, which is why nan_indication is set to true by default. If the user wants to silence NaNs, they can always simply set it to false. nan_color if unset, defaults to a noticeable yellow-black pattern. As a direct and planned consequence, stacked graphs with negative values in them trigger the NaN handling too. But silencing NaN handling for those graphs is also possible and semi-sane, because then NaNs and negatives are ignored and the graph behaves (almost) like they were zeros. commit 9c1f8f08b400e01e19c534f3810c9c9ab485760c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 26 08:10:19 2021 +0700 Refactor out graph_map_value_to_widget_coordinates() function commit 1905991ddecabdae3e15ffb8ee5b18a1bee1a711 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 28 13:39:29 2021 +0700 Add an example file for graph.baseline_value property commit b31b39d66f4c037a9484b71a3f910703ae01fac0 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 07:03:49 2021 +0700 Add an example file for graph.clamp_bars=false/true comparison commit a1fc850e3047f9ee2c9ddeffacfbabf3ded34171 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 04:06:45 2021 +0700 Add an example file for graphs with negative values and shapes commit 2f012f8ff1bb5094c14e724a61e59a2a3a296bc8 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 01:06:05 2021 +0700 Add an example file for a graph displaying negative values commit 7b14fbd59a4c4c51962fd3110a1a900dd5f4eb98 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 11:18:26 2021 +0700 Add an example file for graphs with step_width < 1 commit dfd9a54544b637e2fbe3d1325f9cc24b3f4d2313 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 10:17:38 2021 +0700 Add an example file for graph.stack=false/true comparison Should also demonstrate that multiple data groups are possible for non-stacked graphs too. commit 5b532106c3fd255f9b66a66cfb88d977b489ab5d Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 09:34:14 2021 +0700 Add an example file for disabling graph data groups with nil colors commit ac6f1083c02fcb428f089a616f9b0ebaa5496b6c Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:57:25 2021 +0700 Add an example file for step_shape/width/spacing on stacked graphs commit b3703db6a5139e73e72c1cfd198ece2cd9be7df2 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:43:56 2021 +0700 Add an example file for scale=false/true on stacked graph commit b000aed4a12cb44fb501e1c8432a71a14b2f8f3f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 20 08:22:19 2021 +0700 Document deprecation of graph's width and height commit cb0ad617bd6c83a499896797318e3b0b588c3566 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 07:53:37 2021 +0700 Deprecate width and height properties of wibox.widget.graph commit 8c64e14ad91a074b784166a7054e53444cccb9d2 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 04:37:59 2021 +0700 Refactor graph:draw() to separate concerns Firstly, summation of group values for stacked graphs is extracted into graph:preprocess_values(). This makes graph:draw() smaller and exposes the fact, that stacked graphs are almost entirely just a form of a more general concept: pre-render data manipulation. It's something a user might want to customize, and now they can do it by monkey-patching the method. Secondly, the logic for deciding, if and with which color a data group should be drawn, is extracted into graph:should_draw_data_group() and get_data_group_color() methods respectively. This makes it customizable too and makes more obvious the fact, that it is a general thing that has nothing to do with stacked graphs in particular. With this there are only 5 lines remaining in graph:draw(), that could be said to be specific to stacked graphs, all pertaining to the `prev_y` variable, which merely preserves the bar coordinates from a previous data group. commit 2a32b6305d893f413303728a0a3da1dcd9a5591e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 22:52:37 2021 +0700 Pointlessly optimize graph:add_value() 1) Purge old values first, then insert the new one, if needed. Since Lua grows arrays in power of two steps, and our guess_capacity() also takes care to return even integers, it would be a shame to overstep the capacity by one by inserting first and thus getting the table size doubled only to never use the most of it. 2) Use a removal procedure that doesn't call the slow length operator at all. Previous code called it twice (once in table.remove) for every removed value. commit 680ea8f5d81e9040afb4662b3229e937e4b89e02 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:23:38 2021 +0700 Set graph data series color before the painting loop commit b970f0f221ae76250f2a94433ff79d653caa6e21 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:05:37 2021 +0700 Drop a large no longer necessary `if` in graph:draw_values() This commit is almost purely a whitespace change. commit 6b4f0541c70ec99d7f6e276bae64939f337120d6 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:00:50 2021 +0700 Extract values rendering code into graph:draw_values() method This is to avoid ungodly if nesting levels, which are needed to bail out of drawing values early and skip to drawing the border. The next commit will get rid of the if. commit e0950e4f99d1ea1c4b8cea2cc67629fa40030c2a Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 14:50:20 2021 +0700 Track graph draw() usage to guess necessary array capacity The last step of divorcing add_values() from width calculations, while ensuring that user doesn't observe a lack of data to draw. The widget now tracks an approximate number of values that it has been asked to draw() in _private.last_drawn_values_num, and truncates its values array based on that (if not overridden by the capacity property). commit 2e3d542282f35e7c0fed454da0f2eada0fcf3b78 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 13:35:12 2021 +0700 Add `capacity` property to graph widget This is the second step of divorcing add_values() from calculations involving widths. How many values are stored and how many values are drawn are distinct concepts. There isn't even a single definitive "how many values are drawn" because the widget could be rendered in several places with distinct widths simultaneously, and in every such place the data must be drawn appropriately, e.g. with auto-scaling, that takes only the visible data points into account. And the draw() method can do that now. The default capacity value of nil uses heuristics based on widget's _private.width to guess some capacity that would tolerate some dynamic widget resizing. If the user finds the heuristic failing them, they can set the capacity property to the desired maximum number of values that will be kept in the _private.values array (in each data group). commit 08d4a80686f7fe363551c35589a460839f60853e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 03:27:18 2021 +0700 Extract values size computation into compute_drawn_values_num() This is the first step of making add_values() not concern itself (directly) with widget dimensions. The user can monkey-patch the method to override how many values are (logically) drawn, because it affects graph auto-scaling and can be useful for rendering of step_shape-s that paint outside of their step_width. I didn't make it a full-blown function-valued property, because it seems a fringe functionality, which I'm too lazy to document. commit 3c5617c98ab1147618ed22e8414a77fc56b45f6e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 02:32:29 2021 +0700 Copy graph widget prototype methods over in bulk commit 169538837ece41a03d02ba8fbc0e4097062b67a3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun Apr 25 05:58:01 2021 +0700 Remove graph.baseline_y property After giving it some thought I've concluded, that it is unlikely to be useful as it is now. I'd better reserve the name for some more sensible functionality, like fixing the baseline_value defined axis at the given widget coordinate, adjusting the scaling accordingly, so min_value/max_value won't generally correspond to widget top and bottom anymore. commit 202ffaa9a0b820c7c471e1121d01b91f8f76cd38 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 12:06:55 2021 +0700 Add baseline_y and baseline_value properties to graph widget Now values in graph have a *baseline*, tunable with baseline_y and baseline_value properties. It is the (for now invisible) horizontal line from which all bars in the graph grow. I call it a "baseline" instead of "axis", because it doesn't actually have to correspond to the 0 value. But it can, and actually does now by default. baseline_y and baseline_value properties determine the vertical placement of the baseline in the widget. baseline_y is the position of the baseline in normalized widget coordinates, e.g. 0 corresponds to widget's top, 1 - to widget's bottom, and 0.5 - to widget's middle. So if e.g. one sets baseline_y = 0, then no matter how the widget is resized and how its values are scaled, bars in it will be drawn starting from the top. baseline_value is the position of the baseline in value coordinates. So if one sets baseline_value = 10, then no matter what happens, bars for values greater than 10 will be drawn as growing up from this line, and bars for values smaller as growing down. Where and if it will be seen in the widget, depends on the scaling. baseline_value = 0 is the familiar zero axis. baseline_y is unset by default and has priority, i.e. if it's set, then baseline_value is ignored. baseline_value defaults to 0. Prior to this commit, bars were always drawn from the bottom edge of the widget, or, in the new terms, the widget had baseline_y = 1. But it was indistinguishable from baseline_value = 0, because the widget clamped all negative values to 0, so the difference between the two could never be observed. baseline_value = 0 is clearly more useful and familiar to people, so now it is in force by default. commit df7e19abaebf0a871b5ca217b4cc3365533267a6 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 11:18:12 2021 +0700 Move value clamping from graph:add_value() into draw() Previously all values were forcibly clamped between 0 (not tunable at all) and max_value (1 by default). Thus graphs with negative values were not an option. NaN values were conflated with zeros, even though it may be useful to maintain the distinction and render them differently. And if user happened to dynamically change the max_value property, they discovered, that their data got truncated. Preserving user data as it was given is more flexible and makes things easier to debug. This commit does exactly that. So what happens now to the newly allowed values during draw()? There are 3 new cases to consider: 1) NaNs 2) Negatives 3) Values outside of min_value..max_value range NaNs are presently simply not drawn. An empty place is reserved where the value should've been, but nothing else is done. This is distinct from how a zero is handled, e.g. if step_shape is set, it is called with the height parameter = 0, which can still end up drawing something, depending on the shape. One could also consider addition of nan_color/nan_shape properties which could be drawn to alert user to a gap in their data. Negative values necessitate introduction of the concept of the zero axis, from which they will be drawn DOWN, in contrast to positive values. Where should this axis be drawn? How should step_shape-s with inherent direction, e.g. arrows, be drawn for negatives? This will be addressed in the next commit. Values outside of min/max_value range can imply shapes that go beyond widget dimensions. An arrow shape that goes beyond the roof is visibly different from an arrow that saturates and merely touches the widget top, and the user may reasonably desire either behavior. Therefore this commit introduces a boolean clamp_bars property, which defaults to true for reasons of robustness and backward compatibility with the behaviour prior to this commit. The property of course affects only the display of the values, the actual user data stays intact. commit c9c24fb1ab741cd6bb8074971d568e0646c51532 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:29:01 2021 +0700 Use rectangles for drawing graph bars of all sizes I have benchmarked various bar drawing strategies and graph sizes and found out that drawing graph bars with rectangles is as fast or even marginally faster than using fat lines. Thus the code that tries to special-case 1px bars as lines seems to be not worth the additional complexity it introduces, and this commit removes it. Incidentally, I've also established, that rectangles are more robust. If one draws graph bars and feeds cairo very large coordinates (~2^62), then corresponding rectangles fail to render, but all the other rectangles in the same series get rendered successfully, whereas a single large coordinate in a move_to/line_to() sequence is enough to cause all lines in the batch to fail to render. commit bc7f4be5e06405b5aa7fb443a5ace2b9b9575b1f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:01:42 2021 +0700 Cache cairo methods used in the inner loop of graph:draw() I have benchmarked the draw() method, both on cairo xcb and on argb32 image surfaces, and have discovered that cairo context method calls are unnaturally slow, to the point, that almost nothing else in the function matters. For example, drawing bars with vertical lines made the whole draw() function almost 2 times slower than doing the same with rectangles, merely because the former requires two method calls (move_to/line_to), and the latter only one. Simply caching hotpath cairo methods in local variables, reduced the difference between the drawing methods to a negligible one and made the whole function 4 times faster on a typical 100-bar graph, (1.4ms to 0.33ms on my laptop) thus bringing closer my secret dream of drawing live audio data with awesome graphs (kidding). I guess, LGI does something not very optimal there, like gobject-introspecting the method every time, or something. There might be some untapped optimization potential, possibly in upstream, like a method cache, from which everything in awesome could benefit, even though I imagine not many things are so call-intensive as the graph widget, to really feel the difference. commit 538df25bdf02b53d0bbe81809eef2e6fdb05ff6f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 07:32:37 2021 +0700 Use 1-based index in the inner graph draw loop It's used there only once for a calculation and then several times to index some tables, and index 0 is bad for lua tables performance-wise. commit 7412546916cd0a0a490130a120255ffb719398b8 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:55:12 2021 +0700 Leave graph.max_value uninitialized at construction This is a user-visible change, but the previous behavior was probably not what user expected anyway, namely with graph.scale set the graph mysteriously couldn't zoom in onto values that are smaller than 1. commit eb81d7929a161e79c13262cfa73ac2e6a820e8ed Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:48:29 2021 +0700 Allow graph.max_value be nil This makes its behave symmetrically to min_value. Now it defaults to 1, when graph.scale is not set, and to the maximum value in the graph data, when graph.scale is set. commit f622a8430cbfde7007b58d7f4c9c76b08c91e997 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 12:01:13 2021 +0700 Calculate max values num in graph:add_value() more precisely At most so many values were kept in the widget values array, as there are pixels of usable widget width (i.e. sans border). This is exactly right for the surely most common case of graphs with 1px-wide bars, but is more than needed for graphs with step_width+step_spacing>1, and not enough for graphs with subpixel bars. Moreover this made graph autoscaling behave weirdly in graphs with thicker bars, because old values survived longer in the array and kept affecting min_value/max_value calculations even after they were shifted off-screen. The maximum number of kept values is now calculated exactly as the number of values that can be displayed at once, taking step_width and step_spacing into account. It might be worth adding a max_values_num property to the graph to let user override this calculation, in case they draw shapes in step_shape() outside of its supposed rectangle, so our assumptions of visibility are wrong, or the widget often gets resized, resulting in the annoyance of visibly truncated series. commit 0aa63249a18641251a1bf77423739164f0eab39f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 02:30:13 2021 +0700 Fix border_color/border_width bugs in graph widget add_value() wrongly assumed that border_width is always set, when border_color is, so if user set only the latter, an error occurred. add_value() assumed that border_width is always < width which led to awesome hanging up in an infinite loop, should that fail to be the case. The graph border_width property was driven by the border_color property, instead of the other way round. If border_color wasn't set, the border wasn't drawn all, even if user sets non-zero border_width, which is a confusing behavior, in contrast to all other widgets, which simply use a plethora of fallback colors to draw the border, when needed. This also meant that, despite being documented, the beautiful.graph_border_color fallback color could not and was not ever used. If the border_color was set though, an unset border_width was immediately assumed to be 1 instead of 0, also contrary to what other widgets do. Theme colors shouldn't influence widget layout, so this commit fixes this behavior to: 1) border_width always defaults to 0, if unset. 2) border_color falls back to beautiful.graph_border_color or white. Maybe one should also add a beautiful.graph_border_width theme variable, like some widgets do. commit dad4a3253b8fef971fb82720d2a407fcc0a879ad Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 03:11:27 2021 +0700 Implement multiple data series drawing for non-stacked graph The widget can already hold and draw multiple data groups, so it makes little sense to limit drawing only to the first group when graph.stack = false, probably leading to user's confusion. Now user can call add_value(v, group) with group > 1 and the data series will be drawn (over groups with lesser id-s) with graph.stack_colors[group] color, if stack_colors is set, or with graph.color, if stack_colors isn't set. Thus nothing visibly changes for all current users of non-stacked graphs with only single data series, but the new functionality is now there for those who need it. One can't fully emulate this functionality, e.g. with multiple graphs widgets in a wibox.layout.stack, because data series in a single widget can enjoy a common auto-scaling. commit 3e645c976184ab5f50d625271c25062db9ab7ed9 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:59:12 2021 +0700 Don't draw a stacked graph data group if there's no color for it If stack_colors[idx] is nil for the group `idx`, don't draw it at all (as opposed, e.g. to the effect of drawing it with a transparent color, or as though it had all values == 0, which both produce different results). This code is still subtly different from prior behavior, because now user can have a non-contiguous stack_colors table and turn the display of any data group on and off at will, whereas the code two commits earlier would just stop rendering as soon as ipairs(stack_colors) does, i.e. no data series were drawn past the first nil hole. This commit only looks large, but is mostly a whitespace change due to nesting from a pair of wrapping ifs. commit 8c0273786de1ade66bdd977699f98b588da9f487 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:32:42 2021 +0700 Unify stacked and non-stacked graph rendering Now stacked graphs support everything the nonstacked do, like step_width ~= 1 and custom step_shape-s. Technical notes: This code no longer calls cr:stroke() for every single value in a stacked graph, but rather paints a whole series and then cr:stroke/fill()-s it at all once with its color, improving performance considerably. Additionally the graph no longer relies on the presence of the stack_colors property to render series. It draws all data that it has, and, if there's no corresponding color in stack_colors, falls back to graph.color. This will be changed in the next commit to the prior behavior of only drawing those series for which there's a color in the stack_colors table, but that would've made this commit's diff much larger due to required nesting ifs over large code chunks. This commit also takes care to properly handle NaNs and negative values. Those are currently (almost) guaranteed to not occur in the values array due to clipping in graph:add_value(), but I intend to eventually drop the clipping, because it does nothing that draw() really relies upon, and allowing unclipped and NaN values is more flexible and less surprising, e.g. when user dynamically toggles graph.scale. Eventually I intend to implement drawing negative values in non-stacked graphs and possibly even in the stacked ones. commit 38c6589c51b2aa057d7c2f40182ecf32b5b2ff5e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 10:04:15 2021 +0700 Convert graph:draw() to use `self` commit f21f1b2550426df1c4ea8abde079b1ccef84b394 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 12:27:26 2021 +0700 Fix scaling for stacked graphs Stacked graphs have cumulative heights, values from all groups that end up in the same graph bar are effectively summed. Previous code didn't take that into account when calculating min/max_value for the purposes of rescaling, but simply examined every value separately. This code adds up all values up front and finds min/max among those, to determine proper scaling that would fit the graph exactly. commit 1ce4c7bf63bdd6c99c195d668347bec49355e921 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 09:49:35 2021 +0700 Draw shaped graph bars only up to the baseline Prior to this commit, every step_shape bar was, for some reason, drawn with height = full widget's height, regardless of the value it represents. So for the most bars their lower end was far outside the box and therefore clipped off. So e.g. one couldn't see the lower round edges of rounded rectangles. This commit fixes that by appropriately varying the height that is passed into step_shape(), so that lower edges of the bars always exactly touch the lower edge of the graph. If the user wants to restore the prior behavior, they can simply ignore the passed-in height in step_shape() and just always draw their shape with height = full widget height. But they couldn't replicate the effect of this commit in their rc.lua without essentially writing their own graph widget. commit e25185b08da23745b5815f85f6e9e43222ed2294 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 08:42:54 2021 +0700 Avoid incremental transform shifting during graph bar rendering Instead of letting addition errors and cognitive load accumulate simply use the same offset calculation that is used for lines and reset the transform back to the top-left corner after every drawn bar. It's conceptually simpler, and will be useful later for stacked graphs and more. It also fixes a discrepancy between plain and step_shape rendering. Prior to this commit, bar coloring occurred with a disturbed transform, which was centered at the last drawn bar, wherever it happened to be. This meant that nontrivial colors, like gradients and images, were arbitrarily offset when step_shape was used. commit f3e98866d8583c35d0b557c09f529299edc9ba2c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:45:29 2021 +0700 Refactor out graph bar height expression into a variable commit f54528d6892f5aaa4595917853c9e2130f743fdb Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:20:25 2021 +0700 Draw graph bars with lines only when step_width == 1 Before this commit graph bars of step_width < 1 were drawn with lines of width 1, but that's a bit unfortunate, because it led to much overpainting, while still ending up with a graphically incorrect result. Let's draw such bars with rectangles and let cairo do things with subpixels, getting more graphically accurate result, while still keeping the common case of step_width == 1 (supposedly) fast-drawn with lines. commit b6515dca67c18b78eb5657378131a62ab43d4cfc Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:54:07 2021 +0700 Fix unsharp edges when drawing graph bars with rectangles The 0.5 offset is only helpful for 1-width strokes, not for fills. commit 7f3db8f07e82f821bcd1dc418d66564f537609d6 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:21:01 2021 +0700 Do coordinate transformations even if the value isn't drawn The graph presently doesn't draw values < 0, but there was a discrepancy between drawing with and without step_shape. The default line/rectangle drawing reserves an empty place where the skipped value should've been, but no such empty place is reserved, when step_shape is used. This commit fixes that. Skipped values always have their empty space now. commit 7b731ebfe96af7a9dba9c57371da6761cf22c00c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:03:30 2021 +0700 Fix graph bar scaling when min_value is not 0 Previous calculation lead to underscaling of values so that even value = max_value wasn't drawn to full height. commit dca59fd8b7fff0e7e646f2dfc6bd92047028e5a8 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:18:40 2021 +0700 Calculate the horizontal graph bar offset only when needed commit 940ddf4b9825e9c3a91d276208c089d8c74e49db Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:17:23 2021 +0700 Call cr:move_to() only when drawing the graph with lines It's redundant otherwise and might even interfere with user's drawing in step_shape(). commit b5d5cd10ca29fb29cd751faaa95137c2fe89ff63 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:04:06 2021 +0700 Fix wrong horizontal offset calculations in widget.graph Indices in the loop are zero-based, so things like i-1 and i>1 aren't necessary and actually wouldn't have been correct even in a 1-based loop. step_shape = gears.shape.rectangle and step_shape = nil, which is supposed to default to rectangle according to docs, were drawn differently due to mismatched offset calculations, which led to problems especially noticeable when step_spacing is not zero, e.g: 1) whole graph shifted left too much, drawn partially off-screen. 2) whole graph shifted right too much, and the first bar placed closer to the rest than others. commit dc369fa198281e000afc1bd17326f3a264adf7d1 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 02:09:21 2021 +0700 Fix the "border_width affects graph bar width" bug Graph bars were drawn with lines of border_width thickness instead of 1, i.e. possibly too fat to be readable. commit 1ba73378cf29ed27a6ecd18716394779ceb543d9 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 00:13:03 2021 +0700 Simplify internal values representation in graph even more New values in graph._private.values were pushed to the end, and old values were removed from the beginning of the table. In draw() however newer values are drawn first, at the left edge of the graph, necessitating annoying idx = #values - idx conversions everywhere. In particular in stacked graph that means that values that are drawn together may have different indices. One has to shift the values array in add_value() either way, so one might as well insert new values at the beginning of the array and simplify the rest of the code. This doesn't seem like much yet, but it will prove to be much more convenient and less error-prone in what I'm going to do later. commit f70523ee68dbd6b67050ace4b82903a9b22440c7 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 13:35:10 2021 +0700 Simplify internal values representation in graph widget graph._private.values was either a table of values or a table of tables of values, depending on whether "stack" property is enabled, ... or a mix of both if user forgot to pass the second "group" parameter to graph:add_value(). This resulted in pretty fragile and confusing behavior, e.g. leaving user to wonder, why they see nothing more after setting stack=true for a graph in a previously working rc.lua, and demanding a very careful use of add_value() from the user, or else some values or even entire stacks would mysteriously vanish and errors in draw() get triggered, sometimes after a period of seemingly normal functioning. Now `values` is always a table of tables and the group parameter in add_value() defaults to 1, thus making the non-stacked graph a trivial variant of the stacked graph.
2021-05-20 17:14:03 +02:00
function graph:pick_data_group_color(group_idx)
-- Use an individual group color, if there's one
local data_group_colors = self._private.group_colors
local clr = data_group_colors and data_group_colors[group_idx]
-- Or fall back to some other colors
return clr or self._private.color or prop_fallbacks.color
end
2021-03-28 11:33:38 +02:00
graph: complete widget overhaul and bug fixes Squashed commit of the following: commit 69821f51fe1e8652715658543e50631ece495090 Author: Alex Belykh <albel727@ngs.ru> Date: Mon May 10 19:17:55 2021 +0700 Refactor property handling in wibox.widget.graph In addition, baseline_value, step_width and step_spacing properties are all set to their default non-nil numeric values during instance creation now. commit 842eb429bf5df4b2ba46b2e6f7646afe89b1a3c3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 9 06:18:45 2021 +0700 Make graph:draw_values() private There's no documentation nor confidence that it's a good API, so I'm hiding it for now. commit 720746780574e4bad14a71fc4174955139c2eb50 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 04:04:13 2021 +0700 Document graph_should_draw_data_group() for posterity commit 7c73f3754ce6ba86b118f1170a6f9e406ab4841d Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:59:21 2021 +0700 Make graph:should_draw_data_group() private There's no confidence whether it's a good API, so I'm hiding it for now. commit b3539e20adb6423118af726ae4f8751ef864b5d2 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:36:44 2021 +0700 Make graph:preprocess_values() private There's no confidence whether it's a good API, so I'm hiding it for now. commit 2df4400e780a6260f8d0ab1c60734fbb11bf3c4f Author: Alex Belykh <albel727@ngs.ru> Date: Thu May 6 10:05:23 2021 +0700 Add spec/wibox/widget/graph_spec.lua commit ce01a9771ab439b8df841f1565aa4aa8a44a5e0d Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 08:16:18 2021 +0700 Improve graph.capacity documentation commit e28fade8f655802f5bf89154169f68d256b8e332 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 07:36:01 2021 +0700 Document graph:compute_drawn_values_num() commit dd68332292465422d08b48e9f0229f143beccbc6 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 05:24:23 2021 +0700 Document graph:pick_data_group_color() method commit ca6dc55f77aca88a6e9cc90bb6fd0045739f41f5 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 04:13:58 2021 +0700 Guard against setting data groups sparsely in graph:add_value() commit cdea5f126f7984e72924f6dffbd312c4fe3b032f Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 02:36:12 2021 +0700 Refactor scaling and baseline choice out of graph:draw() commit 10135deafe8a9390ba66ab657ebb1fb41d549e8c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 21:34:48 2021 +0700 Document graph.nan_color and graph.stack properties commit 0d73bb84ac66b87e16aee472bd1f299549f66a1c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 20:49:24 2021 +0700 Use a better example in docs for the graph.stack property commit c27d160a0826d033d49d6141608c40aa8a0642fa Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 16:05:38 2021 +0700 Document graph.baseline_value property commit 459bc8176c13d4ea32af0dcdc2399d3b929b1c6a Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:53:47 2021 +0700 Document graph.clamp_bars property commit e732ee0b3b007d213e689b98cc00ae7cb141ae08 Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:34 2021 +0700 Improve graph:add_value() and :clear() documentation commit ae73e3aa9f8d60409cbe4cb4da9612f31af4f38e Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:11 2021 +0700 Improve graph min_value/max_value/scale documentation commit a7350996a9def773b91b1631e6c0507e33d8f93b Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:27:36 2021 +0700 Cleanup graph examples to show only relevant things in docs commit 30740c26f4f41c16f4c8a3c339bcb7bd299e14cd Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 14:04:36 2021 +0700 Rename graph.stack_colors property to `group_colors` The colors are used for stacked and non-stacked graphs alike, so the name didn't make sense. Also document and clarify some color stuff. commit 1ea01bfd410971cf0d0c25fd74cd764d5eb7279d Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 21:25:41 2021 +0700 Add an example file for code coverage of some fringe graph cases commit eae68e39553f88a6275a0c65bf040b5d4c262b54 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 20:23:32 2021 +0700 Add step_shape to the graph/nan_color example for code coverage commit 8027147bc2eab34a97b49a428fb8cef27f122bed Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:55:18 2021 +0700 Add example files for graph.nan_color/nan_indication properties commit 89a25a165c52b2c15b6c4d95ecb777a9067e66f9 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:33:07 2021 +0700 Make widget graph:add_value(v) default v to NaN 0 as a default makes no sense and silences the error that is failing to specify a value, to which the user should rather be alerted. The graph widget has sane NaN handling now and will do just that. commit 2835d552440be48d356a7f7b1d64963696dd550b Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:20:48 2021 +0700 Implement graph.nan_color and nan_indication properties Now, whenever there's a NaN among values and graph.nan_indication is set, the corresponding area, where the value bar should have been drawn, is filled with the nan_color from top to bottom. The painting is done after all data is rendered, to make sure that it won't be overpainted and go unnoticed. NaNs among graph's values are inevitably a sign of some error and it is arguably not a sane default behavior to simply not render them at all. The user should take immediate note of any errors by default, instead of wondering at a mysteriously empty graph widget, which is why nan_indication is set to true by default. If the user wants to silence NaNs, they can always simply set it to false. nan_color if unset, defaults to a noticeable yellow-black pattern. As a direct and planned consequence, stacked graphs with negative values in them trigger the NaN handling too. But silencing NaN handling for those graphs is also possible and semi-sane, because then NaNs and negatives are ignored and the graph behaves (almost) like they were zeros. commit 9c1f8f08b400e01e19c534f3810c9c9ab485760c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 26 08:10:19 2021 +0700 Refactor out graph_map_value_to_widget_coordinates() function commit 1905991ddecabdae3e15ffb8ee5b18a1bee1a711 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 28 13:39:29 2021 +0700 Add an example file for graph.baseline_value property commit b31b39d66f4c037a9484b71a3f910703ae01fac0 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 07:03:49 2021 +0700 Add an example file for graph.clamp_bars=false/true comparison commit a1fc850e3047f9ee2c9ddeffacfbabf3ded34171 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 04:06:45 2021 +0700 Add an example file for graphs with negative values and shapes commit 2f012f8ff1bb5094c14e724a61e59a2a3a296bc8 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 01:06:05 2021 +0700 Add an example file for a graph displaying negative values commit 7b14fbd59a4c4c51962fd3110a1a900dd5f4eb98 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 11:18:26 2021 +0700 Add an example file for graphs with step_width < 1 commit dfd9a54544b637e2fbe3d1325f9cc24b3f4d2313 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 10:17:38 2021 +0700 Add an example file for graph.stack=false/true comparison Should also demonstrate that multiple data groups are possible for non-stacked graphs too. commit 5b532106c3fd255f9b66a66cfb88d977b489ab5d Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 09:34:14 2021 +0700 Add an example file for disabling graph data groups with nil colors commit ac6f1083c02fcb428f089a616f9b0ebaa5496b6c Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:57:25 2021 +0700 Add an example file for step_shape/width/spacing on stacked graphs commit b3703db6a5139e73e72c1cfd198ece2cd9be7df2 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:43:56 2021 +0700 Add an example file for scale=false/true on stacked graph commit b000aed4a12cb44fb501e1c8432a71a14b2f8f3f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 20 08:22:19 2021 +0700 Document deprecation of graph's width and height commit cb0ad617bd6c83a499896797318e3b0b588c3566 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 07:53:37 2021 +0700 Deprecate width and height properties of wibox.widget.graph commit 8c64e14ad91a074b784166a7054e53444cccb9d2 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 04:37:59 2021 +0700 Refactor graph:draw() to separate concerns Firstly, summation of group values for stacked graphs is extracted into graph:preprocess_values(). This makes graph:draw() smaller and exposes the fact, that stacked graphs are almost entirely just a form of a more general concept: pre-render data manipulation. It's something a user might want to customize, and now they can do it by monkey-patching the method. Secondly, the logic for deciding, if and with which color a data group should be drawn, is extracted into graph:should_draw_data_group() and get_data_group_color() methods respectively. This makes it customizable too and makes more obvious the fact, that it is a general thing that has nothing to do with stacked graphs in particular. With this there are only 5 lines remaining in graph:draw(), that could be said to be specific to stacked graphs, all pertaining to the `prev_y` variable, which merely preserves the bar coordinates from a previous data group. commit 2a32b6305d893f413303728a0a3da1dcd9a5591e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 22:52:37 2021 +0700 Pointlessly optimize graph:add_value() 1) Purge old values first, then insert the new one, if needed. Since Lua grows arrays in power of two steps, and our guess_capacity() also takes care to return even integers, it would be a shame to overstep the capacity by one by inserting first and thus getting the table size doubled only to never use the most of it. 2) Use a removal procedure that doesn't call the slow length operator at all. Previous code called it twice (once in table.remove) for every removed value. commit 680ea8f5d81e9040afb4662b3229e937e4b89e02 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:23:38 2021 +0700 Set graph data series color before the painting loop commit b970f0f221ae76250f2a94433ff79d653caa6e21 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:05:37 2021 +0700 Drop a large no longer necessary `if` in graph:draw_values() This commit is almost purely a whitespace change. commit 6b4f0541c70ec99d7f6e276bae64939f337120d6 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:00:50 2021 +0700 Extract values rendering code into graph:draw_values() method This is to avoid ungodly if nesting levels, which are needed to bail out of drawing values early and skip to drawing the border. The next commit will get rid of the if. commit e0950e4f99d1ea1c4b8cea2cc67629fa40030c2a Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 14:50:20 2021 +0700 Track graph draw() usage to guess necessary array capacity The last step of divorcing add_values() from width calculations, while ensuring that user doesn't observe a lack of data to draw. The widget now tracks an approximate number of values that it has been asked to draw() in _private.last_drawn_values_num, and truncates its values array based on that (if not overridden by the capacity property). commit 2e3d542282f35e7c0fed454da0f2eada0fcf3b78 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 13:35:12 2021 +0700 Add `capacity` property to graph widget This is the second step of divorcing add_values() from calculations involving widths. How many values are stored and how many values are drawn are distinct concepts. There isn't even a single definitive "how many values are drawn" because the widget could be rendered in several places with distinct widths simultaneously, and in every such place the data must be drawn appropriately, e.g. with auto-scaling, that takes only the visible data points into account. And the draw() method can do that now. The default capacity value of nil uses heuristics based on widget's _private.width to guess some capacity that would tolerate some dynamic widget resizing. If the user finds the heuristic failing them, they can set the capacity property to the desired maximum number of values that will be kept in the _private.values array (in each data group). commit 08d4a80686f7fe363551c35589a460839f60853e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 03:27:18 2021 +0700 Extract values size computation into compute_drawn_values_num() This is the first step of making add_values() not concern itself (directly) with widget dimensions. The user can monkey-patch the method to override how many values are (logically) drawn, because it affects graph auto-scaling and can be useful for rendering of step_shape-s that paint outside of their step_width. I didn't make it a full-blown function-valued property, because it seems a fringe functionality, which I'm too lazy to document. commit 3c5617c98ab1147618ed22e8414a77fc56b45f6e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 02:32:29 2021 +0700 Copy graph widget prototype methods over in bulk commit 169538837ece41a03d02ba8fbc0e4097062b67a3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun Apr 25 05:58:01 2021 +0700 Remove graph.baseline_y property After giving it some thought I've concluded, that it is unlikely to be useful as it is now. I'd better reserve the name for some more sensible functionality, like fixing the baseline_value defined axis at the given widget coordinate, adjusting the scaling accordingly, so min_value/max_value won't generally correspond to widget top and bottom anymore. commit 202ffaa9a0b820c7c471e1121d01b91f8f76cd38 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 12:06:55 2021 +0700 Add baseline_y and baseline_value properties to graph widget Now values in graph have a *baseline*, tunable with baseline_y and baseline_value properties. It is the (for now invisible) horizontal line from which all bars in the graph grow. I call it a "baseline" instead of "axis", because it doesn't actually have to correspond to the 0 value. But it can, and actually does now by default. baseline_y and baseline_value properties determine the vertical placement of the baseline in the widget. baseline_y is the position of the baseline in normalized widget coordinates, e.g. 0 corresponds to widget's top, 1 - to widget's bottom, and 0.5 - to widget's middle. So if e.g. one sets baseline_y = 0, then no matter how the widget is resized and how its values are scaled, bars in it will be drawn starting from the top. baseline_value is the position of the baseline in value coordinates. So if one sets baseline_value = 10, then no matter what happens, bars for values greater than 10 will be drawn as growing up from this line, and bars for values smaller as growing down. Where and if it will be seen in the widget, depends on the scaling. baseline_value = 0 is the familiar zero axis. baseline_y is unset by default and has priority, i.e. if it's set, then baseline_value is ignored. baseline_value defaults to 0. Prior to this commit, bars were always drawn from the bottom edge of the widget, or, in the new terms, the widget had baseline_y = 1. But it was indistinguishable from baseline_value = 0, because the widget clamped all negative values to 0, so the difference between the two could never be observed. baseline_value = 0 is clearly more useful and familiar to people, so now it is in force by default. commit df7e19abaebf0a871b5ca217b4cc3365533267a6 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 11:18:12 2021 +0700 Move value clamping from graph:add_value() into draw() Previously all values were forcibly clamped between 0 (not tunable at all) and max_value (1 by default). Thus graphs with negative values were not an option. NaN values were conflated with zeros, even though it may be useful to maintain the distinction and render them differently. And if user happened to dynamically change the max_value property, they discovered, that their data got truncated. Preserving user data as it was given is more flexible and makes things easier to debug. This commit does exactly that. So what happens now to the newly allowed values during draw()? There are 3 new cases to consider: 1) NaNs 2) Negatives 3) Values outside of min_value..max_value range NaNs are presently simply not drawn. An empty place is reserved where the value should've been, but nothing else is done. This is distinct from how a zero is handled, e.g. if step_shape is set, it is called with the height parameter = 0, which can still end up drawing something, depending on the shape. One could also consider addition of nan_color/nan_shape properties which could be drawn to alert user to a gap in their data. Negative values necessitate introduction of the concept of the zero axis, from which they will be drawn DOWN, in contrast to positive values. Where should this axis be drawn? How should step_shape-s with inherent direction, e.g. arrows, be drawn for negatives? This will be addressed in the next commit. Values outside of min/max_value range can imply shapes that go beyond widget dimensions. An arrow shape that goes beyond the roof is visibly different from an arrow that saturates and merely touches the widget top, and the user may reasonably desire either behavior. Therefore this commit introduces a boolean clamp_bars property, which defaults to true for reasons of robustness and backward compatibility with the behaviour prior to this commit. The property of course affects only the display of the values, the actual user data stays intact. commit c9c24fb1ab741cd6bb8074971d568e0646c51532 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:29:01 2021 +0700 Use rectangles for drawing graph bars of all sizes I have benchmarked various bar drawing strategies and graph sizes and found out that drawing graph bars with rectangles is as fast or even marginally faster than using fat lines. Thus the code that tries to special-case 1px bars as lines seems to be not worth the additional complexity it introduces, and this commit removes it. Incidentally, I've also established, that rectangles are more robust. If one draws graph bars and feeds cairo very large coordinates (~2^62), then corresponding rectangles fail to render, but all the other rectangles in the same series get rendered successfully, whereas a single large coordinate in a move_to/line_to() sequence is enough to cause all lines in the batch to fail to render. commit bc7f4be5e06405b5aa7fb443a5ace2b9b9575b1f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:01:42 2021 +0700 Cache cairo methods used in the inner loop of graph:draw() I have benchmarked the draw() method, both on cairo xcb and on argb32 image surfaces, and have discovered that cairo context method calls are unnaturally slow, to the point, that almost nothing else in the function matters. For example, drawing bars with vertical lines made the whole draw() function almost 2 times slower than doing the same with rectangles, merely because the former requires two method calls (move_to/line_to), and the latter only one. Simply caching hotpath cairo methods in local variables, reduced the difference between the drawing methods to a negligible one and made the whole function 4 times faster on a typical 100-bar graph, (1.4ms to 0.33ms on my laptop) thus bringing closer my secret dream of drawing live audio data with awesome graphs (kidding). I guess, LGI does something not very optimal there, like gobject-introspecting the method every time, or something. There might be some untapped optimization potential, possibly in upstream, like a method cache, from which everything in awesome could benefit, even though I imagine not many things are so call-intensive as the graph widget, to really feel the difference. commit 538df25bdf02b53d0bbe81809eef2e6fdb05ff6f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 07:32:37 2021 +0700 Use 1-based index in the inner graph draw loop It's used there only once for a calculation and then several times to index some tables, and index 0 is bad for lua tables performance-wise. commit 7412546916cd0a0a490130a120255ffb719398b8 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:55:12 2021 +0700 Leave graph.max_value uninitialized at construction This is a user-visible change, but the previous behavior was probably not what user expected anyway, namely with graph.scale set the graph mysteriously couldn't zoom in onto values that are smaller than 1. commit eb81d7929a161e79c13262cfa73ac2e6a820e8ed Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:48:29 2021 +0700 Allow graph.max_value be nil This makes its behave symmetrically to min_value. Now it defaults to 1, when graph.scale is not set, and to the maximum value in the graph data, when graph.scale is set. commit f622a8430cbfde7007b58d7f4c9c76b08c91e997 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 12:01:13 2021 +0700 Calculate max values num in graph:add_value() more precisely At most so many values were kept in the widget values array, as there are pixels of usable widget width (i.e. sans border). This is exactly right for the surely most common case of graphs with 1px-wide bars, but is more than needed for graphs with step_width+step_spacing>1, and not enough for graphs with subpixel bars. Moreover this made graph autoscaling behave weirdly in graphs with thicker bars, because old values survived longer in the array and kept affecting min_value/max_value calculations even after they were shifted off-screen. The maximum number of kept values is now calculated exactly as the number of values that can be displayed at once, taking step_width and step_spacing into account. It might be worth adding a max_values_num property to the graph to let user override this calculation, in case they draw shapes in step_shape() outside of its supposed rectangle, so our assumptions of visibility are wrong, or the widget often gets resized, resulting in the annoyance of visibly truncated series. commit 0aa63249a18641251a1bf77423739164f0eab39f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 02:30:13 2021 +0700 Fix border_color/border_width bugs in graph widget add_value() wrongly assumed that border_width is always set, when border_color is, so if user set only the latter, an error occurred. add_value() assumed that border_width is always < width which led to awesome hanging up in an infinite loop, should that fail to be the case. The graph border_width property was driven by the border_color property, instead of the other way round. If border_color wasn't set, the border wasn't drawn all, even if user sets non-zero border_width, which is a confusing behavior, in contrast to all other widgets, which simply use a plethora of fallback colors to draw the border, when needed. This also meant that, despite being documented, the beautiful.graph_border_color fallback color could not and was not ever used. If the border_color was set though, an unset border_width was immediately assumed to be 1 instead of 0, also contrary to what other widgets do. Theme colors shouldn't influence widget layout, so this commit fixes this behavior to: 1) border_width always defaults to 0, if unset. 2) border_color falls back to beautiful.graph_border_color or white. Maybe one should also add a beautiful.graph_border_width theme variable, like some widgets do. commit dad4a3253b8fef971fb82720d2a407fcc0a879ad Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 03:11:27 2021 +0700 Implement multiple data series drawing for non-stacked graph The widget can already hold and draw multiple data groups, so it makes little sense to limit drawing only to the first group when graph.stack = false, probably leading to user's confusion. Now user can call add_value(v, group) with group > 1 and the data series will be drawn (over groups with lesser id-s) with graph.stack_colors[group] color, if stack_colors is set, or with graph.color, if stack_colors isn't set. Thus nothing visibly changes for all current users of non-stacked graphs with only single data series, but the new functionality is now there for those who need it. One can't fully emulate this functionality, e.g. with multiple graphs widgets in a wibox.layout.stack, because data series in a single widget can enjoy a common auto-scaling. commit 3e645c976184ab5f50d625271c25062db9ab7ed9 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:59:12 2021 +0700 Don't draw a stacked graph data group if there's no color for it If stack_colors[idx] is nil for the group `idx`, don't draw it at all (as opposed, e.g. to the effect of drawing it with a transparent color, or as though it had all values == 0, which both produce different results). This code is still subtly different from prior behavior, because now user can have a non-contiguous stack_colors table and turn the display of any data group on and off at will, whereas the code two commits earlier would just stop rendering as soon as ipairs(stack_colors) does, i.e. no data series were drawn past the first nil hole. This commit only looks large, but is mostly a whitespace change due to nesting from a pair of wrapping ifs. commit 8c0273786de1ade66bdd977699f98b588da9f487 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:32:42 2021 +0700 Unify stacked and non-stacked graph rendering Now stacked graphs support everything the nonstacked do, like step_width ~= 1 and custom step_shape-s. Technical notes: This code no longer calls cr:stroke() for every single value in a stacked graph, but rather paints a whole series and then cr:stroke/fill()-s it at all once with its color, improving performance considerably. Additionally the graph no longer relies on the presence of the stack_colors property to render series. It draws all data that it has, and, if there's no corresponding color in stack_colors, falls back to graph.color. This will be changed in the next commit to the prior behavior of only drawing those series for which there's a color in the stack_colors table, but that would've made this commit's diff much larger due to required nesting ifs over large code chunks. This commit also takes care to properly handle NaNs and negative values. Those are currently (almost) guaranteed to not occur in the values array due to clipping in graph:add_value(), but I intend to eventually drop the clipping, because it does nothing that draw() really relies upon, and allowing unclipped and NaN values is more flexible and less surprising, e.g. when user dynamically toggles graph.scale. Eventually I intend to implement drawing negative values in non-stacked graphs and possibly even in the stacked ones. commit 38c6589c51b2aa057d7c2f40182ecf32b5b2ff5e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 10:04:15 2021 +0700 Convert graph:draw() to use `self` commit f21f1b2550426df1c4ea8abde079b1ccef84b394 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 12:27:26 2021 +0700 Fix scaling for stacked graphs Stacked graphs have cumulative heights, values from all groups that end up in the same graph bar are effectively summed. Previous code didn't take that into account when calculating min/max_value for the purposes of rescaling, but simply examined every value separately. This code adds up all values up front and finds min/max among those, to determine proper scaling that would fit the graph exactly. commit 1ce4c7bf63bdd6c99c195d668347bec49355e921 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 09:49:35 2021 +0700 Draw shaped graph bars only up to the baseline Prior to this commit, every step_shape bar was, for some reason, drawn with height = full widget's height, regardless of the value it represents. So for the most bars their lower end was far outside the box and therefore clipped off. So e.g. one couldn't see the lower round edges of rounded rectangles. This commit fixes that by appropriately varying the height that is passed into step_shape(), so that lower edges of the bars always exactly touch the lower edge of the graph. If the user wants to restore the prior behavior, they can simply ignore the passed-in height in step_shape() and just always draw their shape with height = full widget height. But they couldn't replicate the effect of this commit in their rc.lua without essentially writing their own graph widget. commit e25185b08da23745b5815f85f6e9e43222ed2294 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 08:42:54 2021 +0700 Avoid incremental transform shifting during graph bar rendering Instead of letting addition errors and cognitive load accumulate simply use the same offset calculation that is used for lines and reset the transform back to the top-left corner after every drawn bar. It's conceptually simpler, and will be useful later for stacked graphs and more. It also fixes a discrepancy between plain and step_shape rendering. Prior to this commit, bar coloring occurred with a disturbed transform, which was centered at the last drawn bar, wherever it happened to be. This meant that nontrivial colors, like gradients and images, were arbitrarily offset when step_shape was used. commit f3e98866d8583c35d0b557c09f529299edc9ba2c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:45:29 2021 +0700 Refactor out graph bar height expression into a variable commit f54528d6892f5aaa4595917853c9e2130f743fdb Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:20:25 2021 +0700 Draw graph bars with lines only when step_width == 1 Before this commit graph bars of step_width < 1 were drawn with lines of width 1, but that's a bit unfortunate, because it led to much overpainting, while still ending up with a graphically incorrect result. Let's draw such bars with rectangles and let cairo do things with subpixels, getting more graphically accurate result, while still keeping the common case of step_width == 1 (supposedly) fast-drawn with lines. commit b6515dca67c18b78eb5657378131a62ab43d4cfc Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:54:07 2021 +0700 Fix unsharp edges when drawing graph bars with rectangles The 0.5 offset is only helpful for 1-width strokes, not for fills. commit 7f3db8f07e82f821bcd1dc418d66564f537609d6 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:21:01 2021 +0700 Do coordinate transformations even if the value isn't drawn The graph presently doesn't draw values < 0, but there was a discrepancy between drawing with and without step_shape. The default line/rectangle drawing reserves an empty place where the skipped value should've been, but no such empty place is reserved, when step_shape is used. This commit fixes that. Skipped values always have their empty space now. commit 7b731ebfe96af7a9dba9c57371da6761cf22c00c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:03:30 2021 +0700 Fix graph bar scaling when min_value is not 0 Previous calculation lead to underscaling of values so that even value = max_value wasn't drawn to full height. commit dca59fd8b7fff0e7e646f2dfc6bd92047028e5a8 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:18:40 2021 +0700 Calculate the horizontal graph bar offset only when needed commit 940ddf4b9825e9c3a91d276208c089d8c74e49db Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:17:23 2021 +0700 Call cr:move_to() only when drawing the graph with lines It's redundant otherwise and might even interfere with user's drawing in step_shape(). commit b5d5cd10ca29fb29cd751faaa95137c2fe89ff63 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:04:06 2021 +0700 Fix wrong horizontal offset calculations in widget.graph Indices in the loop are zero-based, so things like i-1 and i>1 aren't necessary and actually wouldn't have been correct even in a 1-based loop. step_shape = gears.shape.rectangle and step_shape = nil, which is supposed to default to rectangle according to docs, were drawn differently due to mismatched offset calculations, which led to problems especially noticeable when step_spacing is not zero, e.g: 1) whole graph shifted left too much, drawn partially off-screen. 2) whole graph shifted right too much, and the first bar placed closer to the rest than others. commit dc369fa198281e000afc1bd17326f3a264adf7d1 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 02:09:21 2021 +0700 Fix the "border_width affects graph bar width" bug Graph bars were drawn with lines of border_width thickness instead of 1, i.e. possibly too fat to be readable. commit 1ba73378cf29ed27a6ecd18716394779ceb543d9 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 00:13:03 2021 +0700 Simplify internal values representation in graph even more New values in graph._private.values were pushed to the end, and old values were removed from the beginning of the table. In draw() however newer values are drawn first, at the left edge of the graph, necessitating annoying idx = #values - idx conversions everywhere. In particular in stacked graph that means that values that are drawn together may have different indices. One has to shift the values array in add_value() either way, so one might as well insert new values at the beginning of the array and simplify the rest of the code. This doesn't seem like much yet, but it will prove to be much more convenient and less error-prone in what I'm going to do later. commit f70523ee68dbd6b67050ace4b82903a9b22440c7 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 13:35:10 2021 +0700 Simplify internal values representation in graph widget graph._private.values was either a table of values or a table of tables of values, depending on whether "stack" property is enabled, ... or a mix of both if user forgot to pass the second "group" parameter to graph:add_value(). This resulted in pretty fragile and confusing behavior, e.g. leaving user to wonder, why they see nothing more after setting stack=true for a graph in a previously working rc.lua, and demanding a very careful use of add_value() from the user, or else some values or even entire stacks would mysteriously vanish and errors in draw() get triggered, sometimes after a period of seemingly normal functioning. Now `values` is always a table of tables and the group parameter in add_value() defaults to 1, thus making the non-stacked graph a trivial variant of the stacked graph.
2021-05-20 17:14:03 +02:00
--- Determine if a data group should be rendered.
--
-- The graph uses this method to decide whether the given data group
-- should get its values rendered.
--
-- The default implementation says yes to all data groups, unless
-- `group_colors` property is set, in which case only those groups are
-- rendered, for which there are colors in the `group_colors` table,
-- so one can e.g. disable groups by setting their colors to false.
--
-- @method should_draw_data_group
-- @tparam number group_idx The index of the data group.
-- @treturn boolean true if the group should be rendered, false otherwise.
-- @local I'm not confident, that this is good API, so I'm making it private.
local function graph_should_draw_data_group(self, group_idx)
-- This default implementation decides, whether a group should be drawn,
-- based on presence of colors, for reasons of backward compatibility.
local data_group_colors = self._private.group_colors
if not data_group_colors then
-- The colors table isn't set, all data groups are deemed enabled.
return true
2016-05-23 07:30:44 +02:00
end
graph: complete widget overhaul and bug fixes Squashed commit of the following: commit 69821f51fe1e8652715658543e50631ece495090 Author: Alex Belykh <albel727@ngs.ru> Date: Mon May 10 19:17:55 2021 +0700 Refactor property handling in wibox.widget.graph In addition, baseline_value, step_width and step_spacing properties are all set to their default non-nil numeric values during instance creation now. commit 842eb429bf5df4b2ba46b2e6f7646afe89b1a3c3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 9 06:18:45 2021 +0700 Make graph:draw_values() private There's no documentation nor confidence that it's a good API, so I'm hiding it for now. commit 720746780574e4bad14a71fc4174955139c2eb50 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 04:04:13 2021 +0700 Document graph_should_draw_data_group() for posterity commit 7c73f3754ce6ba86b118f1170a6f9e406ab4841d Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:59:21 2021 +0700 Make graph:should_draw_data_group() private There's no confidence whether it's a good API, so I'm hiding it for now. commit b3539e20adb6423118af726ae4f8751ef864b5d2 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:36:44 2021 +0700 Make graph:preprocess_values() private There's no confidence whether it's a good API, so I'm hiding it for now. commit 2df4400e780a6260f8d0ab1c60734fbb11bf3c4f Author: Alex Belykh <albel727@ngs.ru> Date: Thu May 6 10:05:23 2021 +0700 Add spec/wibox/widget/graph_spec.lua commit ce01a9771ab439b8df841f1565aa4aa8a44a5e0d Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 08:16:18 2021 +0700 Improve graph.capacity documentation commit e28fade8f655802f5bf89154169f68d256b8e332 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 07:36:01 2021 +0700 Document graph:compute_drawn_values_num() commit dd68332292465422d08b48e9f0229f143beccbc6 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 05:24:23 2021 +0700 Document graph:pick_data_group_color() method commit ca6dc55f77aca88a6e9cc90bb6fd0045739f41f5 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 04:13:58 2021 +0700 Guard against setting data groups sparsely in graph:add_value() commit cdea5f126f7984e72924f6dffbd312c4fe3b032f Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 02:36:12 2021 +0700 Refactor scaling and baseline choice out of graph:draw() commit 10135deafe8a9390ba66ab657ebb1fb41d549e8c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 21:34:48 2021 +0700 Document graph.nan_color and graph.stack properties commit 0d73bb84ac66b87e16aee472bd1f299549f66a1c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 20:49:24 2021 +0700 Use a better example in docs for the graph.stack property commit c27d160a0826d033d49d6141608c40aa8a0642fa Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 16:05:38 2021 +0700 Document graph.baseline_value property commit 459bc8176c13d4ea32af0dcdc2399d3b929b1c6a Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:53:47 2021 +0700 Document graph.clamp_bars property commit e732ee0b3b007d213e689b98cc00ae7cb141ae08 Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:34 2021 +0700 Improve graph:add_value() and :clear() documentation commit ae73e3aa9f8d60409cbe4cb4da9612f31af4f38e Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:11 2021 +0700 Improve graph min_value/max_value/scale documentation commit a7350996a9def773b91b1631e6c0507e33d8f93b Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:27:36 2021 +0700 Cleanup graph examples to show only relevant things in docs commit 30740c26f4f41c16f4c8a3c339bcb7bd299e14cd Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 14:04:36 2021 +0700 Rename graph.stack_colors property to `group_colors` The colors are used for stacked and non-stacked graphs alike, so the name didn't make sense. Also document and clarify some color stuff. commit 1ea01bfd410971cf0d0c25fd74cd764d5eb7279d Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 21:25:41 2021 +0700 Add an example file for code coverage of some fringe graph cases commit eae68e39553f88a6275a0c65bf040b5d4c262b54 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 20:23:32 2021 +0700 Add step_shape to the graph/nan_color example for code coverage commit 8027147bc2eab34a97b49a428fb8cef27f122bed Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:55:18 2021 +0700 Add example files for graph.nan_color/nan_indication properties commit 89a25a165c52b2c15b6c4d95ecb777a9067e66f9 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:33:07 2021 +0700 Make widget graph:add_value(v) default v to NaN 0 as a default makes no sense and silences the error that is failing to specify a value, to which the user should rather be alerted. The graph widget has sane NaN handling now and will do just that. commit 2835d552440be48d356a7f7b1d64963696dd550b Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:20:48 2021 +0700 Implement graph.nan_color and nan_indication properties Now, whenever there's a NaN among values and graph.nan_indication is set, the corresponding area, where the value bar should have been drawn, is filled with the nan_color from top to bottom. The painting is done after all data is rendered, to make sure that it won't be overpainted and go unnoticed. NaNs among graph's values are inevitably a sign of some error and it is arguably not a sane default behavior to simply not render them at all. The user should take immediate note of any errors by default, instead of wondering at a mysteriously empty graph widget, which is why nan_indication is set to true by default. If the user wants to silence NaNs, they can always simply set it to false. nan_color if unset, defaults to a noticeable yellow-black pattern. As a direct and planned consequence, stacked graphs with negative values in them trigger the NaN handling too. But silencing NaN handling for those graphs is also possible and semi-sane, because then NaNs and negatives are ignored and the graph behaves (almost) like they were zeros. commit 9c1f8f08b400e01e19c534f3810c9c9ab485760c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 26 08:10:19 2021 +0700 Refactor out graph_map_value_to_widget_coordinates() function commit 1905991ddecabdae3e15ffb8ee5b18a1bee1a711 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 28 13:39:29 2021 +0700 Add an example file for graph.baseline_value property commit b31b39d66f4c037a9484b71a3f910703ae01fac0 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 07:03:49 2021 +0700 Add an example file for graph.clamp_bars=false/true comparison commit a1fc850e3047f9ee2c9ddeffacfbabf3ded34171 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 04:06:45 2021 +0700 Add an example file for graphs with negative values and shapes commit 2f012f8ff1bb5094c14e724a61e59a2a3a296bc8 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 01:06:05 2021 +0700 Add an example file for a graph displaying negative values commit 7b14fbd59a4c4c51962fd3110a1a900dd5f4eb98 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 11:18:26 2021 +0700 Add an example file for graphs with step_width < 1 commit dfd9a54544b637e2fbe3d1325f9cc24b3f4d2313 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 10:17:38 2021 +0700 Add an example file for graph.stack=false/true comparison Should also demonstrate that multiple data groups are possible for non-stacked graphs too. commit 5b532106c3fd255f9b66a66cfb88d977b489ab5d Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 09:34:14 2021 +0700 Add an example file for disabling graph data groups with nil colors commit ac6f1083c02fcb428f089a616f9b0ebaa5496b6c Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:57:25 2021 +0700 Add an example file for step_shape/width/spacing on stacked graphs commit b3703db6a5139e73e72c1cfd198ece2cd9be7df2 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:43:56 2021 +0700 Add an example file for scale=false/true on stacked graph commit b000aed4a12cb44fb501e1c8432a71a14b2f8f3f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 20 08:22:19 2021 +0700 Document deprecation of graph's width and height commit cb0ad617bd6c83a499896797318e3b0b588c3566 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 07:53:37 2021 +0700 Deprecate width and height properties of wibox.widget.graph commit 8c64e14ad91a074b784166a7054e53444cccb9d2 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 04:37:59 2021 +0700 Refactor graph:draw() to separate concerns Firstly, summation of group values for stacked graphs is extracted into graph:preprocess_values(). This makes graph:draw() smaller and exposes the fact, that stacked graphs are almost entirely just a form of a more general concept: pre-render data manipulation. It's something a user might want to customize, and now they can do it by monkey-patching the method. Secondly, the logic for deciding, if and with which color a data group should be drawn, is extracted into graph:should_draw_data_group() and get_data_group_color() methods respectively. This makes it customizable too and makes more obvious the fact, that it is a general thing that has nothing to do with stacked graphs in particular. With this there are only 5 lines remaining in graph:draw(), that could be said to be specific to stacked graphs, all pertaining to the `prev_y` variable, which merely preserves the bar coordinates from a previous data group. commit 2a32b6305d893f413303728a0a3da1dcd9a5591e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 22:52:37 2021 +0700 Pointlessly optimize graph:add_value() 1) Purge old values first, then insert the new one, if needed. Since Lua grows arrays in power of two steps, and our guess_capacity() also takes care to return even integers, it would be a shame to overstep the capacity by one by inserting first and thus getting the table size doubled only to never use the most of it. 2) Use a removal procedure that doesn't call the slow length operator at all. Previous code called it twice (once in table.remove) for every removed value. commit 680ea8f5d81e9040afb4662b3229e937e4b89e02 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:23:38 2021 +0700 Set graph data series color before the painting loop commit b970f0f221ae76250f2a94433ff79d653caa6e21 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:05:37 2021 +0700 Drop a large no longer necessary `if` in graph:draw_values() This commit is almost purely a whitespace change. commit 6b4f0541c70ec99d7f6e276bae64939f337120d6 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:00:50 2021 +0700 Extract values rendering code into graph:draw_values() method This is to avoid ungodly if nesting levels, which are needed to bail out of drawing values early and skip to drawing the border. The next commit will get rid of the if. commit e0950e4f99d1ea1c4b8cea2cc67629fa40030c2a Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 14:50:20 2021 +0700 Track graph draw() usage to guess necessary array capacity The last step of divorcing add_values() from width calculations, while ensuring that user doesn't observe a lack of data to draw. The widget now tracks an approximate number of values that it has been asked to draw() in _private.last_drawn_values_num, and truncates its values array based on that (if not overridden by the capacity property). commit 2e3d542282f35e7c0fed454da0f2eada0fcf3b78 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 13:35:12 2021 +0700 Add `capacity` property to graph widget This is the second step of divorcing add_values() from calculations involving widths. How many values are stored and how many values are drawn are distinct concepts. There isn't even a single definitive "how many values are drawn" because the widget could be rendered in several places with distinct widths simultaneously, and in every such place the data must be drawn appropriately, e.g. with auto-scaling, that takes only the visible data points into account. And the draw() method can do that now. The default capacity value of nil uses heuristics based on widget's _private.width to guess some capacity that would tolerate some dynamic widget resizing. If the user finds the heuristic failing them, they can set the capacity property to the desired maximum number of values that will be kept in the _private.values array (in each data group). commit 08d4a80686f7fe363551c35589a460839f60853e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 03:27:18 2021 +0700 Extract values size computation into compute_drawn_values_num() This is the first step of making add_values() not concern itself (directly) with widget dimensions. The user can monkey-patch the method to override how many values are (logically) drawn, because it affects graph auto-scaling and can be useful for rendering of step_shape-s that paint outside of their step_width. I didn't make it a full-blown function-valued property, because it seems a fringe functionality, which I'm too lazy to document. commit 3c5617c98ab1147618ed22e8414a77fc56b45f6e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 02:32:29 2021 +0700 Copy graph widget prototype methods over in bulk commit 169538837ece41a03d02ba8fbc0e4097062b67a3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun Apr 25 05:58:01 2021 +0700 Remove graph.baseline_y property After giving it some thought I've concluded, that it is unlikely to be useful as it is now. I'd better reserve the name for some more sensible functionality, like fixing the baseline_value defined axis at the given widget coordinate, adjusting the scaling accordingly, so min_value/max_value won't generally correspond to widget top and bottom anymore. commit 202ffaa9a0b820c7c471e1121d01b91f8f76cd38 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 12:06:55 2021 +0700 Add baseline_y and baseline_value properties to graph widget Now values in graph have a *baseline*, tunable with baseline_y and baseline_value properties. It is the (for now invisible) horizontal line from which all bars in the graph grow. I call it a "baseline" instead of "axis", because it doesn't actually have to correspond to the 0 value. But it can, and actually does now by default. baseline_y and baseline_value properties determine the vertical placement of the baseline in the widget. baseline_y is the position of the baseline in normalized widget coordinates, e.g. 0 corresponds to widget's top, 1 - to widget's bottom, and 0.5 - to widget's middle. So if e.g. one sets baseline_y = 0, then no matter how the widget is resized and how its values are scaled, bars in it will be drawn starting from the top. baseline_value is the position of the baseline in value coordinates. So if one sets baseline_value = 10, then no matter what happens, bars for values greater than 10 will be drawn as growing up from this line, and bars for values smaller as growing down. Where and if it will be seen in the widget, depends on the scaling. baseline_value = 0 is the familiar zero axis. baseline_y is unset by default and has priority, i.e. if it's set, then baseline_value is ignored. baseline_value defaults to 0. Prior to this commit, bars were always drawn from the bottom edge of the widget, or, in the new terms, the widget had baseline_y = 1. But it was indistinguishable from baseline_value = 0, because the widget clamped all negative values to 0, so the difference between the two could never be observed. baseline_value = 0 is clearly more useful and familiar to people, so now it is in force by default. commit df7e19abaebf0a871b5ca217b4cc3365533267a6 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 11:18:12 2021 +0700 Move value clamping from graph:add_value() into draw() Previously all values were forcibly clamped between 0 (not tunable at all) and max_value (1 by default). Thus graphs with negative values were not an option. NaN values were conflated with zeros, even though it may be useful to maintain the distinction and render them differently. And if user happened to dynamically change the max_value property, they discovered, that their data got truncated. Preserving user data as it was given is more flexible and makes things easier to debug. This commit does exactly that. So what happens now to the newly allowed values during draw()? There are 3 new cases to consider: 1) NaNs 2) Negatives 3) Values outside of min_value..max_value range NaNs are presently simply not drawn. An empty place is reserved where the value should've been, but nothing else is done. This is distinct from how a zero is handled, e.g. if step_shape is set, it is called with the height parameter = 0, which can still end up drawing something, depending on the shape. One could also consider addition of nan_color/nan_shape properties which could be drawn to alert user to a gap in their data. Negative values necessitate introduction of the concept of the zero axis, from which they will be drawn DOWN, in contrast to positive values. Where should this axis be drawn? How should step_shape-s with inherent direction, e.g. arrows, be drawn for negatives? This will be addressed in the next commit. Values outside of min/max_value range can imply shapes that go beyond widget dimensions. An arrow shape that goes beyond the roof is visibly different from an arrow that saturates and merely touches the widget top, and the user may reasonably desire either behavior. Therefore this commit introduces a boolean clamp_bars property, which defaults to true for reasons of robustness and backward compatibility with the behaviour prior to this commit. The property of course affects only the display of the values, the actual user data stays intact. commit c9c24fb1ab741cd6bb8074971d568e0646c51532 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:29:01 2021 +0700 Use rectangles for drawing graph bars of all sizes I have benchmarked various bar drawing strategies and graph sizes and found out that drawing graph bars with rectangles is as fast or even marginally faster than using fat lines. Thus the code that tries to special-case 1px bars as lines seems to be not worth the additional complexity it introduces, and this commit removes it. Incidentally, I've also established, that rectangles are more robust. If one draws graph bars and feeds cairo very large coordinates (~2^62), then corresponding rectangles fail to render, but all the other rectangles in the same series get rendered successfully, whereas a single large coordinate in a move_to/line_to() sequence is enough to cause all lines in the batch to fail to render. commit bc7f4be5e06405b5aa7fb443a5ace2b9b9575b1f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:01:42 2021 +0700 Cache cairo methods used in the inner loop of graph:draw() I have benchmarked the draw() method, both on cairo xcb and on argb32 image surfaces, and have discovered that cairo context method calls are unnaturally slow, to the point, that almost nothing else in the function matters. For example, drawing bars with vertical lines made the whole draw() function almost 2 times slower than doing the same with rectangles, merely because the former requires two method calls (move_to/line_to), and the latter only one. Simply caching hotpath cairo methods in local variables, reduced the difference between the drawing methods to a negligible one and made the whole function 4 times faster on a typical 100-bar graph, (1.4ms to 0.33ms on my laptop) thus bringing closer my secret dream of drawing live audio data with awesome graphs (kidding). I guess, LGI does something not very optimal there, like gobject-introspecting the method every time, or something. There might be some untapped optimization potential, possibly in upstream, like a method cache, from which everything in awesome could benefit, even though I imagine not many things are so call-intensive as the graph widget, to really feel the difference. commit 538df25bdf02b53d0bbe81809eef2e6fdb05ff6f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 07:32:37 2021 +0700 Use 1-based index in the inner graph draw loop It's used there only once for a calculation and then several times to index some tables, and index 0 is bad for lua tables performance-wise. commit 7412546916cd0a0a490130a120255ffb719398b8 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:55:12 2021 +0700 Leave graph.max_value uninitialized at construction This is a user-visible change, but the previous behavior was probably not what user expected anyway, namely with graph.scale set the graph mysteriously couldn't zoom in onto values that are smaller than 1. commit eb81d7929a161e79c13262cfa73ac2e6a820e8ed Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:48:29 2021 +0700 Allow graph.max_value be nil This makes its behave symmetrically to min_value. Now it defaults to 1, when graph.scale is not set, and to the maximum value in the graph data, when graph.scale is set. commit f622a8430cbfde7007b58d7f4c9c76b08c91e997 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 12:01:13 2021 +0700 Calculate max values num in graph:add_value() more precisely At most so many values were kept in the widget values array, as there are pixels of usable widget width (i.e. sans border). This is exactly right for the surely most common case of graphs with 1px-wide bars, but is more than needed for graphs with step_width+step_spacing>1, and not enough for graphs with subpixel bars. Moreover this made graph autoscaling behave weirdly in graphs with thicker bars, because old values survived longer in the array and kept affecting min_value/max_value calculations even after they were shifted off-screen. The maximum number of kept values is now calculated exactly as the number of values that can be displayed at once, taking step_width and step_spacing into account. It might be worth adding a max_values_num property to the graph to let user override this calculation, in case they draw shapes in step_shape() outside of its supposed rectangle, so our assumptions of visibility are wrong, or the widget often gets resized, resulting in the annoyance of visibly truncated series. commit 0aa63249a18641251a1bf77423739164f0eab39f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 02:30:13 2021 +0700 Fix border_color/border_width bugs in graph widget add_value() wrongly assumed that border_width is always set, when border_color is, so if user set only the latter, an error occurred. add_value() assumed that border_width is always < width which led to awesome hanging up in an infinite loop, should that fail to be the case. The graph border_width property was driven by the border_color property, instead of the other way round. If border_color wasn't set, the border wasn't drawn all, even if user sets non-zero border_width, which is a confusing behavior, in contrast to all other widgets, which simply use a plethora of fallback colors to draw the border, when needed. This also meant that, despite being documented, the beautiful.graph_border_color fallback color could not and was not ever used. If the border_color was set though, an unset border_width was immediately assumed to be 1 instead of 0, also contrary to what other widgets do. Theme colors shouldn't influence widget layout, so this commit fixes this behavior to: 1) border_width always defaults to 0, if unset. 2) border_color falls back to beautiful.graph_border_color or white. Maybe one should also add a beautiful.graph_border_width theme variable, like some widgets do. commit dad4a3253b8fef971fb82720d2a407fcc0a879ad Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 03:11:27 2021 +0700 Implement multiple data series drawing for non-stacked graph The widget can already hold and draw multiple data groups, so it makes little sense to limit drawing only to the first group when graph.stack = false, probably leading to user's confusion. Now user can call add_value(v, group) with group > 1 and the data series will be drawn (over groups with lesser id-s) with graph.stack_colors[group] color, if stack_colors is set, or with graph.color, if stack_colors isn't set. Thus nothing visibly changes for all current users of non-stacked graphs with only single data series, but the new functionality is now there for those who need it. One can't fully emulate this functionality, e.g. with multiple graphs widgets in a wibox.layout.stack, because data series in a single widget can enjoy a common auto-scaling. commit 3e645c976184ab5f50d625271c25062db9ab7ed9 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:59:12 2021 +0700 Don't draw a stacked graph data group if there's no color for it If stack_colors[idx] is nil for the group `idx`, don't draw it at all (as opposed, e.g. to the effect of drawing it with a transparent color, or as though it had all values == 0, which both produce different results). This code is still subtly different from prior behavior, because now user can have a non-contiguous stack_colors table and turn the display of any data group on and off at will, whereas the code two commits earlier would just stop rendering as soon as ipairs(stack_colors) does, i.e. no data series were drawn past the first nil hole. This commit only looks large, but is mostly a whitespace change due to nesting from a pair of wrapping ifs. commit 8c0273786de1ade66bdd977699f98b588da9f487 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:32:42 2021 +0700 Unify stacked and non-stacked graph rendering Now stacked graphs support everything the nonstacked do, like step_width ~= 1 and custom step_shape-s. Technical notes: This code no longer calls cr:stroke() for every single value in a stacked graph, but rather paints a whole series and then cr:stroke/fill()-s it at all once with its color, improving performance considerably. Additionally the graph no longer relies on the presence of the stack_colors property to render series. It draws all data that it has, and, if there's no corresponding color in stack_colors, falls back to graph.color. This will be changed in the next commit to the prior behavior of only drawing those series for which there's a color in the stack_colors table, but that would've made this commit's diff much larger due to required nesting ifs over large code chunks. This commit also takes care to properly handle NaNs and negative values. Those are currently (almost) guaranteed to not occur in the values array due to clipping in graph:add_value(), but I intend to eventually drop the clipping, because it does nothing that draw() really relies upon, and allowing unclipped and NaN values is more flexible and less surprising, e.g. when user dynamically toggles graph.scale. Eventually I intend to implement drawing negative values in non-stacked graphs and possibly even in the stacked ones. commit 38c6589c51b2aa057d7c2f40182ecf32b5b2ff5e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 10:04:15 2021 +0700 Convert graph:draw() to use `self` commit f21f1b2550426df1c4ea8abde079b1ccef84b394 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 12:27:26 2021 +0700 Fix scaling for stacked graphs Stacked graphs have cumulative heights, values from all groups that end up in the same graph bar are effectively summed. Previous code didn't take that into account when calculating min/max_value for the purposes of rescaling, but simply examined every value separately. This code adds up all values up front and finds min/max among those, to determine proper scaling that would fit the graph exactly. commit 1ce4c7bf63bdd6c99c195d668347bec49355e921 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 09:49:35 2021 +0700 Draw shaped graph bars only up to the baseline Prior to this commit, every step_shape bar was, for some reason, drawn with height = full widget's height, regardless of the value it represents. So for the most bars their lower end was far outside the box and therefore clipped off. So e.g. one couldn't see the lower round edges of rounded rectangles. This commit fixes that by appropriately varying the height that is passed into step_shape(), so that lower edges of the bars always exactly touch the lower edge of the graph. If the user wants to restore the prior behavior, they can simply ignore the passed-in height in step_shape() and just always draw their shape with height = full widget height. But they couldn't replicate the effect of this commit in their rc.lua without essentially writing their own graph widget. commit e25185b08da23745b5815f85f6e9e43222ed2294 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 08:42:54 2021 +0700 Avoid incremental transform shifting during graph bar rendering Instead of letting addition errors and cognitive load accumulate simply use the same offset calculation that is used for lines and reset the transform back to the top-left corner after every drawn bar. It's conceptually simpler, and will be useful later for stacked graphs and more. It also fixes a discrepancy between plain and step_shape rendering. Prior to this commit, bar coloring occurred with a disturbed transform, which was centered at the last drawn bar, wherever it happened to be. This meant that nontrivial colors, like gradients and images, were arbitrarily offset when step_shape was used. commit f3e98866d8583c35d0b557c09f529299edc9ba2c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:45:29 2021 +0700 Refactor out graph bar height expression into a variable commit f54528d6892f5aaa4595917853c9e2130f743fdb Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:20:25 2021 +0700 Draw graph bars with lines only when step_width == 1 Before this commit graph bars of step_width < 1 were drawn with lines of width 1, but that's a bit unfortunate, because it led to much overpainting, while still ending up with a graphically incorrect result. Let's draw such bars with rectangles and let cairo do things with subpixels, getting more graphically accurate result, while still keeping the common case of step_width == 1 (supposedly) fast-drawn with lines. commit b6515dca67c18b78eb5657378131a62ab43d4cfc Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:54:07 2021 +0700 Fix unsharp edges when drawing graph bars with rectangles The 0.5 offset is only helpful for 1-width strokes, not for fills. commit 7f3db8f07e82f821bcd1dc418d66564f537609d6 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:21:01 2021 +0700 Do coordinate transformations even if the value isn't drawn The graph presently doesn't draw values < 0, but there was a discrepancy between drawing with and without step_shape. The default line/rectangle drawing reserves an empty place where the skipped value should've been, but no such empty place is reserved, when step_shape is used. This commit fixes that. Skipped values always have their empty space now. commit 7b731ebfe96af7a9dba9c57371da6761cf22c00c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:03:30 2021 +0700 Fix graph bar scaling when min_value is not 0 Previous calculation lead to underscaling of values so that even value = max_value wasn't drawn to full height. commit dca59fd8b7fff0e7e646f2dfc6bd92047028e5a8 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:18:40 2021 +0700 Calculate the horizontal graph bar offset only when needed commit 940ddf4b9825e9c3a91d276208c089d8c74e49db Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:17:23 2021 +0700 Call cr:move_to() only when drawing the graph with lines It's redundant otherwise and might even interfere with user's drawing in step_shape(). commit b5d5cd10ca29fb29cd751faaa95137c2fe89ff63 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:04:06 2021 +0700 Fix wrong horizontal offset calculations in widget.graph Indices in the loop are zero-based, so things like i-1 and i>1 aren't necessary and actually wouldn't have been correct even in a 1-based loop. step_shape = gears.shape.rectangle and step_shape = nil, which is supposed to default to rectangle according to docs, were drawn differently due to mismatched offset calculations, which led to problems especially noticeable when step_spacing is not zero, e.g: 1) whole graph shifted left too much, drawn partially off-screen. 2) whole graph shifted right too much, and the first bar placed closer to the rest than others. commit dc369fa198281e000afc1bd17326f3a264adf7d1 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 02:09:21 2021 +0700 Fix the "border_width affects graph bar width" bug Graph bars were drawn with lines of border_width thickness instead of 1, i.e. possibly too fat to be readable. commit 1ba73378cf29ed27a6ecd18716394779ceb543d9 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 00:13:03 2021 +0700 Simplify internal values representation in graph even more New values in graph._private.values were pushed to the end, and old values were removed from the beginning of the table. In draw() however newer values are drawn first, at the left edge of the graph, necessitating annoying idx = #values - idx conversions everywhere. In particular in stacked graph that means that values that are drawn together may have different indices. One has to shift the values array in add_value() either way, so one might as well insert new values at the beginning of the array and simplify the rest of the code. This doesn't seem like much yet, but it will prove to be much more convenient and less error-prone in what I'm going to do later. commit f70523ee68dbd6b67050ace4b82903a9b22440c7 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 13:35:10 2021 +0700 Simplify internal values representation in graph widget graph._private.values was either a table of values or a table of tables of values, depending on whether "stack" property is enabled, ... or a mix of both if user forgot to pass the second "group" parameter to graph:add_value(). This resulted in pretty fragile and confusing behavior, e.g. leaving user to wonder, why they see nothing more after setting stack=true for a graph in a previously working rc.lua, and demanding a very careful use of add_value() from the user, or else some values or even entire stacks would mysteriously vanish and errors in draw() get triggered, sometimes after a period of seemingly normal functioning. Now `values` is always a table of tables and the group parameter in add_value() defaults to 1, thus making the non-stacked graph a trivial variant of the stacked graph.
2021-05-20 17:14:03 +02:00
-- A group is enabled if it has a color, i.e. nil color means "don't draw it"
return not not data_group_colors[group_idx]
end
2016-05-23 07:30:44 +02:00
graph: complete widget overhaul and bug fixes Squashed commit of the following: commit 69821f51fe1e8652715658543e50631ece495090 Author: Alex Belykh <albel727@ngs.ru> Date: Mon May 10 19:17:55 2021 +0700 Refactor property handling in wibox.widget.graph In addition, baseline_value, step_width and step_spacing properties are all set to their default non-nil numeric values during instance creation now. commit 842eb429bf5df4b2ba46b2e6f7646afe89b1a3c3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 9 06:18:45 2021 +0700 Make graph:draw_values() private There's no documentation nor confidence that it's a good API, so I'm hiding it for now. commit 720746780574e4bad14a71fc4174955139c2eb50 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 04:04:13 2021 +0700 Document graph_should_draw_data_group() for posterity commit 7c73f3754ce6ba86b118f1170a6f9e406ab4841d Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:59:21 2021 +0700 Make graph:should_draw_data_group() private There's no confidence whether it's a good API, so I'm hiding it for now. commit b3539e20adb6423118af726ae4f8751ef864b5d2 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:36:44 2021 +0700 Make graph:preprocess_values() private There's no confidence whether it's a good API, so I'm hiding it for now. commit 2df4400e780a6260f8d0ab1c60734fbb11bf3c4f Author: Alex Belykh <albel727@ngs.ru> Date: Thu May 6 10:05:23 2021 +0700 Add spec/wibox/widget/graph_spec.lua commit ce01a9771ab439b8df841f1565aa4aa8a44a5e0d Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 08:16:18 2021 +0700 Improve graph.capacity documentation commit e28fade8f655802f5bf89154169f68d256b8e332 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 07:36:01 2021 +0700 Document graph:compute_drawn_values_num() commit dd68332292465422d08b48e9f0229f143beccbc6 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 05:24:23 2021 +0700 Document graph:pick_data_group_color() method commit ca6dc55f77aca88a6e9cc90bb6fd0045739f41f5 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 04:13:58 2021 +0700 Guard against setting data groups sparsely in graph:add_value() commit cdea5f126f7984e72924f6dffbd312c4fe3b032f Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 02:36:12 2021 +0700 Refactor scaling and baseline choice out of graph:draw() commit 10135deafe8a9390ba66ab657ebb1fb41d549e8c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 21:34:48 2021 +0700 Document graph.nan_color and graph.stack properties commit 0d73bb84ac66b87e16aee472bd1f299549f66a1c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 20:49:24 2021 +0700 Use a better example in docs for the graph.stack property commit c27d160a0826d033d49d6141608c40aa8a0642fa Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 16:05:38 2021 +0700 Document graph.baseline_value property commit 459bc8176c13d4ea32af0dcdc2399d3b929b1c6a Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:53:47 2021 +0700 Document graph.clamp_bars property commit e732ee0b3b007d213e689b98cc00ae7cb141ae08 Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:34 2021 +0700 Improve graph:add_value() and :clear() documentation commit ae73e3aa9f8d60409cbe4cb4da9612f31af4f38e Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:11 2021 +0700 Improve graph min_value/max_value/scale documentation commit a7350996a9def773b91b1631e6c0507e33d8f93b Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:27:36 2021 +0700 Cleanup graph examples to show only relevant things in docs commit 30740c26f4f41c16f4c8a3c339bcb7bd299e14cd Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 14:04:36 2021 +0700 Rename graph.stack_colors property to `group_colors` The colors are used for stacked and non-stacked graphs alike, so the name didn't make sense. Also document and clarify some color stuff. commit 1ea01bfd410971cf0d0c25fd74cd764d5eb7279d Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 21:25:41 2021 +0700 Add an example file for code coverage of some fringe graph cases commit eae68e39553f88a6275a0c65bf040b5d4c262b54 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 20:23:32 2021 +0700 Add step_shape to the graph/nan_color example for code coverage commit 8027147bc2eab34a97b49a428fb8cef27f122bed Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:55:18 2021 +0700 Add example files for graph.nan_color/nan_indication properties commit 89a25a165c52b2c15b6c4d95ecb777a9067e66f9 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:33:07 2021 +0700 Make widget graph:add_value(v) default v to NaN 0 as a default makes no sense and silences the error that is failing to specify a value, to which the user should rather be alerted. The graph widget has sane NaN handling now and will do just that. commit 2835d552440be48d356a7f7b1d64963696dd550b Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:20:48 2021 +0700 Implement graph.nan_color and nan_indication properties Now, whenever there's a NaN among values and graph.nan_indication is set, the corresponding area, where the value bar should have been drawn, is filled with the nan_color from top to bottom. The painting is done after all data is rendered, to make sure that it won't be overpainted and go unnoticed. NaNs among graph's values are inevitably a sign of some error and it is arguably not a sane default behavior to simply not render them at all. The user should take immediate note of any errors by default, instead of wondering at a mysteriously empty graph widget, which is why nan_indication is set to true by default. If the user wants to silence NaNs, they can always simply set it to false. nan_color if unset, defaults to a noticeable yellow-black pattern. As a direct and planned consequence, stacked graphs with negative values in them trigger the NaN handling too. But silencing NaN handling for those graphs is also possible and semi-sane, because then NaNs and negatives are ignored and the graph behaves (almost) like they were zeros. commit 9c1f8f08b400e01e19c534f3810c9c9ab485760c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 26 08:10:19 2021 +0700 Refactor out graph_map_value_to_widget_coordinates() function commit 1905991ddecabdae3e15ffb8ee5b18a1bee1a711 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 28 13:39:29 2021 +0700 Add an example file for graph.baseline_value property commit b31b39d66f4c037a9484b71a3f910703ae01fac0 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 07:03:49 2021 +0700 Add an example file for graph.clamp_bars=false/true comparison commit a1fc850e3047f9ee2c9ddeffacfbabf3ded34171 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 04:06:45 2021 +0700 Add an example file for graphs with negative values and shapes commit 2f012f8ff1bb5094c14e724a61e59a2a3a296bc8 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 01:06:05 2021 +0700 Add an example file for a graph displaying negative values commit 7b14fbd59a4c4c51962fd3110a1a900dd5f4eb98 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 11:18:26 2021 +0700 Add an example file for graphs with step_width < 1 commit dfd9a54544b637e2fbe3d1325f9cc24b3f4d2313 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 10:17:38 2021 +0700 Add an example file for graph.stack=false/true comparison Should also demonstrate that multiple data groups are possible for non-stacked graphs too. commit 5b532106c3fd255f9b66a66cfb88d977b489ab5d Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 09:34:14 2021 +0700 Add an example file for disabling graph data groups with nil colors commit ac6f1083c02fcb428f089a616f9b0ebaa5496b6c Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:57:25 2021 +0700 Add an example file for step_shape/width/spacing on stacked graphs commit b3703db6a5139e73e72c1cfd198ece2cd9be7df2 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:43:56 2021 +0700 Add an example file for scale=false/true on stacked graph commit b000aed4a12cb44fb501e1c8432a71a14b2f8f3f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 20 08:22:19 2021 +0700 Document deprecation of graph's width and height commit cb0ad617bd6c83a499896797318e3b0b588c3566 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 07:53:37 2021 +0700 Deprecate width and height properties of wibox.widget.graph commit 8c64e14ad91a074b784166a7054e53444cccb9d2 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 04:37:59 2021 +0700 Refactor graph:draw() to separate concerns Firstly, summation of group values for stacked graphs is extracted into graph:preprocess_values(). This makes graph:draw() smaller and exposes the fact, that stacked graphs are almost entirely just a form of a more general concept: pre-render data manipulation. It's something a user might want to customize, and now they can do it by monkey-patching the method. Secondly, the logic for deciding, if and with which color a data group should be drawn, is extracted into graph:should_draw_data_group() and get_data_group_color() methods respectively. This makes it customizable too and makes more obvious the fact, that it is a general thing that has nothing to do with stacked graphs in particular. With this there are only 5 lines remaining in graph:draw(), that could be said to be specific to stacked graphs, all pertaining to the `prev_y` variable, which merely preserves the bar coordinates from a previous data group. commit 2a32b6305d893f413303728a0a3da1dcd9a5591e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 22:52:37 2021 +0700 Pointlessly optimize graph:add_value() 1) Purge old values first, then insert the new one, if needed. Since Lua grows arrays in power of two steps, and our guess_capacity() also takes care to return even integers, it would be a shame to overstep the capacity by one by inserting first and thus getting the table size doubled only to never use the most of it. 2) Use a removal procedure that doesn't call the slow length operator at all. Previous code called it twice (once in table.remove) for every removed value. commit 680ea8f5d81e9040afb4662b3229e937e4b89e02 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:23:38 2021 +0700 Set graph data series color before the painting loop commit b970f0f221ae76250f2a94433ff79d653caa6e21 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:05:37 2021 +0700 Drop a large no longer necessary `if` in graph:draw_values() This commit is almost purely a whitespace change. commit 6b4f0541c70ec99d7f6e276bae64939f337120d6 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:00:50 2021 +0700 Extract values rendering code into graph:draw_values() method This is to avoid ungodly if nesting levels, which are needed to bail out of drawing values early and skip to drawing the border. The next commit will get rid of the if. commit e0950e4f99d1ea1c4b8cea2cc67629fa40030c2a Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 14:50:20 2021 +0700 Track graph draw() usage to guess necessary array capacity The last step of divorcing add_values() from width calculations, while ensuring that user doesn't observe a lack of data to draw. The widget now tracks an approximate number of values that it has been asked to draw() in _private.last_drawn_values_num, and truncates its values array based on that (if not overridden by the capacity property). commit 2e3d542282f35e7c0fed454da0f2eada0fcf3b78 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 13:35:12 2021 +0700 Add `capacity` property to graph widget This is the second step of divorcing add_values() from calculations involving widths. How many values are stored and how many values are drawn are distinct concepts. There isn't even a single definitive "how many values are drawn" because the widget could be rendered in several places with distinct widths simultaneously, and in every such place the data must be drawn appropriately, e.g. with auto-scaling, that takes only the visible data points into account. And the draw() method can do that now. The default capacity value of nil uses heuristics based on widget's _private.width to guess some capacity that would tolerate some dynamic widget resizing. If the user finds the heuristic failing them, they can set the capacity property to the desired maximum number of values that will be kept in the _private.values array (in each data group). commit 08d4a80686f7fe363551c35589a460839f60853e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 03:27:18 2021 +0700 Extract values size computation into compute_drawn_values_num() This is the first step of making add_values() not concern itself (directly) with widget dimensions. The user can monkey-patch the method to override how many values are (logically) drawn, because it affects graph auto-scaling and can be useful for rendering of step_shape-s that paint outside of their step_width. I didn't make it a full-blown function-valued property, because it seems a fringe functionality, which I'm too lazy to document. commit 3c5617c98ab1147618ed22e8414a77fc56b45f6e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 02:32:29 2021 +0700 Copy graph widget prototype methods over in bulk commit 169538837ece41a03d02ba8fbc0e4097062b67a3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun Apr 25 05:58:01 2021 +0700 Remove graph.baseline_y property After giving it some thought I've concluded, that it is unlikely to be useful as it is now. I'd better reserve the name for some more sensible functionality, like fixing the baseline_value defined axis at the given widget coordinate, adjusting the scaling accordingly, so min_value/max_value won't generally correspond to widget top and bottom anymore. commit 202ffaa9a0b820c7c471e1121d01b91f8f76cd38 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 12:06:55 2021 +0700 Add baseline_y and baseline_value properties to graph widget Now values in graph have a *baseline*, tunable with baseline_y and baseline_value properties. It is the (for now invisible) horizontal line from which all bars in the graph grow. I call it a "baseline" instead of "axis", because it doesn't actually have to correspond to the 0 value. But it can, and actually does now by default. baseline_y and baseline_value properties determine the vertical placement of the baseline in the widget. baseline_y is the position of the baseline in normalized widget coordinates, e.g. 0 corresponds to widget's top, 1 - to widget's bottom, and 0.5 - to widget's middle. So if e.g. one sets baseline_y = 0, then no matter how the widget is resized and how its values are scaled, bars in it will be drawn starting from the top. baseline_value is the position of the baseline in value coordinates. So if one sets baseline_value = 10, then no matter what happens, bars for values greater than 10 will be drawn as growing up from this line, and bars for values smaller as growing down. Where and if it will be seen in the widget, depends on the scaling. baseline_value = 0 is the familiar zero axis. baseline_y is unset by default and has priority, i.e. if it's set, then baseline_value is ignored. baseline_value defaults to 0. Prior to this commit, bars were always drawn from the bottom edge of the widget, or, in the new terms, the widget had baseline_y = 1. But it was indistinguishable from baseline_value = 0, because the widget clamped all negative values to 0, so the difference between the two could never be observed. baseline_value = 0 is clearly more useful and familiar to people, so now it is in force by default. commit df7e19abaebf0a871b5ca217b4cc3365533267a6 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 11:18:12 2021 +0700 Move value clamping from graph:add_value() into draw() Previously all values were forcibly clamped between 0 (not tunable at all) and max_value (1 by default). Thus graphs with negative values were not an option. NaN values were conflated with zeros, even though it may be useful to maintain the distinction and render them differently. And if user happened to dynamically change the max_value property, they discovered, that their data got truncated. Preserving user data as it was given is more flexible and makes things easier to debug. This commit does exactly that. So what happens now to the newly allowed values during draw()? There are 3 new cases to consider: 1) NaNs 2) Negatives 3) Values outside of min_value..max_value range NaNs are presently simply not drawn. An empty place is reserved where the value should've been, but nothing else is done. This is distinct from how a zero is handled, e.g. if step_shape is set, it is called with the height parameter = 0, which can still end up drawing something, depending on the shape. One could also consider addition of nan_color/nan_shape properties which could be drawn to alert user to a gap in their data. Negative values necessitate introduction of the concept of the zero axis, from which they will be drawn DOWN, in contrast to positive values. Where should this axis be drawn? How should step_shape-s with inherent direction, e.g. arrows, be drawn for negatives? This will be addressed in the next commit. Values outside of min/max_value range can imply shapes that go beyond widget dimensions. An arrow shape that goes beyond the roof is visibly different from an arrow that saturates and merely touches the widget top, and the user may reasonably desire either behavior. Therefore this commit introduces a boolean clamp_bars property, which defaults to true for reasons of robustness and backward compatibility with the behaviour prior to this commit. The property of course affects only the display of the values, the actual user data stays intact. commit c9c24fb1ab741cd6bb8074971d568e0646c51532 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:29:01 2021 +0700 Use rectangles for drawing graph bars of all sizes I have benchmarked various bar drawing strategies and graph sizes and found out that drawing graph bars with rectangles is as fast or even marginally faster than using fat lines. Thus the code that tries to special-case 1px bars as lines seems to be not worth the additional complexity it introduces, and this commit removes it. Incidentally, I've also established, that rectangles are more robust. If one draws graph bars and feeds cairo very large coordinates (~2^62), then corresponding rectangles fail to render, but all the other rectangles in the same series get rendered successfully, whereas a single large coordinate in a move_to/line_to() sequence is enough to cause all lines in the batch to fail to render. commit bc7f4be5e06405b5aa7fb443a5ace2b9b9575b1f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:01:42 2021 +0700 Cache cairo methods used in the inner loop of graph:draw() I have benchmarked the draw() method, both on cairo xcb and on argb32 image surfaces, and have discovered that cairo context method calls are unnaturally slow, to the point, that almost nothing else in the function matters. For example, drawing bars with vertical lines made the whole draw() function almost 2 times slower than doing the same with rectangles, merely because the former requires two method calls (move_to/line_to), and the latter only one. Simply caching hotpath cairo methods in local variables, reduced the difference between the drawing methods to a negligible one and made the whole function 4 times faster on a typical 100-bar graph, (1.4ms to 0.33ms on my laptop) thus bringing closer my secret dream of drawing live audio data with awesome graphs (kidding). I guess, LGI does something not very optimal there, like gobject-introspecting the method every time, or something. There might be some untapped optimization potential, possibly in upstream, like a method cache, from which everything in awesome could benefit, even though I imagine not many things are so call-intensive as the graph widget, to really feel the difference. commit 538df25bdf02b53d0bbe81809eef2e6fdb05ff6f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 07:32:37 2021 +0700 Use 1-based index in the inner graph draw loop It's used there only once for a calculation and then several times to index some tables, and index 0 is bad for lua tables performance-wise. commit 7412546916cd0a0a490130a120255ffb719398b8 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:55:12 2021 +0700 Leave graph.max_value uninitialized at construction This is a user-visible change, but the previous behavior was probably not what user expected anyway, namely with graph.scale set the graph mysteriously couldn't zoom in onto values that are smaller than 1. commit eb81d7929a161e79c13262cfa73ac2e6a820e8ed Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:48:29 2021 +0700 Allow graph.max_value be nil This makes its behave symmetrically to min_value. Now it defaults to 1, when graph.scale is not set, and to the maximum value in the graph data, when graph.scale is set. commit f622a8430cbfde7007b58d7f4c9c76b08c91e997 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 12:01:13 2021 +0700 Calculate max values num in graph:add_value() more precisely At most so many values were kept in the widget values array, as there are pixels of usable widget width (i.e. sans border). This is exactly right for the surely most common case of graphs with 1px-wide bars, but is more than needed for graphs with step_width+step_spacing>1, and not enough for graphs with subpixel bars. Moreover this made graph autoscaling behave weirdly in graphs with thicker bars, because old values survived longer in the array and kept affecting min_value/max_value calculations even after they were shifted off-screen. The maximum number of kept values is now calculated exactly as the number of values that can be displayed at once, taking step_width and step_spacing into account. It might be worth adding a max_values_num property to the graph to let user override this calculation, in case they draw shapes in step_shape() outside of its supposed rectangle, so our assumptions of visibility are wrong, or the widget often gets resized, resulting in the annoyance of visibly truncated series. commit 0aa63249a18641251a1bf77423739164f0eab39f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 02:30:13 2021 +0700 Fix border_color/border_width bugs in graph widget add_value() wrongly assumed that border_width is always set, when border_color is, so if user set only the latter, an error occurred. add_value() assumed that border_width is always < width which led to awesome hanging up in an infinite loop, should that fail to be the case. The graph border_width property was driven by the border_color property, instead of the other way round. If border_color wasn't set, the border wasn't drawn all, even if user sets non-zero border_width, which is a confusing behavior, in contrast to all other widgets, which simply use a plethora of fallback colors to draw the border, when needed. This also meant that, despite being documented, the beautiful.graph_border_color fallback color could not and was not ever used. If the border_color was set though, an unset border_width was immediately assumed to be 1 instead of 0, also contrary to what other widgets do. Theme colors shouldn't influence widget layout, so this commit fixes this behavior to: 1) border_width always defaults to 0, if unset. 2) border_color falls back to beautiful.graph_border_color or white. Maybe one should also add a beautiful.graph_border_width theme variable, like some widgets do. commit dad4a3253b8fef971fb82720d2a407fcc0a879ad Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 03:11:27 2021 +0700 Implement multiple data series drawing for non-stacked graph The widget can already hold and draw multiple data groups, so it makes little sense to limit drawing only to the first group when graph.stack = false, probably leading to user's confusion. Now user can call add_value(v, group) with group > 1 and the data series will be drawn (over groups with lesser id-s) with graph.stack_colors[group] color, if stack_colors is set, or with graph.color, if stack_colors isn't set. Thus nothing visibly changes for all current users of non-stacked graphs with only single data series, but the new functionality is now there for those who need it. One can't fully emulate this functionality, e.g. with multiple graphs widgets in a wibox.layout.stack, because data series in a single widget can enjoy a common auto-scaling. commit 3e645c976184ab5f50d625271c25062db9ab7ed9 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:59:12 2021 +0700 Don't draw a stacked graph data group if there's no color for it If stack_colors[idx] is nil for the group `idx`, don't draw it at all (as opposed, e.g. to the effect of drawing it with a transparent color, or as though it had all values == 0, which both produce different results). This code is still subtly different from prior behavior, because now user can have a non-contiguous stack_colors table and turn the display of any data group on and off at will, whereas the code two commits earlier would just stop rendering as soon as ipairs(stack_colors) does, i.e. no data series were drawn past the first nil hole. This commit only looks large, but is mostly a whitespace change due to nesting from a pair of wrapping ifs. commit 8c0273786de1ade66bdd977699f98b588da9f487 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:32:42 2021 +0700 Unify stacked and non-stacked graph rendering Now stacked graphs support everything the nonstacked do, like step_width ~= 1 and custom step_shape-s. Technical notes: This code no longer calls cr:stroke() for every single value in a stacked graph, but rather paints a whole series and then cr:stroke/fill()-s it at all once with its color, improving performance considerably. Additionally the graph no longer relies on the presence of the stack_colors property to render series. It draws all data that it has, and, if there's no corresponding color in stack_colors, falls back to graph.color. This will be changed in the next commit to the prior behavior of only drawing those series for which there's a color in the stack_colors table, but that would've made this commit's diff much larger due to required nesting ifs over large code chunks. This commit also takes care to properly handle NaNs and negative values. Those are currently (almost) guaranteed to not occur in the values array due to clipping in graph:add_value(), but I intend to eventually drop the clipping, because it does nothing that draw() really relies upon, and allowing unclipped and NaN values is more flexible and less surprising, e.g. when user dynamically toggles graph.scale. Eventually I intend to implement drawing negative values in non-stacked graphs and possibly even in the stacked ones. commit 38c6589c51b2aa057d7c2f40182ecf32b5b2ff5e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 10:04:15 2021 +0700 Convert graph:draw() to use `self` commit f21f1b2550426df1c4ea8abde079b1ccef84b394 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 12:27:26 2021 +0700 Fix scaling for stacked graphs Stacked graphs have cumulative heights, values from all groups that end up in the same graph bar are effectively summed. Previous code didn't take that into account when calculating min/max_value for the purposes of rescaling, but simply examined every value separately. This code adds up all values up front and finds min/max among those, to determine proper scaling that would fit the graph exactly. commit 1ce4c7bf63bdd6c99c195d668347bec49355e921 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 09:49:35 2021 +0700 Draw shaped graph bars only up to the baseline Prior to this commit, every step_shape bar was, for some reason, drawn with height = full widget's height, regardless of the value it represents. So for the most bars their lower end was far outside the box and therefore clipped off. So e.g. one couldn't see the lower round edges of rounded rectangles. This commit fixes that by appropriately varying the height that is passed into step_shape(), so that lower edges of the bars always exactly touch the lower edge of the graph. If the user wants to restore the prior behavior, they can simply ignore the passed-in height in step_shape() and just always draw their shape with height = full widget height. But they couldn't replicate the effect of this commit in their rc.lua without essentially writing their own graph widget. commit e25185b08da23745b5815f85f6e9e43222ed2294 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 08:42:54 2021 +0700 Avoid incremental transform shifting during graph bar rendering Instead of letting addition errors and cognitive load accumulate simply use the same offset calculation that is used for lines and reset the transform back to the top-left corner after every drawn bar. It's conceptually simpler, and will be useful later for stacked graphs and more. It also fixes a discrepancy between plain and step_shape rendering. Prior to this commit, bar coloring occurred with a disturbed transform, which was centered at the last drawn bar, wherever it happened to be. This meant that nontrivial colors, like gradients and images, were arbitrarily offset when step_shape was used. commit f3e98866d8583c35d0b557c09f529299edc9ba2c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:45:29 2021 +0700 Refactor out graph bar height expression into a variable commit f54528d6892f5aaa4595917853c9e2130f743fdb Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:20:25 2021 +0700 Draw graph bars with lines only when step_width == 1 Before this commit graph bars of step_width < 1 were drawn with lines of width 1, but that's a bit unfortunate, because it led to much overpainting, while still ending up with a graphically incorrect result. Let's draw such bars with rectangles and let cairo do things with subpixels, getting more graphically accurate result, while still keeping the common case of step_width == 1 (supposedly) fast-drawn with lines. commit b6515dca67c18b78eb5657378131a62ab43d4cfc Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:54:07 2021 +0700 Fix unsharp edges when drawing graph bars with rectangles The 0.5 offset is only helpful for 1-width strokes, not for fills. commit 7f3db8f07e82f821bcd1dc418d66564f537609d6 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:21:01 2021 +0700 Do coordinate transformations even if the value isn't drawn The graph presently doesn't draw values < 0, but there was a discrepancy between drawing with and without step_shape. The default line/rectangle drawing reserves an empty place where the skipped value should've been, but no such empty place is reserved, when step_shape is used. This commit fixes that. Skipped values always have their empty space now. commit 7b731ebfe96af7a9dba9c57371da6761cf22c00c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:03:30 2021 +0700 Fix graph bar scaling when min_value is not 0 Previous calculation lead to underscaling of values so that even value = max_value wasn't drawn to full height. commit dca59fd8b7fff0e7e646f2dfc6bd92047028e5a8 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:18:40 2021 +0700 Calculate the horizontal graph bar offset only when needed commit 940ddf4b9825e9c3a91d276208c089d8c74e49db Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:17:23 2021 +0700 Call cr:move_to() only when drawing the graph with lines It's redundant otherwise and might even interfere with user's drawing in step_shape(). commit b5d5cd10ca29fb29cd751faaa95137c2fe89ff63 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:04:06 2021 +0700 Fix wrong horizontal offset calculations in widget.graph Indices in the loop are zero-based, so things like i-1 and i>1 aren't necessary and actually wouldn't have been correct even in a 1-based loop. step_shape = gears.shape.rectangle and step_shape = nil, which is supposed to default to rectangle according to docs, were drawn differently due to mismatched offset calculations, which led to problems especially noticeable when step_spacing is not zero, e.g: 1) whole graph shifted left too much, drawn partially off-screen. 2) whole graph shifted right too much, and the first bar placed closer to the rest than others. commit dc369fa198281e000afc1bd17326f3a264adf7d1 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 02:09:21 2021 +0700 Fix the "border_width affects graph bar width" bug Graph bars were drawn with lines of border_width thickness instead of 1, i.e. possibly too fat to be readable. commit 1ba73378cf29ed27a6ecd18716394779ceb543d9 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 00:13:03 2021 +0700 Simplify internal values representation in graph even more New values in graph._private.values were pushed to the end, and old values were removed from the beginning of the table. In draw() however newer values are drawn first, at the left edge of the graph, necessitating annoying idx = #values - idx conversions everywhere. In particular in stacked graph that means that values that are drawn together may have different indices. One has to shift the values array in add_value() either way, so one might as well insert new values at the beginning of the array and simplify the rest of the code. This doesn't seem like much yet, but it will prove to be much more convenient and less error-prone in what I'm going to do later. commit f70523ee68dbd6b67050ace4b82903a9b22440c7 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 13:35:10 2021 +0700 Simplify internal values representation in graph widget graph._private.values was either a table of values or a table of tables of values, depending on whether "stack" property is enabled, ... or a mix of both if user forgot to pass the second "group" parameter to graph:add_value(). This resulted in pretty fragile and confusing behavior, e.g. leaving user to wonder, why they see nothing more after setting stack=true for a graph in a previously working rc.lua, and demanding a very careful use of add_value() from the user, or else some values or even entire stacks would mysteriously vanish and errors in draw() get triggered, sometimes after a period of seemingly normal functioning. Now `values` is always a table of tables and the group parameter in add_value() defaults to 1, thus making the non-stacked graph a trivial variant of the stacked graph.
2021-05-20 17:14:03 +02:00
local function graph_preprocess_values(self, values, drawn_values_num)
-- TODO: elevate to function documentation, if we decide to make it public API.
--- Preprocesses values before drawing them.
-- This function can return up to 2 values: drawn_values and scaling_values
-- The former will be used as values to draw in place of the original data.
-- The latter will be used as values to scan for min/max value for scaling.
-- Either can be nil, which means: use values as is.
-- This default implementation is only used to implement
-- presumming values for stacked graphs.
if not self._private.stack then
return
end
-- Prepare to draw a stacked graph
-- summed_values[i] = sum [1,#values] of values[c][i]
local summed_values = {}
-- drawn_values[c][i] = sum [1,c] of values[c][i]
local drawn_values = {}
local nan = 0/0
-- Add stacked values up to get values we need to render
for group_idx, group_values in ipairs(values) do
local drawn_row = {}
drawn_values[group_idx] = drawn_row
if graph_should_draw_data_group(self, group_idx) then
for idx, value in ipairs(group_values) do
if idx > drawn_values_num then
break
2016-05-23 07:30:44 +02:00
end
graph: complete widget overhaul and bug fixes Squashed commit of the following: commit 69821f51fe1e8652715658543e50631ece495090 Author: Alex Belykh <albel727@ngs.ru> Date: Mon May 10 19:17:55 2021 +0700 Refactor property handling in wibox.widget.graph In addition, baseline_value, step_width and step_spacing properties are all set to their default non-nil numeric values during instance creation now. commit 842eb429bf5df4b2ba46b2e6f7646afe89b1a3c3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 9 06:18:45 2021 +0700 Make graph:draw_values() private There's no documentation nor confidence that it's a good API, so I'm hiding it for now. commit 720746780574e4bad14a71fc4174955139c2eb50 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 04:04:13 2021 +0700 Document graph_should_draw_data_group() for posterity commit 7c73f3754ce6ba86b118f1170a6f9e406ab4841d Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:59:21 2021 +0700 Make graph:should_draw_data_group() private There's no confidence whether it's a good API, so I'm hiding it for now. commit b3539e20adb6423118af726ae4f8751ef864b5d2 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:36:44 2021 +0700 Make graph:preprocess_values() private There's no confidence whether it's a good API, so I'm hiding it for now. commit 2df4400e780a6260f8d0ab1c60734fbb11bf3c4f Author: Alex Belykh <albel727@ngs.ru> Date: Thu May 6 10:05:23 2021 +0700 Add spec/wibox/widget/graph_spec.lua commit ce01a9771ab439b8df841f1565aa4aa8a44a5e0d Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 08:16:18 2021 +0700 Improve graph.capacity documentation commit e28fade8f655802f5bf89154169f68d256b8e332 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 07:36:01 2021 +0700 Document graph:compute_drawn_values_num() commit dd68332292465422d08b48e9f0229f143beccbc6 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 05:24:23 2021 +0700 Document graph:pick_data_group_color() method commit ca6dc55f77aca88a6e9cc90bb6fd0045739f41f5 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 04:13:58 2021 +0700 Guard against setting data groups sparsely in graph:add_value() commit cdea5f126f7984e72924f6dffbd312c4fe3b032f Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 02:36:12 2021 +0700 Refactor scaling and baseline choice out of graph:draw() commit 10135deafe8a9390ba66ab657ebb1fb41d549e8c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 21:34:48 2021 +0700 Document graph.nan_color and graph.stack properties commit 0d73bb84ac66b87e16aee472bd1f299549f66a1c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 20:49:24 2021 +0700 Use a better example in docs for the graph.stack property commit c27d160a0826d033d49d6141608c40aa8a0642fa Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 16:05:38 2021 +0700 Document graph.baseline_value property commit 459bc8176c13d4ea32af0dcdc2399d3b929b1c6a Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:53:47 2021 +0700 Document graph.clamp_bars property commit e732ee0b3b007d213e689b98cc00ae7cb141ae08 Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:34 2021 +0700 Improve graph:add_value() and :clear() documentation commit ae73e3aa9f8d60409cbe4cb4da9612f31af4f38e Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:11 2021 +0700 Improve graph min_value/max_value/scale documentation commit a7350996a9def773b91b1631e6c0507e33d8f93b Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:27:36 2021 +0700 Cleanup graph examples to show only relevant things in docs commit 30740c26f4f41c16f4c8a3c339bcb7bd299e14cd Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 14:04:36 2021 +0700 Rename graph.stack_colors property to `group_colors` The colors are used for stacked and non-stacked graphs alike, so the name didn't make sense. Also document and clarify some color stuff. commit 1ea01bfd410971cf0d0c25fd74cd764d5eb7279d Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 21:25:41 2021 +0700 Add an example file for code coverage of some fringe graph cases commit eae68e39553f88a6275a0c65bf040b5d4c262b54 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 20:23:32 2021 +0700 Add step_shape to the graph/nan_color example for code coverage commit 8027147bc2eab34a97b49a428fb8cef27f122bed Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:55:18 2021 +0700 Add example files for graph.nan_color/nan_indication properties commit 89a25a165c52b2c15b6c4d95ecb777a9067e66f9 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:33:07 2021 +0700 Make widget graph:add_value(v) default v to NaN 0 as a default makes no sense and silences the error that is failing to specify a value, to which the user should rather be alerted. The graph widget has sane NaN handling now and will do just that. commit 2835d552440be48d356a7f7b1d64963696dd550b Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:20:48 2021 +0700 Implement graph.nan_color and nan_indication properties Now, whenever there's a NaN among values and graph.nan_indication is set, the corresponding area, where the value bar should have been drawn, is filled with the nan_color from top to bottom. The painting is done after all data is rendered, to make sure that it won't be overpainted and go unnoticed. NaNs among graph's values are inevitably a sign of some error and it is arguably not a sane default behavior to simply not render them at all. The user should take immediate note of any errors by default, instead of wondering at a mysteriously empty graph widget, which is why nan_indication is set to true by default. If the user wants to silence NaNs, they can always simply set it to false. nan_color if unset, defaults to a noticeable yellow-black pattern. As a direct and planned consequence, stacked graphs with negative values in them trigger the NaN handling too. But silencing NaN handling for those graphs is also possible and semi-sane, because then NaNs and negatives are ignored and the graph behaves (almost) like they were zeros. commit 9c1f8f08b400e01e19c534f3810c9c9ab485760c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 26 08:10:19 2021 +0700 Refactor out graph_map_value_to_widget_coordinates() function commit 1905991ddecabdae3e15ffb8ee5b18a1bee1a711 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 28 13:39:29 2021 +0700 Add an example file for graph.baseline_value property commit b31b39d66f4c037a9484b71a3f910703ae01fac0 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 07:03:49 2021 +0700 Add an example file for graph.clamp_bars=false/true comparison commit a1fc850e3047f9ee2c9ddeffacfbabf3ded34171 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 04:06:45 2021 +0700 Add an example file for graphs with negative values and shapes commit 2f012f8ff1bb5094c14e724a61e59a2a3a296bc8 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 01:06:05 2021 +0700 Add an example file for a graph displaying negative values commit 7b14fbd59a4c4c51962fd3110a1a900dd5f4eb98 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 11:18:26 2021 +0700 Add an example file for graphs with step_width < 1 commit dfd9a54544b637e2fbe3d1325f9cc24b3f4d2313 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 10:17:38 2021 +0700 Add an example file for graph.stack=false/true comparison Should also demonstrate that multiple data groups are possible for non-stacked graphs too. commit 5b532106c3fd255f9b66a66cfb88d977b489ab5d Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 09:34:14 2021 +0700 Add an example file for disabling graph data groups with nil colors commit ac6f1083c02fcb428f089a616f9b0ebaa5496b6c Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:57:25 2021 +0700 Add an example file for step_shape/width/spacing on stacked graphs commit b3703db6a5139e73e72c1cfd198ece2cd9be7df2 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:43:56 2021 +0700 Add an example file for scale=false/true on stacked graph commit b000aed4a12cb44fb501e1c8432a71a14b2f8f3f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 20 08:22:19 2021 +0700 Document deprecation of graph's width and height commit cb0ad617bd6c83a499896797318e3b0b588c3566 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 07:53:37 2021 +0700 Deprecate width and height properties of wibox.widget.graph commit 8c64e14ad91a074b784166a7054e53444cccb9d2 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 04:37:59 2021 +0700 Refactor graph:draw() to separate concerns Firstly, summation of group values for stacked graphs is extracted into graph:preprocess_values(). This makes graph:draw() smaller and exposes the fact, that stacked graphs are almost entirely just a form of a more general concept: pre-render data manipulation. It's something a user might want to customize, and now they can do it by monkey-patching the method. Secondly, the logic for deciding, if and with which color a data group should be drawn, is extracted into graph:should_draw_data_group() and get_data_group_color() methods respectively. This makes it customizable too and makes more obvious the fact, that it is a general thing that has nothing to do with stacked graphs in particular. With this there are only 5 lines remaining in graph:draw(), that could be said to be specific to stacked graphs, all pertaining to the `prev_y` variable, which merely preserves the bar coordinates from a previous data group. commit 2a32b6305d893f413303728a0a3da1dcd9a5591e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 22:52:37 2021 +0700 Pointlessly optimize graph:add_value() 1) Purge old values first, then insert the new one, if needed. Since Lua grows arrays in power of two steps, and our guess_capacity() also takes care to return even integers, it would be a shame to overstep the capacity by one by inserting first and thus getting the table size doubled only to never use the most of it. 2) Use a removal procedure that doesn't call the slow length operator at all. Previous code called it twice (once in table.remove) for every removed value. commit 680ea8f5d81e9040afb4662b3229e937e4b89e02 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:23:38 2021 +0700 Set graph data series color before the painting loop commit b970f0f221ae76250f2a94433ff79d653caa6e21 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:05:37 2021 +0700 Drop a large no longer necessary `if` in graph:draw_values() This commit is almost purely a whitespace change. commit 6b4f0541c70ec99d7f6e276bae64939f337120d6 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:00:50 2021 +0700 Extract values rendering code into graph:draw_values() method This is to avoid ungodly if nesting levels, which are needed to bail out of drawing values early and skip to drawing the border. The next commit will get rid of the if. commit e0950e4f99d1ea1c4b8cea2cc67629fa40030c2a Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 14:50:20 2021 +0700 Track graph draw() usage to guess necessary array capacity The last step of divorcing add_values() from width calculations, while ensuring that user doesn't observe a lack of data to draw. The widget now tracks an approximate number of values that it has been asked to draw() in _private.last_drawn_values_num, and truncates its values array based on that (if not overridden by the capacity property). commit 2e3d542282f35e7c0fed454da0f2eada0fcf3b78 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 13:35:12 2021 +0700 Add `capacity` property to graph widget This is the second step of divorcing add_values() from calculations involving widths. How many values are stored and how many values are drawn are distinct concepts. There isn't even a single definitive "how many values are drawn" because the widget could be rendered in several places with distinct widths simultaneously, and in every such place the data must be drawn appropriately, e.g. with auto-scaling, that takes only the visible data points into account. And the draw() method can do that now. The default capacity value of nil uses heuristics based on widget's _private.width to guess some capacity that would tolerate some dynamic widget resizing. If the user finds the heuristic failing them, they can set the capacity property to the desired maximum number of values that will be kept in the _private.values array (in each data group). commit 08d4a80686f7fe363551c35589a460839f60853e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 03:27:18 2021 +0700 Extract values size computation into compute_drawn_values_num() This is the first step of making add_values() not concern itself (directly) with widget dimensions. The user can monkey-patch the method to override how many values are (logically) drawn, because it affects graph auto-scaling and can be useful for rendering of step_shape-s that paint outside of their step_width. I didn't make it a full-blown function-valued property, because it seems a fringe functionality, which I'm too lazy to document. commit 3c5617c98ab1147618ed22e8414a77fc56b45f6e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 02:32:29 2021 +0700 Copy graph widget prototype methods over in bulk commit 169538837ece41a03d02ba8fbc0e4097062b67a3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun Apr 25 05:58:01 2021 +0700 Remove graph.baseline_y property After giving it some thought I've concluded, that it is unlikely to be useful as it is now. I'd better reserve the name for some more sensible functionality, like fixing the baseline_value defined axis at the given widget coordinate, adjusting the scaling accordingly, so min_value/max_value won't generally correspond to widget top and bottom anymore. commit 202ffaa9a0b820c7c471e1121d01b91f8f76cd38 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 12:06:55 2021 +0700 Add baseline_y and baseline_value properties to graph widget Now values in graph have a *baseline*, tunable with baseline_y and baseline_value properties. It is the (for now invisible) horizontal line from which all bars in the graph grow. I call it a "baseline" instead of "axis", because it doesn't actually have to correspond to the 0 value. But it can, and actually does now by default. baseline_y and baseline_value properties determine the vertical placement of the baseline in the widget. baseline_y is the position of the baseline in normalized widget coordinates, e.g. 0 corresponds to widget's top, 1 - to widget's bottom, and 0.5 - to widget's middle. So if e.g. one sets baseline_y = 0, then no matter how the widget is resized and how its values are scaled, bars in it will be drawn starting from the top. baseline_value is the position of the baseline in value coordinates. So if one sets baseline_value = 10, then no matter what happens, bars for values greater than 10 will be drawn as growing up from this line, and bars for values smaller as growing down. Where and if it will be seen in the widget, depends on the scaling. baseline_value = 0 is the familiar zero axis. baseline_y is unset by default and has priority, i.e. if it's set, then baseline_value is ignored. baseline_value defaults to 0. Prior to this commit, bars were always drawn from the bottom edge of the widget, or, in the new terms, the widget had baseline_y = 1. But it was indistinguishable from baseline_value = 0, because the widget clamped all negative values to 0, so the difference between the two could never be observed. baseline_value = 0 is clearly more useful and familiar to people, so now it is in force by default. commit df7e19abaebf0a871b5ca217b4cc3365533267a6 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 11:18:12 2021 +0700 Move value clamping from graph:add_value() into draw() Previously all values were forcibly clamped between 0 (not tunable at all) and max_value (1 by default). Thus graphs with negative values were not an option. NaN values were conflated with zeros, even though it may be useful to maintain the distinction and render them differently. And if user happened to dynamically change the max_value property, they discovered, that their data got truncated. Preserving user data as it was given is more flexible and makes things easier to debug. This commit does exactly that. So what happens now to the newly allowed values during draw()? There are 3 new cases to consider: 1) NaNs 2) Negatives 3) Values outside of min_value..max_value range NaNs are presently simply not drawn. An empty place is reserved where the value should've been, but nothing else is done. This is distinct from how a zero is handled, e.g. if step_shape is set, it is called with the height parameter = 0, which can still end up drawing something, depending on the shape. One could also consider addition of nan_color/nan_shape properties which could be drawn to alert user to a gap in their data. Negative values necessitate introduction of the concept of the zero axis, from which they will be drawn DOWN, in contrast to positive values. Where should this axis be drawn? How should step_shape-s with inherent direction, e.g. arrows, be drawn for negatives? This will be addressed in the next commit. Values outside of min/max_value range can imply shapes that go beyond widget dimensions. An arrow shape that goes beyond the roof is visibly different from an arrow that saturates and merely touches the widget top, and the user may reasonably desire either behavior. Therefore this commit introduces a boolean clamp_bars property, which defaults to true for reasons of robustness and backward compatibility with the behaviour prior to this commit. The property of course affects only the display of the values, the actual user data stays intact. commit c9c24fb1ab741cd6bb8074971d568e0646c51532 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:29:01 2021 +0700 Use rectangles for drawing graph bars of all sizes I have benchmarked various bar drawing strategies and graph sizes and found out that drawing graph bars with rectangles is as fast or even marginally faster than using fat lines. Thus the code that tries to special-case 1px bars as lines seems to be not worth the additional complexity it introduces, and this commit removes it. Incidentally, I've also established, that rectangles are more robust. If one draws graph bars and feeds cairo very large coordinates (~2^62), then corresponding rectangles fail to render, but all the other rectangles in the same series get rendered successfully, whereas a single large coordinate in a move_to/line_to() sequence is enough to cause all lines in the batch to fail to render. commit bc7f4be5e06405b5aa7fb443a5ace2b9b9575b1f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:01:42 2021 +0700 Cache cairo methods used in the inner loop of graph:draw() I have benchmarked the draw() method, both on cairo xcb and on argb32 image surfaces, and have discovered that cairo context method calls are unnaturally slow, to the point, that almost nothing else in the function matters. For example, drawing bars with vertical lines made the whole draw() function almost 2 times slower than doing the same with rectangles, merely because the former requires two method calls (move_to/line_to), and the latter only one. Simply caching hotpath cairo methods in local variables, reduced the difference between the drawing methods to a negligible one and made the whole function 4 times faster on a typical 100-bar graph, (1.4ms to 0.33ms on my laptop) thus bringing closer my secret dream of drawing live audio data with awesome graphs (kidding). I guess, LGI does something not very optimal there, like gobject-introspecting the method every time, or something. There might be some untapped optimization potential, possibly in upstream, like a method cache, from which everything in awesome could benefit, even though I imagine not many things are so call-intensive as the graph widget, to really feel the difference. commit 538df25bdf02b53d0bbe81809eef2e6fdb05ff6f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 07:32:37 2021 +0700 Use 1-based index in the inner graph draw loop It's used there only once for a calculation and then several times to index some tables, and index 0 is bad for lua tables performance-wise. commit 7412546916cd0a0a490130a120255ffb719398b8 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:55:12 2021 +0700 Leave graph.max_value uninitialized at construction This is a user-visible change, but the previous behavior was probably not what user expected anyway, namely with graph.scale set the graph mysteriously couldn't zoom in onto values that are smaller than 1. commit eb81d7929a161e79c13262cfa73ac2e6a820e8ed Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:48:29 2021 +0700 Allow graph.max_value be nil This makes its behave symmetrically to min_value. Now it defaults to 1, when graph.scale is not set, and to the maximum value in the graph data, when graph.scale is set. commit f622a8430cbfde7007b58d7f4c9c76b08c91e997 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 12:01:13 2021 +0700 Calculate max values num in graph:add_value() more precisely At most so many values were kept in the widget values array, as there are pixels of usable widget width (i.e. sans border). This is exactly right for the surely most common case of graphs with 1px-wide bars, but is more than needed for graphs with step_width+step_spacing>1, and not enough for graphs with subpixel bars. Moreover this made graph autoscaling behave weirdly in graphs with thicker bars, because old values survived longer in the array and kept affecting min_value/max_value calculations even after they were shifted off-screen. The maximum number of kept values is now calculated exactly as the number of values that can be displayed at once, taking step_width and step_spacing into account. It might be worth adding a max_values_num property to the graph to let user override this calculation, in case they draw shapes in step_shape() outside of its supposed rectangle, so our assumptions of visibility are wrong, or the widget often gets resized, resulting in the annoyance of visibly truncated series. commit 0aa63249a18641251a1bf77423739164f0eab39f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 02:30:13 2021 +0700 Fix border_color/border_width bugs in graph widget add_value() wrongly assumed that border_width is always set, when border_color is, so if user set only the latter, an error occurred. add_value() assumed that border_width is always < width which led to awesome hanging up in an infinite loop, should that fail to be the case. The graph border_width property was driven by the border_color property, instead of the other way round. If border_color wasn't set, the border wasn't drawn all, even if user sets non-zero border_width, which is a confusing behavior, in contrast to all other widgets, which simply use a plethora of fallback colors to draw the border, when needed. This also meant that, despite being documented, the beautiful.graph_border_color fallback color could not and was not ever used. If the border_color was set though, an unset border_width was immediately assumed to be 1 instead of 0, also contrary to what other widgets do. Theme colors shouldn't influence widget layout, so this commit fixes this behavior to: 1) border_width always defaults to 0, if unset. 2) border_color falls back to beautiful.graph_border_color or white. Maybe one should also add a beautiful.graph_border_width theme variable, like some widgets do. commit dad4a3253b8fef971fb82720d2a407fcc0a879ad Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 03:11:27 2021 +0700 Implement multiple data series drawing for non-stacked graph The widget can already hold and draw multiple data groups, so it makes little sense to limit drawing only to the first group when graph.stack = false, probably leading to user's confusion. Now user can call add_value(v, group) with group > 1 and the data series will be drawn (over groups with lesser id-s) with graph.stack_colors[group] color, if stack_colors is set, or with graph.color, if stack_colors isn't set. Thus nothing visibly changes for all current users of non-stacked graphs with only single data series, but the new functionality is now there for those who need it. One can't fully emulate this functionality, e.g. with multiple graphs widgets in a wibox.layout.stack, because data series in a single widget can enjoy a common auto-scaling. commit 3e645c976184ab5f50d625271c25062db9ab7ed9 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:59:12 2021 +0700 Don't draw a stacked graph data group if there's no color for it If stack_colors[idx] is nil for the group `idx`, don't draw it at all (as opposed, e.g. to the effect of drawing it with a transparent color, or as though it had all values == 0, which both produce different results). This code is still subtly different from prior behavior, because now user can have a non-contiguous stack_colors table and turn the display of any data group on and off at will, whereas the code two commits earlier would just stop rendering as soon as ipairs(stack_colors) does, i.e. no data series were drawn past the first nil hole. This commit only looks large, but is mostly a whitespace change due to nesting from a pair of wrapping ifs. commit 8c0273786de1ade66bdd977699f98b588da9f487 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:32:42 2021 +0700 Unify stacked and non-stacked graph rendering Now stacked graphs support everything the nonstacked do, like step_width ~= 1 and custom step_shape-s. Technical notes: This code no longer calls cr:stroke() for every single value in a stacked graph, but rather paints a whole series and then cr:stroke/fill()-s it at all once with its color, improving performance considerably. Additionally the graph no longer relies on the presence of the stack_colors property to render series. It draws all data that it has, and, if there's no corresponding color in stack_colors, falls back to graph.color. This will be changed in the next commit to the prior behavior of only drawing those series for which there's a color in the stack_colors table, but that would've made this commit's diff much larger due to required nesting ifs over large code chunks. This commit also takes care to properly handle NaNs and negative values. Those are currently (almost) guaranteed to not occur in the values array due to clipping in graph:add_value(), but I intend to eventually drop the clipping, because it does nothing that draw() really relies upon, and allowing unclipped and NaN values is more flexible and less surprising, e.g. when user dynamically toggles graph.scale. Eventually I intend to implement drawing negative values in non-stacked graphs and possibly even in the stacked ones. commit 38c6589c51b2aa057d7c2f40182ecf32b5b2ff5e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 10:04:15 2021 +0700 Convert graph:draw() to use `self` commit f21f1b2550426df1c4ea8abde079b1ccef84b394 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 12:27:26 2021 +0700 Fix scaling for stacked graphs Stacked graphs have cumulative heights, values from all groups that end up in the same graph bar are effectively summed. Previous code didn't take that into account when calculating min/max_value for the purposes of rescaling, but simply examined every value separately. This code adds up all values up front and finds min/max among those, to determine proper scaling that would fit the graph exactly. commit 1ce4c7bf63bdd6c99c195d668347bec49355e921 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 09:49:35 2021 +0700 Draw shaped graph bars only up to the baseline Prior to this commit, every step_shape bar was, for some reason, drawn with height = full widget's height, regardless of the value it represents. So for the most bars their lower end was far outside the box and therefore clipped off. So e.g. one couldn't see the lower round edges of rounded rectangles. This commit fixes that by appropriately varying the height that is passed into step_shape(), so that lower edges of the bars always exactly touch the lower edge of the graph. If the user wants to restore the prior behavior, they can simply ignore the passed-in height in step_shape() and just always draw their shape with height = full widget height. But they couldn't replicate the effect of this commit in their rc.lua without essentially writing their own graph widget. commit e25185b08da23745b5815f85f6e9e43222ed2294 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 08:42:54 2021 +0700 Avoid incremental transform shifting during graph bar rendering Instead of letting addition errors and cognitive load accumulate simply use the same offset calculation that is used for lines and reset the transform back to the top-left corner after every drawn bar. It's conceptually simpler, and will be useful later for stacked graphs and more. It also fixes a discrepancy between plain and step_shape rendering. Prior to this commit, bar coloring occurred with a disturbed transform, which was centered at the last drawn bar, wherever it happened to be. This meant that nontrivial colors, like gradients and images, were arbitrarily offset when step_shape was used. commit f3e98866d8583c35d0b557c09f529299edc9ba2c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:45:29 2021 +0700 Refactor out graph bar height expression into a variable commit f54528d6892f5aaa4595917853c9e2130f743fdb Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:20:25 2021 +0700 Draw graph bars with lines only when step_width == 1 Before this commit graph bars of step_width < 1 were drawn with lines of width 1, but that's a bit unfortunate, because it led to much overpainting, while still ending up with a graphically incorrect result. Let's draw such bars with rectangles and let cairo do things with subpixels, getting more graphically accurate result, while still keeping the common case of step_width == 1 (supposedly) fast-drawn with lines. commit b6515dca67c18b78eb5657378131a62ab43d4cfc Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:54:07 2021 +0700 Fix unsharp edges when drawing graph bars with rectangles The 0.5 offset is only helpful for 1-width strokes, not for fills. commit 7f3db8f07e82f821bcd1dc418d66564f537609d6 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:21:01 2021 +0700 Do coordinate transformations even if the value isn't drawn The graph presently doesn't draw values < 0, but there was a discrepancy between drawing with and without step_shape. The default line/rectangle drawing reserves an empty place where the skipped value should've been, but no such empty place is reserved, when step_shape is used. This commit fixes that. Skipped values always have their empty space now. commit 7b731ebfe96af7a9dba9c57371da6761cf22c00c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:03:30 2021 +0700 Fix graph bar scaling when min_value is not 0 Previous calculation lead to underscaling of values so that even value = max_value wasn't drawn to full height. commit dca59fd8b7fff0e7e646f2dfc6bd92047028e5a8 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:18:40 2021 +0700 Calculate the horizontal graph bar offset only when needed commit 940ddf4b9825e9c3a91d276208c089d8c74e49db Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:17:23 2021 +0700 Call cr:move_to() only when drawing the graph with lines It's redundant otherwise and might even interfere with user's drawing in step_shape(). commit b5d5cd10ca29fb29cd751faaa95137c2fe89ff63 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:04:06 2021 +0700 Fix wrong horizontal offset calculations in widget.graph Indices in the loop are zero-based, so things like i-1 and i>1 aren't necessary and actually wouldn't have been correct even in a 1-based loop. step_shape = gears.shape.rectangle and step_shape = nil, which is supposed to default to rectangle according to docs, were drawn differently due to mismatched offset calculations, which led to problems especially noticeable when step_spacing is not zero, e.g: 1) whole graph shifted left too much, drawn partially off-screen. 2) whole graph shifted right too much, and the first bar placed closer to the rest than others. commit dc369fa198281e000afc1bd17326f3a264adf7d1 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 02:09:21 2021 +0700 Fix the "border_width affects graph bar width" bug Graph bars were drawn with lines of border_width thickness instead of 1, i.e. possibly too fat to be readable. commit 1ba73378cf29ed27a6ecd18716394779ceb543d9 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 00:13:03 2021 +0700 Simplify internal values representation in graph even more New values in graph._private.values were pushed to the end, and old values were removed from the beginning of the table. In draw() however newer values are drawn first, at the left edge of the graph, necessitating annoying idx = #values - idx conversions everywhere. In particular in stacked graph that means that values that are drawn together may have different indices. One has to shift the values array in add_value() either way, so one might as well insert new values at the beginning of the array and simplify the rest of the code. This doesn't seem like much yet, but it will prove to be much more convenient and less error-prone in what I'm going to do later. commit f70523ee68dbd6b67050ace4b82903a9b22440c7 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 13:35:10 2021 +0700 Simplify internal values representation in graph widget graph._private.values was either a table of values or a table of tables of values, depending on whether "stack" property is enabled, ... or a mix of both if user forgot to pass the second "group" parameter to graph:add_value(). This resulted in pretty fragile and confusing behavior, e.g. leaving user to wonder, why they see nothing more after setting stack=true for a graph in a previously working rc.lua, and demanding a very careful use of add_value() from the user, or else some values or even entire stacks would mysteriously vanish and errors in draw() get triggered, sometimes after a period of seemingly normal functioning. Now `values` is always a table of tables and the group parameter in add_value() defaults to 1, thus making the non-stacked graph a trivial variant of the stacked graph.
2021-05-20 17:14:03 +02:00
-- drawn_values will have NaN values in it due to negatives/NaNs in input.
-- we can't simply treat them like zeros during rendering,
-- in case step_shape() draws visible shapes for actual zero values too.
local acc = summed_values[idx] or 0
if value >= 0 then
acc = acc + value
drawn_row[idx] = acc
else
drawn_row[idx] = nan
2016-05-23 07:30:44 +02:00
end
graph: complete widget overhaul and bug fixes Squashed commit of the following: commit 69821f51fe1e8652715658543e50631ece495090 Author: Alex Belykh <albel727@ngs.ru> Date: Mon May 10 19:17:55 2021 +0700 Refactor property handling in wibox.widget.graph In addition, baseline_value, step_width and step_spacing properties are all set to their default non-nil numeric values during instance creation now. commit 842eb429bf5df4b2ba46b2e6f7646afe89b1a3c3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 9 06:18:45 2021 +0700 Make graph:draw_values() private There's no documentation nor confidence that it's a good API, so I'm hiding it for now. commit 720746780574e4bad14a71fc4174955139c2eb50 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 04:04:13 2021 +0700 Document graph_should_draw_data_group() for posterity commit 7c73f3754ce6ba86b118f1170a6f9e406ab4841d Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:59:21 2021 +0700 Make graph:should_draw_data_group() private There's no confidence whether it's a good API, so I'm hiding it for now. commit b3539e20adb6423118af726ae4f8751ef864b5d2 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:36:44 2021 +0700 Make graph:preprocess_values() private There's no confidence whether it's a good API, so I'm hiding it for now. commit 2df4400e780a6260f8d0ab1c60734fbb11bf3c4f Author: Alex Belykh <albel727@ngs.ru> Date: Thu May 6 10:05:23 2021 +0700 Add spec/wibox/widget/graph_spec.lua commit ce01a9771ab439b8df841f1565aa4aa8a44a5e0d Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 08:16:18 2021 +0700 Improve graph.capacity documentation commit e28fade8f655802f5bf89154169f68d256b8e332 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 07:36:01 2021 +0700 Document graph:compute_drawn_values_num() commit dd68332292465422d08b48e9f0229f143beccbc6 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 05:24:23 2021 +0700 Document graph:pick_data_group_color() method commit ca6dc55f77aca88a6e9cc90bb6fd0045739f41f5 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 04:13:58 2021 +0700 Guard against setting data groups sparsely in graph:add_value() commit cdea5f126f7984e72924f6dffbd312c4fe3b032f Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 02:36:12 2021 +0700 Refactor scaling and baseline choice out of graph:draw() commit 10135deafe8a9390ba66ab657ebb1fb41d549e8c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 21:34:48 2021 +0700 Document graph.nan_color and graph.stack properties commit 0d73bb84ac66b87e16aee472bd1f299549f66a1c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 20:49:24 2021 +0700 Use a better example in docs for the graph.stack property commit c27d160a0826d033d49d6141608c40aa8a0642fa Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 16:05:38 2021 +0700 Document graph.baseline_value property commit 459bc8176c13d4ea32af0dcdc2399d3b929b1c6a Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:53:47 2021 +0700 Document graph.clamp_bars property commit e732ee0b3b007d213e689b98cc00ae7cb141ae08 Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:34 2021 +0700 Improve graph:add_value() and :clear() documentation commit ae73e3aa9f8d60409cbe4cb4da9612f31af4f38e Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:11 2021 +0700 Improve graph min_value/max_value/scale documentation commit a7350996a9def773b91b1631e6c0507e33d8f93b Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:27:36 2021 +0700 Cleanup graph examples to show only relevant things in docs commit 30740c26f4f41c16f4c8a3c339bcb7bd299e14cd Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 14:04:36 2021 +0700 Rename graph.stack_colors property to `group_colors` The colors are used for stacked and non-stacked graphs alike, so the name didn't make sense. Also document and clarify some color stuff. commit 1ea01bfd410971cf0d0c25fd74cd764d5eb7279d Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 21:25:41 2021 +0700 Add an example file for code coverage of some fringe graph cases commit eae68e39553f88a6275a0c65bf040b5d4c262b54 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 20:23:32 2021 +0700 Add step_shape to the graph/nan_color example for code coverage commit 8027147bc2eab34a97b49a428fb8cef27f122bed Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:55:18 2021 +0700 Add example files for graph.nan_color/nan_indication properties commit 89a25a165c52b2c15b6c4d95ecb777a9067e66f9 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:33:07 2021 +0700 Make widget graph:add_value(v) default v to NaN 0 as a default makes no sense and silences the error that is failing to specify a value, to which the user should rather be alerted. The graph widget has sane NaN handling now and will do just that. commit 2835d552440be48d356a7f7b1d64963696dd550b Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:20:48 2021 +0700 Implement graph.nan_color and nan_indication properties Now, whenever there's a NaN among values and graph.nan_indication is set, the corresponding area, where the value bar should have been drawn, is filled with the nan_color from top to bottom. The painting is done after all data is rendered, to make sure that it won't be overpainted and go unnoticed. NaNs among graph's values are inevitably a sign of some error and it is arguably not a sane default behavior to simply not render them at all. The user should take immediate note of any errors by default, instead of wondering at a mysteriously empty graph widget, which is why nan_indication is set to true by default. If the user wants to silence NaNs, they can always simply set it to false. nan_color if unset, defaults to a noticeable yellow-black pattern. As a direct and planned consequence, stacked graphs with negative values in them trigger the NaN handling too. But silencing NaN handling for those graphs is also possible and semi-sane, because then NaNs and negatives are ignored and the graph behaves (almost) like they were zeros. commit 9c1f8f08b400e01e19c534f3810c9c9ab485760c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 26 08:10:19 2021 +0700 Refactor out graph_map_value_to_widget_coordinates() function commit 1905991ddecabdae3e15ffb8ee5b18a1bee1a711 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 28 13:39:29 2021 +0700 Add an example file for graph.baseline_value property commit b31b39d66f4c037a9484b71a3f910703ae01fac0 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 07:03:49 2021 +0700 Add an example file for graph.clamp_bars=false/true comparison commit a1fc850e3047f9ee2c9ddeffacfbabf3ded34171 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 04:06:45 2021 +0700 Add an example file for graphs with negative values and shapes commit 2f012f8ff1bb5094c14e724a61e59a2a3a296bc8 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 01:06:05 2021 +0700 Add an example file for a graph displaying negative values commit 7b14fbd59a4c4c51962fd3110a1a900dd5f4eb98 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 11:18:26 2021 +0700 Add an example file for graphs with step_width < 1 commit dfd9a54544b637e2fbe3d1325f9cc24b3f4d2313 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 10:17:38 2021 +0700 Add an example file for graph.stack=false/true comparison Should also demonstrate that multiple data groups are possible for non-stacked graphs too. commit 5b532106c3fd255f9b66a66cfb88d977b489ab5d Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 09:34:14 2021 +0700 Add an example file for disabling graph data groups with nil colors commit ac6f1083c02fcb428f089a616f9b0ebaa5496b6c Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:57:25 2021 +0700 Add an example file for step_shape/width/spacing on stacked graphs commit b3703db6a5139e73e72c1cfd198ece2cd9be7df2 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:43:56 2021 +0700 Add an example file for scale=false/true on stacked graph commit b000aed4a12cb44fb501e1c8432a71a14b2f8f3f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 20 08:22:19 2021 +0700 Document deprecation of graph's width and height commit cb0ad617bd6c83a499896797318e3b0b588c3566 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 07:53:37 2021 +0700 Deprecate width and height properties of wibox.widget.graph commit 8c64e14ad91a074b784166a7054e53444cccb9d2 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 04:37:59 2021 +0700 Refactor graph:draw() to separate concerns Firstly, summation of group values for stacked graphs is extracted into graph:preprocess_values(). This makes graph:draw() smaller and exposes the fact, that stacked graphs are almost entirely just a form of a more general concept: pre-render data manipulation. It's something a user might want to customize, and now they can do it by monkey-patching the method. Secondly, the logic for deciding, if and with which color a data group should be drawn, is extracted into graph:should_draw_data_group() and get_data_group_color() methods respectively. This makes it customizable too and makes more obvious the fact, that it is a general thing that has nothing to do with stacked graphs in particular. With this there are only 5 lines remaining in graph:draw(), that could be said to be specific to stacked graphs, all pertaining to the `prev_y` variable, which merely preserves the bar coordinates from a previous data group. commit 2a32b6305d893f413303728a0a3da1dcd9a5591e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 22:52:37 2021 +0700 Pointlessly optimize graph:add_value() 1) Purge old values first, then insert the new one, if needed. Since Lua grows arrays in power of two steps, and our guess_capacity() also takes care to return even integers, it would be a shame to overstep the capacity by one by inserting first and thus getting the table size doubled only to never use the most of it. 2) Use a removal procedure that doesn't call the slow length operator at all. Previous code called it twice (once in table.remove) for every removed value. commit 680ea8f5d81e9040afb4662b3229e937e4b89e02 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:23:38 2021 +0700 Set graph data series color before the painting loop commit b970f0f221ae76250f2a94433ff79d653caa6e21 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:05:37 2021 +0700 Drop a large no longer necessary `if` in graph:draw_values() This commit is almost purely a whitespace change. commit 6b4f0541c70ec99d7f6e276bae64939f337120d6 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:00:50 2021 +0700 Extract values rendering code into graph:draw_values() method This is to avoid ungodly if nesting levels, which are needed to bail out of drawing values early and skip to drawing the border. The next commit will get rid of the if. commit e0950e4f99d1ea1c4b8cea2cc67629fa40030c2a Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 14:50:20 2021 +0700 Track graph draw() usage to guess necessary array capacity The last step of divorcing add_values() from width calculations, while ensuring that user doesn't observe a lack of data to draw. The widget now tracks an approximate number of values that it has been asked to draw() in _private.last_drawn_values_num, and truncates its values array based on that (if not overridden by the capacity property). commit 2e3d542282f35e7c0fed454da0f2eada0fcf3b78 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 13:35:12 2021 +0700 Add `capacity` property to graph widget This is the second step of divorcing add_values() from calculations involving widths. How many values are stored and how many values are drawn are distinct concepts. There isn't even a single definitive "how many values are drawn" because the widget could be rendered in several places with distinct widths simultaneously, and in every such place the data must be drawn appropriately, e.g. with auto-scaling, that takes only the visible data points into account. And the draw() method can do that now. The default capacity value of nil uses heuristics based on widget's _private.width to guess some capacity that would tolerate some dynamic widget resizing. If the user finds the heuristic failing them, they can set the capacity property to the desired maximum number of values that will be kept in the _private.values array (in each data group). commit 08d4a80686f7fe363551c35589a460839f60853e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 03:27:18 2021 +0700 Extract values size computation into compute_drawn_values_num() This is the first step of making add_values() not concern itself (directly) with widget dimensions. The user can monkey-patch the method to override how many values are (logically) drawn, because it affects graph auto-scaling and can be useful for rendering of step_shape-s that paint outside of their step_width. I didn't make it a full-blown function-valued property, because it seems a fringe functionality, which I'm too lazy to document. commit 3c5617c98ab1147618ed22e8414a77fc56b45f6e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 02:32:29 2021 +0700 Copy graph widget prototype methods over in bulk commit 169538837ece41a03d02ba8fbc0e4097062b67a3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun Apr 25 05:58:01 2021 +0700 Remove graph.baseline_y property After giving it some thought I've concluded, that it is unlikely to be useful as it is now. I'd better reserve the name for some more sensible functionality, like fixing the baseline_value defined axis at the given widget coordinate, adjusting the scaling accordingly, so min_value/max_value won't generally correspond to widget top and bottom anymore. commit 202ffaa9a0b820c7c471e1121d01b91f8f76cd38 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 12:06:55 2021 +0700 Add baseline_y and baseline_value properties to graph widget Now values in graph have a *baseline*, tunable with baseline_y and baseline_value properties. It is the (for now invisible) horizontal line from which all bars in the graph grow. I call it a "baseline" instead of "axis", because it doesn't actually have to correspond to the 0 value. But it can, and actually does now by default. baseline_y and baseline_value properties determine the vertical placement of the baseline in the widget. baseline_y is the position of the baseline in normalized widget coordinates, e.g. 0 corresponds to widget's top, 1 - to widget's bottom, and 0.5 - to widget's middle. So if e.g. one sets baseline_y = 0, then no matter how the widget is resized and how its values are scaled, bars in it will be drawn starting from the top. baseline_value is the position of the baseline in value coordinates. So if one sets baseline_value = 10, then no matter what happens, bars for values greater than 10 will be drawn as growing up from this line, and bars for values smaller as growing down. Where and if it will be seen in the widget, depends on the scaling. baseline_value = 0 is the familiar zero axis. baseline_y is unset by default and has priority, i.e. if it's set, then baseline_value is ignored. baseline_value defaults to 0. Prior to this commit, bars were always drawn from the bottom edge of the widget, or, in the new terms, the widget had baseline_y = 1. But it was indistinguishable from baseline_value = 0, because the widget clamped all negative values to 0, so the difference between the two could never be observed. baseline_value = 0 is clearly more useful and familiar to people, so now it is in force by default. commit df7e19abaebf0a871b5ca217b4cc3365533267a6 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 11:18:12 2021 +0700 Move value clamping from graph:add_value() into draw() Previously all values were forcibly clamped between 0 (not tunable at all) and max_value (1 by default). Thus graphs with negative values were not an option. NaN values were conflated with zeros, even though it may be useful to maintain the distinction and render them differently. And if user happened to dynamically change the max_value property, they discovered, that their data got truncated. Preserving user data as it was given is more flexible and makes things easier to debug. This commit does exactly that. So what happens now to the newly allowed values during draw()? There are 3 new cases to consider: 1) NaNs 2) Negatives 3) Values outside of min_value..max_value range NaNs are presently simply not drawn. An empty place is reserved where the value should've been, but nothing else is done. This is distinct from how a zero is handled, e.g. if step_shape is set, it is called with the height parameter = 0, which can still end up drawing something, depending on the shape. One could also consider addition of nan_color/nan_shape properties which could be drawn to alert user to a gap in their data. Negative values necessitate introduction of the concept of the zero axis, from which they will be drawn DOWN, in contrast to positive values. Where should this axis be drawn? How should step_shape-s with inherent direction, e.g. arrows, be drawn for negatives? This will be addressed in the next commit. Values outside of min/max_value range can imply shapes that go beyond widget dimensions. An arrow shape that goes beyond the roof is visibly different from an arrow that saturates and merely touches the widget top, and the user may reasonably desire either behavior. Therefore this commit introduces a boolean clamp_bars property, which defaults to true for reasons of robustness and backward compatibility with the behaviour prior to this commit. The property of course affects only the display of the values, the actual user data stays intact. commit c9c24fb1ab741cd6bb8074971d568e0646c51532 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:29:01 2021 +0700 Use rectangles for drawing graph bars of all sizes I have benchmarked various bar drawing strategies and graph sizes and found out that drawing graph bars with rectangles is as fast or even marginally faster than using fat lines. Thus the code that tries to special-case 1px bars as lines seems to be not worth the additional complexity it introduces, and this commit removes it. Incidentally, I've also established, that rectangles are more robust. If one draws graph bars and feeds cairo very large coordinates (~2^62), then corresponding rectangles fail to render, but all the other rectangles in the same series get rendered successfully, whereas a single large coordinate in a move_to/line_to() sequence is enough to cause all lines in the batch to fail to render. commit bc7f4be5e06405b5aa7fb443a5ace2b9b9575b1f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:01:42 2021 +0700 Cache cairo methods used in the inner loop of graph:draw() I have benchmarked the draw() method, both on cairo xcb and on argb32 image surfaces, and have discovered that cairo context method calls are unnaturally slow, to the point, that almost nothing else in the function matters. For example, drawing bars with vertical lines made the whole draw() function almost 2 times slower than doing the same with rectangles, merely because the former requires two method calls (move_to/line_to), and the latter only one. Simply caching hotpath cairo methods in local variables, reduced the difference between the drawing methods to a negligible one and made the whole function 4 times faster on a typical 100-bar graph, (1.4ms to 0.33ms on my laptop) thus bringing closer my secret dream of drawing live audio data with awesome graphs (kidding). I guess, LGI does something not very optimal there, like gobject-introspecting the method every time, or something. There might be some untapped optimization potential, possibly in upstream, like a method cache, from which everything in awesome could benefit, even though I imagine not many things are so call-intensive as the graph widget, to really feel the difference. commit 538df25bdf02b53d0bbe81809eef2e6fdb05ff6f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 07:32:37 2021 +0700 Use 1-based index in the inner graph draw loop It's used there only once for a calculation and then several times to index some tables, and index 0 is bad for lua tables performance-wise. commit 7412546916cd0a0a490130a120255ffb719398b8 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:55:12 2021 +0700 Leave graph.max_value uninitialized at construction This is a user-visible change, but the previous behavior was probably not what user expected anyway, namely with graph.scale set the graph mysteriously couldn't zoom in onto values that are smaller than 1. commit eb81d7929a161e79c13262cfa73ac2e6a820e8ed Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:48:29 2021 +0700 Allow graph.max_value be nil This makes its behave symmetrically to min_value. Now it defaults to 1, when graph.scale is not set, and to the maximum value in the graph data, when graph.scale is set. commit f622a8430cbfde7007b58d7f4c9c76b08c91e997 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 12:01:13 2021 +0700 Calculate max values num in graph:add_value() more precisely At most so many values were kept in the widget values array, as there are pixels of usable widget width (i.e. sans border). This is exactly right for the surely most common case of graphs with 1px-wide bars, but is more than needed for graphs with step_width+step_spacing>1, and not enough for graphs with subpixel bars. Moreover this made graph autoscaling behave weirdly in graphs with thicker bars, because old values survived longer in the array and kept affecting min_value/max_value calculations even after they were shifted off-screen. The maximum number of kept values is now calculated exactly as the number of values that can be displayed at once, taking step_width and step_spacing into account. It might be worth adding a max_values_num property to the graph to let user override this calculation, in case they draw shapes in step_shape() outside of its supposed rectangle, so our assumptions of visibility are wrong, or the widget often gets resized, resulting in the annoyance of visibly truncated series. commit 0aa63249a18641251a1bf77423739164f0eab39f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 02:30:13 2021 +0700 Fix border_color/border_width bugs in graph widget add_value() wrongly assumed that border_width is always set, when border_color is, so if user set only the latter, an error occurred. add_value() assumed that border_width is always < width which led to awesome hanging up in an infinite loop, should that fail to be the case. The graph border_width property was driven by the border_color property, instead of the other way round. If border_color wasn't set, the border wasn't drawn all, even if user sets non-zero border_width, which is a confusing behavior, in contrast to all other widgets, which simply use a plethora of fallback colors to draw the border, when needed. This also meant that, despite being documented, the beautiful.graph_border_color fallback color could not and was not ever used. If the border_color was set though, an unset border_width was immediately assumed to be 1 instead of 0, also contrary to what other widgets do. Theme colors shouldn't influence widget layout, so this commit fixes this behavior to: 1) border_width always defaults to 0, if unset. 2) border_color falls back to beautiful.graph_border_color or white. Maybe one should also add a beautiful.graph_border_width theme variable, like some widgets do. commit dad4a3253b8fef971fb82720d2a407fcc0a879ad Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 03:11:27 2021 +0700 Implement multiple data series drawing for non-stacked graph The widget can already hold and draw multiple data groups, so it makes little sense to limit drawing only to the first group when graph.stack = false, probably leading to user's confusion. Now user can call add_value(v, group) with group > 1 and the data series will be drawn (over groups with lesser id-s) with graph.stack_colors[group] color, if stack_colors is set, or with graph.color, if stack_colors isn't set. Thus nothing visibly changes for all current users of non-stacked graphs with only single data series, but the new functionality is now there for those who need it. One can't fully emulate this functionality, e.g. with multiple graphs widgets in a wibox.layout.stack, because data series in a single widget can enjoy a common auto-scaling. commit 3e645c976184ab5f50d625271c25062db9ab7ed9 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:59:12 2021 +0700 Don't draw a stacked graph data group if there's no color for it If stack_colors[idx] is nil for the group `idx`, don't draw it at all (as opposed, e.g. to the effect of drawing it with a transparent color, or as though it had all values == 0, which both produce different results). This code is still subtly different from prior behavior, because now user can have a non-contiguous stack_colors table and turn the display of any data group on and off at will, whereas the code two commits earlier would just stop rendering as soon as ipairs(stack_colors) does, i.e. no data series were drawn past the first nil hole. This commit only looks large, but is mostly a whitespace change due to nesting from a pair of wrapping ifs. commit 8c0273786de1ade66bdd977699f98b588da9f487 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:32:42 2021 +0700 Unify stacked and non-stacked graph rendering Now stacked graphs support everything the nonstacked do, like step_width ~= 1 and custom step_shape-s. Technical notes: This code no longer calls cr:stroke() for every single value in a stacked graph, but rather paints a whole series and then cr:stroke/fill()-s it at all once with its color, improving performance considerably. Additionally the graph no longer relies on the presence of the stack_colors property to render series. It draws all data that it has, and, if there's no corresponding color in stack_colors, falls back to graph.color. This will be changed in the next commit to the prior behavior of only drawing those series for which there's a color in the stack_colors table, but that would've made this commit's diff much larger due to required nesting ifs over large code chunks. This commit also takes care to properly handle NaNs and negative values. Those are currently (almost) guaranteed to not occur in the values array due to clipping in graph:add_value(), but I intend to eventually drop the clipping, because it does nothing that draw() really relies upon, and allowing unclipped and NaN values is more flexible and less surprising, e.g. when user dynamically toggles graph.scale. Eventually I intend to implement drawing negative values in non-stacked graphs and possibly even in the stacked ones. commit 38c6589c51b2aa057d7c2f40182ecf32b5b2ff5e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 10:04:15 2021 +0700 Convert graph:draw() to use `self` commit f21f1b2550426df1c4ea8abde079b1ccef84b394 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 12:27:26 2021 +0700 Fix scaling for stacked graphs Stacked graphs have cumulative heights, values from all groups that end up in the same graph bar are effectively summed. Previous code didn't take that into account when calculating min/max_value for the purposes of rescaling, but simply examined every value separately. This code adds up all values up front and finds min/max among those, to determine proper scaling that would fit the graph exactly. commit 1ce4c7bf63bdd6c99c195d668347bec49355e921 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 09:49:35 2021 +0700 Draw shaped graph bars only up to the baseline Prior to this commit, every step_shape bar was, for some reason, drawn with height = full widget's height, regardless of the value it represents. So for the most bars their lower end was far outside the box and therefore clipped off. So e.g. one couldn't see the lower round edges of rounded rectangles. This commit fixes that by appropriately varying the height that is passed into step_shape(), so that lower edges of the bars always exactly touch the lower edge of the graph. If the user wants to restore the prior behavior, they can simply ignore the passed-in height in step_shape() and just always draw their shape with height = full widget height. But they couldn't replicate the effect of this commit in their rc.lua without essentially writing their own graph widget. commit e25185b08da23745b5815f85f6e9e43222ed2294 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 08:42:54 2021 +0700 Avoid incremental transform shifting during graph bar rendering Instead of letting addition errors and cognitive load accumulate simply use the same offset calculation that is used for lines and reset the transform back to the top-left corner after every drawn bar. It's conceptually simpler, and will be useful later for stacked graphs and more. It also fixes a discrepancy between plain and step_shape rendering. Prior to this commit, bar coloring occurred with a disturbed transform, which was centered at the last drawn bar, wherever it happened to be. This meant that nontrivial colors, like gradients and images, were arbitrarily offset when step_shape was used. commit f3e98866d8583c35d0b557c09f529299edc9ba2c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:45:29 2021 +0700 Refactor out graph bar height expression into a variable commit f54528d6892f5aaa4595917853c9e2130f743fdb Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:20:25 2021 +0700 Draw graph bars with lines only when step_width == 1 Before this commit graph bars of step_width < 1 were drawn with lines of width 1, but that's a bit unfortunate, because it led to much overpainting, while still ending up with a graphically incorrect result. Let's draw such bars with rectangles and let cairo do things with subpixels, getting more graphically accurate result, while still keeping the common case of step_width == 1 (supposedly) fast-drawn with lines. commit b6515dca67c18b78eb5657378131a62ab43d4cfc Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:54:07 2021 +0700 Fix unsharp edges when drawing graph bars with rectangles The 0.5 offset is only helpful for 1-width strokes, not for fills. commit 7f3db8f07e82f821bcd1dc418d66564f537609d6 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:21:01 2021 +0700 Do coordinate transformations even if the value isn't drawn The graph presently doesn't draw values < 0, but there was a discrepancy between drawing with and without step_shape. The default line/rectangle drawing reserves an empty place where the skipped value should've been, but no such empty place is reserved, when step_shape is used. This commit fixes that. Skipped values always have their empty space now. commit 7b731ebfe96af7a9dba9c57371da6761cf22c00c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:03:30 2021 +0700 Fix graph bar scaling when min_value is not 0 Previous calculation lead to underscaling of values so that even value = max_value wasn't drawn to full height. commit dca59fd8b7fff0e7e646f2dfc6bd92047028e5a8 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:18:40 2021 +0700 Calculate the horizontal graph bar offset only when needed commit 940ddf4b9825e9c3a91d276208c089d8c74e49db Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:17:23 2021 +0700 Call cr:move_to() only when drawing the graph with lines It's redundant otherwise and might even interfere with user's drawing in step_shape(). commit b5d5cd10ca29fb29cd751faaa95137c2fe89ff63 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:04:06 2021 +0700 Fix wrong horizontal offset calculations in widget.graph Indices in the loop are zero-based, so things like i-1 and i>1 aren't necessary and actually wouldn't have been correct even in a 1-based loop. step_shape = gears.shape.rectangle and step_shape = nil, which is supposed to default to rectangle according to docs, were drawn differently due to mismatched offset calculations, which led to problems especially noticeable when step_spacing is not zero, e.g: 1) whole graph shifted left too much, drawn partially off-screen. 2) whole graph shifted right too much, and the first bar placed closer to the rest than others. commit dc369fa198281e000afc1bd17326f3a264adf7d1 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 02:09:21 2021 +0700 Fix the "border_width affects graph bar width" bug Graph bars were drawn with lines of border_width thickness instead of 1, i.e. possibly too fat to be readable. commit 1ba73378cf29ed27a6ecd18716394779ceb543d9 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 00:13:03 2021 +0700 Simplify internal values representation in graph even more New values in graph._private.values were pushed to the end, and old values were removed from the beginning of the table. In draw() however newer values are drawn first, at the left edge of the graph, necessitating annoying idx = #values - idx conversions everywhere. In particular in stacked graph that means that values that are drawn together may have different indices. One has to shift the values array in add_value() either way, so one might as well insert new values at the beginning of the array and simplify the rest of the code. This doesn't seem like much yet, but it will prove to be much more convenient and less error-prone in what I'm going to do later. commit f70523ee68dbd6b67050ace4b82903a9b22440c7 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 13:35:10 2021 +0700 Simplify internal values representation in graph widget graph._private.values was either a table of values or a table of tables of values, depending on whether "stack" property is enabled, ... or a mix of both if user forgot to pass the second "group" parameter to graph:add_value(). This resulted in pretty fragile and confusing behavior, e.g. leaving user to wonder, why they see nothing more after setting stack=true for a graph in a previously working rc.lua, and demanding a very careful use of add_value() from the user, or else some values or even entire stacks would mysteriously vanish and errors in draw() get triggered, sometimes after a period of seemingly normal functioning. Now `values` is always a table of tables and the group parameter in add_value() defaults to 1, thus making the non-stacked graph a trivial variant of the stacked graph.
2021-05-20 17:14:03 +02:00
summed_values[idx] = acc
2016-05-23 07:30:44 +02:00
end
end
graph: complete widget overhaul and bug fixes Squashed commit of the following: commit 69821f51fe1e8652715658543e50631ece495090 Author: Alex Belykh <albel727@ngs.ru> Date: Mon May 10 19:17:55 2021 +0700 Refactor property handling in wibox.widget.graph In addition, baseline_value, step_width and step_spacing properties are all set to their default non-nil numeric values during instance creation now. commit 842eb429bf5df4b2ba46b2e6f7646afe89b1a3c3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 9 06:18:45 2021 +0700 Make graph:draw_values() private There's no documentation nor confidence that it's a good API, so I'm hiding it for now. commit 720746780574e4bad14a71fc4174955139c2eb50 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 04:04:13 2021 +0700 Document graph_should_draw_data_group() for posterity commit 7c73f3754ce6ba86b118f1170a6f9e406ab4841d Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:59:21 2021 +0700 Make graph:should_draw_data_group() private There's no confidence whether it's a good API, so I'm hiding it for now. commit b3539e20adb6423118af726ae4f8751ef864b5d2 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:36:44 2021 +0700 Make graph:preprocess_values() private There's no confidence whether it's a good API, so I'm hiding it for now. commit 2df4400e780a6260f8d0ab1c60734fbb11bf3c4f Author: Alex Belykh <albel727@ngs.ru> Date: Thu May 6 10:05:23 2021 +0700 Add spec/wibox/widget/graph_spec.lua commit ce01a9771ab439b8df841f1565aa4aa8a44a5e0d Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 08:16:18 2021 +0700 Improve graph.capacity documentation commit e28fade8f655802f5bf89154169f68d256b8e332 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 07:36:01 2021 +0700 Document graph:compute_drawn_values_num() commit dd68332292465422d08b48e9f0229f143beccbc6 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 05:24:23 2021 +0700 Document graph:pick_data_group_color() method commit ca6dc55f77aca88a6e9cc90bb6fd0045739f41f5 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 04:13:58 2021 +0700 Guard against setting data groups sparsely in graph:add_value() commit cdea5f126f7984e72924f6dffbd312c4fe3b032f Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 02:36:12 2021 +0700 Refactor scaling and baseline choice out of graph:draw() commit 10135deafe8a9390ba66ab657ebb1fb41d549e8c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 21:34:48 2021 +0700 Document graph.nan_color and graph.stack properties commit 0d73bb84ac66b87e16aee472bd1f299549f66a1c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 20:49:24 2021 +0700 Use a better example in docs for the graph.stack property commit c27d160a0826d033d49d6141608c40aa8a0642fa Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 16:05:38 2021 +0700 Document graph.baseline_value property commit 459bc8176c13d4ea32af0dcdc2399d3b929b1c6a Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:53:47 2021 +0700 Document graph.clamp_bars property commit e732ee0b3b007d213e689b98cc00ae7cb141ae08 Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:34 2021 +0700 Improve graph:add_value() and :clear() documentation commit ae73e3aa9f8d60409cbe4cb4da9612f31af4f38e Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:11 2021 +0700 Improve graph min_value/max_value/scale documentation commit a7350996a9def773b91b1631e6c0507e33d8f93b Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:27:36 2021 +0700 Cleanup graph examples to show only relevant things in docs commit 30740c26f4f41c16f4c8a3c339bcb7bd299e14cd Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 14:04:36 2021 +0700 Rename graph.stack_colors property to `group_colors` The colors are used for stacked and non-stacked graphs alike, so the name didn't make sense. Also document and clarify some color stuff. commit 1ea01bfd410971cf0d0c25fd74cd764d5eb7279d Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 21:25:41 2021 +0700 Add an example file for code coverage of some fringe graph cases commit eae68e39553f88a6275a0c65bf040b5d4c262b54 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 20:23:32 2021 +0700 Add step_shape to the graph/nan_color example for code coverage commit 8027147bc2eab34a97b49a428fb8cef27f122bed Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:55:18 2021 +0700 Add example files for graph.nan_color/nan_indication properties commit 89a25a165c52b2c15b6c4d95ecb777a9067e66f9 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:33:07 2021 +0700 Make widget graph:add_value(v) default v to NaN 0 as a default makes no sense and silences the error that is failing to specify a value, to which the user should rather be alerted. The graph widget has sane NaN handling now and will do just that. commit 2835d552440be48d356a7f7b1d64963696dd550b Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:20:48 2021 +0700 Implement graph.nan_color and nan_indication properties Now, whenever there's a NaN among values and graph.nan_indication is set, the corresponding area, where the value bar should have been drawn, is filled with the nan_color from top to bottom. The painting is done after all data is rendered, to make sure that it won't be overpainted and go unnoticed. NaNs among graph's values are inevitably a sign of some error and it is arguably not a sane default behavior to simply not render them at all. The user should take immediate note of any errors by default, instead of wondering at a mysteriously empty graph widget, which is why nan_indication is set to true by default. If the user wants to silence NaNs, they can always simply set it to false. nan_color if unset, defaults to a noticeable yellow-black pattern. As a direct and planned consequence, stacked graphs with negative values in them trigger the NaN handling too. But silencing NaN handling for those graphs is also possible and semi-sane, because then NaNs and negatives are ignored and the graph behaves (almost) like they were zeros. commit 9c1f8f08b400e01e19c534f3810c9c9ab485760c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 26 08:10:19 2021 +0700 Refactor out graph_map_value_to_widget_coordinates() function commit 1905991ddecabdae3e15ffb8ee5b18a1bee1a711 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 28 13:39:29 2021 +0700 Add an example file for graph.baseline_value property commit b31b39d66f4c037a9484b71a3f910703ae01fac0 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 07:03:49 2021 +0700 Add an example file for graph.clamp_bars=false/true comparison commit a1fc850e3047f9ee2c9ddeffacfbabf3ded34171 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 04:06:45 2021 +0700 Add an example file for graphs with negative values and shapes commit 2f012f8ff1bb5094c14e724a61e59a2a3a296bc8 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 01:06:05 2021 +0700 Add an example file for a graph displaying negative values commit 7b14fbd59a4c4c51962fd3110a1a900dd5f4eb98 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 11:18:26 2021 +0700 Add an example file for graphs with step_width < 1 commit dfd9a54544b637e2fbe3d1325f9cc24b3f4d2313 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 10:17:38 2021 +0700 Add an example file for graph.stack=false/true comparison Should also demonstrate that multiple data groups are possible for non-stacked graphs too. commit 5b532106c3fd255f9b66a66cfb88d977b489ab5d Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 09:34:14 2021 +0700 Add an example file for disabling graph data groups with nil colors commit ac6f1083c02fcb428f089a616f9b0ebaa5496b6c Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:57:25 2021 +0700 Add an example file for step_shape/width/spacing on stacked graphs commit b3703db6a5139e73e72c1cfd198ece2cd9be7df2 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:43:56 2021 +0700 Add an example file for scale=false/true on stacked graph commit b000aed4a12cb44fb501e1c8432a71a14b2f8f3f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 20 08:22:19 2021 +0700 Document deprecation of graph's width and height commit cb0ad617bd6c83a499896797318e3b0b588c3566 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 07:53:37 2021 +0700 Deprecate width and height properties of wibox.widget.graph commit 8c64e14ad91a074b784166a7054e53444cccb9d2 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 04:37:59 2021 +0700 Refactor graph:draw() to separate concerns Firstly, summation of group values for stacked graphs is extracted into graph:preprocess_values(). This makes graph:draw() smaller and exposes the fact, that stacked graphs are almost entirely just a form of a more general concept: pre-render data manipulation. It's something a user might want to customize, and now they can do it by monkey-patching the method. Secondly, the logic for deciding, if and with which color a data group should be drawn, is extracted into graph:should_draw_data_group() and get_data_group_color() methods respectively. This makes it customizable too and makes more obvious the fact, that it is a general thing that has nothing to do with stacked graphs in particular. With this there are only 5 lines remaining in graph:draw(), that could be said to be specific to stacked graphs, all pertaining to the `prev_y` variable, which merely preserves the bar coordinates from a previous data group. commit 2a32b6305d893f413303728a0a3da1dcd9a5591e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 22:52:37 2021 +0700 Pointlessly optimize graph:add_value() 1) Purge old values first, then insert the new one, if needed. Since Lua grows arrays in power of two steps, and our guess_capacity() also takes care to return even integers, it would be a shame to overstep the capacity by one by inserting first and thus getting the table size doubled only to never use the most of it. 2) Use a removal procedure that doesn't call the slow length operator at all. Previous code called it twice (once in table.remove) for every removed value. commit 680ea8f5d81e9040afb4662b3229e937e4b89e02 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:23:38 2021 +0700 Set graph data series color before the painting loop commit b970f0f221ae76250f2a94433ff79d653caa6e21 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:05:37 2021 +0700 Drop a large no longer necessary `if` in graph:draw_values() This commit is almost purely a whitespace change. commit 6b4f0541c70ec99d7f6e276bae64939f337120d6 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:00:50 2021 +0700 Extract values rendering code into graph:draw_values() method This is to avoid ungodly if nesting levels, which are needed to bail out of drawing values early and skip to drawing the border. The next commit will get rid of the if. commit e0950e4f99d1ea1c4b8cea2cc67629fa40030c2a Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 14:50:20 2021 +0700 Track graph draw() usage to guess necessary array capacity The last step of divorcing add_values() from width calculations, while ensuring that user doesn't observe a lack of data to draw. The widget now tracks an approximate number of values that it has been asked to draw() in _private.last_drawn_values_num, and truncates its values array based on that (if not overridden by the capacity property). commit 2e3d542282f35e7c0fed454da0f2eada0fcf3b78 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 13:35:12 2021 +0700 Add `capacity` property to graph widget This is the second step of divorcing add_values() from calculations involving widths. How many values are stored and how many values are drawn are distinct concepts. There isn't even a single definitive "how many values are drawn" because the widget could be rendered in several places with distinct widths simultaneously, and in every such place the data must be drawn appropriately, e.g. with auto-scaling, that takes only the visible data points into account. And the draw() method can do that now. The default capacity value of nil uses heuristics based on widget's _private.width to guess some capacity that would tolerate some dynamic widget resizing. If the user finds the heuristic failing them, they can set the capacity property to the desired maximum number of values that will be kept in the _private.values array (in each data group). commit 08d4a80686f7fe363551c35589a460839f60853e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 03:27:18 2021 +0700 Extract values size computation into compute_drawn_values_num() This is the first step of making add_values() not concern itself (directly) with widget dimensions. The user can monkey-patch the method to override how many values are (logically) drawn, because it affects graph auto-scaling and can be useful for rendering of step_shape-s that paint outside of their step_width. I didn't make it a full-blown function-valued property, because it seems a fringe functionality, which I'm too lazy to document. commit 3c5617c98ab1147618ed22e8414a77fc56b45f6e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 02:32:29 2021 +0700 Copy graph widget prototype methods over in bulk commit 169538837ece41a03d02ba8fbc0e4097062b67a3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun Apr 25 05:58:01 2021 +0700 Remove graph.baseline_y property After giving it some thought I've concluded, that it is unlikely to be useful as it is now. I'd better reserve the name for some more sensible functionality, like fixing the baseline_value defined axis at the given widget coordinate, adjusting the scaling accordingly, so min_value/max_value won't generally correspond to widget top and bottom anymore. commit 202ffaa9a0b820c7c471e1121d01b91f8f76cd38 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 12:06:55 2021 +0700 Add baseline_y and baseline_value properties to graph widget Now values in graph have a *baseline*, tunable with baseline_y and baseline_value properties. It is the (for now invisible) horizontal line from which all bars in the graph grow. I call it a "baseline" instead of "axis", because it doesn't actually have to correspond to the 0 value. But it can, and actually does now by default. baseline_y and baseline_value properties determine the vertical placement of the baseline in the widget. baseline_y is the position of the baseline in normalized widget coordinates, e.g. 0 corresponds to widget's top, 1 - to widget's bottom, and 0.5 - to widget's middle. So if e.g. one sets baseline_y = 0, then no matter how the widget is resized and how its values are scaled, bars in it will be drawn starting from the top. baseline_value is the position of the baseline in value coordinates. So if one sets baseline_value = 10, then no matter what happens, bars for values greater than 10 will be drawn as growing up from this line, and bars for values smaller as growing down. Where and if it will be seen in the widget, depends on the scaling. baseline_value = 0 is the familiar zero axis. baseline_y is unset by default and has priority, i.e. if it's set, then baseline_value is ignored. baseline_value defaults to 0. Prior to this commit, bars were always drawn from the bottom edge of the widget, or, in the new terms, the widget had baseline_y = 1. But it was indistinguishable from baseline_value = 0, because the widget clamped all negative values to 0, so the difference between the two could never be observed. baseline_value = 0 is clearly more useful and familiar to people, so now it is in force by default. commit df7e19abaebf0a871b5ca217b4cc3365533267a6 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 11:18:12 2021 +0700 Move value clamping from graph:add_value() into draw() Previously all values were forcibly clamped between 0 (not tunable at all) and max_value (1 by default). Thus graphs with negative values were not an option. NaN values were conflated with zeros, even though it may be useful to maintain the distinction and render them differently. And if user happened to dynamically change the max_value property, they discovered, that their data got truncated. Preserving user data as it was given is more flexible and makes things easier to debug. This commit does exactly that. So what happens now to the newly allowed values during draw()? There are 3 new cases to consider: 1) NaNs 2) Negatives 3) Values outside of min_value..max_value range NaNs are presently simply not drawn. An empty place is reserved where the value should've been, but nothing else is done. This is distinct from how a zero is handled, e.g. if step_shape is set, it is called with the height parameter = 0, which can still end up drawing something, depending on the shape. One could also consider addition of nan_color/nan_shape properties which could be drawn to alert user to a gap in their data. Negative values necessitate introduction of the concept of the zero axis, from which they will be drawn DOWN, in contrast to positive values. Where should this axis be drawn? How should step_shape-s with inherent direction, e.g. arrows, be drawn for negatives? This will be addressed in the next commit. Values outside of min/max_value range can imply shapes that go beyond widget dimensions. An arrow shape that goes beyond the roof is visibly different from an arrow that saturates and merely touches the widget top, and the user may reasonably desire either behavior. Therefore this commit introduces a boolean clamp_bars property, which defaults to true for reasons of robustness and backward compatibility with the behaviour prior to this commit. The property of course affects only the display of the values, the actual user data stays intact. commit c9c24fb1ab741cd6bb8074971d568e0646c51532 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:29:01 2021 +0700 Use rectangles for drawing graph bars of all sizes I have benchmarked various bar drawing strategies and graph sizes and found out that drawing graph bars with rectangles is as fast or even marginally faster than using fat lines. Thus the code that tries to special-case 1px bars as lines seems to be not worth the additional complexity it introduces, and this commit removes it. Incidentally, I've also established, that rectangles are more robust. If one draws graph bars and feeds cairo very large coordinates (~2^62), then corresponding rectangles fail to render, but all the other rectangles in the same series get rendered successfully, whereas a single large coordinate in a move_to/line_to() sequence is enough to cause all lines in the batch to fail to render. commit bc7f4be5e06405b5aa7fb443a5ace2b9b9575b1f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:01:42 2021 +0700 Cache cairo methods used in the inner loop of graph:draw() I have benchmarked the draw() method, both on cairo xcb and on argb32 image surfaces, and have discovered that cairo context method calls are unnaturally slow, to the point, that almost nothing else in the function matters. For example, drawing bars with vertical lines made the whole draw() function almost 2 times slower than doing the same with rectangles, merely because the former requires two method calls (move_to/line_to), and the latter only one. Simply caching hotpath cairo methods in local variables, reduced the difference between the drawing methods to a negligible one and made the whole function 4 times faster on a typical 100-bar graph, (1.4ms to 0.33ms on my laptop) thus bringing closer my secret dream of drawing live audio data with awesome graphs (kidding). I guess, LGI does something not very optimal there, like gobject-introspecting the method every time, or something. There might be some untapped optimization potential, possibly in upstream, like a method cache, from which everything in awesome could benefit, even though I imagine not many things are so call-intensive as the graph widget, to really feel the difference. commit 538df25bdf02b53d0bbe81809eef2e6fdb05ff6f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 07:32:37 2021 +0700 Use 1-based index in the inner graph draw loop It's used there only once for a calculation and then several times to index some tables, and index 0 is bad for lua tables performance-wise. commit 7412546916cd0a0a490130a120255ffb719398b8 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:55:12 2021 +0700 Leave graph.max_value uninitialized at construction This is a user-visible change, but the previous behavior was probably not what user expected anyway, namely with graph.scale set the graph mysteriously couldn't zoom in onto values that are smaller than 1. commit eb81d7929a161e79c13262cfa73ac2e6a820e8ed Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:48:29 2021 +0700 Allow graph.max_value be nil This makes its behave symmetrically to min_value. Now it defaults to 1, when graph.scale is not set, and to the maximum value in the graph data, when graph.scale is set. commit f622a8430cbfde7007b58d7f4c9c76b08c91e997 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 12:01:13 2021 +0700 Calculate max values num in graph:add_value() more precisely At most so many values were kept in the widget values array, as there are pixels of usable widget width (i.e. sans border). This is exactly right for the surely most common case of graphs with 1px-wide bars, but is more than needed for graphs with step_width+step_spacing>1, and not enough for graphs with subpixel bars. Moreover this made graph autoscaling behave weirdly in graphs with thicker bars, because old values survived longer in the array and kept affecting min_value/max_value calculations even after they were shifted off-screen. The maximum number of kept values is now calculated exactly as the number of values that can be displayed at once, taking step_width and step_spacing into account. It might be worth adding a max_values_num property to the graph to let user override this calculation, in case they draw shapes in step_shape() outside of its supposed rectangle, so our assumptions of visibility are wrong, or the widget often gets resized, resulting in the annoyance of visibly truncated series. commit 0aa63249a18641251a1bf77423739164f0eab39f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 02:30:13 2021 +0700 Fix border_color/border_width bugs in graph widget add_value() wrongly assumed that border_width is always set, when border_color is, so if user set only the latter, an error occurred. add_value() assumed that border_width is always < width which led to awesome hanging up in an infinite loop, should that fail to be the case. The graph border_width property was driven by the border_color property, instead of the other way round. If border_color wasn't set, the border wasn't drawn all, even if user sets non-zero border_width, which is a confusing behavior, in contrast to all other widgets, which simply use a plethora of fallback colors to draw the border, when needed. This also meant that, despite being documented, the beautiful.graph_border_color fallback color could not and was not ever used. If the border_color was set though, an unset border_width was immediately assumed to be 1 instead of 0, also contrary to what other widgets do. Theme colors shouldn't influence widget layout, so this commit fixes this behavior to: 1) border_width always defaults to 0, if unset. 2) border_color falls back to beautiful.graph_border_color or white. Maybe one should also add a beautiful.graph_border_width theme variable, like some widgets do. commit dad4a3253b8fef971fb82720d2a407fcc0a879ad Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 03:11:27 2021 +0700 Implement multiple data series drawing for non-stacked graph The widget can already hold and draw multiple data groups, so it makes little sense to limit drawing only to the first group when graph.stack = false, probably leading to user's confusion. Now user can call add_value(v, group) with group > 1 and the data series will be drawn (over groups with lesser id-s) with graph.stack_colors[group] color, if stack_colors is set, or with graph.color, if stack_colors isn't set. Thus nothing visibly changes for all current users of non-stacked graphs with only single data series, but the new functionality is now there for those who need it. One can't fully emulate this functionality, e.g. with multiple graphs widgets in a wibox.layout.stack, because data series in a single widget can enjoy a common auto-scaling. commit 3e645c976184ab5f50d625271c25062db9ab7ed9 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:59:12 2021 +0700 Don't draw a stacked graph data group if there's no color for it If stack_colors[idx] is nil for the group `idx`, don't draw it at all (as opposed, e.g. to the effect of drawing it with a transparent color, or as though it had all values == 0, which both produce different results). This code is still subtly different from prior behavior, because now user can have a non-contiguous stack_colors table and turn the display of any data group on and off at will, whereas the code two commits earlier would just stop rendering as soon as ipairs(stack_colors) does, i.e. no data series were drawn past the first nil hole. This commit only looks large, but is mostly a whitespace change due to nesting from a pair of wrapping ifs. commit 8c0273786de1ade66bdd977699f98b588da9f487 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:32:42 2021 +0700 Unify stacked and non-stacked graph rendering Now stacked graphs support everything the nonstacked do, like step_width ~= 1 and custom step_shape-s. Technical notes: This code no longer calls cr:stroke() for every single value in a stacked graph, but rather paints a whole series and then cr:stroke/fill()-s it at all once with its color, improving performance considerably. Additionally the graph no longer relies on the presence of the stack_colors property to render series. It draws all data that it has, and, if there's no corresponding color in stack_colors, falls back to graph.color. This will be changed in the next commit to the prior behavior of only drawing those series for which there's a color in the stack_colors table, but that would've made this commit's diff much larger due to required nesting ifs over large code chunks. This commit also takes care to properly handle NaNs and negative values. Those are currently (almost) guaranteed to not occur in the values array due to clipping in graph:add_value(), but I intend to eventually drop the clipping, because it does nothing that draw() really relies upon, and allowing unclipped and NaN values is more flexible and less surprising, e.g. when user dynamically toggles graph.scale. Eventually I intend to implement drawing negative values in non-stacked graphs and possibly even in the stacked ones. commit 38c6589c51b2aa057d7c2f40182ecf32b5b2ff5e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 10:04:15 2021 +0700 Convert graph:draw() to use `self` commit f21f1b2550426df1c4ea8abde079b1ccef84b394 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 12:27:26 2021 +0700 Fix scaling for stacked graphs Stacked graphs have cumulative heights, values from all groups that end up in the same graph bar are effectively summed. Previous code didn't take that into account when calculating min/max_value for the purposes of rescaling, but simply examined every value separately. This code adds up all values up front and finds min/max among those, to determine proper scaling that would fit the graph exactly. commit 1ce4c7bf63bdd6c99c195d668347bec49355e921 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 09:49:35 2021 +0700 Draw shaped graph bars only up to the baseline Prior to this commit, every step_shape bar was, for some reason, drawn with height = full widget's height, regardless of the value it represents. So for the most bars their lower end was far outside the box and therefore clipped off. So e.g. one couldn't see the lower round edges of rounded rectangles. This commit fixes that by appropriately varying the height that is passed into step_shape(), so that lower edges of the bars always exactly touch the lower edge of the graph. If the user wants to restore the prior behavior, they can simply ignore the passed-in height in step_shape() and just always draw their shape with height = full widget height. But they couldn't replicate the effect of this commit in their rc.lua without essentially writing their own graph widget. commit e25185b08da23745b5815f85f6e9e43222ed2294 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 08:42:54 2021 +0700 Avoid incremental transform shifting during graph bar rendering Instead of letting addition errors and cognitive load accumulate simply use the same offset calculation that is used for lines and reset the transform back to the top-left corner after every drawn bar. It's conceptually simpler, and will be useful later for stacked graphs and more. It also fixes a discrepancy between plain and step_shape rendering. Prior to this commit, bar coloring occurred with a disturbed transform, which was centered at the last drawn bar, wherever it happened to be. This meant that nontrivial colors, like gradients and images, were arbitrarily offset when step_shape was used. commit f3e98866d8583c35d0b557c09f529299edc9ba2c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:45:29 2021 +0700 Refactor out graph bar height expression into a variable commit f54528d6892f5aaa4595917853c9e2130f743fdb Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:20:25 2021 +0700 Draw graph bars with lines only when step_width == 1 Before this commit graph bars of step_width < 1 were drawn with lines of width 1, but that's a bit unfortunate, because it led to much overpainting, while still ending up with a graphically incorrect result. Let's draw such bars with rectangles and let cairo do things with subpixels, getting more graphically accurate result, while still keeping the common case of step_width == 1 (supposedly) fast-drawn with lines. commit b6515dca67c18b78eb5657378131a62ab43d4cfc Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:54:07 2021 +0700 Fix unsharp edges when drawing graph bars with rectangles The 0.5 offset is only helpful for 1-width strokes, not for fills. commit 7f3db8f07e82f821bcd1dc418d66564f537609d6 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:21:01 2021 +0700 Do coordinate transformations even if the value isn't drawn The graph presently doesn't draw values < 0, but there was a discrepancy between drawing with and without step_shape. The default line/rectangle drawing reserves an empty place where the skipped value should've been, but no such empty place is reserved, when step_shape is used. This commit fixes that. Skipped values always have their empty space now. commit 7b731ebfe96af7a9dba9c57371da6761cf22c00c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:03:30 2021 +0700 Fix graph bar scaling when min_value is not 0 Previous calculation lead to underscaling of values so that even value = max_value wasn't drawn to full height. commit dca59fd8b7fff0e7e646f2dfc6bd92047028e5a8 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:18:40 2021 +0700 Calculate the horizontal graph bar offset only when needed commit 940ddf4b9825e9c3a91d276208c089d8c74e49db Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:17:23 2021 +0700 Call cr:move_to() only when drawing the graph with lines It's redundant otherwise and might even interfere with user's drawing in step_shape(). commit b5d5cd10ca29fb29cd751faaa95137c2fe89ff63 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:04:06 2021 +0700 Fix wrong horizontal offset calculations in widget.graph Indices in the loop are zero-based, so things like i-1 and i>1 aren't necessary and actually wouldn't have been correct even in a 1-based loop. step_shape = gears.shape.rectangle and step_shape = nil, which is supposed to default to rectangle according to docs, were drawn differently due to mismatched offset calculations, which led to problems especially noticeable when step_spacing is not zero, e.g: 1) whole graph shifted left too much, drawn partially off-screen. 2) whole graph shifted right too much, and the first bar placed closer to the rest than others. commit dc369fa198281e000afc1bd17326f3a264adf7d1 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 02:09:21 2021 +0700 Fix the "border_width affects graph bar width" bug Graph bars were drawn with lines of border_width thickness instead of 1, i.e. possibly too fat to be readable. commit 1ba73378cf29ed27a6ecd18716394779ceb543d9 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 00:13:03 2021 +0700 Simplify internal values representation in graph even more New values in graph._private.values were pushed to the end, and old values were removed from the beginning of the table. In draw() however newer values are drawn first, at the left edge of the graph, necessitating annoying idx = #values - idx conversions everywhere. In particular in stacked graph that means that values that are drawn together may have different indices. One has to shift the values array in add_value() either way, so one might as well insert new values at the beginning of the array and simplify the rest of the code. This doesn't seem like much yet, but it will prove to be much more convenient and less error-prone in what I'm going to do later. commit f70523ee68dbd6b67050ace4b82903a9b22440c7 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 13:35:10 2021 +0700 Simplify internal values representation in graph widget graph._private.values was either a table of values or a table of tables of values, depending on whether "stack" property is enabled, ... or a mix of both if user forgot to pass the second "group" parameter to graph:add_value(). This resulted in pretty fragile and confusing behavior, e.g. leaving user to wonder, why they see nothing more after setting stack=true for a graph in a previously working rc.lua, and demanding a very careful use of add_value() from the user, or else some values or even entire stacks would mysteriously vanish and errors in draw() get triggered, sometimes after a period of seemingly normal functioning. Now `values` is always a table of tables and the group parameter in add_value() defaults to 1, thus making the non-stacked graph a trivial variant of the stacked graph.
2021-05-20 17:14:03 +02:00
end
-- In a stacked graph it's sufficient to examine only the last summed row
-- to determine the max_value, since all values are necessarily >= 0
-- and the min_value should be always at most 0
local scaling_values = { {0}, summed_values }
return drawn_values, scaling_values
end
local function graph_map_value_to_widget_coordinates(self, value, min_value, max_value, height)
-- Scale the value so that [min_value..max_value] maps to [0..1]
value = (value - min_value) / (max_value - min_value)
-- Check whether value is NaN
if value == value then
if self._private.clamp_bars then
-- Don't allow the bar to exceed widget's dimensions
value = math_min(1, math_max(0, value))
end
-- Drawing bars up from the lower edge of the widget
return height * (1 - value)
end
return value --NaN
end
local function graph_choose_coordinate_system(self, scaling_values, drawn_values_num, height)
local scale = self._private.scale
local max_value = self._private.max_value or (scale and -math.huge or 1)
local min_value = self._private.min_value or (scale and math.huge or 0)
if scale then
for _, group_values in ipairs(scaling_values) do
for idx, v in ipairs(group_values) do
-- Do not let off-screen values affect autoscaling
if idx > drawn_values_num then
break
end
-- We don't use math.min/max here to be sure that
-- min/max_value don't accidentally get assigned a NaN
2016-05-23 07:30:44 +02:00
if v > max_value then
max_value = v
end
if min_value > v then
2016-08-16 06:59:37 +02:00
min_value = v
end
2016-05-23 07:30:44 +02:00
end
end
graph: complete widget overhaul and bug fixes Squashed commit of the following: commit 69821f51fe1e8652715658543e50631ece495090 Author: Alex Belykh <albel727@ngs.ru> Date: Mon May 10 19:17:55 2021 +0700 Refactor property handling in wibox.widget.graph In addition, baseline_value, step_width and step_spacing properties are all set to their default non-nil numeric values during instance creation now. commit 842eb429bf5df4b2ba46b2e6f7646afe89b1a3c3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 9 06:18:45 2021 +0700 Make graph:draw_values() private There's no documentation nor confidence that it's a good API, so I'm hiding it for now. commit 720746780574e4bad14a71fc4174955139c2eb50 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 04:04:13 2021 +0700 Document graph_should_draw_data_group() for posterity commit 7c73f3754ce6ba86b118f1170a6f9e406ab4841d Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:59:21 2021 +0700 Make graph:should_draw_data_group() private There's no confidence whether it's a good API, so I'm hiding it for now. commit b3539e20adb6423118af726ae4f8751ef864b5d2 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:36:44 2021 +0700 Make graph:preprocess_values() private There's no confidence whether it's a good API, so I'm hiding it for now. commit 2df4400e780a6260f8d0ab1c60734fbb11bf3c4f Author: Alex Belykh <albel727@ngs.ru> Date: Thu May 6 10:05:23 2021 +0700 Add spec/wibox/widget/graph_spec.lua commit ce01a9771ab439b8df841f1565aa4aa8a44a5e0d Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 08:16:18 2021 +0700 Improve graph.capacity documentation commit e28fade8f655802f5bf89154169f68d256b8e332 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 07:36:01 2021 +0700 Document graph:compute_drawn_values_num() commit dd68332292465422d08b48e9f0229f143beccbc6 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 05:24:23 2021 +0700 Document graph:pick_data_group_color() method commit ca6dc55f77aca88a6e9cc90bb6fd0045739f41f5 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 04:13:58 2021 +0700 Guard against setting data groups sparsely in graph:add_value() commit cdea5f126f7984e72924f6dffbd312c4fe3b032f Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 02:36:12 2021 +0700 Refactor scaling and baseline choice out of graph:draw() commit 10135deafe8a9390ba66ab657ebb1fb41d549e8c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 21:34:48 2021 +0700 Document graph.nan_color and graph.stack properties commit 0d73bb84ac66b87e16aee472bd1f299549f66a1c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 20:49:24 2021 +0700 Use a better example in docs for the graph.stack property commit c27d160a0826d033d49d6141608c40aa8a0642fa Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 16:05:38 2021 +0700 Document graph.baseline_value property commit 459bc8176c13d4ea32af0dcdc2399d3b929b1c6a Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:53:47 2021 +0700 Document graph.clamp_bars property commit e732ee0b3b007d213e689b98cc00ae7cb141ae08 Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:34 2021 +0700 Improve graph:add_value() and :clear() documentation commit ae73e3aa9f8d60409cbe4cb4da9612f31af4f38e Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:11 2021 +0700 Improve graph min_value/max_value/scale documentation commit a7350996a9def773b91b1631e6c0507e33d8f93b Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:27:36 2021 +0700 Cleanup graph examples to show only relevant things in docs commit 30740c26f4f41c16f4c8a3c339bcb7bd299e14cd Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 14:04:36 2021 +0700 Rename graph.stack_colors property to `group_colors` The colors are used for stacked and non-stacked graphs alike, so the name didn't make sense. Also document and clarify some color stuff. commit 1ea01bfd410971cf0d0c25fd74cd764d5eb7279d Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 21:25:41 2021 +0700 Add an example file for code coverage of some fringe graph cases commit eae68e39553f88a6275a0c65bf040b5d4c262b54 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 20:23:32 2021 +0700 Add step_shape to the graph/nan_color example for code coverage commit 8027147bc2eab34a97b49a428fb8cef27f122bed Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:55:18 2021 +0700 Add example files for graph.nan_color/nan_indication properties commit 89a25a165c52b2c15b6c4d95ecb777a9067e66f9 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:33:07 2021 +0700 Make widget graph:add_value(v) default v to NaN 0 as a default makes no sense and silences the error that is failing to specify a value, to which the user should rather be alerted. The graph widget has sane NaN handling now and will do just that. commit 2835d552440be48d356a7f7b1d64963696dd550b Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:20:48 2021 +0700 Implement graph.nan_color and nan_indication properties Now, whenever there's a NaN among values and graph.nan_indication is set, the corresponding area, where the value bar should have been drawn, is filled with the nan_color from top to bottom. The painting is done after all data is rendered, to make sure that it won't be overpainted and go unnoticed. NaNs among graph's values are inevitably a sign of some error and it is arguably not a sane default behavior to simply not render them at all. The user should take immediate note of any errors by default, instead of wondering at a mysteriously empty graph widget, which is why nan_indication is set to true by default. If the user wants to silence NaNs, they can always simply set it to false. nan_color if unset, defaults to a noticeable yellow-black pattern. As a direct and planned consequence, stacked graphs with negative values in them trigger the NaN handling too. But silencing NaN handling for those graphs is also possible and semi-sane, because then NaNs and negatives are ignored and the graph behaves (almost) like they were zeros. commit 9c1f8f08b400e01e19c534f3810c9c9ab485760c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 26 08:10:19 2021 +0700 Refactor out graph_map_value_to_widget_coordinates() function commit 1905991ddecabdae3e15ffb8ee5b18a1bee1a711 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 28 13:39:29 2021 +0700 Add an example file for graph.baseline_value property commit b31b39d66f4c037a9484b71a3f910703ae01fac0 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 07:03:49 2021 +0700 Add an example file for graph.clamp_bars=false/true comparison commit a1fc850e3047f9ee2c9ddeffacfbabf3ded34171 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 04:06:45 2021 +0700 Add an example file for graphs with negative values and shapes commit 2f012f8ff1bb5094c14e724a61e59a2a3a296bc8 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 01:06:05 2021 +0700 Add an example file for a graph displaying negative values commit 7b14fbd59a4c4c51962fd3110a1a900dd5f4eb98 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 11:18:26 2021 +0700 Add an example file for graphs with step_width < 1 commit dfd9a54544b637e2fbe3d1325f9cc24b3f4d2313 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 10:17:38 2021 +0700 Add an example file for graph.stack=false/true comparison Should also demonstrate that multiple data groups are possible for non-stacked graphs too. commit 5b532106c3fd255f9b66a66cfb88d977b489ab5d Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 09:34:14 2021 +0700 Add an example file for disabling graph data groups with nil colors commit ac6f1083c02fcb428f089a616f9b0ebaa5496b6c Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:57:25 2021 +0700 Add an example file for step_shape/width/spacing on stacked graphs commit b3703db6a5139e73e72c1cfd198ece2cd9be7df2 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:43:56 2021 +0700 Add an example file for scale=false/true on stacked graph commit b000aed4a12cb44fb501e1c8432a71a14b2f8f3f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 20 08:22:19 2021 +0700 Document deprecation of graph's width and height commit cb0ad617bd6c83a499896797318e3b0b588c3566 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 07:53:37 2021 +0700 Deprecate width and height properties of wibox.widget.graph commit 8c64e14ad91a074b784166a7054e53444cccb9d2 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 04:37:59 2021 +0700 Refactor graph:draw() to separate concerns Firstly, summation of group values for stacked graphs is extracted into graph:preprocess_values(). This makes graph:draw() smaller and exposes the fact, that stacked graphs are almost entirely just a form of a more general concept: pre-render data manipulation. It's something a user might want to customize, and now they can do it by monkey-patching the method. Secondly, the logic for deciding, if and with which color a data group should be drawn, is extracted into graph:should_draw_data_group() and get_data_group_color() methods respectively. This makes it customizable too and makes more obvious the fact, that it is a general thing that has nothing to do with stacked graphs in particular. With this there are only 5 lines remaining in graph:draw(), that could be said to be specific to stacked graphs, all pertaining to the `prev_y` variable, which merely preserves the bar coordinates from a previous data group. commit 2a32b6305d893f413303728a0a3da1dcd9a5591e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 22:52:37 2021 +0700 Pointlessly optimize graph:add_value() 1) Purge old values first, then insert the new one, if needed. Since Lua grows arrays in power of two steps, and our guess_capacity() also takes care to return even integers, it would be a shame to overstep the capacity by one by inserting first and thus getting the table size doubled only to never use the most of it. 2) Use a removal procedure that doesn't call the slow length operator at all. Previous code called it twice (once in table.remove) for every removed value. commit 680ea8f5d81e9040afb4662b3229e937e4b89e02 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:23:38 2021 +0700 Set graph data series color before the painting loop commit b970f0f221ae76250f2a94433ff79d653caa6e21 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:05:37 2021 +0700 Drop a large no longer necessary `if` in graph:draw_values() This commit is almost purely a whitespace change. commit 6b4f0541c70ec99d7f6e276bae64939f337120d6 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:00:50 2021 +0700 Extract values rendering code into graph:draw_values() method This is to avoid ungodly if nesting levels, which are needed to bail out of drawing values early and skip to drawing the border. The next commit will get rid of the if. commit e0950e4f99d1ea1c4b8cea2cc67629fa40030c2a Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 14:50:20 2021 +0700 Track graph draw() usage to guess necessary array capacity The last step of divorcing add_values() from width calculations, while ensuring that user doesn't observe a lack of data to draw. The widget now tracks an approximate number of values that it has been asked to draw() in _private.last_drawn_values_num, and truncates its values array based on that (if not overridden by the capacity property). commit 2e3d542282f35e7c0fed454da0f2eada0fcf3b78 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 13:35:12 2021 +0700 Add `capacity` property to graph widget This is the second step of divorcing add_values() from calculations involving widths. How many values are stored and how many values are drawn are distinct concepts. There isn't even a single definitive "how many values are drawn" because the widget could be rendered in several places with distinct widths simultaneously, and in every such place the data must be drawn appropriately, e.g. with auto-scaling, that takes only the visible data points into account. And the draw() method can do that now. The default capacity value of nil uses heuristics based on widget's _private.width to guess some capacity that would tolerate some dynamic widget resizing. If the user finds the heuristic failing them, they can set the capacity property to the desired maximum number of values that will be kept in the _private.values array (in each data group). commit 08d4a80686f7fe363551c35589a460839f60853e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 03:27:18 2021 +0700 Extract values size computation into compute_drawn_values_num() This is the first step of making add_values() not concern itself (directly) with widget dimensions. The user can monkey-patch the method to override how many values are (logically) drawn, because it affects graph auto-scaling and can be useful for rendering of step_shape-s that paint outside of their step_width. I didn't make it a full-blown function-valued property, because it seems a fringe functionality, which I'm too lazy to document. commit 3c5617c98ab1147618ed22e8414a77fc56b45f6e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 02:32:29 2021 +0700 Copy graph widget prototype methods over in bulk commit 169538837ece41a03d02ba8fbc0e4097062b67a3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun Apr 25 05:58:01 2021 +0700 Remove graph.baseline_y property After giving it some thought I've concluded, that it is unlikely to be useful as it is now. I'd better reserve the name for some more sensible functionality, like fixing the baseline_value defined axis at the given widget coordinate, adjusting the scaling accordingly, so min_value/max_value won't generally correspond to widget top and bottom anymore. commit 202ffaa9a0b820c7c471e1121d01b91f8f76cd38 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 12:06:55 2021 +0700 Add baseline_y and baseline_value properties to graph widget Now values in graph have a *baseline*, tunable with baseline_y and baseline_value properties. It is the (for now invisible) horizontal line from which all bars in the graph grow. I call it a "baseline" instead of "axis", because it doesn't actually have to correspond to the 0 value. But it can, and actually does now by default. baseline_y and baseline_value properties determine the vertical placement of the baseline in the widget. baseline_y is the position of the baseline in normalized widget coordinates, e.g. 0 corresponds to widget's top, 1 - to widget's bottom, and 0.5 - to widget's middle. So if e.g. one sets baseline_y = 0, then no matter how the widget is resized and how its values are scaled, bars in it will be drawn starting from the top. baseline_value is the position of the baseline in value coordinates. So if one sets baseline_value = 10, then no matter what happens, bars for values greater than 10 will be drawn as growing up from this line, and bars for values smaller as growing down. Where and if it will be seen in the widget, depends on the scaling. baseline_value = 0 is the familiar zero axis. baseline_y is unset by default and has priority, i.e. if it's set, then baseline_value is ignored. baseline_value defaults to 0. Prior to this commit, bars were always drawn from the bottom edge of the widget, or, in the new terms, the widget had baseline_y = 1. But it was indistinguishable from baseline_value = 0, because the widget clamped all negative values to 0, so the difference between the two could never be observed. baseline_value = 0 is clearly more useful and familiar to people, so now it is in force by default. commit df7e19abaebf0a871b5ca217b4cc3365533267a6 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 11:18:12 2021 +0700 Move value clamping from graph:add_value() into draw() Previously all values were forcibly clamped between 0 (not tunable at all) and max_value (1 by default). Thus graphs with negative values were not an option. NaN values were conflated with zeros, even though it may be useful to maintain the distinction and render them differently. And if user happened to dynamically change the max_value property, they discovered, that their data got truncated. Preserving user data as it was given is more flexible and makes things easier to debug. This commit does exactly that. So what happens now to the newly allowed values during draw()? There are 3 new cases to consider: 1) NaNs 2) Negatives 3) Values outside of min_value..max_value range NaNs are presently simply not drawn. An empty place is reserved where the value should've been, but nothing else is done. This is distinct from how a zero is handled, e.g. if step_shape is set, it is called with the height parameter = 0, which can still end up drawing something, depending on the shape. One could also consider addition of nan_color/nan_shape properties which could be drawn to alert user to a gap in their data. Negative values necessitate introduction of the concept of the zero axis, from which they will be drawn DOWN, in contrast to positive values. Where should this axis be drawn? How should step_shape-s with inherent direction, e.g. arrows, be drawn for negatives? This will be addressed in the next commit. Values outside of min/max_value range can imply shapes that go beyond widget dimensions. An arrow shape that goes beyond the roof is visibly different from an arrow that saturates and merely touches the widget top, and the user may reasonably desire either behavior. Therefore this commit introduces a boolean clamp_bars property, which defaults to true for reasons of robustness and backward compatibility with the behaviour prior to this commit. The property of course affects only the display of the values, the actual user data stays intact. commit c9c24fb1ab741cd6bb8074971d568e0646c51532 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:29:01 2021 +0700 Use rectangles for drawing graph bars of all sizes I have benchmarked various bar drawing strategies and graph sizes and found out that drawing graph bars with rectangles is as fast or even marginally faster than using fat lines. Thus the code that tries to special-case 1px bars as lines seems to be not worth the additional complexity it introduces, and this commit removes it. Incidentally, I've also established, that rectangles are more robust. If one draws graph bars and feeds cairo very large coordinates (~2^62), then corresponding rectangles fail to render, but all the other rectangles in the same series get rendered successfully, whereas a single large coordinate in a move_to/line_to() sequence is enough to cause all lines in the batch to fail to render. commit bc7f4be5e06405b5aa7fb443a5ace2b9b9575b1f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:01:42 2021 +0700 Cache cairo methods used in the inner loop of graph:draw() I have benchmarked the draw() method, both on cairo xcb and on argb32 image surfaces, and have discovered that cairo context method calls are unnaturally slow, to the point, that almost nothing else in the function matters. For example, drawing bars with vertical lines made the whole draw() function almost 2 times slower than doing the same with rectangles, merely because the former requires two method calls (move_to/line_to), and the latter only one. Simply caching hotpath cairo methods in local variables, reduced the difference between the drawing methods to a negligible one and made the whole function 4 times faster on a typical 100-bar graph, (1.4ms to 0.33ms on my laptop) thus bringing closer my secret dream of drawing live audio data with awesome graphs (kidding). I guess, LGI does something not very optimal there, like gobject-introspecting the method every time, or something. There might be some untapped optimization potential, possibly in upstream, like a method cache, from which everything in awesome could benefit, even though I imagine not many things are so call-intensive as the graph widget, to really feel the difference. commit 538df25bdf02b53d0bbe81809eef2e6fdb05ff6f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 07:32:37 2021 +0700 Use 1-based index in the inner graph draw loop It's used there only once for a calculation and then several times to index some tables, and index 0 is bad for lua tables performance-wise. commit 7412546916cd0a0a490130a120255ffb719398b8 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:55:12 2021 +0700 Leave graph.max_value uninitialized at construction This is a user-visible change, but the previous behavior was probably not what user expected anyway, namely with graph.scale set the graph mysteriously couldn't zoom in onto values that are smaller than 1. commit eb81d7929a161e79c13262cfa73ac2e6a820e8ed Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:48:29 2021 +0700 Allow graph.max_value be nil This makes its behave symmetrically to min_value. Now it defaults to 1, when graph.scale is not set, and to the maximum value in the graph data, when graph.scale is set. commit f622a8430cbfde7007b58d7f4c9c76b08c91e997 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 12:01:13 2021 +0700 Calculate max values num in graph:add_value() more precisely At most so many values were kept in the widget values array, as there are pixels of usable widget width (i.e. sans border). This is exactly right for the surely most common case of graphs with 1px-wide bars, but is more than needed for graphs with step_width+step_spacing>1, and not enough for graphs with subpixel bars. Moreover this made graph autoscaling behave weirdly in graphs with thicker bars, because old values survived longer in the array and kept affecting min_value/max_value calculations even after they were shifted off-screen. The maximum number of kept values is now calculated exactly as the number of values that can be displayed at once, taking step_width and step_spacing into account. It might be worth adding a max_values_num property to the graph to let user override this calculation, in case they draw shapes in step_shape() outside of its supposed rectangle, so our assumptions of visibility are wrong, or the widget often gets resized, resulting in the annoyance of visibly truncated series. commit 0aa63249a18641251a1bf77423739164f0eab39f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 02:30:13 2021 +0700 Fix border_color/border_width bugs in graph widget add_value() wrongly assumed that border_width is always set, when border_color is, so if user set only the latter, an error occurred. add_value() assumed that border_width is always < width which led to awesome hanging up in an infinite loop, should that fail to be the case. The graph border_width property was driven by the border_color property, instead of the other way round. If border_color wasn't set, the border wasn't drawn all, even if user sets non-zero border_width, which is a confusing behavior, in contrast to all other widgets, which simply use a plethora of fallback colors to draw the border, when needed. This also meant that, despite being documented, the beautiful.graph_border_color fallback color could not and was not ever used. If the border_color was set though, an unset border_width was immediately assumed to be 1 instead of 0, also contrary to what other widgets do. Theme colors shouldn't influence widget layout, so this commit fixes this behavior to: 1) border_width always defaults to 0, if unset. 2) border_color falls back to beautiful.graph_border_color or white. Maybe one should also add a beautiful.graph_border_width theme variable, like some widgets do. commit dad4a3253b8fef971fb82720d2a407fcc0a879ad Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 03:11:27 2021 +0700 Implement multiple data series drawing for non-stacked graph The widget can already hold and draw multiple data groups, so it makes little sense to limit drawing only to the first group when graph.stack = false, probably leading to user's confusion. Now user can call add_value(v, group) with group > 1 and the data series will be drawn (over groups with lesser id-s) with graph.stack_colors[group] color, if stack_colors is set, or with graph.color, if stack_colors isn't set. Thus nothing visibly changes for all current users of non-stacked graphs with only single data series, but the new functionality is now there for those who need it. One can't fully emulate this functionality, e.g. with multiple graphs widgets in a wibox.layout.stack, because data series in a single widget can enjoy a common auto-scaling. commit 3e645c976184ab5f50d625271c25062db9ab7ed9 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:59:12 2021 +0700 Don't draw a stacked graph data group if there's no color for it If stack_colors[idx] is nil for the group `idx`, don't draw it at all (as opposed, e.g. to the effect of drawing it with a transparent color, or as though it had all values == 0, which both produce different results). This code is still subtly different from prior behavior, because now user can have a non-contiguous stack_colors table and turn the display of any data group on and off at will, whereas the code two commits earlier would just stop rendering as soon as ipairs(stack_colors) does, i.e. no data series were drawn past the first nil hole. This commit only looks large, but is mostly a whitespace change due to nesting from a pair of wrapping ifs. commit 8c0273786de1ade66bdd977699f98b588da9f487 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:32:42 2021 +0700 Unify stacked and non-stacked graph rendering Now stacked graphs support everything the nonstacked do, like step_width ~= 1 and custom step_shape-s. Technical notes: This code no longer calls cr:stroke() for every single value in a stacked graph, but rather paints a whole series and then cr:stroke/fill()-s it at all once with its color, improving performance considerably. Additionally the graph no longer relies on the presence of the stack_colors property to render series. It draws all data that it has, and, if there's no corresponding color in stack_colors, falls back to graph.color. This will be changed in the next commit to the prior behavior of only drawing those series for which there's a color in the stack_colors table, but that would've made this commit's diff much larger due to required nesting ifs over large code chunks. This commit also takes care to properly handle NaNs and negative values. Those are currently (almost) guaranteed to not occur in the values array due to clipping in graph:add_value(), but I intend to eventually drop the clipping, because it does nothing that draw() really relies upon, and allowing unclipped and NaN values is more flexible and less surprising, e.g. when user dynamically toggles graph.scale. Eventually I intend to implement drawing negative values in non-stacked graphs and possibly even in the stacked ones. commit 38c6589c51b2aa057d7c2f40182ecf32b5b2ff5e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 10:04:15 2021 +0700 Convert graph:draw() to use `self` commit f21f1b2550426df1c4ea8abde079b1ccef84b394 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 12:27:26 2021 +0700 Fix scaling for stacked graphs Stacked graphs have cumulative heights, values from all groups that end up in the same graph bar are effectively summed. Previous code didn't take that into account when calculating min/max_value for the purposes of rescaling, but simply examined every value separately. This code adds up all values up front and finds min/max among those, to determine proper scaling that would fit the graph exactly. commit 1ce4c7bf63bdd6c99c195d668347bec49355e921 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 09:49:35 2021 +0700 Draw shaped graph bars only up to the baseline Prior to this commit, every step_shape bar was, for some reason, drawn with height = full widget's height, regardless of the value it represents. So for the most bars their lower end was far outside the box and therefore clipped off. So e.g. one couldn't see the lower round edges of rounded rectangles. This commit fixes that by appropriately varying the height that is passed into step_shape(), so that lower edges of the bars always exactly touch the lower edge of the graph. If the user wants to restore the prior behavior, they can simply ignore the passed-in height in step_shape() and just always draw their shape with height = full widget height. But they couldn't replicate the effect of this commit in their rc.lua without essentially writing their own graph widget. commit e25185b08da23745b5815f85f6e9e43222ed2294 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 08:42:54 2021 +0700 Avoid incremental transform shifting during graph bar rendering Instead of letting addition errors and cognitive load accumulate simply use the same offset calculation that is used for lines and reset the transform back to the top-left corner after every drawn bar. It's conceptually simpler, and will be useful later for stacked graphs and more. It also fixes a discrepancy between plain and step_shape rendering. Prior to this commit, bar coloring occurred with a disturbed transform, which was centered at the last drawn bar, wherever it happened to be. This meant that nontrivial colors, like gradients and images, were arbitrarily offset when step_shape was used. commit f3e98866d8583c35d0b557c09f529299edc9ba2c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:45:29 2021 +0700 Refactor out graph bar height expression into a variable commit f54528d6892f5aaa4595917853c9e2130f743fdb Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:20:25 2021 +0700 Draw graph bars with lines only when step_width == 1 Before this commit graph bars of step_width < 1 were drawn with lines of width 1, but that's a bit unfortunate, because it led to much overpainting, while still ending up with a graphically incorrect result. Let's draw such bars with rectangles and let cairo do things with subpixels, getting more graphically accurate result, while still keeping the common case of step_width == 1 (supposedly) fast-drawn with lines. commit b6515dca67c18b78eb5657378131a62ab43d4cfc Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:54:07 2021 +0700 Fix unsharp edges when drawing graph bars with rectangles The 0.5 offset is only helpful for 1-width strokes, not for fills. commit 7f3db8f07e82f821bcd1dc418d66564f537609d6 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:21:01 2021 +0700 Do coordinate transformations even if the value isn't drawn The graph presently doesn't draw values < 0, but there was a discrepancy between drawing with and without step_shape. The default line/rectangle drawing reserves an empty place where the skipped value should've been, but no such empty place is reserved, when step_shape is used. This commit fixes that. Skipped values always have their empty space now. commit 7b731ebfe96af7a9dba9c57371da6761cf22c00c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:03:30 2021 +0700 Fix graph bar scaling when min_value is not 0 Previous calculation lead to underscaling of values so that even value = max_value wasn't drawn to full height. commit dca59fd8b7fff0e7e646f2dfc6bd92047028e5a8 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:18:40 2021 +0700 Calculate the horizontal graph bar offset only when needed commit 940ddf4b9825e9c3a91d276208c089d8c74e49db Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:17:23 2021 +0700 Call cr:move_to() only when drawing the graph with lines It's redundant otherwise and might even interfere with user's drawing in step_shape(). commit b5d5cd10ca29fb29cd751faaa95137c2fe89ff63 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:04:06 2021 +0700 Fix wrong horizontal offset calculations in widget.graph Indices in the loop are zero-based, so things like i-1 and i>1 aren't necessary and actually wouldn't have been correct even in a 1-based loop. step_shape = gears.shape.rectangle and step_shape = nil, which is supposed to default to rectangle according to docs, were drawn differently due to mismatched offset calculations, which led to problems especially noticeable when step_spacing is not zero, e.g: 1) whole graph shifted left too much, drawn partially off-screen. 2) whole graph shifted right too much, and the first bar placed closer to the rest than others. commit dc369fa198281e000afc1bd17326f3a264adf7d1 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 02:09:21 2021 +0700 Fix the "border_width affects graph bar width" bug Graph bars were drawn with lines of border_width thickness instead of 1, i.e. possibly too fat to be readable. commit 1ba73378cf29ed27a6ecd18716394779ceb543d9 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 00:13:03 2021 +0700 Simplify internal values representation in graph even more New values in graph._private.values were pushed to the end, and old values were removed from the beginning of the table. In draw() however newer values are drawn first, at the left edge of the graph, necessitating annoying idx = #values - idx conversions everywhere. In particular in stacked graph that means that values that are drawn together may have different indices. One has to shift the values array in add_value() either way, so one might as well insert new values at the beginning of the array and simplify the rest of the code. This doesn't seem like much yet, but it will prove to be much more convenient and less error-prone in what I'm going to do later. commit f70523ee68dbd6b67050ace4b82903a9b22440c7 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 13:35:10 2021 +0700 Simplify internal values representation in graph widget graph._private.values was either a table of values or a table of tables of values, depending on whether "stack" property is enabled, ... or a mix of both if user forgot to pass the second "group" parameter to graph:add_value(). This resulted in pretty fragile and confusing behavior, e.g. leaving user to wonder, why they see nothing more after setting stack=true for a graph in a previously working rc.lua, and demanding a very careful use of add_value() from the user, or else some values or even entire stacks would mysteriously vanish and errors in draw() get triggered, sometimes after a period of seemingly normal functioning. Now `values` is always a table of tables and the group parameter in add_value() defaults to 1, thus making the non-stacked graph a trivial variant of the stacked graph.
2021-05-20 17:14:03 +02:00
if min_value == max_value then
-- If all values are equal in an autoscaled graph,
-- simply draw them in the middle
min_value, max_value = min_value - 1, max_value + 1
end
end
2016-05-23 07:30:44 +02:00
graph: complete widget overhaul and bug fixes Squashed commit of the following: commit 69821f51fe1e8652715658543e50631ece495090 Author: Alex Belykh <albel727@ngs.ru> Date: Mon May 10 19:17:55 2021 +0700 Refactor property handling in wibox.widget.graph In addition, baseline_value, step_width and step_spacing properties are all set to their default non-nil numeric values during instance creation now. commit 842eb429bf5df4b2ba46b2e6f7646afe89b1a3c3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 9 06:18:45 2021 +0700 Make graph:draw_values() private There's no documentation nor confidence that it's a good API, so I'm hiding it for now. commit 720746780574e4bad14a71fc4174955139c2eb50 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 04:04:13 2021 +0700 Document graph_should_draw_data_group() for posterity commit 7c73f3754ce6ba86b118f1170a6f9e406ab4841d Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:59:21 2021 +0700 Make graph:should_draw_data_group() private There's no confidence whether it's a good API, so I'm hiding it for now. commit b3539e20adb6423118af726ae4f8751ef864b5d2 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:36:44 2021 +0700 Make graph:preprocess_values() private There's no confidence whether it's a good API, so I'm hiding it for now. commit 2df4400e780a6260f8d0ab1c60734fbb11bf3c4f Author: Alex Belykh <albel727@ngs.ru> Date: Thu May 6 10:05:23 2021 +0700 Add spec/wibox/widget/graph_spec.lua commit ce01a9771ab439b8df841f1565aa4aa8a44a5e0d Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 08:16:18 2021 +0700 Improve graph.capacity documentation commit e28fade8f655802f5bf89154169f68d256b8e332 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 07:36:01 2021 +0700 Document graph:compute_drawn_values_num() commit dd68332292465422d08b48e9f0229f143beccbc6 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 05:24:23 2021 +0700 Document graph:pick_data_group_color() method commit ca6dc55f77aca88a6e9cc90bb6fd0045739f41f5 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 04:13:58 2021 +0700 Guard against setting data groups sparsely in graph:add_value() commit cdea5f126f7984e72924f6dffbd312c4fe3b032f Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 02:36:12 2021 +0700 Refactor scaling and baseline choice out of graph:draw() commit 10135deafe8a9390ba66ab657ebb1fb41d549e8c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 21:34:48 2021 +0700 Document graph.nan_color and graph.stack properties commit 0d73bb84ac66b87e16aee472bd1f299549f66a1c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 20:49:24 2021 +0700 Use a better example in docs for the graph.stack property commit c27d160a0826d033d49d6141608c40aa8a0642fa Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 16:05:38 2021 +0700 Document graph.baseline_value property commit 459bc8176c13d4ea32af0dcdc2399d3b929b1c6a Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:53:47 2021 +0700 Document graph.clamp_bars property commit e732ee0b3b007d213e689b98cc00ae7cb141ae08 Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:34 2021 +0700 Improve graph:add_value() and :clear() documentation commit ae73e3aa9f8d60409cbe4cb4da9612f31af4f38e Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:11 2021 +0700 Improve graph min_value/max_value/scale documentation commit a7350996a9def773b91b1631e6c0507e33d8f93b Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:27:36 2021 +0700 Cleanup graph examples to show only relevant things in docs commit 30740c26f4f41c16f4c8a3c339bcb7bd299e14cd Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 14:04:36 2021 +0700 Rename graph.stack_colors property to `group_colors` The colors are used for stacked and non-stacked graphs alike, so the name didn't make sense. Also document and clarify some color stuff. commit 1ea01bfd410971cf0d0c25fd74cd764d5eb7279d Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 21:25:41 2021 +0700 Add an example file for code coverage of some fringe graph cases commit eae68e39553f88a6275a0c65bf040b5d4c262b54 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 20:23:32 2021 +0700 Add step_shape to the graph/nan_color example for code coverage commit 8027147bc2eab34a97b49a428fb8cef27f122bed Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:55:18 2021 +0700 Add example files for graph.nan_color/nan_indication properties commit 89a25a165c52b2c15b6c4d95ecb777a9067e66f9 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:33:07 2021 +0700 Make widget graph:add_value(v) default v to NaN 0 as a default makes no sense and silences the error that is failing to specify a value, to which the user should rather be alerted. The graph widget has sane NaN handling now and will do just that. commit 2835d552440be48d356a7f7b1d64963696dd550b Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:20:48 2021 +0700 Implement graph.nan_color and nan_indication properties Now, whenever there's a NaN among values and graph.nan_indication is set, the corresponding area, where the value bar should have been drawn, is filled with the nan_color from top to bottom. The painting is done after all data is rendered, to make sure that it won't be overpainted and go unnoticed. NaNs among graph's values are inevitably a sign of some error and it is arguably not a sane default behavior to simply not render them at all. The user should take immediate note of any errors by default, instead of wondering at a mysteriously empty graph widget, which is why nan_indication is set to true by default. If the user wants to silence NaNs, they can always simply set it to false. nan_color if unset, defaults to a noticeable yellow-black pattern. As a direct and planned consequence, stacked graphs with negative values in them trigger the NaN handling too. But silencing NaN handling for those graphs is also possible and semi-sane, because then NaNs and negatives are ignored and the graph behaves (almost) like they were zeros. commit 9c1f8f08b400e01e19c534f3810c9c9ab485760c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 26 08:10:19 2021 +0700 Refactor out graph_map_value_to_widget_coordinates() function commit 1905991ddecabdae3e15ffb8ee5b18a1bee1a711 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 28 13:39:29 2021 +0700 Add an example file for graph.baseline_value property commit b31b39d66f4c037a9484b71a3f910703ae01fac0 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 07:03:49 2021 +0700 Add an example file for graph.clamp_bars=false/true comparison commit a1fc850e3047f9ee2c9ddeffacfbabf3ded34171 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 04:06:45 2021 +0700 Add an example file for graphs with negative values and shapes commit 2f012f8ff1bb5094c14e724a61e59a2a3a296bc8 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 01:06:05 2021 +0700 Add an example file for a graph displaying negative values commit 7b14fbd59a4c4c51962fd3110a1a900dd5f4eb98 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 11:18:26 2021 +0700 Add an example file for graphs with step_width < 1 commit dfd9a54544b637e2fbe3d1325f9cc24b3f4d2313 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 10:17:38 2021 +0700 Add an example file for graph.stack=false/true comparison Should also demonstrate that multiple data groups are possible for non-stacked graphs too. commit 5b532106c3fd255f9b66a66cfb88d977b489ab5d Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 09:34:14 2021 +0700 Add an example file for disabling graph data groups with nil colors commit ac6f1083c02fcb428f089a616f9b0ebaa5496b6c Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:57:25 2021 +0700 Add an example file for step_shape/width/spacing on stacked graphs commit b3703db6a5139e73e72c1cfd198ece2cd9be7df2 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:43:56 2021 +0700 Add an example file for scale=false/true on stacked graph commit b000aed4a12cb44fb501e1c8432a71a14b2f8f3f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 20 08:22:19 2021 +0700 Document deprecation of graph's width and height commit cb0ad617bd6c83a499896797318e3b0b588c3566 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 07:53:37 2021 +0700 Deprecate width and height properties of wibox.widget.graph commit 8c64e14ad91a074b784166a7054e53444cccb9d2 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 04:37:59 2021 +0700 Refactor graph:draw() to separate concerns Firstly, summation of group values for stacked graphs is extracted into graph:preprocess_values(). This makes graph:draw() smaller and exposes the fact, that stacked graphs are almost entirely just a form of a more general concept: pre-render data manipulation. It's something a user might want to customize, and now they can do it by monkey-patching the method. Secondly, the logic for deciding, if and with which color a data group should be drawn, is extracted into graph:should_draw_data_group() and get_data_group_color() methods respectively. This makes it customizable too and makes more obvious the fact, that it is a general thing that has nothing to do with stacked graphs in particular. With this there are only 5 lines remaining in graph:draw(), that could be said to be specific to stacked graphs, all pertaining to the `prev_y` variable, which merely preserves the bar coordinates from a previous data group. commit 2a32b6305d893f413303728a0a3da1dcd9a5591e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 22:52:37 2021 +0700 Pointlessly optimize graph:add_value() 1) Purge old values first, then insert the new one, if needed. Since Lua grows arrays in power of two steps, and our guess_capacity() also takes care to return even integers, it would be a shame to overstep the capacity by one by inserting first and thus getting the table size doubled only to never use the most of it. 2) Use a removal procedure that doesn't call the slow length operator at all. Previous code called it twice (once in table.remove) for every removed value. commit 680ea8f5d81e9040afb4662b3229e937e4b89e02 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:23:38 2021 +0700 Set graph data series color before the painting loop commit b970f0f221ae76250f2a94433ff79d653caa6e21 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:05:37 2021 +0700 Drop a large no longer necessary `if` in graph:draw_values() This commit is almost purely a whitespace change. commit 6b4f0541c70ec99d7f6e276bae64939f337120d6 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:00:50 2021 +0700 Extract values rendering code into graph:draw_values() method This is to avoid ungodly if nesting levels, which are needed to bail out of drawing values early and skip to drawing the border. The next commit will get rid of the if. commit e0950e4f99d1ea1c4b8cea2cc67629fa40030c2a Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 14:50:20 2021 +0700 Track graph draw() usage to guess necessary array capacity The last step of divorcing add_values() from width calculations, while ensuring that user doesn't observe a lack of data to draw. The widget now tracks an approximate number of values that it has been asked to draw() in _private.last_drawn_values_num, and truncates its values array based on that (if not overridden by the capacity property). commit 2e3d542282f35e7c0fed454da0f2eada0fcf3b78 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 13:35:12 2021 +0700 Add `capacity` property to graph widget This is the second step of divorcing add_values() from calculations involving widths. How many values are stored and how many values are drawn are distinct concepts. There isn't even a single definitive "how many values are drawn" because the widget could be rendered in several places with distinct widths simultaneously, and in every such place the data must be drawn appropriately, e.g. with auto-scaling, that takes only the visible data points into account. And the draw() method can do that now. The default capacity value of nil uses heuristics based on widget's _private.width to guess some capacity that would tolerate some dynamic widget resizing. If the user finds the heuristic failing them, they can set the capacity property to the desired maximum number of values that will be kept in the _private.values array (in each data group). commit 08d4a80686f7fe363551c35589a460839f60853e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 03:27:18 2021 +0700 Extract values size computation into compute_drawn_values_num() This is the first step of making add_values() not concern itself (directly) with widget dimensions. The user can monkey-patch the method to override how many values are (logically) drawn, because it affects graph auto-scaling and can be useful for rendering of step_shape-s that paint outside of their step_width. I didn't make it a full-blown function-valued property, because it seems a fringe functionality, which I'm too lazy to document. commit 3c5617c98ab1147618ed22e8414a77fc56b45f6e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 02:32:29 2021 +0700 Copy graph widget prototype methods over in bulk commit 169538837ece41a03d02ba8fbc0e4097062b67a3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun Apr 25 05:58:01 2021 +0700 Remove graph.baseline_y property After giving it some thought I've concluded, that it is unlikely to be useful as it is now. I'd better reserve the name for some more sensible functionality, like fixing the baseline_value defined axis at the given widget coordinate, adjusting the scaling accordingly, so min_value/max_value won't generally correspond to widget top and bottom anymore. commit 202ffaa9a0b820c7c471e1121d01b91f8f76cd38 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 12:06:55 2021 +0700 Add baseline_y and baseline_value properties to graph widget Now values in graph have a *baseline*, tunable with baseline_y and baseline_value properties. It is the (for now invisible) horizontal line from which all bars in the graph grow. I call it a "baseline" instead of "axis", because it doesn't actually have to correspond to the 0 value. But it can, and actually does now by default. baseline_y and baseline_value properties determine the vertical placement of the baseline in the widget. baseline_y is the position of the baseline in normalized widget coordinates, e.g. 0 corresponds to widget's top, 1 - to widget's bottom, and 0.5 - to widget's middle. So if e.g. one sets baseline_y = 0, then no matter how the widget is resized and how its values are scaled, bars in it will be drawn starting from the top. baseline_value is the position of the baseline in value coordinates. So if one sets baseline_value = 10, then no matter what happens, bars for values greater than 10 will be drawn as growing up from this line, and bars for values smaller as growing down. Where and if it will be seen in the widget, depends on the scaling. baseline_value = 0 is the familiar zero axis. baseline_y is unset by default and has priority, i.e. if it's set, then baseline_value is ignored. baseline_value defaults to 0. Prior to this commit, bars were always drawn from the bottom edge of the widget, or, in the new terms, the widget had baseline_y = 1. But it was indistinguishable from baseline_value = 0, because the widget clamped all negative values to 0, so the difference between the two could never be observed. baseline_value = 0 is clearly more useful and familiar to people, so now it is in force by default. commit df7e19abaebf0a871b5ca217b4cc3365533267a6 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 11:18:12 2021 +0700 Move value clamping from graph:add_value() into draw() Previously all values were forcibly clamped between 0 (not tunable at all) and max_value (1 by default). Thus graphs with negative values were not an option. NaN values were conflated with zeros, even though it may be useful to maintain the distinction and render them differently. And if user happened to dynamically change the max_value property, they discovered, that their data got truncated. Preserving user data as it was given is more flexible and makes things easier to debug. This commit does exactly that. So what happens now to the newly allowed values during draw()? There are 3 new cases to consider: 1) NaNs 2) Negatives 3) Values outside of min_value..max_value range NaNs are presently simply not drawn. An empty place is reserved where the value should've been, but nothing else is done. This is distinct from how a zero is handled, e.g. if step_shape is set, it is called with the height parameter = 0, which can still end up drawing something, depending on the shape. One could also consider addition of nan_color/nan_shape properties which could be drawn to alert user to a gap in their data. Negative values necessitate introduction of the concept of the zero axis, from which they will be drawn DOWN, in contrast to positive values. Where should this axis be drawn? How should step_shape-s with inherent direction, e.g. arrows, be drawn for negatives? This will be addressed in the next commit. Values outside of min/max_value range can imply shapes that go beyond widget dimensions. An arrow shape that goes beyond the roof is visibly different from an arrow that saturates and merely touches the widget top, and the user may reasonably desire either behavior. Therefore this commit introduces a boolean clamp_bars property, which defaults to true for reasons of robustness and backward compatibility with the behaviour prior to this commit. The property of course affects only the display of the values, the actual user data stays intact. commit c9c24fb1ab741cd6bb8074971d568e0646c51532 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:29:01 2021 +0700 Use rectangles for drawing graph bars of all sizes I have benchmarked various bar drawing strategies and graph sizes and found out that drawing graph bars with rectangles is as fast or even marginally faster than using fat lines. Thus the code that tries to special-case 1px bars as lines seems to be not worth the additional complexity it introduces, and this commit removes it. Incidentally, I've also established, that rectangles are more robust. If one draws graph bars and feeds cairo very large coordinates (~2^62), then corresponding rectangles fail to render, but all the other rectangles in the same series get rendered successfully, whereas a single large coordinate in a move_to/line_to() sequence is enough to cause all lines in the batch to fail to render. commit bc7f4be5e06405b5aa7fb443a5ace2b9b9575b1f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:01:42 2021 +0700 Cache cairo methods used in the inner loop of graph:draw() I have benchmarked the draw() method, both on cairo xcb and on argb32 image surfaces, and have discovered that cairo context method calls are unnaturally slow, to the point, that almost nothing else in the function matters. For example, drawing bars with vertical lines made the whole draw() function almost 2 times slower than doing the same with rectangles, merely because the former requires two method calls (move_to/line_to), and the latter only one. Simply caching hotpath cairo methods in local variables, reduced the difference between the drawing methods to a negligible one and made the whole function 4 times faster on a typical 100-bar graph, (1.4ms to 0.33ms on my laptop) thus bringing closer my secret dream of drawing live audio data with awesome graphs (kidding). I guess, LGI does something not very optimal there, like gobject-introspecting the method every time, or something. There might be some untapped optimization potential, possibly in upstream, like a method cache, from which everything in awesome could benefit, even though I imagine not many things are so call-intensive as the graph widget, to really feel the difference. commit 538df25bdf02b53d0bbe81809eef2e6fdb05ff6f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 07:32:37 2021 +0700 Use 1-based index in the inner graph draw loop It's used there only once for a calculation and then several times to index some tables, and index 0 is bad for lua tables performance-wise. commit 7412546916cd0a0a490130a120255ffb719398b8 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:55:12 2021 +0700 Leave graph.max_value uninitialized at construction This is a user-visible change, but the previous behavior was probably not what user expected anyway, namely with graph.scale set the graph mysteriously couldn't zoom in onto values that are smaller than 1. commit eb81d7929a161e79c13262cfa73ac2e6a820e8ed Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:48:29 2021 +0700 Allow graph.max_value be nil This makes its behave symmetrically to min_value. Now it defaults to 1, when graph.scale is not set, and to the maximum value in the graph data, when graph.scale is set. commit f622a8430cbfde7007b58d7f4c9c76b08c91e997 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 12:01:13 2021 +0700 Calculate max values num in graph:add_value() more precisely At most so many values were kept in the widget values array, as there are pixels of usable widget width (i.e. sans border). This is exactly right for the surely most common case of graphs with 1px-wide bars, but is more than needed for graphs with step_width+step_spacing>1, and not enough for graphs with subpixel bars. Moreover this made graph autoscaling behave weirdly in graphs with thicker bars, because old values survived longer in the array and kept affecting min_value/max_value calculations even after they were shifted off-screen. The maximum number of kept values is now calculated exactly as the number of values that can be displayed at once, taking step_width and step_spacing into account. It might be worth adding a max_values_num property to the graph to let user override this calculation, in case they draw shapes in step_shape() outside of its supposed rectangle, so our assumptions of visibility are wrong, or the widget often gets resized, resulting in the annoyance of visibly truncated series. commit 0aa63249a18641251a1bf77423739164f0eab39f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 02:30:13 2021 +0700 Fix border_color/border_width bugs in graph widget add_value() wrongly assumed that border_width is always set, when border_color is, so if user set only the latter, an error occurred. add_value() assumed that border_width is always < width which led to awesome hanging up in an infinite loop, should that fail to be the case. The graph border_width property was driven by the border_color property, instead of the other way round. If border_color wasn't set, the border wasn't drawn all, even if user sets non-zero border_width, which is a confusing behavior, in contrast to all other widgets, which simply use a plethora of fallback colors to draw the border, when needed. This also meant that, despite being documented, the beautiful.graph_border_color fallback color could not and was not ever used. If the border_color was set though, an unset border_width was immediately assumed to be 1 instead of 0, also contrary to what other widgets do. Theme colors shouldn't influence widget layout, so this commit fixes this behavior to: 1) border_width always defaults to 0, if unset. 2) border_color falls back to beautiful.graph_border_color or white. Maybe one should also add a beautiful.graph_border_width theme variable, like some widgets do. commit dad4a3253b8fef971fb82720d2a407fcc0a879ad Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 03:11:27 2021 +0700 Implement multiple data series drawing for non-stacked graph The widget can already hold and draw multiple data groups, so it makes little sense to limit drawing only to the first group when graph.stack = false, probably leading to user's confusion. Now user can call add_value(v, group) with group > 1 and the data series will be drawn (over groups with lesser id-s) with graph.stack_colors[group] color, if stack_colors is set, or with graph.color, if stack_colors isn't set. Thus nothing visibly changes for all current users of non-stacked graphs with only single data series, but the new functionality is now there for those who need it. One can't fully emulate this functionality, e.g. with multiple graphs widgets in a wibox.layout.stack, because data series in a single widget can enjoy a common auto-scaling. commit 3e645c976184ab5f50d625271c25062db9ab7ed9 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:59:12 2021 +0700 Don't draw a stacked graph data group if there's no color for it If stack_colors[idx] is nil for the group `idx`, don't draw it at all (as opposed, e.g. to the effect of drawing it with a transparent color, or as though it had all values == 0, which both produce different results). This code is still subtly different from prior behavior, because now user can have a non-contiguous stack_colors table and turn the display of any data group on and off at will, whereas the code two commits earlier would just stop rendering as soon as ipairs(stack_colors) does, i.e. no data series were drawn past the first nil hole. This commit only looks large, but is mostly a whitespace change due to nesting from a pair of wrapping ifs. commit 8c0273786de1ade66bdd977699f98b588da9f487 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:32:42 2021 +0700 Unify stacked and non-stacked graph rendering Now stacked graphs support everything the nonstacked do, like step_width ~= 1 and custom step_shape-s. Technical notes: This code no longer calls cr:stroke() for every single value in a stacked graph, but rather paints a whole series and then cr:stroke/fill()-s it at all once with its color, improving performance considerably. Additionally the graph no longer relies on the presence of the stack_colors property to render series. It draws all data that it has, and, if there's no corresponding color in stack_colors, falls back to graph.color. This will be changed in the next commit to the prior behavior of only drawing those series for which there's a color in the stack_colors table, but that would've made this commit's diff much larger due to required nesting ifs over large code chunks. This commit also takes care to properly handle NaNs and negative values. Those are currently (almost) guaranteed to not occur in the values array due to clipping in graph:add_value(), but I intend to eventually drop the clipping, because it does nothing that draw() really relies upon, and allowing unclipped and NaN values is more flexible and less surprising, e.g. when user dynamically toggles graph.scale. Eventually I intend to implement drawing negative values in non-stacked graphs and possibly even in the stacked ones. commit 38c6589c51b2aa057d7c2f40182ecf32b5b2ff5e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 10:04:15 2021 +0700 Convert graph:draw() to use `self` commit f21f1b2550426df1c4ea8abde079b1ccef84b394 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 12:27:26 2021 +0700 Fix scaling for stacked graphs Stacked graphs have cumulative heights, values from all groups that end up in the same graph bar are effectively summed. Previous code didn't take that into account when calculating min/max_value for the purposes of rescaling, but simply examined every value separately. This code adds up all values up front and finds min/max among those, to determine proper scaling that would fit the graph exactly. commit 1ce4c7bf63bdd6c99c195d668347bec49355e921 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 09:49:35 2021 +0700 Draw shaped graph bars only up to the baseline Prior to this commit, every step_shape bar was, for some reason, drawn with height = full widget's height, regardless of the value it represents. So for the most bars their lower end was far outside the box and therefore clipped off. So e.g. one couldn't see the lower round edges of rounded rectangles. This commit fixes that by appropriately varying the height that is passed into step_shape(), so that lower edges of the bars always exactly touch the lower edge of the graph. If the user wants to restore the prior behavior, they can simply ignore the passed-in height in step_shape() and just always draw their shape with height = full widget height. But they couldn't replicate the effect of this commit in their rc.lua without essentially writing their own graph widget. commit e25185b08da23745b5815f85f6e9e43222ed2294 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 08:42:54 2021 +0700 Avoid incremental transform shifting during graph bar rendering Instead of letting addition errors and cognitive load accumulate simply use the same offset calculation that is used for lines and reset the transform back to the top-left corner after every drawn bar. It's conceptually simpler, and will be useful later for stacked graphs and more. It also fixes a discrepancy between plain and step_shape rendering. Prior to this commit, bar coloring occurred with a disturbed transform, which was centered at the last drawn bar, wherever it happened to be. This meant that nontrivial colors, like gradients and images, were arbitrarily offset when step_shape was used. commit f3e98866d8583c35d0b557c09f529299edc9ba2c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:45:29 2021 +0700 Refactor out graph bar height expression into a variable commit f54528d6892f5aaa4595917853c9e2130f743fdb Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:20:25 2021 +0700 Draw graph bars with lines only when step_width == 1 Before this commit graph bars of step_width < 1 were drawn with lines of width 1, but that's a bit unfortunate, because it led to much overpainting, while still ending up with a graphically incorrect result. Let's draw such bars with rectangles and let cairo do things with subpixels, getting more graphically accurate result, while still keeping the common case of step_width == 1 (supposedly) fast-drawn with lines. commit b6515dca67c18b78eb5657378131a62ab43d4cfc Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:54:07 2021 +0700 Fix unsharp edges when drawing graph bars with rectangles The 0.5 offset is only helpful for 1-width strokes, not for fills. commit 7f3db8f07e82f821bcd1dc418d66564f537609d6 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:21:01 2021 +0700 Do coordinate transformations even if the value isn't drawn The graph presently doesn't draw values < 0, but there was a discrepancy between drawing with and without step_shape. The default line/rectangle drawing reserves an empty place where the skipped value should've been, but no such empty place is reserved, when step_shape is used. This commit fixes that. Skipped values always have their empty space now. commit 7b731ebfe96af7a9dba9c57371da6761cf22c00c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:03:30 2021 +0700 Fix graph bar scaling when min_value is not 0 Previous calculation lead to underscaling of values so that even value = max_value wasn't drawn to full height. commit dca59fd8b7fff0e7e646f2dfc6bd92047028e5a8 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:18:40 2021 +0700 Calculate the horizontal graph bar offset only when needed commit 940ddf4b9825e9c3a91d276208c089d8c74e49db Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:17:23 2021 +0700 Call cr:move_to() only when drawing the graph with lines It's redundant otherwise and might even interfere with user's drawing in step_shape(). commit b5d5cd10ca29fb29cd751faaa95137c2fe89ff63 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:04:06 2021 +0700 Fix wrong horizontal offset calculations in widget.graph Indices in the loop are zero-based, so things like i-1 and i>1 aren't necessary and actually wouldn't have been correct even in a 1-based loop. step_shape = gears.shape.rectangle and step_shape = nil, which is supposed to default to rectangle according to docs, were drawn differently due to mismatched offset calculations, which led to problems especially noticeable when step_spacing is not zero, e.g: 1) whole graph shifted left too much, drawn partially off-screen. 2) whole graph shifted right too much, and the first bar placed closer to the rest than others. commit dc369fa198281e000afc1bd17326f3a264adf7d1 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 02:09:21 2021 +0700 Fix the "border_width affects graph bar width" bug Graph bars were drawn with lines of border_width thickness instead of 1, i.e. possibly too fat to be readable. commit 1ba73378cf29ed27a6ecd18716394779ceb543d9 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 00:13:03 2021 +0700 Simplify internal values representation in graph even more New values in graph._private.values were pushed to the end, and old values were removed from the beginning of the table. In draw() however newer values are drawn first, at the left edge of the graph, necessitating annoying idx = #values - idx conversions everywhere. In particular in stacked graph that means that values that are drawn together may have different indices. One has to shift the values array in add_value() either way, so one might as well insert new values at the beginning of the array and simplify the rest of the code. This doesn't seem like much yet, but it will prove to be much more convenient and less error-prone in what I'm going to do later. commit f70523ee68dbd6b67050ace4b82903a9b22440c7 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 13:35:10 2021 +0700 Simplify internal values representation in graph widget graph._private.values was either a table of values or a table of tables of values, depending on whether "stack" property is enabled, ... or a mix of both if user forgot to pass the second "group" parameter to graph:add_value(). This resulted in pretty fragile and confusing behavior, e.g. leaving user to wonder, why they see nothing more after setting stack=true for a graph in a previously working rc.lua, and demanding a very careful use of add_value() from the user, or else some values or even entire stacks would mysteriously vanish and errors in draw() get triggered, sometimes after a period of seemingly normal functioning. Now `values` is always a table of tables and the group parameter in add_value() defaults to 1, thus making the non-stacked graph a trivial variant of the stacked graph.
2021-05-20 17:14:03 +02:00
-- The position of the baseline in value coordinates
-- It defaults to the usual zero axis
local baseline_value = self._private.baseline_value or prop_fallbacks.baseline_value
-- Let's map it into widget coordinates
local baseline_y = graph_map_value_to_widget_coordinates(
self, baseline_value, min_value, max_value, height
)
return min_value, max_value, baseline_y
end
local function graph_draw_values(self, cr, _, height, drawn_values_num)
local values = self._private.values
local step_shape = self._private.step_shape
local step_spacing = self._private.step_spacing or prop_fallbacks.step_spacing
local step_width = self._private.step_width or prop_fallbacks.step_width
-- Cache methods used in the inner loop for a 3x performance boost
local cairo_rectangle = cr.rectangle
local cairo_translate = cr.translate
local cairo_set_matrix = cr.set_matrix
local map_coords = graph_map_value_to_widget_coordinates
-- Preserve the transform centered at the top-left corner of the graph
local pristine_transform = step_shape and cr:get_matrix()
local drawn_values, scaling_values = graph_preprocess_values(self, values, drawn_values_num)
-- If preprocessor returned drawn_values = nil, then simply draw the values we have
drawn_values = drawn_values or values
-- If preprocessor returned scaling_values = nil, then
-- all drawn values need to be examined to determine proper scaling
scaling_values = scaling_values or drawn_values
local min_value, max_value, baseline_y = graph_choose_coordinate_system(
self, scaling_values, drawn_values_num, height
)
local nan_x = self._private.nan_indication and {}
local prev_y = self._private.stack and {}
for group_idx, group_values in ipairs(drawn_values) do
if graph_should_draw_data_group(self, group_idx) then
-- Set the data series' color early, in case the user
-- wants to do their own painting inside step_shape()
cr:set_source(color(self:pick_data_group_color(group_idx)))
for i = 1, math_min(#group_values, drawn_values_num) do
local value = group_values[i]
local value_y = map_coords(self, value, min_value, max_value, height)
local not_nan = value_y == value_y
-- The coordinate of the i-th bar's left edge
local x = (i-1)*(step_width + step_spacing)
if not_nan then
local base_y = baseline_y
if prev_y then
-- Draw from where the previous stacked series left off
base_y = prev_y[i] or base_y
-- Save our y for the next stacked series
prev_y[i] = value_y
end
if step_shape then
graph: complete widget overhaul and bug fixes Squashed commit of the following: commit 69821f51fe1e8652715658543e50631ece495090 Author: Alex Belykh <albel727@ngs.ru> Date: Mon May 10 19:17:55 2021 +0700 Refactor property handling in wibox.widget.graph In addition, baseline_value, step_width and step_spacing properties are all set to their default non-nil numeric values during instance creation now. commit 842eb429bf5df4b2ba46b2e6f7646afe89b1a3c3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 9 06:18:45 2021 +0700 Make graph:draw_values() private There's no documentation nor confidence that it's a good API, so I'm hiding it for now. commit 720746780574e4bad14a71fc4174955139c2eb50 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 04:04:13 2021 +0700 Document graph_should_draw_data_group() for posterity commit 7c73f3754ce6ba86b118f1170a6f9e406ab4841d Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:59:21 2021 +0700 Make graph:should_draw_data_group() private There's no confidence whether it's a good API, so I'm hiding it for now. commit b3539e20adb6423118af726ae4f8751ef864b5d2 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:36:44 2021 +0700 Make graph:preprocess_values() private There's no confidence whether it's a good API, so I'm hiding it for now. commit 2df4400e780a6260f8d0ab1c60734fbb11bf3c4f Author: Alex Belykh <albel727@ngs.ru> Date: Thu May 6 10:05:23 2021 +0700 Add spec/wibox/widget/graph_spec.lua commit ce01a9771ab439b8df841f1565aa4aa8a44a5e0d Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 08:16:18 2021 +0700 Improve graph.capacity documentation commit e28fade8f655802f5bf89154169f68d256b8e332 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 07:36:01 2021 +0700 Document graph:compute_drawn_values_num() commit dd68332292465422d08b48e9f0229f143beccbc6 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 05:24:23 2021 +0700 Document graph:pick_data_group_color() method commit ca6dc55f77aca88a6e9cc90bb6fd0045739f41f5 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 04:13:58 2021 +0700 Guard against setting data groups sparsely in graph:add_value() commit cdea5f126f7984e72924f6dffbd312c4fe3b032f Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 02:36:12 2021 +0700 Refactor scaling and baseline choice out of graph:draw() commit 10135deafe8a9390ba66ab657ebb1fb41d549e8c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 21:34:48 2021 +0700 Document graph.nan_color and graph.stack properties commit 0d73bb84ac66b87e16aee472bd1f299549f66a1c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 20:49:24 2021 +0700 Use a better example in docs for the graph.stack property commit c27d160a0826d033d49d6141608c40aa8a0642fa Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 16:05:38 2021 +0700 Document graph.baseline_value property commit 459bc8176c13d4ea32af0dcdc2399d3b929b1c6a Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:53:47 2021 +0700 Document graph.clamp_bars property commit e732ee0b3b007d213e689b98cc00ae7cb141ae08 Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:34 2021 +0700 Improve graph:add_value() and :clear() documentation commit ae73e3aa9f8d60409cbe4cb4da9612f31af4f38e Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:11 2021 +0700 Improve graph min_value/max_value/scale documentation commit a7350996a9def773b91b1631e6c0507e33d8f93b Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:27:36 2021 +0700 Cleanup graph examples to show only relevant things in docs commit 30740c26f4f41c16f4c8a3c339bcb7bd299e14cd Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 14:04:36 2021 +0700 Rename graph.stack_colors property to `group_colors` The colors are used for stacked and non-stacked graphs alike, so the name didn't make sense. Also document and clarify some color stuff. commit 1ea01bfd410971cf0d0c25fd74cd764d5eb7279d Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 21:25:41 2021 +0700 Add an example file for code coverage of some fringe graph cases commit eae68e39553f88a6275a0c65bf040b5d4c262b54 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 20:23:32 2021 +0700 Add step_shape to the graph/nan_color example for code coverage commit 8027147bc2eab34a97b49a428fb8cef27f122bed Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:55:18 2021 +0700 Add example files for graph.nan_color/nan_indication properties commit 89a25a165c52b2c15b6c4d95ecb777a9067e66f9 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:33:07 2021 +0700 Make widget graph:add_value(v) default v to NaN 0 as a default makes no sense and silences the error that is failing to specify a value, to which the user should rather be alerted. The graph widget has sane NaN handling now and will do just that. commit 2835d552440be48d356a7f7b1d64963696dd550b Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:20:48 2021 +0700 Implement graph.nan_color and nan_indication properties Now, whenever there's a NaN among values and graph.nan_indication is set, the corresponding area, where the value bar should have been drawn, is filled with the nan_color from top to bottom. The painting is done after all data is rendered, to make sure that it won't be overpainted and go unnoticed. NaNs among graph's values are inevitably a sign of some error and it is arguably not a sane default behavior to simply not render them at all. The user should take immediate note of any errors by default, instead of wondering at a mysteriously empty graph widget, which is why nan_indication is set to true by default. If the user wants to silence NaNs, they can always simply set it to false. nan_color if unset, defaults to a noticeable yellow-black pattern. As a direct and planned consequence, stacked graphs with negative values in them trigger the NaN handling too. But silencing NaN handling for those graphs is also possible and semi-sane, because then NaNs and negatives are ignored and the graph behaves (almost) like they were zeros. commit 9c1f8f08b400e01e19c534f3810c9c9ab485760c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 26 08:10:19 2021 +0700 Refactor out graph_map_value_to_widget_coordinates() function commit 1905991ddecabdae3e15ffb8ee5b18a1bee1a711 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 28 13:39:29 2021 +0700 Add an example file for graph.baseline_value property commit b31b39d66f4c037a9484b71a3f910703ae01fac0 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 07:03:49 2021 +0700 Add an example file for graph.clamp_bars=false/true comparison commit a1fc850e3047f9ee2c9ddeffacfbabf3ded34171 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 04:06:45 2021 +0700 Add an example file for graphs with negative values and shapes commit 2f012f8ff1bb5094c14e724a61e59a2a3a296bc8 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 01:06:05 2021 +0700 Add an example file for a graph displaying negative values commit 7b14fbd59a4c4c51962fd3110a1a900dd5f4eb98 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 11:18:26 2021 +0700 Add an example file for graphs with step_width < 1 commit dfd9a54544b637e2fbe3d1325f9cc24b3f4d2313 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 10:17:38 2021 +0700 Add an example file for graph.stack=false/true comparison Should also demonstrate that multiple data groups are possible for non-stacked graphs too. commit 5b532106c3fd255f9b66a66cfb88d977b489ab5d Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 09:34:14 2021 +0700 Add an example file for disabling graph data groups with nil colors commit ac6f1083c02fcb428f089a616f9b0ebaa5496b6c Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:57:25 2021 +0700 Add an example file for step_shape/width/spacing on stacked graphs commit b3703db6a5139e73e72c1cfd198ece2cd9be7df2 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:43:56 2021 +0700 Add an example file for scale=false/true on stacked graph commit b000aed4a12cb44fb501e1c8432a71a14b2f8f3f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 20 08:22:19 2021 +0700 Document deprecation of graph's width and height commit cb0ad617bd6c83a499896797318e3b0b588c3566 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 07:53:37 2021 +0700 Deprecate width and height properties of wibox.widget.graph commit 8c64e14ad91a074b784166a7054e53444cccb9d2 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 04:37:59 2021 +0700 Refactor graph:draw() to separate concerns Firstly, summation of group values for stacked graphs is extracted into graph:preprocess_values(). This makes graph:draw() smaller and exposes the fact, that stacked graphs are almost entirely just a form of a more general concept: pre-render data manipulation. It's something a user might want to customize, and now they can do it by monkey-patching the method. Secondly, the logic for deciding, if and with which color a data group should be drawn, is extracted into graph:should_draw_data_group() and get_data_group_color() methods respectively. This makes it customizable too and makes more obvious the fact, that it is a general thing that has nothing to do with stacked graphs in particular. With this there are only 5 lines remaining in graph:draw(), that could be said to be specific to stacked graphs, all pertaining to the `prev_y` variable, which merely preserves the bar coordinates from a previous data group. commit 2a32b6305d893f413303728a0a3da1dcd9a5591e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 22:52:37 2021 +0700 Pointlessly optimize graph:add_value() 1) Purge old values first, then insert the new one, if needed. Since Lua grows arrays in power of two steps, and our guess_capacity() also takes care to return even integers, it would be a shame to overstep the capacity by one by inserting first and thus getting the table size doubled only to never use the most of it. 2) Use a removal procedure that doesn't call the slow length operator at all. Previous code called it twice (once in table.remove) for every removed value. commit 680ea8f5d81e9040afb4662b3229e937e4b89e02 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:23:38 2021 +0700 Set graph data series color before the painting loop commit b970f0f221ae76250f2a94433ff79d653caa6e21 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:05:37 2021 +0700 Drop a large no longer necessary `if` in graph:draw_values() This commit is almost purely a whitespace change. commit 6b4f0541c70ec99d7f6e276bae64939f337120d6 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:00:50 2021 +0700 Extract values rendering code into graph:draw_values() method This is to avoid ungodly if nesting levels, which are needed to bail out of drawing values early and skip to drawing the border. The next commit will get rid of the if. commit e0950e4f99d1ea1c4b8cea2cc67629fa40030c2a Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 14:50:20 2021 +0700 Track graph draw() usage to guess necessary array capacity The last step of divorcing add_values() from width calculations, while ensuring that user doesn't observe a lack of data to draw. The widget now tracks an approximate number of values that it has been asked to draw() in _private.last_drawn_values_num, and truncates its values array based on that (if not overridden by the capacity property). commit 2e3d542282f35e7c0fed454da0f2eada0fcf3b78 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 13:35:12 2021 +0700 Add `capacity` property to graph widget This is the second step of divorcing add_values() from calculations involving widths. How many values are stored and how many values are drawn are distinct concepts. There isn't even a single definitive "how many values are drawn" because the widget could be rendered in several places with distinct widths simultaneously, and in every such place the data must be drawn appropriately, e.g. with auto-scaling, that takes only the visible data points into account. And the draw() method can do that now. The default capacity value of nil uses heuristics based on widget's _private.width to guess some capacity that would tolerate some dynamic widget resizing. If the user finds the heuristic failing them, they can set the capacity property to the desired maximum number of values that will be kept in the _private.values array (in each data group). commit 08d4a80686f7fe363551c35589a460839f60853e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 03:27:18 2021 +0700 Extract values size computation into compute_drawn_values_num() This is the first step of making add_values() not concern itself (directly) with widget dimensions. The user can monkey-patch the method to override how many values are (logically) drawn, because it affects graph auto-scaling and can be useful for rendering of step_shape-s that paint outside of their step_width. I didn't make it a full-blown function-valued property, because it seems a fringe functionality, which I'm too lazy to document. commit 3c5617c98ab1147618ed22e8414a77fc56b45f6e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 02:32:29 2021 +0700 Copy graph widget prototype methods over in bulk commit 169538837ece41a03d02ba8fbc0e4097062b67a3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun Apr 25 05:58:01 2021 +0700 Remove graph.baseline_y property After giving it some thought I've concluded, that it is unlikely to be useful as it is now. I'd better reserve the name for some more sensible functionality, like fixing the baseline_value defined axis at the given widget coordinate, adjusting the scaling accordingly, so min_value/max_value won't generally correspond to widget top and bottom anymore. commit 202ffaa9a0b820c7c471e1121d01b91f8f76cd38 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 12:06:55 2021 +0700 Add baseline_y and baseline_value properties to graph widget Now values in graph have a *baseline*, tunable with baseline_y and baseline_value properties. It is the (for now invisible) horizontal line from which all bars in the graph grow. I call it a "baseline" instead of "axis", because it doesn't actually have to correspond to the 0 value. But it can, and actually does now by default. baseline_y and baseline_value properties determine the vertical placement of the baseline in the widget. baseline_y is the position of the baseline in normalized widget coordinates, e.g. 0 corresponds to widget's top, 1 - to widget's bottom, and 0.5 - to widget's middle. So if e.g. one sets baseline_y = 0, then no matter how the widget is resized and how its values are scaled, bars in it will be drawn starting from the top. baseline_value is the position of the baseline in value coordinates. So if one sets baseline_value = 10, then no matter what happens, bars for values greater than 10 will be drawn as growing up from this line, and bars for values smaller as growing down. Where and if it will be seen in the widget, depends on the scaling. baseline_value = 0 is the familiar zero axis. baseline_y is unset by default and has priority, i.e. if it's set, then baseline_value is ignored. baseline_value defaults to 0. Prior to this commit, bars were always drawn from the bottom edge of the widget, or, in the new terms, the widget had baseline_y = 1. But it was indistinguishable from baseline_value = 0, because the widget clamped all negative values to 0, so the difference between the two could never be observed. baseline_value = 0 is clearly more useful and familiar to people, so now it is in force by default. commit df7e19abaebf0a871b5ca217b4cc3365533267a6 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 11:18:12 2021 +0700 Move value clamping from graph:add_value() into draw() Previously all values were forcibly clamped between 0 (not tunable at all) and max_value (1 by default). Thus graphs with negative values were not an option. NaN values were conflated with zeros, even though it may be useful to maintain the distinction and render them differently. And if user happened to dynamically change the max_value property, they discovered, that their data got truncated. Preserving user data as it was given is more flexible and makes things easier to debug. This commit does exactly that. So what happens now to the newly allowed values during draw()? There are 3 new cases to consider: 1) NaNs 2) Negatives 3) Values outside of min_value..max_value range NaNs are presently simply not drawn. An empty place is reserved where the value should've been, but nothing else is done. This is distinct from how a zero is handled, e.g. if step_shape is set, it is called with the height parameter = 0, which can still end up drawing something, depending on the shape. One could also consider addition of nan_color/nan_shape properties which could be drawn to alert user to a gap in their data. Negative values necessitate introduction of the concept of the zero axis, from which they will be drawn DOWN, in contrast to positive values. Where should this axis be drawn? How should step_shape-s with inherent direction, e.g. arrows, be drawn for negatives? This will be addressed in the next commit. Values outside of min/max_value range can imply shapes that go beyond widget dimensions. An arrow shape that goes beyond the roof is visibly different from an arrow that saturates and merely touches the widget top, and the user may reasonably desire either behavior. Therefore this commit introduces a boolean clamp_bars property, which defaults to true for reasons of robustness and backward compatibility with the behaviour prior to this commit. The property of course affects only the display of the values, the actual user data stays intact. commit c9c24fb1ab741cd6bb8074971d568e0646c51532 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:29:01 2021 +0700 Use rectangles for drawing graph bars of all sizes I have benchmarked various bar drawing strategies and graph sizes and found out that drawing graph bars with rectangles is as fast or even marginally faster than using fat lines. Thus the code that tries to special-case 1px bars as lines seems to be not worth the additional complexity it introduces, and this commit removes it. Incidentally, I've also established, that rectangles are more robust. If one draws graph bars and feeds cairo very large coordinates (~2^62), then corresponding rectangles fail to render, but all the other rectangles in the same series get rendered successfully, whereas a single large coordinate in a move_to/line_to() sequence is enough to cause all lines in the batch to fail to render. commit bc7f4be5e06405b5aa7fb443a5ace2b9b9575b1f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:01:42 2021 +0700 Cache cairo methods used in the inner loop of graph:draw() I have benchmarked the draw() method, both on cairo xcb and on argb32 image surfaces, and have discovered that cairo context method calls are unnaturally slow, to the point, that almost nothing else in the function matters. For example, drawing bars with vertical lines made the whole draw() function almost 2 times slower than doing the same with rectangles, merely because the former requires two method calls (move_to/line_to), and the latter only one. Simply caching hotpath cairo methods in local variables, reduced the difference between the drawing methods to a negligible one and made the whole function 4 times faster on a typical 100-bar graph, (1.4ms to 0.33ms on my laptop) thus bringing closer my secret dream of drawing live audio data with awesome graphs (kidding). I guess, LGI does something not very optimal there, like gobject-introspecting the method every time, or something. There might be some untapped optimization potential, possibly in upstream, like a method cache, from which everything in awesome could benefit, even though I imagine not many things are so call-intensive as the graph widget, to really feel the difference. commit 538df25bdf02b53d0bbe81809eef2e6fdb05ff6f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 07:32:37 2021 +0700 Use 1-based index in the inner graph draw loop It's used there only once for a calculation and then several times to index some tables, and index 0 is bad for lua tables performance-wise. commit 7412546916cd0a0a490130a120255ffb719398b8 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:55:12 2021 +0700 Leave graph.max_value uninitialized at construction This is a user-visible change, but the previous behavior was probably not what user expected anyway, namely with graph.scale set the graph mysteriously couldn't zoom in onto values that are smaller than 1. commit eb81d7929a161e79c13262cfa73ac2e6a820e8ed Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:48:29 2021 +0700 Allow graph.max_value be nil This makes its behave symmetrically to min_value. Now it defaults to 1, when graph.scale is not set, and to the maximum value in the graph data, when graph.scale is set. commit f622a8430cbfde7007b58d7f4c9c76b08c91e997 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 12:01:13 2021 +0700 Calculate max values num in graph:add_value() more precisely At most so many values were kept in the widget values array, as there are pixels of usable widget width (i.e. sans border). This is exactly right for the surely most common case of graphs with 1px-wide bars, but is more than needed for graphs with step_width+step_spacing>1, and not enough for graphs with subpixel bars. Moreover this made graph autoscaling behave weirdly in graphs with thicker bars, because old values survived longer in the array and kept affecting min_value/max_value calculations even after they were shifted off-screen. The maximum number of kept values is now calculated exactly as the number of values that can be displayed at once, taking step_width and step_spacing into account. It might be worth adding a max_values_num property to the graph to let user override this calculation, in case they draw shapes in step_shape() outside of its supposed rectangle, so our assumptions of visibility are wrong, or the widget often gets resized, resulting in the annoyance of visibly truncated series. commit 0aa63249a18641251a1bf77423739164f0eab39f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 02:30:13 2021 +0700 Fix border_color/border_width bugs in graph widget add_value() wrongly assumed that border_width is always set, when border_color is, so if user set only the latter, an error occurred. add_value() assumed that border_width is always < width which led to awesome hanging up in an infinite loop, should that fail to be the case. The graph border_width property was driven by the border_color property, instead of the other way round. If border_color wasn't set, the border wasn't drawn all, even if user sets non-zero border_width, which is a confusing behavior, in contrast to all other widgets, which simply use a plethora of fallback colors to draw the border, when needed. This also meant that, despite being documented, the beautiful.graph_border_color fallback color could not and was not ever used. If the border_color was set though, an unset border_width was immediately assumed to be 1 instead of 0, also contrary to what other widgets do. Theme colors shouldn't influence widget layout, so this commit fixes this behavior to: 1) border_width always defaults to 0, if unset. 2) border_color falls back to beautiful.graph_border_color or white. Maybe one should also add a beautiful.graph_border_width theme variable, like some widgets do. commit dad4a3253b8fef971fb82720d2a407fcc0a879ad Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 03:11:27 2021 +0700 Implement multiple data series drawing for non-stacked graph The widget can already hold and draw multiple data groups, so it makes little sense to limit drawing only to the first group when graph.stack = false, probably leading to user's confusion. Now user can call add_value(v, group) with group > 1 and the data series will be drawn (over groups with lesser id-s) with graph.stack_colors[group] color, if stack_colors is set, or with graph.color, if stack_colors isn't set. Thus nothing visibly changes for all current users of non-stacked graphs with only single data series, but the new functionality is now there for those who need it. One can't fully emulate this functionality, e.g. with multiple graphs widgets in a wibox.layout.stack, because data series in a single widget can enjoy a common auto-scaling. commit 3e645c976184ab5f50d625271c25062db9ab7ed9 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:59:12 2021 +0700 Don't draw a stacked graph data group if there's no color for it If stack_colors[idx] is nil for the group `idx`, don't draw it at all (as opposed, e.g. to the effect of drawing it with a transparent color, or as though it had all values == 0, which both produce different results). This code is still subtly different from prior behavior, because now user can have a non-contiguous stack_colors table and turn the display of any data group on and off at will, whereas the code two commits earlier would just stop rendering as soon as ipairs(stack_colors) does, i.e. no data series were drawn past the first nil hole. This commit only looks large, but is mostly a whitespace change due to nesting from a pair of wrapping ifs. commit 8c0273786de1ade66bdd977699f98b588da9f487 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:32:42 2021 +0700 Unify stacked and non-stacked graph rendering Now stacked graphs support everything the nonstacked do, like step_width ~= 1 and custom step_shape-s. Technical notes: This code no longer calls cr:stroke() for every single value in a stacked graph, but rather paints a whole series and then cr:stroke/fill()-s it at all once with its color, improving performance considerably. Additionally the graph no longer relies on the presence of the stack_colors property to render series. It draws all data that it has, and, if there's no corresponding color in stack_colors, falls back to graph.color. This will be changed in the next commit to the prior behavior of only drawing those series for which there's a color in the stack_colors table, but that would've made this commit's diff much larger due to required nesting ifs over large code chunks. This commit also takes care to properly handle NaNs and negative values. Those are currently (almost) guaranteed to not occur in the values array due to clipping in graph:add_value(), but I intend to eventually drop the clipping, because it does nothing that draw() really relies upon, and allowing unclipped and NaN values is more flexible and less surprising, e.g. when user dynamically toggles graph.scale. Eventually I intend to implement drawing negative values in non-stacked graphs and possibly even in the stacked ones. commit 38c6589c51b2aa057d7c2f40182ecf32b5b2ff5e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 10:04:15 2021 +0700 Convert graph:draw() to use `self` commit f21f1b2550426df1c4ea8abde079b1ccef84b394 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 12:27:26 2021 +0700 Fix scaling for stacked graphs Stacked graphs have cumulative heights, values from all groups that end up in the same graph bar are effectively summed. Previous code didn't take that into account when calculating min/max_value for the purposes of rescaling, but simply examined every value separately. This code adds up all values up front and finds min/max among those, to determine proper scaling that would fit the graph exactly. commit 1ce4c7bf63bdd6c99c195d668347bec49355e921 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 09:49:35 2021 +0700 Draw shaped graph bars only up to the baseline Prior to this commit, every step_shape bar was, for some reason, drawn with height = full widget's height, regardless of the value it represents. So for the most bars their lower end was far outside the box and therefore clipped off. So e.g. one couldn't see the lower round edges of rounded rectangles. This commit fixes that by appropriately varying the height that is passed into step_shape(), so that lower edges of the bars always exactly touch the lower edge of the graph. If the user wants to restore the prior behavior, they can simply ignore the passed-in height in step_shape() and just always draw their shape with height = full widget height. But they couldn't replicate the effect of this commit in their rc.lua without essentially writing their own graph widget. commit e25185b08da23745b5815f85f6e9e43222ed2294 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 08:42:54 2021 +0700 Avoid incremental transform shifting during graph bar rendering Instead of letting addition errors and cognitive load accumulate simply use the same offset calculation that is used for lines and reset the transform back to the top-left corner after every drawn bar. It's conceptually simpler, and will be useful later for stacked graphs and more. It also fixes a discrepancy between plain and step_shape rendering. Prior to this commit, bar coloring occurred with a disturbed transform, which was centered at the last drawn bar, wherever it happened to be. This meant that nontrivial colors, like gradients and images, were arbitrarily offset when step_shape was used. commit f3e98866d8583c35d0b557c09f529299edc9ba2c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:45:29 2021 +0700 Refactor out graph bar height expression into a variable commit f54528d6892f5aaa4595917853c9e2130f743fdb Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:20:25 2021 +0700 Draw graph bars with lines only when step_width == 1 Before this commit graph bars of step_width < 1 were drawn with lines of width 1, but that's a bit unfortunate, because it led to much overpainting, while still ending up with a graphically incorrect result. Let's draw such bars with rectangles and let cairo do things with subpixels, getting more graphically accurate result, while still keeping the common case of step_width == 1 (supposedly) fast-drawn with lines. commit b6515dca67c18b78eb5657378131a62ab43d4cfc Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:54:07 2021 +0700 Fix unsharp edges when drawing graph bars with rectangles The 0.5 offset is only helpful for 1-width strokes, not for fills. commit 7f3db8f07e82f821bcd1dc418d66564f537609d6 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:21:01 2021 +0700 Do coordinate transformations even if the value isn't drawn The graph presently doesn't draw values < 0, but there was a discrepancy between drawing with and without step_shape. The default line/rectangle drawing reserves an empty place where the skipped value should've been, but no such empty place is reserved, when step_shape is used. This commit fixes that. Skipped values always have their empty space now. commit 7b731ebfe96af7a9dba9c57371da6761cf22c00c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:03:30 2021 +0700 Fix graph bar scaling when min_value is not 0 Previous calculation lead to underscaling of values so that even value = max_value wasn't drawn to full height. commit dca59fd8b7fff0e7e646f2dfc6bd92047028e5a8 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:18:40 2021 +0700 Calculate the horizontal graph bar offset only when needed commit 940ddf4b9825e9c3a91d276208c089d8c74e49db Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:17:23 2021 +0700 Call cr:move_to() only when drawing the graph with lines It's redundant otherwise and might even interfere with user's drawing in step_shape(). commit b5d5cd10ca29fb29cd751faaa95137c2fe89ff63 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:04:06 2021 +0700 Fix wrong horizontal offset calculations in widget.graph Indices in the loop are zero-based, so things like i-1 and i>1 aren't necessary and actually wouldn't have been correct even in a 1-based loop. step_shape = gears.shape.rectangle and step_shape = nil, which is supposed to default to rectangle according to docs, were drawn differently due to mismatched offset calculations, which led to problems especially noticeable when step_spacing is not zero, e.g: 1) whole graph shifted left too much, drawn partially off-screen. 2) whole graph shifted right too much, and the first bar placed closer to the rest than others. commit dc369fa198281e000afc1bd17326f3a264adf7d1 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 02:09:21 2021 +0700 Fix the "border_width affects graph bar width" bug Graph bars were drawn with lines of border_width thickness instead of 1, i.e. possibly too fat to be readable. commit 1ba73378cf29ed27a6ecd18716394779ceb543d9 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 00:13:03 2021 +0700 Simplify internal values representation in graph even more New values in graph._private.values were pushed to the end, and old values were removed from the beginning of the table. In draw() however newer values are drawn first, at the left edge of the graph, necessitating annoying idx = #values - idx conversions everywhere. In particular in stacked graph that means that values that are drawn together may have different indices. One has to shift the values array in add_value() either way, so one might as well insert new values at the beginning of the array and simplify the rest of the code. This doesn't seem like much yet, but it will prove to be much more convenient and less error-prone in what I'm going to do later. commit f70523ee68dbd6b67050ace4b82903a9b22440c7 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 13:35:10 2021 +0700 Simplify internal values representation in graph widget graph._private.values was either a table of values or a table of tables of values, depending on whether "stack" property is enabled, ... or a mix of both if user forgot to pass the second "group" parameter to graph:add_value(). This resulted in pretty fragile and confusing behavior, e.g. leaving user to wonder, why they see nothing more after setting stack=true for a graph in a previously working rc.lua, and demanding a very careful use of add_value() from the user, or else some values or even entire stacks would mysteriously vanish and errors in draw() get triggered, sometimes after a period of seemingly normal functioning. Now `values` is always a table of tables and the group parameter in add_value() defaults to 1, thus making the non-stacked graph a trivial variant of the stacked graph.
2021-05-20 17:14:03 +02:00
-- Shift to the bar beginning
cairo_translate(cr, x, value_y)
step_shape(cr, step_width, base_y - value_y)
-- Undo the shift
cairo_set_matrix(cr, pristine_transform)
else
graph: complete widget overhaul and bug fixes Squashed commit of the following: commit 69821f51fe1e8652715658543e50631ece495090 Author: Alex Belykh <albel727@ngs.ru> Date: Mon May 10 19:17:55 2021 +0700 Refactor property handling in wibox.widget.graph In addition, baseline_value, step_width and step_spacing properties are all set to their default non-nil numeric values during instance creation now. commit 842eb429bf5df4b2ba46b2e6f7646afe89b1a3c3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 9 06:18:45 2021 +0700 Make graph:draw_values() private There's no documentation nor confidence that it's a good API, so I'm hiding it for now. commit 720746780574e4bad14a71fc4174955139c2eb50 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 04:04:13 2021 +0700 Document graph_should_draw_data_group() for posterity commit 7c73f3754ce6ba86b118f1170a6f9e406ab4841d Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:59:21 2021 +0700 Make graph:should_draw_data_group() private There's no confidence whether it's a good API, so I'm hiding it for now. commit b3539e20adb6423118af726ae4f8751ef864b5d2 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:36:44 2021 +0700 Make graph:preprocess_values() private There's no confidence whether it's a good API, so I'm hiding it for now. commit 2df4400e780a6260f8d0ab1c60734fbb11bf3c4f Author: Alex Belykh <albel727@ngs.ru> Date: Thu May 6 10:05:23 2021 +0700 Add spec/wibox/widget/graph_spec.lua commit ce01a9771ab439b8df841f1565aa4aa8a44a5e0d Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 08:16:18 2021 +0700 Improve graph.capacity documentation commit e28fade8f655802f5bf89154169f68d256b8e332 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 07:36:01 2021 +0700 Document graph:compute_drawn_values_num() commit dd68332292465422d08b48e9f0229f143beccbc6 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 05:24:23 2021 +0700 Document graph:pick_data_group_color() method commit ca6dc55f77aca88a6e9cc90bb6fd0045739f41f5 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 04:13:58 2021 +0700 Guard against setting data groups sparsely in graph:add_value() commit cdea5f126f7984e72924f6dffbd312c4fe3b032f Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 02:36:12 2021 +0700 Refactor scaling and baseline choice out of graph:draw() commit 10135deafe8a9390ba66ab657ebb1fb41d549e8c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 21:34:48 2021 +0700 Document graph.nan_color and graph.stack properties commit 0d73bb84ac66b87e16aee472bd1f299549f66a1c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 20:49:24 2021 +0700 Use a better example in docs for the graph.stack property commit c27d160a0826d033d49d6141608c40aa8a0642fa Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 16:05:38 2021 +0700 Document graph.baseline_value property commit 459bc8176c13d4ea32af0dcdc2399d3b929b1c6a Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:53:47 2021 +0700 Document graph.clamp_bars property commit e732ee0b3b007d213e689b98cc00ae7cb141ae08 Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:34 2021 +0700 Improve graph:add_value() and :clear() documentation commit ae73e3aa9f8d60409cbe4cb4da9612f31af4f38e Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:11 2021 +0700 Improve graph min_value/max_value/scale documentation commit a7350996a9def773b91b1631e6c0507e33d8f93b Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:27:36 2021 +0700 Cleanup graph examples to show only relevant things in docs commit 30740c26f4f41c16f4c8a3c339bcb7bd299e14cd Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 14:04:36 2021 +0700 Rename graph.stack_colors property to `group_colors` The colors are used for stacked and non-stacked graphs alike, so the name didn't make sense. Also document and clarify some color stuff. commit 1ea01bfd410971cf0d0c25fd74cd764d5eb7279d Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 21:25:41 2021 +0700 Add an example file for code coverage of some fringe graph cases commit eae68e39553f88a6275a0c65bf040b5d4c262b54 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 20:23:32 2021 +0700 Add step_shape to the graph/nan_color example for code coverage commit 8027147bc2eab34a97b49a428fb8cef27f122bed Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:55:18 2021 +0700 Add example files for graph.nan_color/nan_indication properties commit 89a25a165c52b2c15b6c4d95ecb777a9067e66f9 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:33:07 2021 +0700 Make widget graph:add_value(v) default v to NaN 0 as a default makes no sense and silences the error that is failing to specify a value, to which the user should rather be alerted. The graph widget has sane NaN handling now and will do just that. commit 2835d552440be48d356a7f7b1d64963696dd550b Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:20:48 2021 +0700 Implement graph.nan_color and nan_indication properties Now, whenever there's a NaN among values and graph.nan_indication is set, the corresponding area, where the value bar should have been drawn, is filled with the nan_color from top to bottom. The painting is done after all data is rendered, to make sure that it won't be overpainted and go unnoticed. NaNs among graph's values are inevitably a sign of some error and it is arguably not a sane default behavior to simply not render them at all. The user should take immediate note of any errors by default, instead of wondering at a mysteriously empty graph widget, which is why nan_indication is set to true by default. If the user wants to silence NaNs, they can always simply set it to false. nan_color if unset, defaults to a noticeable yellow-black pattern. As a direct and planned consequence, stacked graphs with negative values in them trigger the NaN handling too. But silencing NaN handling for those graphs is also possible and semi-sane, because then NaNs and negatives are ignored and the graph behaves (almost) like they were zeros. commit 9c1f8f08b400e01e19c534f3810c9c9ab485760c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 26 08:10:19 2021 +0700 Refactor out graph_map_value_to_widget_coordinates() function commit 1905991ddecabdae3e15ffb8ee5b18a1bee1a711 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 28 13:39:29 2021 +0700 Add an example file for graph.baseline_value property commit b31b39d66f4c037a9484b71a3f910703ae01fac0 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 07:03:49 2021 +0700 Add an example file for graph.clamp_bars=false/true comparison commit a1fc850e3047f9ee2c9ddeffacfbabf3ded34171 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 04:06:45 2021 +0700 Add an example file for graphs with negative values and shapes commit 2f012f8ff1bb5094c14e724a61e59a2a3a296bc8 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 01:06:05 2021 +0700 Add an example file for a graph displaying negative values commit 7b14fbd59a4c4c51962fd3110a1a900dd5f4eb98 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 11:18:26 2021 +0700 Add an example file for graphs with step_width < 1 commit dfd9a54544b637e2fbe3d1325f9cc24b3f4d2313 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 10:17:38 2021 +0700 Add an example file for graph.stack=false/true comparison Should also demonstrate that multiple data groups are possible for non-stacked graphs too. commit 5b532106c3fd255f9b66a66cfb88d977b489ab5d Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 09:34:14 2021 +0700 Add an example file for disabling graph data groups with nil colors commit ac6f1083c02fcb428f089a616f9b0ebaa5496b6c Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:57:25 2021 +0700 Add an example file for step_shape/width/spacing on stacked graphs commit b3703db6a5139e73e72c1cfd198ece2cd9be7df2 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:43:56 2021 +0700 Add an example file for scale=false/true on stacked graph commit b000aed4a12cb44fb501e1c8432a71a14b2f8f3f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 20 08:22:19 2021 +0700 Document deprecation of graph's width and height commit cb0ad617bd6c83a499896797318e3b0b588c3566 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 07:53:37 2021 +0700 Deprecate width and height properties of wibox.widget.graph commit 8c64e14ad91a074b784166a7054e53444cccb9d2 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 04:37:59 2021 +0700 Refactor graph:draw() to separate concerns Firstly, summation of group values for stacked graphs is extracted into graph:preprocess_values(). This makes graph:draw() smaller and exposes the fact, that stacked graphs are almost entirely just a form of a more general concept: pre-render data manipulation. It's something a user might want to customize, and now they can do it by monkey-patching the method. Secondly, the logic for deciding, if and with which color a data group should be drawn, is extracted into graph:should_draw_data_group() and get_data_group_color() methods respectively. This makes it customizable too and makes more obvious the fact, that it is a general thing that has nothing to do with stacked graphs in particular. With this there are only 5 lines remaining in graph:draw(), that could be said to be specific to stacked graphs, all pertaining to the `prev_y` variable, which merely preserves the bar coordinates from a previous data group. commit 2a32b6305d893f413303728a0a3da1dcd9a5591e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 22:52:37 2021 +0700 Pointlessly optimize graph:add_value() 1) Purge old values first, then insert the new one, if needed. Since Lua grows arrays in power of two steps, and our guess_capacity() also takes care to return even integers, it would be a shame to overstep the capacity by one by inserting first and thus getting the table size doubled only to never use the most of it. 2) Use a removal procedure that doesn't call the slow length operator at all. Previous code called it twice (once in table.remove) for every removed value. commit 680ea8f5d81e9040afb4662b3229e937e4b89e02 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:23:38 2021 +0700 Set graph data series color before the painting loop commit b970f0f221ae76250f2a94433ff79d653caa6e21 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:05:37 2021 +0700 Drop a large no longer necessary `if` in graph:draw_values() This commit is almost purely a whitespace change. commit 6b4f0541c70ec99d7f6e276bae64939f337120d6 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:00:50 2021 +0700 Extract values rendering code into graph:draw_values() method This is to avoid ungodly if nesting levels, which are needed to bail out of drawing values early and skip to drawing the border. The next commit will get rid of the if. commit e0950e4f99d1ea1c4b8cea2cc67629fa40030c2a Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 14:50:20 2021 +0700 Track graph draw() usage to guess necessary array capacity The last step of divorcing add_values() from width calculations, while ensuring that user doesn't observe a lack of data to draw. The widget now tracks an approximate number of values that it has been asked to draw() in _private.last_drawn_values_num, and truncates its values array based on that (if not overridden by the capacity property). commit 2e3d542282f35e7c0fed454da0f2eada0fcf3b78 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 13:35:12 2021 +0700 Add `capacity` property to graph widget This is the second step of divorcing add_values() from calculations involving widths. How many values are stored and how many values are drawn are distinct concepts. There isn't even a single definitive "how many values are drawn" because the widget could be rendered in several places with distinct widths simultaneously, and in every such place the data must be drawn appropriately, e.g. with auto-scaling, that takes only the visible data points into account. And the draw() method can do that now. The default capacity value of nil uses heuristics based on widget's _private.width to guess some capacity that would tolerate some dynamic widget resizing. If the user finds the heuristic failing them, they can set the capacity property to the desired maximum number of values that will be kept in the _private.values array (in each data group). commit 08d4a80686f7fe363551c35589a460839f60853e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 03:27:18 2021 +0700 Extract values size computation into compute_drawn_values_num() This is the first step of making add_values() not concern itself (directly) with widget dimensions. The user can monkey-patch the method to override how many values are (logically) drawn, because it affects graph auto-scaling and can be useful for rendering of step_shape-s that paint outside of their step_width. I didn't make it a full-blown function-valued property, because it seems a fringe functionality, which I'm too lazy to document. commit 3c5617c98ab1147618ed22e8414a77fc56b45f6e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 02:32:29 2021 +0700 Copy graph widget prototype methods over in bulk commit 169538837ece41a03d02ba8fbc0e4097062b67a3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun Apr 25 05:58:01 2021 +0700 Remove graph.baseline_y property After giving it some thought I've concluded, that it is unlikely to be useful as it is now. I'd better reserve the name for some more sensible functionality, like fixing the baseline_value defined axis at the given widget coordinate, adjusting the scaling accordingly, so min_value/max_value won't generally correspond to widget top and bottom anymore. commit 202ffaa9a0b820c7c471e1121d01b91f8f76cd38 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 12:06:55 2021 +0700 Add baseline_y and baseline_value properties to graph widget Now values in graph have a *baseline*, tunable with baseline_y and baseline_value properties. It is the (for now invisible) horizontal line from which all bars in the graph grow. I call it a "baseline" instead of "axis", because it doesn't actually have to correspond to the 0 value. But it can, and actually does now by default. baseline_y and baseline_value properties determine the vertical placement of the baseline in the widget. baseline_y is the position of the baseline in normalized widget coordinates, e.g. 0 corresponds to widget's top, 1 - to widget's bottom, and 0.5 - to widget's middle. So if e.g. one sets baseline_y = 0, then no matter how the widget is resized and how its values are scaled, bars in it will be drawn starting from the top. baseline_value is the position of the baseline in value coordinates. So if one sets baseline_value = 10, then no matter what happens, bars for values greater than 10 will be drawn as growing up from this line, and bars for values smaller as growing down. Where and if it will be seen in the widget, depends on the scaling. baseline_value = 0 is the familiar zero axis. baseline_y is unset by default and has priority, i.e. if it's set, then baseline_value is ignored. baseline_value defaults to 0. Prior to this commit, bars were always drawn from the bottom edge of the widget, or, in the new terms, the widget had baseline_y = 1. But it was indistinguishable from baseline_value = 0, because the widget clamped all negative values to 0, so the difference between the two could never be observed. baseline_value = 0 is clearly more useful and familiar to people, so now it is in force by default. commit df7e19abaebf0a871b5ca217b4cc3365533267a6 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 11:18:12 2021 +0700 Move value clamping from graph:add_value() into draw() Previously all values were forcibly clamped between 0 (not tunable at all) and max_value (1 by default). Thus graphs with negative values were not an option. NaN values were conflated with zeros, even though it may be useful to maintain the distinction and render them differently. And if user happened to dynamically change the max_value property, they discovered, that their data got truncated. Preserving user data as it was given is more flexible and makes things easier to debug. This commit does exactly that. So what happens now to the newly allowed values during draw()? There are 3 new cases to consider: 1) NaNs 2) Negatives 3) Values outside of min_value..max_value range NaNs are presently simply not drawn. An empty place is reserved where the value should've been, but nothing else is done. This is distinct from how a zero is handled, e.g. if step_shape is set, it is called with the height parameter = 0, which can still end up drawing something, depending on the shape. One could also consider addition of nan_color/nan_shape properties which could be drawn to alert user to a gap in their data. Negative values necessitate introduction of the concept of the zero axis, from which they will be drawn DOWN, in contrast to positive values. Where should this axis be drawn? How should step_shape-s with inherent direction, e.g. arrows, be drawn for negatives? This will be addressed in the next commit. Values outside of min/max_value range can imply shapes that go beyond widget dimensions. An arrow shape that goes beyond the roof is visibly different from an arrow that saturates and merely touches the widget top, and the user may reasonably desire either behavior. Therefore this commit introduces a boolean clamp_bars property, which defaults to true for reasons of robustness and backward compatibility with the behaviour prior to this commit. The property of course affects only the display of the values, the actual user data stays intact. commit c9c24fb1ab741cd6bb8074971d568e0646c51532 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:29:01 2021 +0700 Use rectangles for drawing graph bars of all sizes I have benchmarked various bar drawing strategies and graph sizes and found out that drawing graph bars with rectangles is as fast or even marginally faster than using fat lines. Thus the code that tries to special-case 1px bars as lines seems to be not worth the additional complexity it introduces, and this commit removes it. Incidentally, I've also established, that rectangles are more robust. If one draws graph bars and feeds cairo very large coordinates (~2^62), then corresponding rectangles fail to render, but all the other rectangles in the same series get rendered successfully, whereas a single large coordinate in a move_to/line_to() sequence is enough to cause all lines in the batch to fail to render. commit bc7f4be5e06405b5aa7fb443a5ace2b9b9575b1f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:01:42 2021 +0700 Cache cairo methods used in the inner loop of graph:draw() I have benchmarked the draw() method, both on cairo xcb and on argb32 image surfaces, and have discovered that cairo context method calls are unnaturally slow, to the point, that almost nothing else in the function matters. For example, drawing bars with vertical lines made the whole draw() function almost 2 times slower than doing the same with rectangles, merely because the former requires two method calls (move_to/line_to), and the latter only one. Simply caching hotpath cairo methods in local variables, reduced the difference between the drawing methods to a negligible one and made the whole function 4 times faster on a typical 100-bar graph, (1.4ms to 0.33ms on my laptop) thus bringing closer my secret dream of drawing live audio data with awesome graphs (kidding). I guess, LGI does something not very optimal there, like gobject-introspecting the method every time, or something. There might be some untapped optimization potential, possibly in upstream, like a method cache, from which everything in awesome could benefit, even though I imagine not many things are so call-intensive as the graph widget, to really feel the difference. commit 538df25bdf02b53d0bbe81809eef2e6fdb05ff6f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 07:32:37 2021 +0700 Use 1-based index in the inner graph draw loop It's used there only once for a calculation and then several times to index some tables, and index 0 is bad for lua tables performance-wise. commit 7412546916cd0a0a490130a120255ffb719398b8 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:55:12 2021 +0700 Leave graph.max_value uninitialized at construction This is a user-visible change, but the previous behavior was probably not what user expected anyway, namely with graph.scale set the graph mysteriously couldn't zoom in onto values that are smaller than 1. commit eb81d7929a161e79c13262cfa73ac2e6a820e8ed Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:48:29 2021 +0700 Allow graph.max_value be nil This makes its behave symmetrically to min_value. Now it defaults to 1, when graph.scale is not set, and to the maximum value in the graph data, when graph.scale is set. commit f622a8430cbfde7007b58d7f4c9c76b08c91e997 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 12:01:13 2021 +0700 Calculate max values num in graph:add_value() more precisely At most so many values were kept in the widget values array, as there are pixels of usable widget width (i.e. sans border). This is exactly right for the surely most common case of graphs with 1px-wide bars, but is more than needed for graphs with step_width+step_spacing>1, and not enough for graphs with subpixel bars. Moreover this made graph autoscaling behave weirdly in graphs with thicker bars, because old values survived longer in the array and kept affecting min_value/max_value calculations even after they were shifted off-screen. The maximum number of kept values is now calculated exactly as the number of values that can be displayed at once, taking step_width and step_spacing into account. It might be worth adding a max_values_num property to the graph to let user override this calculation, in case they draw shapes in step_shape() outside of its supposed rectangle, so our assumptions of visibility are wrong, or the widget often gets resized, resulting in the annoyance of visibly truncated series. commit 0aa63249a18641251a1bf77423739164f0eab39f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 02:30:13 2021 +0700 Fix border_color/border_width bugs in graph widget add_value() wrongly assumed that border_width is always set, when border_color is, so if user set only the latter, an error occurred. add_value() assumed that border_width is always < width which led to awesome hanging up in an infinite loop, should that fail to be the case. The graph border_width property was driven by the border_color property, instead of the other way round. If border_color wasn't set, the border wasn't drawn all, even if user sets non-zero border_width, which is a confusing behavior, in contrast to all other widgets, which simply use a plethora of fallback colors to draw the border, when needed. This also meant that, despite being documented, the beautiful.graph_border_color fallback color could not and was not ever used. If the border_color was set though, an unset border_width was immediately assumed to be 1 instead of 0, also contrary to what other widgets do. Theme colors shouldn't influence widget layout, so this commit fixes this behavior to: 1) border_width always defaults to 0, if unset. 2) border_color falls back to beautiful.graph_border_color or white. Maybe one should also add a beautiful.graph_border_width theme variable, like some widgets do. commit dad4a3253b8fef971fb82720d2a407fcc0a879ad Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 03:11:27 2021 +0700 Implement multiple data series drawing for non-stacked graph The widget can already hold and draw multiple data groups, so it makes little sense to limit drawing only to the first group when graph.stack = false, probably leading to user's confusion. Now user can call add_value(v, group) with group > 1 and the data series will be drawn (over groups with lesser id-s) with graph.stack_colors[group] color, if stack_colors is set, or with graph.color, if stack_colors isn't set. Thus nothing visibly changes for all current users of non-stacked graphs with only single data series, but the new functionality is now there for those who need it. One can't fully emulate this functionality, e.g. with multiple graphs widgets in a wibox.layout.stack, because data series in a single widget can enjoy a common auto-scaling. commit 3e645c976184ab5f50d625271c25062db9ab7ed9 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:59:12 2021 +0700 Don't draw a stacked graph data group if there's no color for it If stack_colors[idx] is nil for the group `idx`, don't draw it at all (as opposed, e.g. to the effect of drawing it with a transparent color, or as though it had all values == 0, which both produce different results). This code is still subtly different from prior behavior, because now user can have a non-contiguous stack_colors table and turn the display of any data group on and off at will, whereas the code two commits earlier would just stop rendering as soon as ipairs(stack_colors) does, i.e. no data series were drawn past the first nil hole. This commit only looks large, but is mostly a whitespace change due to nesting from a pair of wrapping ifs. commit 8c0273786de1ade66bdd977699f98b588da9f487 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:32:42 2021 +0700 Unify stacked and non-stacked graph rendering Now stacked graphs support everything the nonstacked do, like step_width ~= 1 and custom step_shape-s. Technical notes: This code no longer calls cr:stroke() for every single value in a stacked graph, but rather paints a whole series and then cr:stroke/fill()-s it at all once with its color, improving performance considerably. Additionally the graph no longer relies on the presence of the stack_colors property to render series. It draws all data that it has, and, if there's no corresponding color in stack_colors, falls back to graph.color. This will be changed in the next commit to the prior behavior of only drawing those series for which there's a color in the stack_colors table, but that would've made this commit's diff much larger due to required nesting ifs over large code chunks. This commit also takes care to properly handle NaNs and negative values. Those are currently (almost) guaranteed to not occur in the values array due to clipping in graph:add_value(), but I intend to eventually drop the clipping, because it does nothing that draw() really relies upon, and allowing unclipped and NaN values is more flexible and less surprising, e.g. when user dynamically toggles graph.scale. Eventually I intend to implement drawing negative values in non-stacked graphs and possibly even in the stacked ones. commit 38c6589c51b2aa057d7c2f40182ecf32b5b2ff5e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 10:04:15 2021 +0700 Convert graph:draw() to use `self` commit f21f1b2550426df1c4ea8abde079b1ccef84b394 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 12:27:26 2021 +0700 Fix scaling for stacked graphs Stacked graphs have cumulative heights, values from all groups that end up in the same graph bar are effectively summed. Previous code didn't take that into account when calculating min/max_value for the purposes of rescaling, but simply examined every value separately. This code adds up all values up front and finds min/max among those, to determine proper scaling that would fit the graph exactly. commit 1ce4c7bf63bdd6c99c195d668347bec49355e921 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 09:49:35 2021 +0700 Draw shaped graph bars only up to the baseline Prior to this commit, every step_shape bar was, for some reason, drawn with height = full widget's height, regardless of the value it represents. So for the most bars their lower end was far outside the box and therefore clipped off. So e.g. one couldn't see the lower round edges of rounded rectangles. This commit fixes that by appropriately varying the height that is passed into step_shape(), so that lower edges of the bars always exactly touch the lower edge of the graph. If the user wants to restore the prior behavior, they can simply ignore the passed-in height in step_shape() and just always draw their shape with height = full widget height. But they couldn't replicate the effect of this commit in their rc.lua without essentially writing their own graph widget. commit e25185b08da23745b5815f85f6e9e43222ed2294 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 08:42:54 2021 +0700 Avoid incremental transform shifting during graph bar rendering Instead of letting addition errors and cognitive load accumulate simply use the same offset calculation that is used for lines and reset the transform back to the top-left corner after every drawn bar. It's conceptually simpler, and will be useful later for stacked graphs and more. It also fixes a discrepancy between plain and step_shape rendering. Prior to this commit, bar coloring occurred with a disturbed transform, which was centered at the last drawn bar, wherever it happened to be. This meant that nontrivial colors, like gradients and images, were arbitrarily offset when step_shape was used. commit f3e98866d8583c35d0b557c09f529299edc9ba2c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:45:29 2021 +0700 Refactor out graph bar height expression into a variable commit f54528d6892f5aaa4595917853c9e2130f743fdb Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:20:25 2021 +0700 Draw graph bars with lines only when step_width == 1 Before this commit graph bars of step_width < 1 were drawn with lines of width 1, but that's a bit unfortunate, because it led to much overpainting, while still ending up with a graphically incorrect result. Let's draw such bars with rectangles and let cairo do things with subpixels, getting more graphically accurate result, while still keeping the common case of step_width == 1 (supposedly) fast-drawn with lines. commit b6515dca67c18b78eb5657378131a62ab43d4cfc Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:54:07 2021 +0700 Fix unsharp edges when drawing graph bars with rectangles The 0.5 offset is only helpful for 1-width strokes, not for fills. commit 7f3db8f07e82f821bcd1dc418d66564f537609d6 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:21:01 2021 +0700 Do coordinate transformations even if the value isn't drawn The graph presently doesn't draw values < 0, but there was a discrepancy between drawing with and without step_shape. The default line/rectangle drawing reserves an empty place where the skipped value should've been, but no such empty place is reserved, when step_shape is used. This commit fixes that. Skipped values always have their empty space now. commit 7b731ebfe96af7a9dba9c57371da6761cf22c00c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:03:30 2021 +0700 Fix graph bar scaling when min_value is not 0 Previous calculation lead to underscaling of values so that even value = max_value wasn't drawn to full height. commit dca59fd8b7fff0e7e646f2dfc6bd92047028e5a8 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:18:40 2021 +0700 Calculate the horizontal graph bar offset only when needed commit 940ddf4b9825e9c3a91d276208c089d8c74e49db Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:17:23 2021 +0700 Call cr:move_to() only when drawing the graph with lines It's redundant otherwise and might even interfere with user's drawing in step_shape(). commit b5d5cd10ca29fb29cd751faaa95137c2fe89ff63 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:04:06 2021 +0700 Fix wrong horizontal offset calculations in widget.graph Indices in the loop are zero-based, so things like i-1 and i>1 aren't necessary and actually wouldn't have been correct even in a 1-based loop. step_shape = gears.shape.rectangle and step_shape = nil, which is supposed to default to rectangle according to docs, were drawn differently due to mismatched offset calculations, which led to problems especially noticeable when step_spacing is not zero, e.g: 1) whole graph shifted left too much, drawn partially off-screen. 2) whole graph shifted right too much, and the first bar placed closer to the rest than others. commit dc369fa198281e000afc1bd17326f3a264adf7d1 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 02:09:21 2021 +0700 Fix the "border_width affects graph bar width" bug Graph bars were drawn with lines of border_width thickness instead of 1, i.e. possibly too fat to be readable. commit 1ba73378cf29ed27a6ecd18716394779ceb543d9 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 00:13:03 2021 +0700 Simplify internal values representation in graph even more New values in graph._private.values were pushed to the end, and old values were removed from the beginning of the table. In draw() however newer values are drawn first, at the left edge of the graph, necessitating annoying idx = #values - idx conversions everywhere. In particular in stacked graph that means that values that are drawn together may have different indices. One has to shift the values array in add_value() either way, so one might as well insert new values at the beginning of the array and simplify the rest of the code. This doesn't seem like much yet, but it will prove to be much more convenient and less error-prone in what I'm going to do later. commit f70523ee68dbd6b67050ace4b82903a9b22440c7 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 13:35:10 2021 +0700 Simplify internal values representation in graph widget graph._private.values was either a table of values or a table of tables of values, depending on whether "stack" property is enabled, ... or a mix of both if user forgot to pass the second "group" parameter to graph:add_value(). This resulted in pretty fragile and confusing behavior, e.g. leaving user to wonder, why they see nothing more after setting stack=true for a graph in a previously working rc.lua, and demanding a very careful use of add_value() from the user, or else some values or even entire stacks would mysteriously vanish and errors in draw() get triggered, sometimes after a period of seemingly normal functioning. Now `values` is always a table of tables and the group parameter in add_value() defaults to 1, thus making the non-stacked graph a trivial variant of the stacked graph.
2021-05-20 17:14:03 +02:00
cairo_rectangle(cr, x, value_y, step_width, base_y - value_y)
end
2016-05-23 07:30:44 +02:00
end
graph: complete widget overhaul and bug fixes Squashed commit of the following: commit 69821f51fe1e8652715658543e50631ece495090 Author: Alex Belykh <albel727@ngs.ru> Date: Mon May 10 19:17:55 2021 +0700 Refactor property handling in wibox.widget.graph In addition, baseline_value, step_width and step_spacing properties are all set to their default non-nil numeric values during instance creation now. commit 842eb429bf5df4b2ba46b2e6f7646afe89b1a3c3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 9 06:18:45 2021 +0700 Make graph:draw_values() private There's no documentation nor confidence that it's a good API, so I'm hiding it for now. commit 720746780574e4bad14a71fc4174955139c2eb50 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 04:04:13 2021 +0700 Document graph_should_draw_data_group() for posterity commit 7c73f3754ce6ba86b118f1170a6f9e406ab4841d Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:59:21 2021 +0700 Make graph:should_draw_data_group() private There's no confidence whether it's a good API, so I'm hiding it for now. commit b3539e20adb6423118af726ae4f8751ef864b5d2 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:36:44 2021 +0700 Make graph:preprocess_values() private There's no confidence whether it's a good API, so I'm hiding it for now. commit 2df4400e780a6260f8d0ab1c60734fbb11bf3c4f Author: Alex Belykh <albel727@ngs.ru> Date: Thu May 6 10:05:23 2021 +0700 Add spec/wibox/widget/graph_spec.lua commit ce01a9771ab439b8df841f1565aa4aa8a44a5e0d Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 08:16:18 2021 +0700 Improve graph.capacity documentation commit e28fade8f655802f5bf89154169f68d256b8e332 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 07:36:01 2021 +0700 Document graph:compute_drawn_values_num() commit dd68332292465422d08b48e9f0229f143beccbc6 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 05:24:23 2021 +0700 Document graph:pick_data_group_color() method commit ca6dc55f77aca88a6e9cc90bb6fd0045739f41f5 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 04:13:58 2021 +0700 Guard against setting data groups sparsely in graph:add_value() commit cdea5f126f7984e72924f6dffbd312c4fe3b032f Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 02:36:12 2021 +0700 Refactor scaling and baseline choice out of graph:draw() commit 10135deafe8a9390ba66ab657ebb1fb41d549e8c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 21:34:48 2021 +0700 Document graph.nan_color and graph.stack properties commit 0d73bb84ac66b87e16aee472bd1f299549f66a1c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 20:49:24 2021 +0700 Use a better example in docs for the graph.stack property commit c27d160a0826d033d49d6141608c40aa8a0642fa Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 16:05:38 2021 +0700 Document graph.baseline_value property commit 459bc8176c13d4ea32af0dcdc2399d3b929b1c6a Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:53:47 2021 +0700 Document graph.clamp_bars property commit e732ee0b3b007d213e689b98cc00ae7cb141ae08 Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:34 2021 +0700 Improve graph:add_value() and :clear() documentation commit ae73e3aa9f8d60409cbe4cb4da9612f31af4f38e Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:11 2021 +0700 Improve graph min_value/max_value/scale documentation commit a7350996a9def773b91b1631e6c0507e33d8f93b Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:27:36 2021 +0700 Cleanup graph examples to show only relevant things in docs commit 30740c26f4f41c16f4c8a3c339bcb7bd299e14cd Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 14:04:36 2021 +0700 Rename graph.stack_colors property to `group_colors` The colors are used for stacked and non-stacked graphs alike, so the name didn't make sense. Also document and clarify some color stuff. commit 1ea01bfd410971cf0d0c25fd74cd764d5eb7279d Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 21:25:41 2021 +0700 Add an example file for code coverage of some fringe graph cases commit eae68e39553f88a6275a0c65bf040b5d4c262b54 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 20:23:32 2021 +0700 Add step_shape to the graph/nan_color example for code coverage commit 8027147bc2eab34a97b49a428fb8cef27f122bed Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:55:18 2021 +0700 Add example files for graph.nan_color/nan_indication properties commit 89a25a165c52b2c15b6c4d95ecb777a9067e66f9 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:33:07 2021 +0700 Make widget graph:add_value(v) default v to NaN 0 as a default makes no sense and silences the error that is failing to specify a value, to which the user should rather be alerted. The graph widget has sane NaN handling now and will do just that. commit 2835d552440be48d356a7f7b1d64963696dd550b Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:20:48 2021 +0700 Implement graph.nan_color and nan_indication properties Now, whenever there's a NaN among values and graph.nan_indication is set, the corresponding area, where the value bar should have been drawn, is filled with the nan_color from top to bottom. The painting is done after all data is rendered, to make sure that it won't be overpainted and go unnoticed. NaNs among graph's values are inevitably a sign of some error and it is arguably not a sane default behavior to simply not render them at all. The user should take immediate note of any errors by default, instead of wondering at a mysteriously empty graph widget, which is why nan_indication is set to true by default. If the user wants to silence NaNs, they can always simply set it to false. nan_color if unset, defaults to a noticeable yellow-black pattern. As a direct and planned consequence, stacked graphs with negative values in them trigger the NaN handling too. But silencing NaN handling for those graphs is also possible and semi-sane, because then NaNs and negatives are ignored and the graph behaves (almost) like they were zeros. commit 9c1f8f08b400e01e19c534f3810c9c9ab485760c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 26 08:10:19 2021 +0700 Refactor out graph_map_value_to_widget_coordinates() function commit 1905991ddecabdae3e15ffb8ee5b18a1bee1a711 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 28 13:39:29 2021 +0700 Add an example file for graph.baseline_value property commit b31b39d66f4c037a9484b71a3f910703ae01fac0 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 07:03:49 2021 +0700 Add an example file for graph.clamp_bars=false/true comparison commit a1fc850e3047f9ee2c9ddeffacfbabf3ded34171 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 04:06:45 2021 +0700 Add an example file for graphs with negative values and shapes commit 2f012f8ff1bb5094c14e724a61e59a2a3a296bc8 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 01:06:05 2021 +0700 Add an example file for a graph displaying negative values commit 7b14fbd59a4c4c51962fd3110a1a900dd5f4eb98 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 11:18:26 2021 +0700 Add an example file for graphs with step_width < 1 commit dfd9a54544b637e2fbe3d1325f9cc24b3f4d2313 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 10:17:38 2021 +0700 Add an example file for graph.stack=false/true comparison Should also demonstrate that multiple data groups are possible for non-stacked graphs too. commit 5b532106c3fd255f9b66a66cfb88d977b489ab5d Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 09:34:14 2021 +0700 Add an example file for disabling graph data groups with nil colors commit ac6f1083c02fcb428f089a616f9b0ebaa5496b6c Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:57:25 2021 +0700 Add an example file for step_shape/width/spacing on stacked graphs commit b3703db6a5139e73e72c1cfd198ece2cd9be7df2 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:43:56 2021 +0700 Add an example file for scale=false/true on stacked graph commit b000aed4a12cb44fb501e1c8432a71a14b2f8f3f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 20 08:22:19 2021 +0700 Document deprecation of graph's width and height commit cb0ad617bd6c83a499896797318e3b0b588c3566 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 07:53:37 2021 +0700 Deprecate width and height properties of wibox.widget.graph commit 8c64e14ad91a074b784166a7054e53444cccb9d2 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 04:37:59 2021 +0700 Refactor graph:draw() to separate concerns Firstly, summation of group values for stacked graphs is extracted into graph:preprocess_values(). This makes graph:draw() smaller and exposes the fact, that stacked graphs are almost entirely just a form of a more general concept: pre-render data manipulation. It's something a user might want to customize, and now they can do it by monkey-patching the method. Secondly, the logic for deciding, if and with which color a data group should be drawn, is extracted into graph:should_draw_data_group() and get_data_group_color() methods respectively. This makes it customizable too and makes more obvious the fact, that it is a general thing that has nothing to do with stacked graphs in particular. With this there are only 5 lines remaining in graph:draw(), that could be said to be specific to stacked graphs, all pertaining to the `prev_y` variable, which merely preserves the bar coordinates from a previous data group. commit 2a32b6305d893f413303728a0a3da1dcd9a5591e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 22:52:37 2021 +0700 Pointlessly optimize graph:add_value() 1) Purge old values first, then insert the new one, if needed. Since Lua grows arrays in power of two steps, and our guess_capacity() also takes care to return even integers, it would be a shame to overstep the capacity by one by inserting first and thus getting the table size doubled only to never use the most of it. 2) Use a removal procedure that doesn't call the slow length operator at all. Previous code called it twice (once in table.remove) for every removed value. commit 680ea8f5d81e9040afb4662b3229e937e4b89e02 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:23:38 2021 +0700 Set graph data series color before the painting loop commit b970f0f221ae76250f2a94433ff79d653caa6e21 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:05:37 2021 +0700 Drop a large no longer necessary `if` in graph:draw_values() This commit is almost purely a whitespace change. commit 6b4f0541c70ec99d7f6e276bae64939f337120d6 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:00:50 2021 +0700 Extract values rendering code into graph:draw_values() method This is to avoid ungodly if nesting levels, which are needed to bail out of drawing values early and skip to drawing the border. The next commit will get rid of the if. commit e0950e4f99d1ea1c4b8cea2cc67629fa40030c2a Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 14:50:20 2021 +0700 Track graph draw() usage to guess necessary array capacity The last step of divorcing add_values() from width calculations, while ensuring that user doesn't observe a lack of data to draw. The widget now tracks an approximate number of values that it has been asked to draw() in _private.last_drawn_values_num, and truncates its values array based on that (if not overridden by the capacity property). commit 2e3d542282f35e7c0fed454da0f2eada0fcf3b78 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 13:35:12 2021 +0700 Add `capacity` property to graph widget This is the second step of divorcing add_values() from calculations involving widths. How many values are stored and how many values are drawn are distinct concepts. There isn't even a single definitive "how many values are drawn" because the widget could be rendered in several places with distinct widths simultaneously, and in every such place the data must be drawn appropriately, e.g. with auto-scaling, that takes only the visible data points into account. And the draw() method can do that now. The default capacity value of nil uses heuristics based on widget's _private.width to guess some capacity that would tolerate some dynamic widget resizing. If the user finds the heuristic failing them, they can set the capacity property to the desired maximum number of values that will be kept in the _private.values array (in each data group). commit 08d4a80686f7fe363551c35589a460839f60853e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 03:27:18 2021 +0700 Extract values size computation into compute_drawn_values_num() This is the first step of making add_values() not concern itself (directly) with widget dimensions. The user can monkey-patch the method to override how many values are (logically) drawn, because it affects graph auto-scaling and can be useful for rendering of step_shape-s that paint outside of their step_width. I didn't make it a full-blown function-valued property, because it seems a fringe functionality, which I'm too lazy to document. commit 3c5617c98ab1147618ed22e8414a77fc56b45f6e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 02:32:29 2021 +0700 Copy graph widget prototype methods over in bulk commit 169538837ece41a03d02ba8fbc0e4097062b67a3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun Apr 25 05:58:01 2021 +0700 Remove graph.baseline_y property After giving it some thought I've concluded, that it is unlikely to be useful as it is now. I'd better reserve the name for some more sensible functionality, like fixing the baseline_value defined axis at the given widget coordinate, adjusting the scaling accordingly, so min_value/max_value won't generally correspond to widget top and bottom anymore. commit 202ffaa9a0b820c7c471e1121d01b91f8f76cd38 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 12:06:55 2021 +0700 Add baseline_y and baseline_value properties to graph widget Now values in graph have a *baseline*, tunable with baseline_y and baseline_value properties. It is the (for now invisible) horizontal line from which all bars in the graph grow. I call it a "baseline" instead of "axis", because it doesn't actually have to correspond to the 0 value. But it can, and actually does now by default. baseline_y and baseline_value properties determine the vertical placement of the baseline in the widget. baseline_y is the position of the baseline in normalized widget coordinates, e.g. 0 corresponds to widget's top, 1 - to widget's bottom, and 0.5 - to widget's middle. So if e.g. one sets baseline_y = 0, then no matter how the widget is resized and how its values are scaled, bars in it will be drawn starting from the top. baseline_value is the position of the baseline in value coordinates. So if one sets baseline_value = 10, then no matter what happens, bars for values greater than 10 will be drawn as growing up from this line, and bars for values smaller as growing down. Where and if it will be seen in the widget, depends on the scaling. baseline_value = 0 is the familiar zero axis. baseline_y is unset by default and has priority, i.e. if it's set, then baseline_value is ignored. baseline_value defaults to 0. Prior to this commit, bars were always drawn from the bottom edge of the widget, or, in the new terms, the widget had baseline_y = 1. But it was indistinguishable from baseline_value = 0, because the widget clamped all negative values to 0, so the difference between the two could never be observed. baseline_value = 0 is clearly more useful and familiar to people, so now it is in force by default. commit df7e19abaebf0a871b5ca217b4cc3365533267a6 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 11:18:12 2021 +0700 Move value clamping from graph:add_value() into draw() Previously all values were forcibly clamped between 0 (not tunable at all) and max_value (1 by default). Thus graphs with negative values were not an option. NaN values were conflated with zeros, even though it may be useful to maintain the distinction and render them differently. And if user happened to dynamically change the max_value property, they discovered, that their data got truncated. Preserving user data as it was given is more flexible and makes things easier to debug. This commit does exactly that. So what happens now to the newly allowed values during draw()? There are 3 new cases to consider: 1) NaNs 2) Negatives 3) Values outside of min_value..max_value range NaNs are presently simply not drawn. An empty place is reserved where the value should've been, but nothing else is done. This is distinct from how a zero is handled, e.g. if step_shape is set, it is called with the height parameter = 0, which can still end up drawing something, depending on the shape. One could also consider addition of nan_color/nan_shape properties which could be drawn to alert user to a gap in their data. Negative values necessitate introduction of the concept of the zero axis, from which they will be drawn DOWN, in contrast to positive values. Where should this axis be drawn? How should step_shape-s with inherent direction, e.g. arrows, be drawn for negatives? This will be addressed in the next commit. Values outside of min/max_value range can imply shapes that go beyond widget dimensions. An arrow shape that goes beyond the roof is visibly different from an arrow that saturates and merely touches the widget top, and the user may reasonably desire either behavior. Therefore this commit introduces a boolean clamp_bars property, which defaults to true for reasons of robustness and backward compatibility with the behaviour prior to this commit. The property of course affects only the display of the values, the actual user data stays intact. commit c9c24fb1ab741cd6bb8074971d568e0646c51532 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:29:01 2021 +0700 Use rectangles for drawing graph bars of all sizes I have benchmarked various bar drawing strategies and graph sizes and found out that drawing graph bars with rectangles is as fast or even marginally faster than using fat lines. Thus the code that tries to special-case 1px bars as lines seems to be not worth the additional complexity it introduces, and this commit removes it. Incidentally, I've also established, that rectangles are more robust. If one draws graph bars and feeds cairo very large coordinates (~2^62), then corresponding rectangles fail to render, but all the other rectangles in the same series get rendered successfully, whereas a single large coordinate in a move_to/line_to() sequence is enough to cause all lines in the batch to fail to render. commit bc7f4be5e06405b5aa7fb443a5ace2b9b9575b1f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:01:42 2021 +0700 Cache cairo methods used in the inner loop of graph:draw() I have benchmarked the draw() method, both on cairo xcb and on argb32 image surfaces, and have discovered that cairo context method calls are unnaturally slow, to the point, that almost nothing else in the function matters. For example, drawing bars with vertical lines made the whole draw() function almost 2 times slower than doing the same with rectangles, merely because the former requires two method calls (move_to/line_to), and the latter only one. Simply caching hotpath cairo methods in local variables, reduced the difference between the drawing methods to a negligible one and made the whole function 4 times faster on a typical 100-bar graph, (1.4ms to 0.33ms on my laptop) thus bringing closer my secret dream of drawing live audio data with awesome graphs (kidding). I guess, LGI does something not very optimal there, like gobject-introspecting the method every time, or something. There might be some untapped optimization potential, possibly in upstream, like a method cache, from which everything in awesome could benefit, even though I imagine not many things are so call-intensive as the graph widget, to really feel the difference. commit 538df25bdf02b53d0bbe81809eef2e6fdb05ff6f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 07:32:37 2021 +0700 Use 1-based index in the inner graph draw loop It's used there only once for a calculation and then several times to index some tables, and index 0 is bad for lua tables performance-wise. commit 7412546916cd0a0a490130a120255ffb719398b8 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:55:12 2021 +0700 Leave graph.max_value uninitialized at construction This is a user-visible change, but the previous behavior was probably not what user expected anyway, namely with graph.scale set the graph mysteriously couldn't zoom in onto values that are smaller than 1. commit eb81d7929a161e79c13262cfa73ac2e6a820e8ed Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:48:29 2021 +0700 Allow graph.max_value be nil This makes its behave symmetrically to min_value. Now it defaults to 1, when graph.scale is not set, and to the maximum value in the graph data, when graph.scale is set. commit f622a8430cbfde7007b58d7f4c9c76b08c91e997 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 12:01:13 2021 +0700 Calculate max values num in graph:add_value() more precisely At most so many values were kept in the widget values array, as there are pixels of usable widget width (i.e. sans border). This is exactly right for the surely most common case of graphs with 1px-wide bars, but is more than needed for graphs with step_width+step_spacing>1, and not enough for graphs with subpixel bars. Moreover this made graph autoscaling behave weirdly in graphs with thicker bars, because old values survived longer in the array and kept affecting min_value/max_value calculations even after they were shifted off-screen. The maximum number of kept values is now calculated exactly as the number of values that can be displayed at once, taking step_width and step_spacing into account. It might be worth adding a max_values_num property to the graph to let user override this calculation, in case they draw shapes in step_shape() outside of its supposed rectangle, so our assumptions of visibility are wrong, or the widget often gets resized, resulting in the annoyance of visibly truncated series. commit 0aa63249a18641251a1bf77423739164f0eab39f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 02:30:13 2021 +0700 Fix border_color/border_width bugs in graph widget add_value() wrongly assumed that border_width is always set, when border_color is, so if user set only the latter, an error occurred. add_value() assumed that border_width is always < width which led to awesome hanging up in an infinite loop, should that fail to be the case. The graph border_width property was driven by the border_color property, instead of the other way round. If border_color wasn't set, the border wasn't drawn all, even if user sets non-zero border_width, which is a confusing behavior, in contrast to all other widgets, which simply use a plethora of fallback colors to draw the border, when needed. This also meant that, despite being documented, the beautiful.graph_border_color fallback color could not and was not ever used. If the border_color was set though, an unset border_width was immediately assumed to be 1 instead of 0, also contrary to what other widgets do. Theme colors shouldn't influence widget layout, so this commit fixes this behavior to: 1) border_width always defaults to 0, if unset. 2) border_color falls back to beautiful.graph_border_color or white. Maybe one should also add a beautiful.graph_border_width theme variable, like some widgets do. commit dad4a3253b8fef971fb82720d2a407fcc0a879ad Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 03:11:27 2021 +0700 Implement multiple data series drawing for non-stacked graph The widget can already hold and draw multiple data groups, so it makes little sense to limit drawing only to the first group when graph.stack = false, probably leading to user's confusion. Now user can call add_value(v, group) with group > 1 and the data series will be drawn (over groups with lesser id-s) with graph.stack_colors[group] color, if stack_colors is set, or with graph.color, if stack_colors isn't set. Thus nothing visibly changes for all current users of non-stacked graphs with only single data series, but the new functionality is now there for those who need it. One can't fully emulate this functionality, e.g. with multiple graphs widgets in a wibox.layout.stack, because data series in a single widget can enjoy a common auto-scaling. commit 3e645c976184ab5f50d625271c25062db9ab7ed9 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:59:12 2021 +0700 Don't draw a stacked graph data group if there's no color for it If stack_colors[idx] is nil for the group `idx`, don't draw it at all (as opposed, e.g. to the effect of drawing it with a transparent color, or as though it had all values == 0, which both produce different results). This code is still subtly different from prior behavior, because now user can have a non-contiguous stack_colors table and turn the display of any data group on and off at will, whereas the code two commits earlier would just stop rendering as soon as ipairs(stack_colors) does, i.e. no data series were drawn past the first nil hole. This commit only looks large, but is mostly a whitespace change due to nesting from a pair of wrapping ifs. commit 8c0273786de1ade66bdd977699f98b588da9f487 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:32:42 2021 +0700 Unify stacked and non-stacked graph rendering Now stacked graphs support everything the nonstacked do, like step_width ~= 1 and custom step_shape-s. Technical notes: This code no longer calls cr:stroke() for every single value in a stacked graph, but rather paints a whole series and then cr:stroke/fill()-s it at all once with its color, improving performance considerably. Additionally the graph no longer relies on the presence of the stack_colors property to render series. It draws all data that it has, and, if there's no corresponding color in stack_colors, falls back to graph.color. This will be changed in the next commit to the prior behavior of only drawing those series for which there's a color in the stack_colors table, but that would've made this commit's diff much larger due to required nesting ifs over large code chunks. This commit also takes care to properly handle NaNs and negative values. Those are currently (almost) guaranteed to not occur in the values array due to clipping in graph:add_value(), but I intend to eventually drop the clipping, because it does nothing that draw() really relies upon, and allowing unclipped and NaN values is more flexible and less surprising, e.g. when user dynamically toggles graph.scale. Eventually I intend to implement drawing negative values in non-stacked graphs and possibly even in the stacked ones. commit 38c6589c51b2aa057d7c2f40182ecf32b5b2ff5e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 10:04:15 2021 +0700 Convert graph:draw() to use `self` commit f21f1b2550426df1c4ea8abde079b1ccef84b394 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 12:27:26 2021 +0700 Fix scaling for stacked graphs Stacked graphs have cumulative heights, values from all groups that end up in the same graph bar are effectively summed. Previous code didn't take that into account when calculating min/max_value for the purposes of rescaling, but simply examined every value separately. This code adds up all values up front and finds min/max among those, to determine proper scaling that would fit the graph exactly. commit 1ce4c7bf63bdd6c99c195d668347bec49355e921 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 09:49:35 2021 +0700 Draw shaped graph bars only up to the baseline Prior to this commit, every step_shape bar was, for some reason, drawn with height = full widget's height, regardless of the value it represents. So for the most bars their lower end was far outside the box and therefore clipped off. So e.g. one couldn't see the lower round edges of rounded rectangles. This commit fixes that by appropriately varying the height that is passed into step_shape(), so that lower edges of the bars always exactly touch the lower edge of the graph. If the user wants to restore the prior behavior, they can simply ignore the passed-in height in step_shape() and just always draw their shape with height = full widget height. But they couldn't replicate the effect of this commit in their rc.lua without essentially writing their own graph widget. commit e25185b08da23745b5815f85f6e9e43222ed2294 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 08:42:54 2021 +0700 Avoid incremental transform shifting during graph bar rendering Instead of letting addition errors and cognitive load accumulate simply use the same offset calculation that is used for lines and reset the transform back to the top-left corner after every drawn bar. It's conceptually simpler, and will be useful later for stacked graphs and more. It also fixes a discrepancy between plain and step_shape rendering. Prior to this commit, bar coloring occurred with a disturbed transform, which was centered at the last drawn bar, wherever it happened to be. This meant that nontrivial colors, like gradients and images, were arbitrarily offset when step_shape was used. commit f3e98866d8583c35d0b557c09f529299edc9ba2c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:45:29 2021 +0700 Refactor out graph bar height expression into a variable commit f54528d6892f5aaa4595917853c9e2130f743fdb Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:20:25 2021 +0700 Draw graph bars with lines only when step_width == 1 Before this commit graph bars of step_width < 1 were drawn with lines of width 1, but that's a bit unfortunate, because it led to much overpainting, while still ending up with a graphically incorrect result. Let's draw such bars with rectangles and let cairo do things with subpixels, getting more graphically accurate result, while still keeping the common case of step_width == 1 (supposedly) fast-drawn with lines. commit b6515dca67c18b78eb5657378131a62ab43d4cfc Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:54:07 2021 +0700 Fix unsharp edges when drawing graph bars with rectangles The 0.5 offset is only helpful for 1-width strokes, not for fills. commit 7f3db8f07e82f821bcd1dc418d66564f537609d6 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:21:01 2021 +0700 Do coordinate transformations even if the value isn't drawn The graph presently doesn't draw values < 0, but there was a discrepancy between drawing with and without step_shape. The default line/rectangle drawing reserves an empty place where the skipped value should've been, but no such empty place is reserved, when step_shape is used. This commit fixes that. Skipped values always have their empty space now. commit 7b731ebfe96af7a9dba9c57371da6761cf22c00c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:03:30 2021 +0700 Fix graph bar scaling when min_value is not 0 Previous calculation lead to underscaling of values so that even value = max_value wasn't drawn to full height. commit dca59fd8b7fff0e7e646f2dfc6bd92047028e5a8 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:18:40 2021 +0700 Calculate the horizontal graph bar offset only when needed commit 940ddf4b9825e9c3a91d276208c089d8c74e49db Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:17:23 2021 +0700 Call cr:move_to() only when drawing the graph with lines It's redundant otherwise and might even interfere with user's drawing in step_shape(). commit b5d5cd10ca29fb29cd751faaa95137c2fe89ff63 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:04:06 2021 +0700 Fix wrong horizontal offset calculations in widget.graph Indices in the loop are zero-based, so things like i-1 and i>1 aren't necessary and actually wouldn't have been correct even in a 1-based loop. step_shape = gears.shape.rectangle and step_shape = nil, which is supposed to default to rectangle according to docs, were drawn differently due to mismatched offset calculations, which led to problems especially noticeable when step_spacing is not zero, e.g: 1) whole graph shifted left too much, drawn partially off-screen. 2) whole graph shifted right too much, and the first bar placed closer to the rest than others. commit dc369fa198281e000afc1bd17326f3a264adf7d1 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 02:09:21 2021 +0700 Fix the "border_width affects graph bar width" bug Graph bars were drawn with lines of border_width thickness instead of 1, i.e. possibly too fat to be readable. commit 1ba73378cf29ed27a6ecd18716394779ceb543d9 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 00:13:03 2021 +0700 Simplify internal values representation in graph even more New values in graph._private.values were pushed to the end, and old values were removed from the beginning of the table. In draw() however newer values are drawn first, at the left edge of the graph, necessitating annoying idx = #values - idx conversions everywhere. In particular in stacked graph that means that values that are drawn together may have different indices. One has to shift the values array in add_value() either way, so one might as well insert new values at the beginning of the array and simplify the rest of the code. This doesn't seem like much yet, but it will prove to be much more convenient and less error-prone in what I'm going to do later. commit f70523ee68dbd6b67050ace4b82903a9b22440c7 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 13:35:10 2021 +0700 Simplify internal values representation in graph widget graph._private.values was either a table of values or a table of tables of values, depending on whether "stack" property is enabled, ... or a mix of both if user forgot to pass the second "group" parameter to graph:add_value(). This resulted in pretty fragile and confusing behavior, e.g. leaving user to wonder, why they see nothing more after setting stack=true for a graph in a previously working rc.lua, and demanding a very careful use of add_value() from the user, or else some values or even entire stacks would mysteriously vanish and errors in draw() get triggered, sometimes after a period of seemingly normal functioning. Now `values` is always a table of tables and the group parameter in add_value() defaults to 1, thus making the non-stacked graph a trivial variant of the stacked graph.
2021-05-20 17:14:03 +02:00
if not not_nan and nan_x then
-- Keep the coordinate to draw NaN indication later
table.insert(nan_x, x)
end
end
graph: complete widget overhaul and bug fixes Squashed commit of the following: commit 69821f51fe1e8652715658543e50631ece495090 Author: Alex Belykh <albel727@ngs.ru> Date: Mon May 10 19:17:55 2021 +0700 Refactor property handling in wibox.widget.graph In addition, baseline_value, step_width and step_spacing properties are all set to their default non-nil numeric values during instance creation now. commit 842eb429bf5df4b2ba46b2e6f7646afe89b1a3c3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 9 06:18:45 2021 +0700 Make graph:draw_values() private There's no documentation nor confidence that it's a good API, so I'm hiding it for now. commit 720746780574e4bad14a71fc4174955139c2eb50 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 04:04:13 2021 +0700 Document graph_should_draw_data_group() for posterity commit 7c73f3754ce6ba86b118f1170a6f9e406ab4841d Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:59:21 2021 +0700 Make graph:should_draw_data_group() private There's no confidence whether it's a good API, so I'm hiding it for now. commit b3539e20adb6423118af726ae4f8751ef864b5d2 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:36:44 2021 +0700 Make graph:preprocess_values() private There's no confidence whether it's a good API, so I'm hiding it for now. commit 2df4400e780a6260f8d0ab1c60734fbb11bf3c4f Author: Alex Belykh <albel727@ngs.ru> Date: Thu May 6 10:05:23 2021 +0700 Add spec/wibox/widget/graph_spec.lua commit ce01a9771ab439b8df841f1565aa4aa8a44a5e0d Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 08:16:18 2021 +0700 Improve graph.capacity documentation commit e28fade8f655802f5bf89154169f68d256b8e332 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 07:36:01 2021 +0700 Document graph:compute_drawn_values_num() commit dd68332292465422d08b48e9f0229f143beccbc6 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 05:24:23 2021 +0700 Document graph:pick_data_group_color() method commit ca6dc55f77aca88a6e9cc90bb6fd0045739f41f5 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 04:13:58 2021 +0700 Guard against setting data groups sparsely in graph:add_value() commit cdea5f126f7984e72924f6dffbd312c4fe3b032f Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 02:36:12 2021 +0700 Refactor scaling and baseline choice out of graph:draw() commit 10135deafe8a9390ba66ab657ebb1fb41d549e8c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 21:34:48 2021 +0700 Document graph.nan_color and graph.stack properties commit 0d73bb84ac66b87e16aee472bd1f299549f66a1c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 20:49:24 2021 +0700 Use a better example in docs for the graph.stack property commit c27d160a0826d033d49d6141608c40aa8a0642fa Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 16:05:38 2021 +0700 Document graph.baseline_value property commit 459bc8176c13d4ea32af0dcdc2399d3b929b1c6a Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:53:47 2021 +0700 Document graph.clamp_bars property commit e732ee0b3b007d213e689b98cc00ae7cb141ae08 Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:34 2021 +0700 Improve graph:add_value() and :clear() documentation commit ae73e3aa9f8d60409cbe4cb4da9612f31af4f38e Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:11 2021 +0700 Improve graph min_value/max_value/scale documentation commit a7350996a9def773b91b1631e6c0507e33d8f93b Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:27:36 2021 +0700 Cleanup graph examples to show only relevant things in docs commit 30740c26f4f41c16f4c8a3c339bcb7bd299e14cd Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 14:04:36 2021 +0700 Rename graph.stack_colors property to `group_colors` The colors are used for stacked and non-stacked graphs alike, so the name didn't make sense. Also document and clarify some color stuff. commit 1ea01bfd410971cf0d0c25fd74cd764d5eb7279d Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 21:25:41 2021 +0700 Add an example file for code coverage of some fringe graph cases commit eae68e39553f88a6275a0c65bf040b5d4c262b54 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 20:23:32 2021 +0700 Add step_shape to the graph/nan_color example for code coverage commit 8027147bc2eab34a97b49a428fb8cef27f122bed Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:55:18 2021 +0700 Add example files for graph.nan_color/nan_indication properties commit 89a25a165c52b2c15b6c4d95ecb777a9067e66f9 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:33:07 2021 +0700 Make widget graph:add_value(v) default v to NaN 0 as a default makes no sense and silences the error that is failing to specify a value, to which the user should rather be alerted. The graph widget has sane NaN handling now and will do just that. commit 2835d552440be48d356a7f7b1d64963696dd550b Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:20:48 2021 +0700 Implement graph.nan_color and nan_indication properties Now, whenever there's a NaN among values and graph.nan_indication is set, the corresponding area, where the value bar should have been drawn, is filled with the nan_color from top to bottom. The painting is done after all data is rendered, to make sure that it won't be overpainted and go unnoticed. NaNs among graph's values are inevitably a sign of some error and it is arguably not a sane default behavior to simply not render them at all. The user should take immediate note of any errors by default, instead of wondering at a mysteriously empty graph widget, which is why nan_indication is set to true by default. If the user wants to silence NaNs, they can always simply set it to false. nan_color if unset, defaults to a noticeable yellow-black pattern. As a direct and planned consequence, stacked graphs with negative values in them trigger the NaN handling too. But silencing NaN handling for those graphs is also possible and semi-sane, because then NaNs and negatives are ignored and the graph behaves (almost) like they were zeros. commit 9c1f8f08b400e01e19c534f3810c9c9ab485760c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 26 08:10:19 2021 +0700 Refactor out graph_map_value_to_widget_coordinates() function commit 1905991ddecabdae3e15ffb8ee5b18a1bee1a711 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 28 13:39:29 2021 +0700 Add an example file for graph.baseline_value property commit b31b39d66f4c037a9484b71a3f910703ae01fac0 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 07:03:49 2021 +0700 Add an example file for graph.clamp_bars=false/true comparison commit a1fc850e3047f9ee2c9ddeffacfbabf3ded34171 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 04:06:45 2021 +0700 Add an example file for graphs with negative values and shapes commit 2f012f8ff1bb5094c14e724a61e59a2a3a296bc8 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 01:06:05 2021 +0700 Add an example file for a graph displaying negative values commit 7b14fbd59a4c4c51962fd3110a1a900dd5f4eb98 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 11:18:26 2021 +0700 Add an example file for graphs with step_width < 1 commit dfd9a54544b637e2fbe3d1325f9cc24b3f4d2313 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 10:17:38 2021 +0700 Add an example file for graph.stack=false/true comparison Should also demonstrate that multiple data groups are possible for non-stacked graphs too. commit 5b532106c3fd255f9b66a66cfb88d977b489ab5d Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 09:34:14 2021 +0700 Add an example file for disabling graph data groups with nil colors commit ac6f1083c02fcb428f089a616f9b0ebaa5496b6c Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:57:25 2021 +0700 Add an example file for step_shape/width/spacing on stacked graphs commit b3703db6a5139e73e72c1cfd198ece2cd9be7df2 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:43:56 2021 +0700 Add an example file for scale=false/true on stacked graph commit b000aed4a12cb44fb501e1c8432a71a14b2f8f3f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 20 08:22:19 2021 +0700 Document deprecation of graph's width and height commit cb0ad617bd6c83a499896797318e3b0b588c3566 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 07:53:37 2021 +0700 Deprecate width and height properties of wibox.widget.graph commit 8c64e14ad91a074b784166a7054e53444cccb9d2 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 04:37:59 2021 +0700 Refactor graph:draw() to separate concerns Firstly, summation of group values for stacked graphs is extracted into graph:preprocess_values(). This makes graph:draw() smaller and exposes the fact, that stacked graphs are almost entirely just a form of a more general concept: pre-render data manipulation. It's something a user might want to customize, and now they can do it by monkey-patching the method. Secondly, the logic for deciding, if and with which color a data group should be drawn, is extracted into graph:should_draw_data_group() and get_data_group_color() methods respectively. This makes it customizable too and makes more obvious the fact, that it is a general thing that has nothing to do with stacked graphs in particular. With this there are only 5 lines remaining in graph:draw(), that could be said to be specific to stacked graphs, all pertaining to the `prev_y` variable, which merely preserves the bar coordinates from a previous data group. commit 2a32b6305d893f413303728a0a3da1dcd9a5591e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 22:52:37 2021 +0700 Pointlessly optimize graph:add_value() 1) Purge old values first, then insert the new one, if needed. Since Lua grows arrays in power of two steps, and our guess_capacity() also takes care to return even integers, it would be a shame to overstep the capacity by one by inserting first and thus getting the table size doubled only to never use the most of it. 2) Use a removal procedure that doesn't call the slow length operator at all. Previous code called it twice (once in table.remove) for every removed value. commit 680ea8f5d81e9040afb4662b3229e937e4b89e02 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:23:38 2021 +0700 Set graph data series color before the painting loop commit b970f0f221ae76250f2a94433ff79d653caa6e21 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:05:37 2021 +0700 Drop a large no longer necessary `if` in graph:draw_values() This commit is almost purely a whitespace change. commit 6b4f0541c70ec99d7f6e276bae64939f337120d6 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:00:50 2021 +0700 Extract values rendering code into graph:draw_values() method This is to avoid ungodly if nesting levels, which are needed to bail out of drawing values early and skip to drawing the border. The next commit will get rid of the if. commit e0950e4f99d1ea1c4b8cea2cc67629fa40030c2a Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 14:50:20 2021 +0700 Track graph draw() usage to guess necessary array capacity The last step of divorcing add_values() from width calculations, while ensuring that user doesn't observe a lack of data to draw. The widget now tracks an approximate number of values that it has been asked to draw() in _private.last_drawn_values_num, and truncates its values array based on that (if not overridden by the capacity property). commit 2e3d542282f35e7c0fed454da0f2eada0fcf3b78 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 13:35:12 2021 +0700 Add `capacity` property to graph widget This is the second step of divorcing add_values() from calculations involving widths. How many values are stored and how many values are drawn are distinct concepts. There isn't even a single definitive "how many values are drawn" because the widget could be rendered in several places with distinct widths simultaneously, and in every such place the data must be drawn appropriately, e.g. with auto-scaling, that takes only the visible data points into account. And the draw() method can do that now. The default capacity value of nil uses heuristics based on widget's _private.width to guess some capacity that would tolerate some dynamic widget resizing. If the user finds the heuristic failing them, they can set the capacity property to the desired maximum number of values that will be kept in the _private.values array (in each data group). commit 08d4a80686f7fe363551c35589a460839f60853e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 03:27:18 2021 +0700 Extract values size computation into compute_drawn_values_num() This is the first step of making add_values() not concern itself (directly) with widget dimensions. The user can monkey-patch the method to override how many values are (logically) drawn, because it affects graph auto-scaling and can be useful for rendering of step_shape-s that paint outside of their step_width. I didn't make it a full-blown function-valued property, because it seems a fringe functionality, which I'm too lazy to document. commit 3c5617c98ab1147618ed22e8414a77fc56b45f6e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 02:32:29 2021 +0700 Copy graph widget prototype methods over in bulk commit 169538837ece41a03d02ba8fbc0e4097062b67a3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun Apr 25 05:58:01 2021 +0700 Remove graph.baseline_y property After giving it some thought I've concluded, that it is unlikely to be useful as it is now. I'd better reserve the name for some more sensible functionality, like fixing the baseline_value defined axis at the given widget coordinate, adjusting the scaling accordingly, so min_value/max_value won't generally correspond to widget top and bottom anymore. commit 202ffaa9a0b820c7c471e1121d01b91f8f76cd38 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 12:06:55 2021 +0700 Add baseline_y and baseline_value properties to graph widget Now values in graph have a *baseline*, tunable with baseline_y and baseline_value properties. It is the (for now invisible) horizontal line from which all bars in the graph grow. I call it a "baseline" instead of "axis", because it doesn't actually have to correspond to the 0 value. But it can, and actually does now by default. baseline_y and baseline_value properties determine the vertical placement of the baseline in the widget. baseline_y is the position of the baseline in normalized widget coordinates, e.g. 0 corresponds to widget's top, 1 - to widget's bottom, and 0.5 - to widget's middle. So if e.g. one sets baseline_y = 0, then no matter how the widget is resized and how its values are scaled, bars in it will be drawn starting from the top. baseline_value is the position of the baseline in value coordinates. So if one sets baseline_value = 10, then no matter what happens, bars for values greater than 10 will be drawn as growing up from this line, and bars for values smaller as growing down. Where and if it will be seen in the widget, depends on the scaling. baseline_value = 0 is the familiar zero axis. baseline_y is unset by default and has priority, i.e. if it's set, then baseline_value is ignored. baseline_value defaults to 0. Prior to this commit, bars were always drawn from the bottom edge of the widget, or, in the new terms, the widget had baseline_y = 1. But it was indistinguishable from baseline_value = 0, because the widget clamped all negative values to 0, so the difference between the two could never be observed. baseline_value = 0 is clearly more useful and familiar to people, so now it is in force by default. commit df7e19abaebf0a871b5ca217b4cc3365533267a6 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 11:18:12 2021 +0700 Move value clamping from graph:add_value() into draw() Previously all values were forcibly clamped between 0 (not tunable at all) and max_value (1 by default). Thus graphs with negative values were not an option. NaN values were conflated with zeros, even though it may be useful to maintain the distinction and render them differently. And if user happened to dynamically change the max_value property, they discovered, that their data got truncated. Preserving user data as it was given is more flexible and makes things easier to debug. This commit does exactly that. So what happens now to the newly allowed values during draw()? There are 3 new cases to consider: 1) NaNs 2) Negatives 3) Values outside of min_value..max_value range NaNs are presently simply not drawn. An empty place is reserved where the value should've been, but nothing else is done. This is distinct from how a zero is handled, e.g. if step_shape is set, it is called with the height parameter = 0, which can still end up drawing something, depending on the shape. One could also consider addition of nan_color/nan_shape properties which could be drawn to alert user to a gap in their data. Negative values necessitate introduction of the concept of the zero axis, from which they will be drawn DOWN, in contrast to positive values. Where should this axis be drawn? How should step_shape-s with inherent direction, e.g. arrows, be drawn for negatives? This will be addressed in the next commit. Values outside of min/max_value range can imply shapes that go beyond widget dimensions. An arrow shape that goes beyond the roof is visibly different from an arrow that saturates and merely touches the widget top, and the user may reasonably desire either behavior. Therefore this commit introduces a boolean clamp_bars property, which defaults to true for reasons of robustness and backward compatibility with the behaviour prior to this commit. The property of course affects only the display of the values, the actual user data stays intact. commit c9c24fb1ab741cd6bb8074971d568e0646c51532 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:29:01 2021 +0700 Use rectangles for drawing graph bars of all sizes I have benchmarked various bar drawing strategies and graph sizes and found out that drawing graph bars with rectangles is as fast or even marginally faster than using fat lines. Thus the code that tries to special-case 1px bars as lines seems to be not worth the additional complexity it introduces, and this commit removes it. Incidentally, I've also established, that rectangles are more robust. If one draws graph bars and feeds cairo very large coordinates (~2^62), then corresponding rectangles fail to render, but all the other rectangles in the same series get rendered successfully, whereas a single large coordinate in a move_to/line_to() sequence is enough to cause all lines in the batch to fail to render. commit bc7f4be5e06405b5aa7fb443a5ace2b9b9575b1f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:01:42 2021 +0700 Cache cairo methods used in the inner loop of graph:draw() I have benchmarked the draw() method, both on cairo xcb and on argb32 image surfaces, and have discovered that cairo context method calls are unnaturally slow, to the point, that almost nothing else in the function matters. For example, drawing bars with vertical lines made the whole draw() function almost 2 times slower than doing the same with rectangles, merely because the former requires two method calls (move_to/line_to), and the latter only one. Simply caching hotpath cairo methods in local variables, reduced the difference between the drawing methods to a negligible one and made the whole function 4 times faster on a typical 100-bar graph, (1.4ms to 0.33ms on my laptop) thus bringing closer my secret dream of drawing live audio data with awesome graphs (kidding). I guess, LGI does something not very optimal there, like gobject-introspecting the method every time, or something. There might be some untapped optimization potential, possibly in upstream, like a method cache, from which everything in awesome could benefit, even though I imagine not many things are so call-intensive as the graph widget, to really feel the difference. commit 538df25bdf02b53d0bbe81809eef2e6fdb05ff6f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 07:32:37 2021 +0700 Use 1-based index in the inner graph draw loop It's used there only once for a calculation and then several times to index some tables, and index 0 is bad for lua tables performance-wise. commit 7412546916cd0a0a490130a120255ffb719398b8 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:55:12 2021 +0700 Leave graph.max_value uninitialized at construction This is a user-visible change, but the previous behavior was probably not what user expected anyway, namely with graph.scale set the graph mysteriously couldn't zoom in onto values that are smaller than 1. commit eb81d7929a161e79c13262cfa73ac2e6a820e8ed Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:48:29 2021 +0700 Allow graph.max_value be nil This makes its behave symmetrically to min_value. Now it defaults to 1, when graph.scale is not set, and to the maximum value in the graph data, when graph.scale is set. commit f622a8430cbfde7007b58d7f4c9c76b08c91e997 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 12:01:13 2021 +0700 Calculate max values num in graph:add_value() more precisely At most so many values were kept in the widget values array, as there are pixels of usable widget width (i.e. sans border). This is exactly right for the surely most common case of graphs with 1px-wide bars, but is more than needed for graphs with step_width+step_spacing>1, and not enough for graphs with subpixel bars. Moreover this made graph autoscaling behave weirdly in graphs with thicker bars, because old values survived longer in the array and kept affecting min_value/max_value calculations even after they were shifted off-screen. The maximum number of kept values is now calculated exactly as the number of values that can be displayed at once, taking step_width and step_spacing into account. It might be worth adding a max_values_num property to the graph to let user override this calculation, in case they draw shapes in step_shape() outside of its supposed rectangle, so our assumptions of visibility are wrong, or the widget often gets resized, resulting in the annoyance of visibly truncated series. commit 0aa63249a18641251a1bf77423739164f0eab39f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 02:30:13 2021 +0700 Fix border_color/border_width bugs in graph widget add_value() wrongly assumed that border_width is always set, when border_color is, so if user set only the latter, an error occurred. add_value() assumed that border_width is always < width which led to awesome hanging up in an infinite loop, should that fail to be the case. The graph border_width property was driven by the border_color property, instead of the other way round. If border_color wasn't set, the border wasn't drawn all, even if user sets non-zero border_width, which is a confusing behavior, in contrast to all other widgets, which simply use a plethora of fallback colors to draw the border, when needed. This also meant that, despite being documented, the beautiful.graph_border_color fallback color could not and was not ever used. If the border_color was set though, an unset border_width was immediately assumed to be 1 instead of 0, also contrary to what other widgets do. Theme colors shouldn't influence widget layout, so this commit fixes this behavior to: 1) border_width always defaults to 0, if unset. 2) border_color falls back to beautiful.graph_border_color or white. Maybe one should also add a beautiful.graph_border_width theme variable, like some widgets do. commit dad4a3253b8fef971fb82720d2a407fcc0a879ad Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 03:11:27 2021 +0700 Implement multiple data series drawing for non-stacked graph The widget can already hold and draw multiple data groups, so it makes little sense to limit drawing only to the first group when graph.stack = false, probably leading to user's confusion. Now user can call add_value(v, group) with group > 1 and the data series will be drawn (over groups with lesser id-s) with graph.stack_colors[group] color, if stack_colors is set, or with graph.color, if stack_colors isn't set. Thus nothing visibly changes for all current users of non-stacked graphs with only single data series, but the new functionality is now there for those who need it. One can't fully emulate this functionality, e.g. with multiple graphs widgets in a wibox.layout.stack, because data series in a single widget can enjoy a common auto-scaling. commit 3e645c976184ab5f50d625271c25062db9ab7ed9 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:59:12 2021 +0700 Don't draw a stacked graph data group if there's no color for it If stack_colors[idx] is nil for the group `idx`, don't draw it at all (as opposed, e.g. to the effect of drawing it with a transparent color, or as though it had all values == 0, which both produce different results). This code is still subtly different from prior behavior, because now user can have a non-contiguous stack_colors table and turn the display of any data group on and off at will, whereas the code two commits earlier would just stop rendering as soon as ipairs(stack_colors) does, i.e. no data series were drawn past the first nil hole. This commit only looks large, but is mostly a whitespace change due to nesting from a pair of wrapping ifs. commit 8c0273786de1ade66bdd977699f98b588da9f487 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:32:42 2021 +0700 Unify stacked and non-stacked graph rendering Now stacked graphs support everything the nonstacked do, like step_width ~= 1 and custom step_shape-s. Technical notes: This code no longer calls cr:stroke() for every single value in a stacked graph, but rather paints a whole series and then cr:stroke/fill()-s it at all once with its color, improving performance considerably. Additionally the graph no longer relies on the presence of the stack_colors property to render series. It draws all data that it has, and, if there's no corresponding color in stack_colors, falls back to graph.color. This will be changed in the next commit to the prior behavior of only drawing those series for which there's a color in the stack_colors table, but that would've made this commit's diff much larger due to required nesting ifs over large code chunks. This commit also takes care to properly handle NaNs and negative values. Those are currently (almost) guaranteed to not occur in the values array due to clipping in graph:add_value(), but I intend to eventually drop the clipping, because it does nothing that draw() really relies upon, and allowing unclipped and NaN values is more flexible and less surprising, e.g. when user dynamically toggles graph.scale. Eventually I intend to implement drawing negative values in non-stacked graphs and possibly even in the stacked ones. commit 38c6589c51b2aa057d7c2f40182ecf32b5b2ff5e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 10:04:15 2021 +0700 Convert graph:draw() to use `self` commit f21f1b2550426df1c4ea8abde079b1ccef84b394 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 12:27:26 2021 +0700 Fix scaling for stacked graphs Stacked graphs have cumulative heights, values from all groups that end up in the same graph bar are effectively summed. Previous code didn't take that into account when calculating min/max_value for the purposes of rescaling, but simply examined every value separately. This code adds up all values up front and finds min/max among those, to determine proper scaling that would fit the graph exactly. commit 1ce4c7bf63bdd6c99c195d668347bec49355e921 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 09:49:35 2021 +0700 Draw shaped graph bars only up to the baseline Prior to this commit, every step_shape bar was, for some reason, drawn with height = full widget's height, regardless of the value it represents. So for the most bars their lower end was far outside the box and therefore clipped off. So e.g. one couldn't see the lower round edges of rounded rectangles. This commit fixes that by appropriately varying the height that is passed into step_shape(), so that lower edges of the bars always exactly touch the lower edge of the graph. If the user wants to restore the prior behavior, they can simply ignore the passed-in height in step_shape() and just always draw their shape with height = full widget height. But they couldn't replicate the effect of this commit in their rc.lua without essentially writing their own graph widget. commit e25185b08da23745b5815f85f6e9e43222ed2294 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 08:42:54 2021 +0700 Avoid incremental transform shifting during graph bar rendering Instead of letting addition errors and cognitive load accumulate simply use the same offset calculation that is used for lines and reset the transform back to the top-left corner after every drawn bar. It's conceptually simpler, and will be useful later for stacked graphs and more. It also fixes a discrepancy between plain and step_shape rendering. Prior to this commit, bar coloring occurred with a disturbed transform, which was centered at the last drawn bar, wherever it happened to be. This meant that nontrivial colors, like gradients and images, were arbitrarily offset when step_shape was used. commit f3e98866d8583c35d0b557c09f529299edc9ba2c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:45:29 2021 +0700 Refactor out graph bar height expression into a variable commit f54528d6892f5aaa4595917853c9e2130f743fdb Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:20:25 2021 +0700 Draw graph bars with lines only when step_width == 1 Before this commit graph bars of step_width < 1 were drawn with lines of width 1, but that's a bit unfortunate, because it led to much overpainting, while still ending up with a graphically incorrect result. Let's draw such bars with rectangles and let cairo do things with subpixels, getting more graphically accurate result, while still keeping the common case of step_width == 1 (supposedly) fast-drawn with lines. commit b6515dca67c18b78eb5657378131a62ab43d4cfc Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:54:07 2021 +0700 Fix unsharp edges when drawing graph bars with rectangles The 0.5 offset is only helpful for 1-width strokes, not for fills. commit 7f3db8f07e82f821bcd1dc418d66564f537609d6 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:21:01 2021 +0700 Do coordinate transformations even if the value isn't drawn The graph presently doesn't draw values < 0, but there was a discrepancy between drawing with and without step_shape. The default line/rectangle drawing reserves an empty place where the skipped value should've been, but no such empty place is reserved, when step_shape is used. This commit fixes that. Skipped values always have their empty space now. commit 7b731ebfe96af7a9dba9c57371da6761cf22c00c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:03:30 2021 +0700 Fix graph bar scaling when min_value is not 0 Previous calculation lead to underscaling of values so that even value = max_value wasn't drawn to full height. commit dca59fd8b7fff0e7e646f2dfc6bd92047028e5a8 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:18:40 2021 +0700 Calculate the horizontal graph bar offset only when needed commit 940ddf4b9825e9c3a91d276208c089d8c74e49db Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:17:23 2021 +0700 Call cr:move_to() only when drawing the graph with lines It's redundant otherwise and might even interfere with user's drawing in step_shape(). commit b5d5cd10ca29fb29cd751faaa95137c2fe89ff63 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:04:06 2021 +0700 Fix wrong horizontal offset calculations in widget.graph Indices in the loop are zero-based, so things like i-1 and i>1 aren't necessary and actually wouldn't have been correct even in a 1-based loop. step_shape = gears.shape.rectangle and step_shape = nil, which is supposed to default to rectangle according to docs, were drawn differently due to mismatched offset calculations, which led to problems especially noticeable when step_spacing is not zero, e.g: 1) whole graph shifted left too much, drawn partially off-screen. 2) whole graph shifted right too much, and the first bar placed closer to the rest than others. commit dc369fa198281e000afc1bd17326f3a264adf7d1 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 02:09:21 2021 +0700 Fix the "border_width affects graph bar width" bug Graph bars were drawn with lines of border_width thickness instead of 1, i.e. possibly too fat to be readable. commit 1ba73378cf29ed27a6ecd18716394779ceb543d9 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 00:13:03 2021 +0700 Simplify internal values representation in graph even more New values in graph._private.values were pushed to the end, and old values were removed from the beginning of the table. In draw() however newer values are drawn first, at the left edge of the graph, necessitating annoying idx = #values - idx conversions everywhere. In particular in stacked graph that means that values that are drawn together may have different indices. One has to shift the values array in add_value() either way, so one might as well insert new values at the beginning of the array and simplify the rest of the code. This doesn't seem like much yet, but it will prove to be much more convenient and less error-prone in what I'm going to do later. commit f70523ee68dbd6b67050ace4b82903a9b22440c7 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 13:35:10 2021 +0700 Simplify internal values representation in graph widget graph._private.values was either a table of values or a table of tables of values, depending on whether "stack" property is enabled, ... or a mix of both if user forgot to pass the second "group" parameter to graph:add_value(). This resulted in pretty fragile and confusing behavior, e.g. leaving user to wonder, why they see nothing more after setting stack=true for a graph in a previously working rc.lua, and demanding a very careful use of add_value() from the user, or else some values or even entire stacks would mysteriously vanish and errors in draw() get triggered, sometimes after a period of seemingly normal functioning. Now `values` is always a table of tables and the group parameter in add_value() defaults to 1, thus making the non-stacked graph a trivial variant of the stacked graph.
2021-05-20 17:14:03 +02:00
-- Paint the data series
cr:fill()
2016-05-23 07:30:44 +02:00
end
graph: complete widget overhaul and bug fixes Squashed commit of the following: commit 69821f51fe1e8652715658543e50631ece495090 Author: Alex Belykh <albel727@ngs.ru> Date: Mon May 10 19:17:55 2021 +0700 Refactor property handling in wibox.widget.graph In addition, baseline_value, step_width and step_spacing properties are all set to their default non-nil numeric values during instance creation now. commit 842eb429bf5df4b2ba46b2e6f7646afe89b1a3c3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 9 06:18:45 2021 +0700 Make graph:draw_values() private There's no documentation nor confidence that it's a good API, so I'm hiding it for now. commit 720746780574e4bad14a71fc4174955139c2eb50 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 04:04:13 2021 +0700 Document graph_should_draw_data_group() for posterity commit 7c73f3754ce6ba86b118f1170a6f9e406ab4841d Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:59:21 2021 +0700 Make graph:should_draw_data_group() private There's no confidence whether it's a good API, so I'm hiding it for now. commit b3539e20adb6423118af726ae4f8751ef864b5d2 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:36:44 2021 +0700 Make graph:preprocess_values() private There's no confidence whether it's a good API, so I'm hiding it for now. commit 2df4400e780a6260f8d0ab1c60734fbb11bf3c4f Author: Alex Belykh <albel727@ngs.ru> Date: Thu May 6 10:05:23 2021 +0700 Add spec/wibox/widget/graph_spec.lua commit ce01a9771ab439b8df841f1565aa4aa8a44a5e0d Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 08:16:18 2021 +0700 Improve graph.capacity documentation commit e28fade8f655802f5bf89154169f68d256b8e332 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 07:36:01 2021 +0700 Document graph:compute_drawn_values_num() commit dd68332292465422d08b48e9f0229f143beccbc6 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 05:24:23 2021 +0700 Document graph:pick_data_group_color() method commit ca6dc55f77aca88a6e9cc90bb6fd0045739f41f5 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 04:13:58 2021 +0700 Guard against setting data groups sparsely in graph:add_value() commit cdea5f126f7984e72924f6dffbd312c4fe3b032f Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 02:36:12 2021 +0700 Refactor scaling and baseline choice out of graph:draw() commit 10135deafe8a9390ba66ab657ebb1fb41d549e8c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 21:34:48 2021 +0700 Document graph.nan_color and graph.stack properties commit 0d73bb84ac66b87e16aee472bd1f299549f66a1c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 20:49:24 2021 +0700 Use a better example in docs for the graph.stack property commit c27d160a0826d033d49d6141608c40aa8a0642fa Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 16:05:38 2021 +0700 Document graph.baseline_value property commit 459bc8176c13d4ea32af0dcdc2399d3b929b1c6a Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:53:47 2021 +0700 Document graph.clamp_bars property commit e732ee0b3b007d213e689b98cc00ae7cb141ae08 Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:34 2021 +0700 Improve graph:add_value() and :clear() documentation commit ae73e3aa9f8d60409cbe4cb4da9612f31af4f38e Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:11 2021 +0700 Improve graph min_value/max_value/scale documentation commit a7350996a9def773b91b1631e6c0507e33d8f93b Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:27:36 2021 +0700 Cleanup graph examples to show only relevant things in docs commit 30740c26f4f41c16f4c8a3c339bcb7bd299e14cd Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 14:04:36 2021 +0700 Rename graph.stack_colors property to `group_colors` The colors are used for stacked and non-stacked graphs alike, so the name didn't make sense. Also document and clarify some color stuff. commit 1ea01bfd410971cf0d0c25fd74cd764d5eb7279d Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 21:25:41 2021 +0700 Add an example file for code coverage of some fringe graph cases commit eae68e39553f88a6275a0c65bf040b5d4c262b54 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 20:23:32 2021 +0700 Add step_shape to the graph/nan_color example for code coverage commit 8027147bc2eab34a97b49a428fb8cef27f122bed Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:55:18 2021 +0700 Add example files for graph.nan_color/nan_indication properties commit 89a25a165c52b2c15b6c4d95ecb777a9067e66f9 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:33:07 2021 +0700 Make widget graph:add_value(v) default v to NaN 0 as a default makes no sense and silences the error that is failing to specify a value, to which the user should rather be alerted. The graph widget has sane NaN handling now and will do just that. commit 2835d552440be48d356a7f7b1d64963696dd550b Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:20:48 2021 +0700 Implement graph.nan_color and nan_indication properties Now, whenever there's a NaN among values and graph.nan_indication is set, the corresponding area, where the value bar should have been drawn, is filled with the nan_color from top to bottom. The painting is done after all data is rendered, to make sure that it won't be overpainted and go unnoticed. NaNs among graph's values are inevitably a sign of some error and it is arguably not a sane default behavior to simply not render them at all. The user should take immediate note of any errors by default, instead of wondering at a mysteriously empty graph widget, which is why nan_indication is set to true by default. If the user wants to silence NaNs, they can always simply set it to false. nan_color if unset, defaults to a noticeable yellow-black pattern. As a direct and planned consequence, stacked graphs with negative values in them trigger the NaN handling too. But silencing NaN handling for those graphs is also possible and semi-sane, because then NaNs and negatives are ignored and the graph behaves (almost) like they were zeros. commit 9c1f8f08b400e01e19c534f3810c9c9ab485760c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 26 08:10:19 2021 +0700 Refactor out graph_map_value_to_widget_coordinates() function commit 1905991ddecabdae3e15ffb8ee5b18a1bee1a711 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 28 13:39:29 2021 +0700 Add an example file for graph.baseline_value property commit b31b39d66f4c037a9484b71a3f910703ae01fac0 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 07:03:49 2021 +0700 Add an example file for graph.clamp_bars=false/true comparison commit a1fc850e3047f9ee2c9ddeffacfbabf3ded34171 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 04:06:45 2021 +0700 Add an example file for graphs with negative values and shapes commit 2f012f8ff1bb5094c14e724a61e59a2a3a296bc8 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 01:06:05 2021 +0700 Add an example file for a graph displaying negative values commit 7b14fbd59a4c4c51962fd3110a1a900dd5f4eb98 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 11:18:26 2021 +0700 Add an example file for graphs with step_width < 1 commit dfd9a54544b637e2fbe3d1325f9cc24b3f4d2313 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 10:17:38 2021 +0700 Add an example file for graph.stack=false/true comparison Should also demonstrate that multiple data groups are possible for non-stacked graphs too. commit 5b532106c3fd255f9b66a66cfb88d977b489ab5d Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 09:34:14 2021 +0700 Add an example file for disabling graph data groups with nil colors commit ac6f1083c02fcb428f089a616f9b0ebaa5496b6c Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:57:25 2021 +0700 Add an example file for step_shape/width/spacing on stacked graphs commit b3703db6a5139e73e72c1cfd198ece2cd9be7df2 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:43:56 2021 +0700 Add an example file for scale=false/true on stacked graph commit b000aed4a12cb44fb501e1c8432a71a14b2f8f3f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 20 08:22:19 2021 +0700 Document deprecation of graph's width and height commit cb0ad617bd6c83a499896797318e3b0b588c3566 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 07:53:37 2021 +0700 Deprecate width and height properties of wibox.widget.graph commit 8c64e14ad91a074b784166a7054e53444cccb9d2 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 04:37:59 2021 +0700 Refactor graph:draw() to separate concerns Firstly, summation of group values for stacked graphs is extracted into graph:preprocess_values(). This makes graph:draw() smaller and exposes the fact, that stacked graphs are almost entirely just a form of a more general concept: pre-render data manipulation. It's something a user might want to customize, and now they can do it by monkey-patching the method. Secondly, the logic for deciding, if and with which color a data group should be drawn, is extracted into graph:should_draw_data_group() and get_data_group_color() methods respectively. This makes it customizable too and makes more obvious the fact, that it is a general thing that has nothing to do with stacked graphs in particular. With this there are only 5 lines remaining in graph:draw(), that could be said to be specific to stacked graphs, all pertaining to the `prev_y` variable, which merely preserves the bar coordinates from a previous data group. commit 2a32b6305d893f413303728a0a3da1dcd9a5591e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 22:52:37 2021 +0700 Pointlessly optimize graph:add_value() 1) Purge old values first, then insert the new one, if needed. Since Lua grows arrays in power of two steps, and our guess_capacity() also takes care to return even integers, it would be a shame to overstep the capacity by one by inserting first and thus getting the table size doubled only to never use the most of it. 2) Use a removal procedure that doesn't call the slow length operator at all. Previous code called it twice (once in table.remove) for every removed value. commit 680ea8f5d81e9040afb4662b3229e937e4b89e02 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:23:38 2021 +0700 Set graph data series color before the painting loop commit b970f0f221ae76250f2a94433ff79d653caa6e21 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:05:37 2021 +0700 Drop a large no longer necessary `if` in graph:draw_values() This commit is almost purely a whitespace change. commit 6b4f0541c70ec99d7f6e276bae64939f337120d6 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:00:50 2021 +0700 Extract values rendering code into graph:draw_values() method This is to avoid ungodly if nesting levels, which are needed to bail out of drawing values early and skip to drawing the border. The next commit will get rid of the if. commit e0950e4f99d1ea1c4b8cea2cc67629fa40030c2a Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 14:50:20 2021 +0700 Track graph draw() usage to guess necessary array capacity The last step of divorcing add_values() from width calculations, while ensuring that user doesn't observe a lack of data to draw. The widget now tracks an approximate number of values that it has been asked to draw() in _private.last_drawn_values_num, and truncates its values array based on that (if not overridden by the capacity property). commit 2e3d542282f35e7c0fed454da0f2eada0fcf3b78 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 13:35:12 2021 +0700 Add `capacity` property to graph widget This is the second step of divorcing add_values() from calculations involving widths. How many values are stored and how many values are drawn are distinct concepts. There isn't even a single definitive "how many values are drawn" because the widget could be rendered in several places with distinct widths simultaneously, and in every such place the data must be drawn appropriately, e.g. with auto-scaling, that takes only the visible data points into account. And the draw() method can do that now. The default capacity value of nil uses heuristics based on widget's _private.width to guess some capacity that would tolerate some dynamic widget resizing. If the user finds the heuristic failing them, they can set the capacity property to the desired maximum number of values that will be kept in the _private.values array (in each data group). commit 08d4a80686f7fe363551c35589a460839f60853e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 03:27:18 2021 +0700 Extract values size computation into compute_drawn_values_num() This is the first step of making add_values() not concern itself (directly) with widget dimensions. The user can monkey-patch the method to override how many values are (logically) drawn, because it affects graph auto-scaling and can be useful for rendering of step_shape-s that paint outside of their step_width. I didn't make it a full-blown function-valued property, because it seems a fringe functionality, which I'm too lazy to document. commit 3c5617c98ab1147618ed22e8414a77fc56b45f6e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 02:32:29 2021 +0700 Copy graph widget prototype methods over in bulk commit 169538837ece41a03d02ba8fbc0e4097062b67a3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun Apr 25 05:58:01 2021 +0700 Remove graph.baseline_y property After giving it some thought I've concluded, that it is unlikely to be useful as it is now. I'd better reserve the name for some more sensible functionality, like fixing the baseline_value defined axis at the given widget coordinate, adjusting the scaling accordingly, so min_value/max_value won't generally correspond to widget top and bottom anymore. commit 202ffaa9a0b820c7c471e1121d01b91f8f76cd38 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 12:06:55 2021 +0700 Add baseline_y and baseline_value properties to graph widget Now values in graph have a *baseline*, tunable with baseline_y and baseline_value properties. It is the (for now invisible) horizontal line from which all bars in the graph grow. I call it a "baseline" instead of "axis", because it doesn't actually have to correspond to the 0 value. But it can, and actually does now by default. baseline_y and baseline_value properties determine the vertical placement of the baseline in the widget. baseline_y is the position of the baseline in normalized widget coordinates, e.g. 0 corresponds to widget's top, 1 - to widget's bottom, and 0.5 - to widget's middle. So if e.g. one sets baseline_y = 0, then no matter how the widget is resized and how its values are scaled, bars in it will be drawn starting from the top. baseline_value is the position of the baseline in value coordinates. So if one sets baseline_value = 10, then no matter what happens, bars for values greater than 10 will be drawn as growing up from this line, and bars for values smaller as growing down. Where and if it will be seen in the widget, depends on the scaling. baseline_value = 0 is the familiar zero axis. baseline_y is unset by default and has priority, i.e. if it's set, then baseline_value is ignored. baseline_value defaults to 0. Prior to this commit, bars were always drawn from the bottom edge of the widget, or, in the new terms, the widget had baseline_y = 1. But it was indistinguishable from baseline_value = 0, because the widget clamped all negative values to 0, so the difference between the two could never be observed. baseline_value = 0 is clearly more useful and familiar to people, so now it is in force by default. commit df7e19abaebf0a871b5ca217b4cc3365533267a6 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 11:18:12 2021 +0700 Move value clamping from graph:add_value() into draw() Previously all values were forcibly clamped between 0 (not tunable at all) and max_value (1 by default). Thus graphs with negative values were not an option. NaN values were conflated with zeros, even though it may be useful to maintain the distinction and render them differently. And if user happened to dynamically change the max_value property, they discovered, that their data got truncated. Preserving user data as it was given is more flexible and makes things easier to debug. This commit does exactly that. So what happens now to the newly allowed values during draw()? There are 3 new cases to consider: 1) NaNs 2) Negatives 3) Values outside of min_value..max_value range NaNs are presently simply not drawn. An empty place is reserved where the value should've been, but nothing else is done. This is distinct from how a zero is handled, e.g. if step_shape is set, it is called with the height parameter = 0, which can still end up drawing something, depending on the shape. One could also consider addition of nan_color/nan_shape properties which could be drawn to alert user to a gap in their data. Negative values necessitate introduction of the concept of the zero axis, from which they will be drawn DOWN, in contrast to positive values. Where should this axis be drawn? How should step_shape-s with inherent direction, e.g. arrows, be drawn for negatives? This will be addressed in the next commit. Values outside of min/max_value range can imply shapes that go beyond widget dimensions. An arrow shape that goes beyond the roof is visibly different from an arrow that saturates and merely touches the widget top, and the user may reasonably desire either behavior. Therefore this commit introduces a boolean clamp_bars property, which defaults to true for reasons of robustness and backward compatibility with the behaviour prior to this commit. The property of course affects only the display of the values, the actual user data stays intact. commit c9c24fb1ab741cd6bb8074971d568e0646c51532 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:29:01 2021 +0700 Use rectangles for drawing graph bars of all sizes I have benchmarked various bar drawing strategies and graph sizes and found out that drawing graph bars with rectangles is as fast or even marginally faster than using fat lines. Thus the code that tries to special-case 1px bars as lines seems to be not worth the additional complexity it introduces, and this commit removes it. Incidentally, I've also established, that rectangles are more robust. If one draws graph bars and feeds cairo very large coordinates (~2^62), then corresponding rectangles fail to render, but all the other rectangles in the same series get rendered successfully, whereas a single large coordinate in a move_to/line_to() sequence is enough to cause all lines in the batch to fail to render. commit bc7f4be5e06405b5aa7fb443a5ace2b9b9575b1f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:01:42 2021 +0700 Cache cairo methods used in the inner loop of graph:draw() I have benchmarked the draw() method, both on cairo xcb and on argb32 image surfaces, and have discovered that cairo context method calls are unnaturally slow, to the point, that almost nothing else in the function matters. For example, drawing bars with vertical lines made the whole draw() function almost 2 times slower than doing the same with rectangles, merely because the former requires two method calls (move_to/line_to), and the latter only one. Simply caching hotpath cairo methods in local variables, reduced the difference between the drawing methods to a negligible one and made the whole function 4 times faster on a typical 100-bar graph, (1.4ms to 0.33ms on my laptop) thus bringing closer my secret dream of drawing live audio data with awesome graphs (kidding). I guess, LGI does something not very optimal there, like gobject-introspecting the method every time, or something. There might be some untapped optimization potential, possibly in upstream, like a method cache, from which everything in awesome could benefit, even though I imagine not many things are so call-intensive as the graph widget, to really feel the difference. commit 538df25bdf02b53d0bbe81809eef2e6fdb05ff6f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 07:32:37 2021 +0700 Use 1-based index in the inner graph draw loop It's used there only once for a calculation and then several times to index some tables, and index 0 is bad for lua tables performance-wise. commit 7412546916cd0a0a490130a120255ffb719398b8 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:55:12 2021 +0700 Leave graph.max_value uninitialized at construction This is a user-visible change, but the previous behavior was probably not what user expected anyway, namely with graph.scale set the graph mysteriously couldn't zoom in onto values that are smaller than 1. commit eb81d7929a161e79c13262cfa73ac2e6a820e8ed Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:48:29 2021 +0700 Allow graph.max_value be nil This makes its behave symmetrically to min_value. Now it defaults to 1, when graph.scale is not set, and to the maximum value in the graph data, when graph.scale is set. commit f622a8430cbfde7007b58d7f4c9c76b08c91e997 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 12:01:13 2021 +0700 Calculate max values num in graph:add_value() more precisely At most so many values were kept in the widget values array, as there are pixels of usable widget width (i.e. sans border). This is exactly right for the surely most common case of graphs with 1px-wide bars, but is more than needed for graphs with step_width+step_spacing>1, and not enough for graphs with subpixel bars. Moreover this made graph autoscaling behave weirdly in graphs with thicker bars, because old values survived longer in the array and kept affecting min_value/max_value calculations even after they were shifted off-screen. The maximum number of kept values is now calculated exactly as the number of values that can be displayed at once, taking step_width and step_spacing into account. It might be worth adding a max_values_num property to the graph to let user override this calculation, in case they draw shapes in step_shape() outside of its supposed rectangle, so our assumptions of visibility are wrong, or the widget often gets resized, resulting in the annoyance of visibly truncated series. commit 0aa63249a18641251a1bf77423739164f0eab39f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 02:30:13 2021 +0700 Fix border_color/border_width bugs in graph widget add_value() wrongly assumed that border_width is always set, when border_color is, so if user set only the latter, an error occurred. add_value() assumed that border_width is always < width which led to awesome hanging up in an infinite loop, should that fail to be the case. The graph border_width property was driven by the border_color property, instead of the other way round. If border_color wasn't set, the border wasn't drawn all, even if user sets non-zero border_width, which is a confusing behavior, in contrast to all other widgets, which simply use a plethora of fallback colors to draw the border, when needed. This also meant that, despite being documented, the beautiful.graph_border_color fallback color could not and was not ever used. If the border_color was set though, an unset border_width was immediately assumed to be 1 instead of 0, also contrary to what other widgets do. Theme colors shouldn't influence widget layout, so this commit fixes this behavior to: 1) border_width always defaults to 0, if unset. 2) border_color falls back to beautiful.graph_border_color or white. Maybe one should also add a beautiful.graph_border_width theme variable, like some widgets do. commit dad4a3253b8fef971fb82720d2a407fcc0a879ad Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 03:11:27 2021 +0700 Implement multiple data series drawing for non-stacked graph The widget can already hold and draw multiple data groups, so it makes little sense to limit drawing only to the first group when graph.stack = false, probably leading to user's confusion. Now user can call add_value(v, group) with group > 1 and the data series will be drawn (over groups with lesser id-s) with graph.stack_colors[group] color, if stack_colors is set, or with graph.color, if stack_colors isn't set. Thus nothing visibly changes for all current users of non-stacked graphs with only single data series, but the new functionality is now there for those who need it. One can't fully emulate this functionality, e.g. with multiple graphs widgets in a wibox.layout.stack, because data series in a single widget can enjoy a common auto-scaling. commit 3e645c976184ab5f50d625271c25062db9ab7ed9 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:59:12 2021 +0700 Don't draw a stacked graph data group if there's no color for it If stack_colors[idx] is nil for the group `idx`, don't draw it at all (as opposed, e.g. to the effect of drawing it with a transparent color, or as though it had all values == 0, which both produce different results). This code is still subtly different from prior behavior, because now user can have a non-contiguous stack_colors table and turn the display of any data group on and off at will, whereas the code two commits earlier would just stop rendering as soon as ipairs(stack_colors) does, i.e. no data series were drawn past the first nil hole. This commit only looks large, but is mostly a whitespace change due to nesting from a pair of wrapping ifs. commit 8c0273786de1ade66bdd977699f98b588da9f487 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:32:42 2021 +0700 Unify stacked and non-stacked graph rendering Now stacked graphs support everything the nonstacked do, like step_width ~= 1 and custom step_shape-s. Technical notes: This code no longer calls cr:stroke() for every single value in a stacked graph, but rather paints a whole series and then cr:stroke/fill()-s it at all once with its color, improving performance considerably. Additionally the graph no longer relies on the presence of the stack_colors property to render series. It draws all data that it has, and, if there's no corresponding color in stack_colors, falls back to graph.color. This will be changed in the next commit to the prior behavior of only drawing those series for which there's a color in the stack_colors table, but that would've made this commit's diff much larger due to required nesting ifs over large code chunks. This commit also takes care to properly handle NaNs and negative values. Those are currently (almost) guaranteed to not occur in the values array due to clipping in graph:add_value(), but I intend to eventually drop the clipping, because it does nothing that draw() really relies upon, and allowing unclipped and NaN values is more flexible and less surprising, e.g. when user dynamically toggles graph.scale. Eventually I intend to implement drawing negative values in non-stacked graphs and possibly even in the stacked ones. commit 38c6589c51b2aa057d7c2f40182ecf32b5b2ff5e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 10:04:15 2021 +0700 Convert graph:draw() to use `self` commit f21f1b2550426df1c4ea8abde079b1ccef84b394 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 12:27:26 2021 +0700 Fix scaling for stacked graphs Stacked graphs have cumulative heights, values from all groups that end up in the same graph bar are effectively summed. Previous code didn't take that into account when calculating min/max_value for the purposes of rescaling, but simply examined every value separately. This code adds up all values up front and finds min/max among those, to determine proper scaling that would fit the graph exactly. commit 1ce4c7bf63bdd6c99c195d668347bec49355e921 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 09:49:35 2021 +0700 Draw shaped graph bars only up to the baseline Prior to this commit, every step_shape bar was, for some reason, drawn with height = full widget's height, regardless of the value it represents. So for the most bars their lower end was far outside the box and therefore clipped off. So e.g. one couldn't see the lower round edges of rounded rectangles. This commit fixes that by appropriately varying the height that is passed into step_shape(), so that lower edges of the bars always exactly touch the lower edge of the graph. If the user wants to restore the prior behavior, they can simply ignore the passed-in height in step_shape() and just always draw their shape with height = full widget height. But they couldn't replicate the effect of this commit in their rc.lua without essentially writing their own graph widget. commit e25185b08da23745b5815f85f6e9e43222ed2294 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 08:42:54 2021 +0700 Avoid incremental transform shifting during graph bar rendering Instead of letting addition errors and cognitive load accumulate simply use the same offset calculation that is used for lines and reset the transform back to the top-left corner after every drawn bar. It's conceptually simpler, and will be useful later for stacked graphs and more. It also fixes a discrepancy between plain and step_shape rendering. Prior to this commit, bar coloring occurred with a disturbed transform, which was centered at the last drawn bar, wherever it happened to be. This meant that nontrivial colors, like gradients and images, were arbitrarily offset when step_shape was used. commit f3e98866d8583c35d0b557c09f529299edc9ba2c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:45:29 2021 +0700 Refactor out graph bar height expression into a variable commit f54528d6892f5aaa4595917853c9e2130f743fdb Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:20:25 2021 +0700 Draw graph bars with lines only when step_width == 1 Before this commit graph bars of step_width < 1 were drawn with lines of width 1, but that's a bit unfortunate, because it led to much overpainting, while still ending up with a graphically incorrect result. Let's draw such bars with rectangles and let cairo do things with subpixels, getting more graphically accurate result, while still keeping the common case of step_width == 1 (supposedly) fast-drawn with lines. commit b6515dca67c18b78eb5657378131a62ab43d4cfc Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:54:07 2021 +0700 Fix unsharp edges when drawing graph bars with rectangles The 0.5 offset is only helpful for 1-width strokes, not for fills. commit 7f3db8f07e82f821bcd1dc418d66564f537609d6 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:21:01 2021 +0700 Do coordinate transformations even if the value isn't drawn The graph presently doesn't draw values < 0, but there was a discrepancy between drawing with and without step_shape. The default line/rectangle drawing reserves an empty place where the skipped value should've been, but no such empty place is reserved, when step_shape is used. This commit fixes that. Skipped values always have their empty space now. commit 7b731ebfe96af7a9dba9c57371da6761cf22c00c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:03:30 2021 +0700 Fix graph bar scaling when min_value is not 0 Previous calculation lead to underscaling of values so that even value = max_value wasn't drawn to full height. commit dca59fd8b7fff0e7e646f2dfc6bd92047028e5a8 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:18:40 2021 +0700 Calculate the horizontal graph bar offset only when needed commit 940ddf4b9825e9c3a91d276208c089d8c74e49db Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:17:23 2021 +0700 Call cr:move_to() only when drawing the graph with lines It's redundant otherwise and might even interfere with user's drawing in step_shape(). commit b5d5cd10ca29fb29cd751faaa95137c2fe89ff63 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:04:06 2021 +0700 Fix wrong horizontal offset calculations in widget.graph Indices in the loop are zero-based, so things like i-1 and i>1 aren't necessary and actually wouldn't have been correct even in a 1-based loop. step_shape = gears.shape.rectangle and step_shape = nil, which is supposed to default to rectangle according to docs, were drawn differently due to mismatched offset calculations, which led to problems especially noticeable when step_spacing is not zero, e.g: 1) whole graph shifted left too much, drawn partially off-screen. 2) whole graph shifted right too much, and the first bar placed closer to the rest than others. commit dc369fa198281e000afc1bd17326f3a264adf7d1 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 02:09:21 2021 +0700 Fix the "border_width affects graph bar width" bug Graph bars were drawn with lines of border_width thickness instead of 1, i.e. possibly too fat to be readable. commit 1ba73378cf29ed27a6ecd18716394779ceb543d9 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 00:13:03 2021 +0700 Simplify internal values representation in graph even more New values in graph._private.values were pushed to the end, and old values were removed from the beginning of the table. In draw() however newer values are drawn first, at the left edge of the graph, necessitating annoying idx = #values - idx conversions everywhere. In particular in stacked graph that means that values that are drawn together may have different indices. One has to shift the values array in add_value() either way, so one might as well insert new values at the beginning of the array and simplify the rest of the code. This doesn't seem like much yet, but it will prove to be much more convenient and less error-prone in what I'm going to do later. commit f70523ee68dbd6b67050ace4b82903a9b22440c7 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 13:35:10 2021 +0700 Simplify internal values representation in graph widget graph._private.values was either a table of values or a table of tables of values, depending on whether "stack" property is enabled, ... or a mix of both if user forgot to pass the second "group" parameter to graph:add_value(). This resulted in pretty fragile and confusing behavior, e.g. leaving user to wonder, why they see nothing more after setting stack=true for a graph in a previously working rc.lua, and demanding a very careful use of add_value() from the user, or else some values or even entire stacks would mysteriously vanish and errors in draw() get triggered, sometimes after a period of seemingly normal functioning. Now `values` is always a table of tables and the group parameter in add_value() defaults to 1, thus making the non-stacked graph a trivial variant of the stacked graph.
2021-05-20 17:14:03 +02:00
end
2016-05-23 07:30:44 +02:00
graph: complete widget overhaul and bug fixes Squashed commit of the following: commit 69821f51fe1e8652715658543e50631ece495090 Author: Alex Belykh <albel727@ngs.ru> Date: Mon May 10 19:17:55 2021 +0700 Refactor property handling in wibox.widget.graph In addition, baseline_value, step_width and step_spacing properties are all set to their default non-nil numeric values during instance creation now. commit 842eb429bf5df4b2ba46b2e6f7646afe89b1a3c3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 9 06:18:45 2021 +0700 Make graph:draw_values() private There's no documentation nor confidence that it's a good API, so I'm hiding it for now. commit 720746780574e4bad14a71fc4174955139c2eb50 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 04:04:13 2021 +0700 Document graph_should_draw_data_group() for posterity commit 7c73f3754ce6ba86b118f1170a6f9e406ab4841d Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:59:21 2021 +0700 Make graph:should_draw_data_group() private There's no confidence whether it's a good API, so I'm hiding it for now. commit b3539e20adb6423118af726ae4f8751ef864b5d2 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:36:44 2021 +0700 Make graph:preprocess_values() private There's no confidence whether it's a good API, so I'm hiding it for now. commit 2df4400e780a6260f8d0ab1c60734fbb11bf3c4f Author: Alex Belykh <albel727@ngs.ru> Date: Thu May 6 10:05:23 2021 +0700 Add spec/wibox/widget/graph_spec.lua commit ce01a9771ab439b8df841f1565aa4aa8a44a5e0d Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 08:16:18 2021 +0700 Improve graph.capacity documentation commit e28fade8f655802f5bf89154169f68d256b8e332 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 07:36:01 2021 +0700 Document graph:compute_drawn_values_num() commit dd68332292465422d08b48e9f0229f143beccbc6 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 05:24:23 2021 +0700 Document graph:pick_data_group_color() method commit ca6dc55f77aca88a6e9cc90bb6fd0045739f41f5 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 04:13:58 2021 +0700 Guard against setting data groups sparsely in graph:add_value() commit cdea5f126f7984e72924f6dffbd312c4fe3b032f Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 02:36:12 2021 +0700 Refactor scaling and baseline choice out of graph:draw() commit 10135deafe8a9390ba66ab657ebb1fb41d549e8c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 21:34:48 2021 +0700 Document graph.nan_color and graph.stack properties commit 0d73bb84ac66b87e16aee472bd1f299549f66a1c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 20:49:24 2021 +0700 Use a better example in docs for the graph.stack property commit c27d160a0826d033d49d6141608c40aa8a0642fa Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 16:05:38 2021 +0700 Document graph.baseline_value property commit 459bc8176c13d4ea32af0dcdc2399d3b929b1c6a Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:53:47 2021 +0700 Document graph.clamp_bars property commit e732ee0b3b007d213e689b98cc00ae7cb141ae08 Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:34 2021 +0700 Improve graph:add_value() and :clear() documentation commit ae73e3aa9f8d60409cbe4cb4da9612f31af4f38e Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:11 2021 +0700 Improve graph min_value/max_value/scale documentation commit a7350996a9def773b91b1631e6c0507e33d8f93b Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:27:36 2021 +0700 Cleanup graph examples to show only relevant things in docs commit 30740c26f4f41c16f4c8a3c339bcb7bd299e14cd Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 14:04:36 2021 +0700 Rename graph.stack_colors property to `group_colors` The colors are used for stacked and non-stacked graphs alike, so the name didn't make sense. Also document and clarify some color stuff. commit 1ea01bfd410971cf0d0c25fd74cd764d5eb7279d Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 21:25:41 2021 +0700 Add an example file for code coverage of some fringe graph cases commit eae68e39553f88a6275a0c65bf040b5d4c262b54 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 20:23:32 2021 +0700 Add step_shape to the graph/nan_color example for code coverage commit 8027147bc2eab34a97b49a428fb8cef27f122bed Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:55:18 2021 +0700 Add example files for graph.nan_color/nan_indication properties commit 89a25a165c52b2c15b6c4d95ecb777a9067e66f9 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:33:07 2021 +0700 Make widget graph:add_value(v) default v to NaN 0 as a default makes no sense and silences the error that is failing to specify a value, to which the user should rather be alerted. The graph widget has sane NaN handling now and will do just that. commit 2835d552440be48d356a7f7b1d64963696dd550b Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:20:48 2021 +0700 Implement graph.nan_color and nan_indication properties Now, whenever there's a NaN among values and graph.nan_indication is set, the corresponding area, where the value bar should have been drawn, is filled with the nan_color from top to bottom. The painting is done after all data is rendered, to make sure that it won't be overpainted and go unnoticed. NaNs among graph's values are inevitably a sign of some error and it is arguably not a sane default behavior to simply not render them at all. The user should take immediate note of any errors by default, instead of wondering at a mysteriously empty graph widget, which is why nan_indication is set to true by default. If the user wants to silence NaNs, they can always simply set it to false. nan_color if unset, defaults to a noticeable yellow-black pattern. As a direct and planned consequence, stacked graphs with negative values in them trigger the NaN handling too. But silencing NaN handling for those graphs is also possible and semi-sane, because then NaNs and negatives are ignored and the graph behaves (almost) like they were zeros. commit 9c1f8f08b400e01e19c534f3810c9c9ab485760c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 26 08:10:19 2021 +0700 Refactor out graph_map_value_to_widget_coordinates() function commit 1905991ddecabdae3e15ffb8ee5b18a1bee1a711 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 28 13:39:29 2021 +0700 Add an example file for graph.baseline_value property commit b31b39d66f4c037a9484b71a3f910703ae01fac0 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 07:03:49 2021 +0700 Add an example file for graph.clamp_bars=false/true comparison commit a1fc850e3047f9ee2c9ddeffacfbabf3ded34171 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 04:06:45 2021 +0700 Add an example file for graphs with negative values and shapes commit 2f012f8ff1bb5094c14e724a61e59a2a3a296bc8 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 01:06:05 2021 +0700 Add an example file for a graph displaying negative values commit 7b14fbd59a4c4c51962fd3110a1a900dd5f4eb98 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 11:18:26 2021 +0700 Add an example file for graphs with step_width < 1 commit dfd9a54544b637e2fbe3d1325f9cc24b3f4d2313 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 10:17:38 2021 +0700 Add an example file for graph.stack=false/true comparison Should also demonstrate that multiple data groups are possible for non-stacked graphs too. commit 5b532106c3fd255f9b66a66cfb88d977b489ab5d Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 09:34:14 2021 +0700 Add an example file for disabling graph data groups with nil colors commit ac6f1083c02fcb428f089a616f9b0ebaa5496b6c Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:57:25 2021 +0700 Add an example file for step_shape/width/spacing on stacked graphs commit b3703db6a5139e73e72c1cfd198ece2cd9be7df2 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:43:56 2021 +0700 Add an example file for scale=false/true on stacked graph commit b000aed4a12cb44fb501e1c8432a71a14b2f8f3f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 20 08:22:19 2021 +0700 Document deprecation of graph's width and height commit cb0ad617bd6c83a499896797318e3b0b588c3566 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 07:53:37 2021 +0700 Deprecate width and height properties of wibox.widget.graph commit 8c64e14ad91a074b784166a7054e53444cccb9d2 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 04:37:59 2021 +0700 Refactor graph:draw() to separate concerns Firstly, summation of group values for stacked graphs is extracted into graph:preprocess_values(). This makes graph:draw() smaller and exposes the fact, that stacked graphs are almost entirely just a form of a more general concept: pre-render data manipulation. It's something a user might want to customize, and now they can do it by monkey-patching the method. Secondly, the logic for deciding, if and with which color a data group should be drawn, is extracted into graph:should_draw_data_group() and get_data_group_color() methods respectively. This makes it customizable too and makes more obvious the fact, that it is a general thing that has nothing to do with stacked graphs in particular. With this there are only 5 lines remaining in graph:draw(), that could be said to be specific to stacked graphs, all pertaining to the `prev_y` variable, which merely preserves the bar coordinates from a previous data group. commit 2a32b6305d893f413303728a0a3da1dcd9a5591e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 22:52:37 2021 +0700 Pointlessly optimize graph:add_value() 1) Purge old values first, then insert the new one, if needed. Since Lua grows arrays in power of two steps, and our guess_capacity() also takes care to return even integers, it would be a shame to overstep the capacity by one by inserting first and thus getting the table size doubled only to never use the most of it. 2) Use a removal procedure that doesn't call the slow length operator at all. Previous code called it twice (once in table.remove) for every removed value. commit 680ea8f5d81e9040afb4662b3229e937e4b89e02 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:23:38 2021 +0700 Set graph data series color before the painting loop commit b970f0f221ae76250f2a94433ff79d653caa6e21 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:05:37 2021 +0700 Drop a large no longer necessary `if` in graph:draw_values() This commit is almost purely a whitespace change. commit 6b4f0541c70ec99d7f6e276bae64939f337120d6 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:00:50 2021 +0700 Extract values rendering code into graph:draw_values() method This is to avoid ungodly if nesting levels, which are needed to bail out of drawing values early and skip to drawing the border. The next commit will get rid of the if. commit e0950e4f99d1ea1c4b8cea2cc67629fa40030c2a Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 14:50:20 2021 +0700 Track graph draw() usage to guess necessary array capacity The last step of divorcing add_values() from width calculations, while ensuring that user doesn't observe a lack of data to draw. The widget now tracks an approximate number of values that it has been asked to draw() in _private.last_drawn_values_num, and truncates its values array based on that (if not overridden by the capacity property). commit 2e3d542282f35e7c0fed454da0f2eada0fcf3b78 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 13:35:12 2021 +0700 Add `capacity` property to graph widget This is the second step of divorcing add_values() from calculations involving widths. How many values are stored and how many values are drawn are distinct concepts. There isn't even a single definitive "how many values are drawn" because the widget could be rendered in several places with distinct widths simultaneously, and in every such place the data must be drawn appropriately, e.g. with auto-scaling, that takes only the visible data points into account. And the draw() method can do that now. The default capacity value of nil uses heuristics based on widget's _private.width to guess some capacity that would tolerate some dynamic widget resizing. If the user finds the heuristic failing them, they can set the capacity property to the desired maximum number of values that will be kept in the _private.values array (in each data group). commit 08d4a80686f7fe363551c35589a460839f60853e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 03:27:18 2021 +0700 Extract values size computation into compute_drawn_values_num() This is the first step of making add_values() not concern itself (directly) with widget dimensions. The user can monkey-patch the method to override how many values are (logically) drawn, because it affects graph auto-scaling and can be useful for rendering of step_shape-s that paint outside of their step_width. I didn't make it a full-blown function-valued property, because it seems a fringe functionality, which I'm too lazy to document. commit 3c5617c98ab1147618ed22e8414a77fc56b45f6e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 02:32:29 2021 +0700 Copy graph widget prototype methods over in bulk commit 169538837ece41a03d02ba8fbc0e4097062b67a3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun Apr 25 05:58:01 2021 +0700 Remove graph.baseline_y property After giving it some thought I've concluded, that it is unlikely to be useful as it is now. I'd better reserve the name for some more sensible functionality, like fixing the baseline_value defined axis at the given widget coordinate, adjusting the scaling accordingly, so min_value/max_value won't generally correspond to widget top and bottom anymore. commit 202ffaa9a0b820c7c471e1121d01b91f8f76cd38 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 12:06:55 2021 +0700 Add baseline_y and baseline_value properties to graph widget Now values in graph have a *baseline*, tunable with baseline_y and baseline_value properties. It is the (for now invisible) horizontal line from which all bars in the graph grow. I call it a "baseline" instead of "axis", because it doesn't actually have to correspond to the 0 value. But it can, and actually does now by default. baseline_y and baseline_value properties determine the vertical placement of the baseline in the widget. baseline_y is the position of the baseline in normalized widget coordinates, e.g. 0 corresponds to widget's top, 1 - to widget's bottom, and 0.5 - to widget's middle. So if e.g. one sets baseline_y = 0, then no matter how the widget is resized and how its values are scaled, bars in it will be drawn starting from the top. baseline_value is the position of the baseline in value coordinates. So if one sets baseline_value = 10, then no matter what happens, bars for values greater than 10 will be drawn as growing up from this line, and bars for values smaller as growing down. Where and if it will be seen in the widget, depends on the scaling. baseline_value = 0 is the familiar zero axis. baseline_y is unset by default and has priority, i.e. if it's set, then baseline_value is ignored. baseline_value defaults to 0. Prior to this commit, bars were always drawn from the bottom edge of the widget, or, in the new terms, the widget had baseline_y = 1. But it was indistinguishable from baseline_value = 0, because the widget clamped all negative values to 0, so the difference between the two could never be observed. baseline_value = 0 is clearly more useful and familiar to people, so now it is in force by default. commit df7e19abaebf0a871b5ca217b4cc3365533267a6 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 11:18:12 2021 +0700 Move value clamping from graph:add_value() into draw() Previously all values were forcibly clamped between 0 (not tunable at all) and max_value (1 by default). Thus graphs with negative values were not an option. NaN values were conflated with zeros, even though it may be useful to maintain the distinction and render them differently. And if user happened to dynamically change the max_value property, they discovered, that their data got truncated. Preserving user data as it was given is more flexible and makes things easier to debug. This commit does exactly that. So what happens now to the newly allowed values during draw()? There are 3 new cases to consider: 1) NaNs 2) Negatives 3) Values outside of min_value..max_value range NaNs are presently simply not drawn. An empty place is reserved where the value should've been, but nothing else is done. This is distinct from how a zero is handled, e.g. if step_shape is set, it is called with the height parameter = 0, which can still end up drawing something, depending on the shape. One could also consider addition of nan_color/nan_shape properties which could be drawn to alert user to a gap in their data. Negative values necessitate introduction of the concept of the zero axis, from which they will be drawn DOWN, in contrast to positive values. Where should this axis be drawn? How should step_shape-s with inherent direction, e.g. arrows, be drawn for negatives? This will be addressed in the next commit. Values outside of min/max_value range can imply shapes that go beyond widget dimensions. An arrow shape that goes beyond the roof is visibly different from an arrow that saturates and merely touches the widget top, and the user may reasonably desire either behavior. Therefore this commit introduces a boolean clamp_bars property, which defaults to true for reasons of robustness and backward compatibility with the behaviour prior to this commit. The property of course affects only the display of the values, the actual user data stays intact. commit c9c24fb1ab741cd6bb8074971d568e0646c51532 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:29:01 2021 +0700 Use rectangles for drawing graph bars of all sizes I have benchmarked various bar drawing strategies and graph sizes and found out that drawing graph bars with rectangles is as fast or even marginally faster than using fat lines. Thus the code that tries to special-case 1px bars as lines seems to be not worth the additional complexity it introduces, and this commit removes it. Incidentally, I've also established, that rectangles are more robust. If one draws graph bars and feeds cairo very large coordinates (~2^62), then corresponding rectangles fail to render, but all the other rectangles in the same series get rendered successfully, whereas a single large coordinate in a move_to/line_to() sequence is enough to cause all lines in the batch to fail to render. commit bc7f4be5e06405b5aa7fb443a5ace2b9b9575b1f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:01:42 2021 +0700 Cache cairo methods used in the inner loop of graph:draw() I have benchmarked the draw() method, both on cairo xcb and on argb32 image surfaces, and have discovered that cairo context method calls are unnaturally slow, to the point, that almost nothing else in the function matters. For example, drawing bars with vertical lines made the whole draw() function almost 2 times slower than doing the same with rectangles, merely because the former requires two method calls (move_to/line_to), and the latter only one. Simply caching hotpath cairo methods in local variables, reduced the difference between the drawing methods to a negligible one and made the whole function 4 times faster on a typical 100-bar graph, (1.4ms to 0.33ms on my laptop) thus bringing closer my secret dream of drawing live audio data with awesome graphs (kidding). I guess, LGI does something not very optimal there, like gobject-introspecting the method every time, or something. There might be some untapped optimization potential, possibly in upstream, like a method cache, from which everything in awesome could benefit, even though I imagine not many things are so call-intensive as the graph widget, to really feel the difference. commit 538df25bdf02b53d0bbe81809eef2e6fdb05ff6f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 07:32:37 2021 +0700 Use 1-based index in the inner graph draw loop It's used there only once for a calculation and then several times to index some tables, and index 0 is bad for lua tables performance-wise. commit 7412546916cd0a0a490130a120255ffb719398b8 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:55:12 2021 +0700 Leave graph.max_value uninitialized at construction This is a user-visible change, but the previous behavior was probably not what user expected anyway, namely with graph.scale set the graph mysteriously couldn't zoom in onto values that are smaller than 1. commit eb81d7929a161e79c13262cfa73ac2e6a820e8ed Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:48:29 2021 +0700 Allow graph.max_value be nil This makes its behave symmetrically to min_value. Now it defaults to 1, when graph.scale is not set, and to the maximum value in the graph data, when graph.scale is set. commit f622a8430cbfde7007b58d7f4c9c76b08c91e997 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 12:01:13 2021 +0700 Calculate max values num in graph:add_value() more precisely At most so many values were kept in the widget values array, as there are pixels of usable widget width (i.e. sans border). This is exactly right for the surely most common case of graphs with 1px-wide bars, but is more than needed for graphs with step_width+step_spacing>1, and not enough for graphs with subpixel bars. Moreover this made graph autoscaling behave weirdly in graphs with thicker bars, because old values survived longer in the array and kept affecting min_value/max_value calculations even after they were shifted off-screen. The maximum number of kept values is now calculated exactly as the number of values that can be displayed at once, taking step_width and step_spacing into account. It might be worth adding a max_values_num property to the graph to let user override this calculation, in case they draw shapes in step_shape() outside of its supposed rectangle, so our assumptions of visibility are wrong, or the widget often gets resized, resulting in the annoyance of visibly truncated series. commit 0aa63249a18641251a1bf77423739164f0eab39f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 02:30:13 2021 +0700 Fix border_color/border_width bugs in graph widget add_value() wrongly assumed that border_width is always set, when border_color is, so if user set only the latter, an error occurred. add_value() assumed that border_width is always < width which led to awesome hanging up in an infinite loop, should that fail to be the case. The graph border_width property was driven by the border_color property, instead of the other way round. If border_color wasn't set, the border wasn't drawn all, even if user sets non-zero border_width, which is a confusing behavior, in contrast to all other widgets, which simply use a plethora of fallback colors to draw the border, when needed. This also meant that, despite being documented, the beautiful.graph_border_color fallback color could not and was not ever used. If the border_color was set though, an unset border_width was immediately assumed to be 1 instead of 0, also contrary to what other widgets do. Theme colors shouldn't influence widget layout, so this commit fixes this behavior to: 1) border_width always defaults to 0, if unset. 2) border_color falls back to beautiful.graph_border_color or white. Maybe one should also add a beautiful.graph_border_width theme variable, like some widgets do. commit dad4a3253b8fef971fb82720d2a407fcc0a879ad Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 03:11:27 2021 +0700 Implement multiple data series drawing for non-stacked graph The widget can already hold and draw multiple data groups, so it makes little sense to limit drawing only to the first group when graph.stack = false, probably leading to user's confusion. Now user can call add_value(v, group) with group > 1 and the data series will be drawn (over groups with lesser id-s) with graph.stack_colors[group] color, if stack_colors is set, or with graph.color, if stack_colors isn't set. Thus nothing visibly changes for all current users of non-stacked graphs with only single data series, but the new functionality is now there for those who need it. One can't fully emulate this functionality, e.g. with multiple graphs widgets in a wibox.layout.stack, because data series in a single widget can enjoy a common auto-scaling. commit 3e645c976184ab5f50d625271c25062db9ab7ed9 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:59:12 2021 +0700 Don't draw a stacked graph data group if there's no color for it If stack_colors[idx] is nil for the group `idx`, don't draw it at all (as opposed, e.g. to the effect of drawing it with a transparent color, or as though it had all values == 0, which both produce different results). This code is still subtly different from prior behavior, because now user can have a non-contiguous stack_colors table and turn the display of any data group on and off at will, whereas the code two commits earlier would just stop rendering as soon as ipairs(stack_colors) does, i.e. no data series were drawn past the first nil hole. This commit only looks large, but is mostly a whitespace change due to nesting from a pair of wrapping ifs. commit 8c0273786de1ade66bdd977699f98b588da9f487 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:32:42 2021 +0700 Unify stacked and non-stacked graph rendering Now stacked graphs support everything the nonstacked do, like step_width ~= 1 and custom step_shape-s. Technical notes: This code no longer calls cr:stroke() for every single value in a stacked graph, but rather paints a whole series and then cr:stroke/fill()-s it at all once with its color, improving performance considerably. Additionally the graph no longer relies on the presence of the stack_colors property to render series. It draws all data that it has, and, if there's no corresponding color in stack_colors, falls back to graph.color. This will be changed in the next commit to the prior behavior of only drawing those series for which there's a color in the stack_colors table, but that would've made this commit's diff much larger due to required nesting ifs over large code chunks. This commit also takes care to properly handle NaNs and negative values. Those are currently (almost) guaranteed to not occur in the values array due to clipping in graph:add_value(), but I intend to eventually drop the clipping, because it does nothing that draw() really relies upon, and allowing unclipped and NaN values is more flexible and less surprising, e.g. when user dynamically toggles graph.scale. Eventually I intend to implement drawing negative values in non-stacked graphs and possibly even in the stacked ones. commit 38c6589c51b2aa057d7c2f40182ecf32b5b2ff5e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 10:04:15 2021 +0700 Convert graph:draw() to use `self` commit f21f1b2550426df1c4ea8abde079b1ccef84b394 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 12:27:26 2021 +0700 Fix scaling for stacked graphs Stacked graphs have cumulative heights, values from all groups that end up in the same graph bar are effectively summed. Previous code didn't take that into account when calculating min/max_value for the purposes of rescaling, but simply examined every value separately. This code adds up all values up front and finds min/max among those, to determine proper scaling that would fit the graph exactly. commit 1ce4c7bf63bdd6c99c195d668347bec49355e921 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 09:49:35 2021 +0700 Draw shaped graph bars only up to the baseline Prior to this commit, every step_shape bar was, for some reason, drawn with height = full widget's height, regardless of the value it represents. So for the most bars their lower end was far outside the box and therefore clipped off. So e.g. one couldn't see the lower round edges of rounded rectangles. This commit fixes that by appropriately varying the height that is passed into step_shape(), so that lower edges of the bars always exactly touch the lower edge of the graph. If the user wants to restore the prior behavior, they can simply ignore the passed-in height in step_shape() and just always draw their shape with height = full widget height. But they couldn't replicate the effect of this commit in their rc.lua without essentially writing their own graph widget. commit e25185b08da23745b5815f85f6e9e43222ed2294 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 08:42:54 2021 +0700 Avoid incremental transform shifting during graph bar rendering Instead of letting addition errors and cognitive load accumulate simply use the same offset calculation that is used for lines and reset the transform back to the top-left corner after every drawn bar. It's conceptually simpler, and will be useful later for stacked graphs and more. It also fixes a discrepancy between plain and step_shape rendering. Prior to this commit, bar coloring occurred with a disturbed transform, which was centered at the last drawn bar, wherever it happened to be. This meant that nontrivial colors, like gradients and images, were arbitrarily offset when step_shape was used. commit f3e98866d8583c35d0b557c09f529299edc9ba2c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:45:29 2021 +0700 Refactor out graph bar height expression into a variable commit f54528d6892f5aaa4595917853c9e2130f743fdb Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:20:25 2021 +0700 Draw graph bars with lines only when step_width == 1 Before this commit graph bars of step_width < 1 were drawn with lines of width 1, but that's a bit unfortunate, because it led to much overpainting, while still ending up with a graphically incorrect result. Let's draw such bars with rectangles and let cairo do things with subpixels, getting more graphically accurate result, while still keeping the common case of step_width == 1 (supposedly) fast-drawn with lines. commit b6515dca67c18b78eb5657378131a62ab43d4cfc Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:54:07 2021 +0700 Fix unsharp edges when drawing graph bars with rectangles The 0.5 offset is only helpful for 1-width strokes, not for fills. commit 7f3db8f07e82f821bcd1dc418d66564f537609d6 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:21:01 2021 +0700 Do coordinate transformations even if the value isn't drawn The graph presently doesn't draw values < 0, but there was a discrepancy between drawing with and without step_shape. The default line/rectangle drawing reserves an empty place where the skipped value should've been, but no such empty place is reserved, when step_shape is used. This commit fixes that. Skipped values always have their empty space now. commit 7b731ebfe96af7a9dba9c57371da6761cf22c00c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:03:30 2021 +0700 Fix graph bar scaling when min_value is not 0 Previous calculation lead to underscaling of values so that even value = max_value wasn't drawn to full height. commit dca59fd8b7fff0e7e646f2dfc6bd92047028e5a8 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:18:40 2021 +0700 Calculate the horizontal graph bar offset only when needed commit 940ddf4b9825e9c3a91d276208c089d8c74e49db Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:17:23 2021 +0700 Call cr:move_to() only when drawing the graph with lines It's redundant otherwise and might even interfere with user's drawing in step_shape(). commit b5d5cd10ca29fb29cd751faaa95137c2fe89ff63 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:04:06 2021 +0700 Fix wrong horizontal offset calculations in widget.graph Indices in the loop are zero-based, so things like i-1 and i>1 aren't necessary and actually wouldn't have been correct even in a 1-based loop. step_shape = gears.shape.rectangle and step_shape = nil, which is supposed to default to rectangle according to docs, were drawn differently due to mismatched offset calculations, which led to problems especially noticeable when step_spacing is not zero, e.g: 1) whole graph shifted left too much, drawn partially off-screen. 2) whole graph shifted right too much, and the first bar placed closer to the rest than others. commit dc369fa198281e000afc1bd17326f3a264adf7d1 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 02:09:21 2021 +0700 Fix the "border_width affects graph bar width" bug Graph bars were drawn with lines of border_width thickness instead of 1, i.e. possibly too fat to be readable. commit 1ba73378cf29ed27a6ecd18716394779ceb543d9 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 00:13:03 2021 +0700 Simplify internal values representation in graph even more New values in graph._private.values were pushed to the end, and old values were removed from the beginning of the table. In draw() however newer values are drawn first, at the left edge of the graph, necessitating annoying idx = #values - idx conversions everywhere. In particular in stacked graph that means that values that are drawn together may have different indices. One has to shift the values array in add_value() either way, so one might as well insert new values at the beginning of the array and simplify the rest of the code. This doesn't seem like much yet, but it will prove to be much more convenient and less error-prone in what I'm going to do later. commit f70523ee68dbd6b67050ace4b82903a9b22440c7 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 13:35:10 2021 +0700 Simplify internal values representation in graph widget graph._private.values was either a table of values or a table of tables of values, depending on whether "stack" property is enabled, ... or a mix of both if user forgot to pass the second "group" parameter to graph:add_value(). This resulted in pretty fragile and confusing behavior, e.g. leaving user to wonder, why they see nothing more after setting stack=true for a graph in a previously working rc.lua, and demanding a very careful use of add_value() from the user, or else some values or even entire stacks would mysteriously vanish and errors in draw() get triggered, sometimes after a period of seemingly normal functioning. Now `values` is always a table of tables and the group parameter in add_value() defaults to 1, thus making the non-stacked graph a trivial variant of the stacked graph.
2021-05-20 17:14:03 +02:00
if nan_x and #nan_x > 0 then
cr:set_source(color(self._private.nan_color or prop_fallbacks.nan_color))
for _, x in ipairs(nan_x) do
-- Draw full-height rectangle with nan_color to indicate NaN
cairo_rectangle(cr, x, 0, step_width, height)
end
cr:fill()
2016-05-23 07:30:44 +02:00
end
graph: complete widget overhaul and bug fixes Squashed commit of the following: commit 69821f51fe1e8652715658543e50631ece495090 Author: Alex Belykh <albel727@ngs.ru> Date: Mon May 10 19:17:55 2021 +0700 Refactor property handling in wibox.widget.graph In addition, baseline_value, step_width and step_spacing properties are all set to their default non-nil numeric values during instance creation now. commit 842eb429bf5df4b2ba46b2e6f7646afe89b1a3c3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 9 06:18:45 2021 +0700 Make graph:draw_values() private There's no documentation nor confidence that it's a good API, so I'm hiding it for now. commit 720746780574e4bad14a71fc4174955139c2eb50 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 04:04:13 2021 +0700 Document graph_should_draw_data_group() for posterity commit 7c73f3754ce6ba86b118f1170a6f9e406ab4841d Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:59:21 2021 +0700 Make graph:should_draw_data_group() private There's no confidence whether it's a good API, so I'm hiding it for now. commit b3539e20adb6423118af726ae4f8751ef864b5d2 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:36:44 2021 +0700 Make graph:preprocess_values() private There's no confidence whether it's a good API, so I'm hiding it for now. commit 2df4400e780a6260f8d0ab1c60734fbb11bf3c4f Author: Alex Belykh <albel727@ngs.ru> Date: Thu May 6 10:05:23 2021 +0700 Add spec/wibox/widget/graph_spec.lua commit ce01a9771ab439b8df841f1565aa4aa8a44a5e0d Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 08:16:18 2021 +0700 Improve graph.capacity documentation commit e28fade8f655802f5bf89154169f68d256b8e332 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 07:36:01 2021 +0700 Document graph:compute_drawn_values_num() commit dd68332292465422d08b48e9f0229f143beccbc6 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 05:24:23 2021 +0700 Document graph:pick_data_group_color() method commit ca6dc55f77aca88a6e9cc90bb6fd0045739f41f5 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 04:13:58 2021 +0700 Guard against setting data groups sparsely in graph:add_value() commit cdea5f126f7984e72924f6dffbd312c4fe3b032f Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 02:36:12 2021 +0700 Refactor scaling and baseline choice out of graph:draw() commit 10135deafe8a9390ba66ab657ebb1fb41d549e8c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 21:34:48 2021 +0700 Document graph.nan_color and graph.stack properties commit 0d73bb84ac66b87e16aee472bd1f299549f66a1c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 20:49:24 2021 +0700 Use a better example in docs for the graph.stack property commit c27d160a0826d033d49d6141608c40aa8a0642fa Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 16:05:38 2021 +0700 Document graph.baseline_value property commit 459bc8176c13d4ea32af0dcdc2399d3b929b1c6a Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:53:47 2021 +0700 Document graph.clamp_bars property commit e732ee0b3b007d213e689b98cc00ae7cb141ae08 Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:34 2021 +0700 Improve graph:add_value() and :clear() documentation commit ae73e3aa9f8d60409cbe4cb4da9612f31af4f38e Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:11 2021 +0700 Improve graph min_value/max_value/scale documentation commit a7350996a9def773b91b1631e6c0507e33d8f93b Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:27:36 2021 +0700 Cleanup graph examples to show only relevant things in docs commit 30740c26f4f41c16f4c8a3c339bcb7bd299e14cd Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 14:04:36 2021 +0700 Rename graph.stack_colors property to `group_colors` The colors are used for stacked and non-stacked graphs alike, so the name didn't make sense. Also document and clarify some color stuff. commit 1ea01bfd410971cf0d0c25fd74cd764d5eb7279d Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 21:25:41 2021 +0700 Add an example file for code coverage of some fringe graph cases commit eae68e39553f88a6275a0c65bf040b5d4c262b54 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 20:23:32 2021 +0700 Add step_shape to the graph/nan_color example for code coverage commit 8027147bc2eab34a97b49a428fb8cef27f122bed Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:55:18 2021 +0700 Add example files for graph.nan_color/nan_indication properties commit 89a25a165c52b2c15b6c4d95ecb777a9067e66f9 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:33:07 2021 +0700 Make widget graph:add_value(v) default v to NaN 0 as a default makes no sense and silences the error that is failing to specify a value, to which the user should rather be alerted. The graph widget has sane NaN handling now and will do just that. commit 2835d552440be48d356a7f7b1d64963696dd550b Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:20:48 2021 +0700 Implement graph.nan_color and nan_indication properties Now, whenever there's a NaN among values and graph.nan_indication is set, the corresponding area, where the value bar should have been drawn, is filled with the nan_color from top to bottom. The painting is done after all data is rendered, to make sure that it won't be overpainted and go unnoticed. NaNs among graph's values are inevitably a sign of some error and it is arguably not a sane default behavior to simply not render them at all. The user should take immediate note of any errors by default, instead of wondering at a mysteriously empty graph widget, which is why nan_indication is set to true by default. If the user wants to silence NaNs, they can always simply set it to false. nan_color if unset, defaults to a noticeable yellow-black pattern. As a direct and planned consequence, stacked graphs with negative values in them trigger the NaN handling too. But silencing NaN handling for those graphs is also possible and semi-sane, because then NaNs and negatives are ignored and the graph behaves (almost) like they were zeros. commit 9c1f8f08b400e01e19c534f3810c9c9ab485760c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 26 08:10:19 2021 +0700 Refactor out graph_map_value_to_widget_coordinates() function commit 1905991ddecabdae3e15ffb8ee5b18a1bee1a711 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 28 13:39:29 2021 +0700 Add an example file for graph.baseline_value property commit b31b39d66f4c037a9484b71a3f910703ae01fac0 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 07:03:49 2021 +0700 Add an example file for graph.clamp_bars=false/true comparison commit a1fc850e3047f9ee2c9ddeffacfbabf3ded34171 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 04:06:45 2021 +0700 Add an example file for graphs with negative values and shapes commit 2f012f8ff1bb5094c14e724a61e59a2a3a296bc8 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 01:06:05 2021 +0700 Add an example file for a graph displaying negative values commit 7b14fbd59a4c4c51962fd3110a1a900dd5f4eb98 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 11:18:26 2021 +0700 Add an example file for graphs with step_width < 1 commit dfd9a54544b637e2fbe3d1325f9cc24b3f4d2313 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 10:17:38 2021 +0700 Add an example file for graph.stack=false/true comparison Should also demonstrate that multiple data groups are possible for non-stacked graphs too. commit 5b532106c3fd255f9b66a66cfb88d977b489ab5d Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 09:34:14 2021 +0700 Add an example file for disabling graph data groups with nil colors commit ac6f1083c02fcb428f089a616f9b0ebaa5496b6c Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:57:25 2021 +0700 Add an example file for step_shape/width/spacing on stacked graphs commit b3703db6a5139e73e72c1cfd198ece2cd9be7df2 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:43:56 2021 +0700 Add an example file for scale=false/true on stacked graph commit b000aed4a12cb44fb501e1c8432a71a14b2f8f3f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 20 08:22:19 2021 +0700 Document deprecation of graph's width and height commit cb0ad617bd6c83a499896797318e3b0b588c3566 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 07:53:37 2021 +0700 Deprecate width and height properties of wibox.widget.graph commit 8c64e14ad91a074b784166a7054e53444cccb9d2 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 04:37:59 2021 +0700 Refactor graph:draw() to separate concerns Firstly, summation of group values for stacked graphs is extracted into graph:preprocess_values(). This makes graph:draw() smaller and exposes the fact, that stacked graphs are almost entirely just a form of a more general concept: pre-render data manipulation. It's something a user might want to customize, and now they can do it by monkey-patching the method. Secondly, the logic for deciding, if and with which color a data group should be drawn, is extracted into graph:should_draw_data_group() and get_data_group_color() methods respectively. This makes it customizable too and makes more obvious the fact, that it is a general thing that has nothing to do with stacked graphs in particular. With this there are only 5 lines remaining in graph:draw(), that could be said to be specific to stacked graphs, all pertaining to the `prev_y` variable, which merely preserves the bar coordinates from a previous data group. commit 2a32b6305d893f413303728a0a3da1dcd9a5591e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 22:52:37 2021 +0700 Pointlessly optimize graph:add_value() 1) Purge old values first, then insert the new one, if needed. Since Lua grows arrays in power of two steps, and our guess_capacity() also takes care to return even integers, it would be a shame to overstep the capacity by one by inserting first and thus getting the table size doubled only to never use the most of it. 2) Use a removal procedure that doesn't call the slow length operator at all. Previous code called it twice (once in table.remove) for every removed value. commit 680ea8f5d81e9040afb4662b3229e937e4b89e02 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:23:38 2021 +0700 Set graph data series color before the painting loop commit b970f0f221ae76250f2a94433ff79d653caa6e21 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:05:37 2021 +0700 Drop a large no longer necessary `if` in graph:draw_values() This commit is almost purely a whitespace change. commit 6b4f0541c70ec99d7f6e276bae64939f337120d6 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:00:50 2021 +0700 Extract values rendering code into graph:draw_values() method This is to avoid ungodly if nesting levels, which are needed to bail out of drawing values early and skip to drawing the border. The next commit will get rid of the if. commit e0950e4f99d1ea1c4b8cea2cc67629fa40030c2a Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 14:50:20 2021 +0700 Track graph draw() usage to guess necessary array capacity The last step of divorcing add_values() from width calculations, while ensuring that user doesn't observe a lack of data to draw. The widget now tracks an approximate number of values that it has been asked to draw() in _private.last_drawn_values_num, and truncates its values array based on that (if not overridden by the capacity property). commit 2e3d542282f35e7c0fed454da0f2eada0fcf3b78 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 13:35:12 2021 +0700 Add `capacity` property to graph widget This is the second step of divorcing add_values() from calculations involving widths. How many values are stored and how many values are drawn are distinct concepts. There isn't even a single definitive "how many values are drawn" because the widget could be rendered in several places with distinct widths simultaneously, and in every such place the data must be drawn appropriately, e.g. with auto-scaling, that takes only the visible data points into account. And the draw() method can do that now. The default capacity value of nil uses heuristics based on widget's _private.width to guess some capacity that would tolerate some dynamic widget resizing. If the user finds the heuristic failing them, they can set the capacity property to the desired maximum number of values that will be kept in the _private.values array (in each data group). commit 08d4a80686f7fe363551c35589a460839f60853e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 03:27:18 2021 +0700 Extract values size computation into compute_drawn_values_num() This is the first step of making add_values() not concern itself (directly) with widget dimensions. The user can monkey-patch the method to override how many values are (logically) drawn, because it affects graph auto-scaling and can be useful for rendering of step_shape-s that paint outside of their step_width. I didn't make it a full-blown function-valued property, because it seems a fringe functionality, which I'm too lazy to document. commit 3c5617c98ab1147618ed22e8414a77fc56b45f6e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 02:32:29 2021 +0700 Copy graph widget prototype methods over in bulk commit 169538837ece41a03d02ba8fbc0e4097062b67a3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun Apr 25 05:58:01 2021 +0700 Remove graph.baseline_y property After giving it some thought I've concluded, that it is unlikely to be useful as it is now. I'd better reserve the name for some more sensible functionality, like fixing the baseline_value defined axis at the given widget coordinate, adjusting the scaling accordingly, so min_value/max_value won't generally correspond to widget top and bottom anymore. commit 202ffaa9a0b820c7c471e1121d01b91f8f76cd38 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 12:06:55 2021 +0700 Add baseline_y and baseline_value properties to graph widget Now values in graph have a *baseline*, tunable with baseline_y and baseline_value properties. It is the (for now invisible) horizontal line from which all bars in the graph grow. I call it a "baseline" instead of "axis", because it doesn't actually have to correspond to the 0 value. But it can, and actually does now by default. baseline_y and baseline_value properties determine the vertical placement of the baseline in the widget. baseline_y is the position of the baseline in normalized widget coordinates, e.g. 0 corresponds to widget's top, 1 - to widget's bottom, and 0.5 - to widget's middle. So if e.g. one sets baseline_y = 0, then no matter how the widget is resized and how its values are scaled, bars in it will be drawn starting from the top. baseline_value is the position of the baseline in value coordinates. So if one sets baseline_value = 10, then no matter what happens, bars for values greater than 10 will be drawn as growing up from this line, and bars for values smaller as growing down. Where and if it will be seen in the widget, depends on the scaling. baseline_value = 0 is the familiar zero axis. baseline_y is unset by default and has priority, i.e. if it's set, then baseline_value is ignored. baseline_value defaults to 0. Prior to this commit, bars were always drawn from the bottom edge of the widget, or, in the new terms, the widget had baseline_y = 1. But it was indistinguishable from baseline_value = 0, because the widget clamped all negative values to 0, so the difference between the two could never be observed. baseline_value = 0 is clearly more useful and familiar to people, so now it is in force by default. commit df7e19abaebf0a871b5ca217b4cc3365533267a6 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 11:18:12 2021 +0700 Move value clamping from graph:add_value() into draw() Previously all values were forcibly clamped between 0 (not tunable at all) and max_value (1 by default). Thus graphs with negative values were not an option. NaN values were conflated with zeros, even though it may be useful to maintain the distinction and render them differently. And if user happened to dynamically change the max_value property, they discovered, that their data got truncated. Preserving user data as it was given is more flexible and makes things easier to debug. This commit does exactly that. So what happens now to the newly allowed values during draw()? There are 3 new cases to consider: 1) NaNs 2) Negatives 3) Values outside of min_value..max_value range NaNs are presently simply not drawn. An empty place is reserved where the value should've been, but nothing else is done. This is distinct from how a zero is handled, e.g. if step_shape is set, it is called with the height parameter = 0, which can still end up drawing something, depending on the shape. One could also consider addition of nan_color/nan_shape properties which could be drawn to alert user to a gap in their data. Negative values necessitate introduction of the concept of the zero axis, from which they will be drawn DOWN, in contrast to positive values. Where should this axis be drawn? How should step_shape-s with inherent direction, e.g. arrows, be drawn for negatives? This will be addressed in the next commit. Values outside of min/max_value range can imply shapes that go beyond widget dimensions. An arrow shape that goes beyond the roof is visibly different from an arrow that saturates and merely touches the widget top, and the user may reasonably desire either behavior. Therefore this commit introduces a boolean clamp_bars property, which defaults to true for reasons of robustness and backward compatibility with the behaviour prior to this commit. The property of course affects only the display of the values, the actual user data stays intact. commit c9c24fb1ab741cd6bb8074971d568e0646c51532 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:29:01 2021 +0700 Use rectangles for drawing graph bars of all sizes I have benchmarked various bar drawing strategies and graph sizes and found out that drawing graph bars with rectangles is as fast or even marginally faster than using fat lines. Thus the code that tries to special-case 1px bars as lines seems to be not worth the additional complexity it introduces, and this commit removes it. Incidentally, I've also established, that rectangles are more robust. If one draws graph bars and feeds cairo very large coordinates (~2^62), then corresponding rectangles fail to render, but all the other rectangles in the same series get rendered successfully, whereas a single large coordinate in a move_to/line_to() sequence is enough to cause all lines in the batch to fail to render. commit bc7f4be5e06405b5aa7fb443a5ace2b9b9575b1f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:01:42 2021 +0700 Cache cairo methods used in the inner loop of graph:draw() I have benchmarked the draw() method, both on cairo xcb and on argb32 image surfaces, and have discovered that cairo context method calls are unnaturally slow, to the point, that almost nothing else in the function matters. For example, drawing bars with vertical lines made the whole draw() function almost 2 times slower than doing the same with rectangles, merely because the former requires two method calls (move_to/line_to), and the latter only one. Simply caching hotpath cairo methods in local variables, reduced the difference between the drawing methods to a negligible one and made the whole function 4 times faster on a typical 100-bar graph, (1.4ms to 0.33ms on my laptop) thus bringing closer my secret dream of drawing live audio data with awesome graphs (kidding). I guess, LGI does something not very optimal there, like gobject-introspecting the method every time, or something. There might be some untapped optimization potential, possibly in upstream, like a method cache, from which everything in awesome could benefit, even though I imagine not many things are so call-intensive as the graph widget, to really feel the difference. commit 538df25bdf02b53d0bbe81809eef2e6fdb05ff6f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 07:32:37 2021 +0700 Use 1-based index in the inner graph draw loop It's used there only once for a calculation and then several times to index some tables, and index 0 is bad for lua tables performance-wise. commit 7412546916cd0a0a490130a120255ffb719398b8 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:55:12 2021 +0700 Leave graph.max_value uninitialized at construction This is a user-visible change, but the previous behavior was probably not what user expected anyway, namely with graph.scale set the graph mysteriously couldn't zoom in onto values that are smaller than 1. commit eb81d7929a161e79c13262cfa73ac2e6a820e8ed Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:48:29 2021 +0700 Allow graph.max_value be nil This makes its behave symmetrically to min_value. Now it defaults to 1, when graph.scale is not set, and to the maximum value in the graph data, when graph.scale is set. commit f622a8430cbfde7007b58d7f4c9c76b08c91e997 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 12:01:13 2021 +0700 Calculate max values num in graph:add_value() more precisely At most so many values were kept in the widget values array, as there are pixels of usable widget width (i.e. sans border). This is exactly right for the surely most common case of graphs with 1px-wide bars, but is more than needed for graphs with step_width+step_spacing>1, and not enough for graphs with subpixel bars. Moreover this made graph autoscaling behave weirdly in graphs with thicker bars, because old values survived longer in the array and kept affecting min_value/max_value calculations even after they were shifted off-screen. The maximum number of kept values is now calculated exactly as the number of values that can be displayed at once, taking step_width and step_spacing into account. It might be worth adding a max_values_num property to the graph to let user override this calculation, in case they draw shapes in step_shape() outside of its supposed rectangle, so our assumptions of visibility are wrong, or the widget often gets resized, resulting in the annoyance of visibly truncated series. commit 0aa63249a18641251a1bf77423739164f0eab39f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 02:30:13 2021 +0700 Fix border_color/border_width bugs in graph widget add_value() wrongly assumed that border_width is always set, when border_color is, so if user set only the latter, an error occurred. add_value() assumed that border_width is always < width which led to awesome hanging up in an infinite loop, should that fail to be the case. The graph border_width property was driven by the border_color property, instead of the other way round. If border_color wasn't set, the border wasn't drawn all, even if user sets non-zero border_width, which is a confusing behavior, in contrast to all other widgets, which simply use a plethora of fallback colors to draw the border, when needed. This also meant that, despite being documented, the beautiful.graph_border_color fallback color could not and was not ever used. If the border_color was set though, an unset border_width was immediately assumed to be 1 instead of 0, also contrary to what other widgets do. Theme colors shouldn't influence widget layout, so this commit fixes this behavior to: 1) border_width always defaults to 0, if unset. 2) border_color falls back to beautiful.graph_border_color or white. Maybe one should also add a beautiful.graph_border_width theme variable, like some widgets do. commit dad4a3253b8fef971fb82720d2a407fcc0a879ad Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 03:11:27 2021 +0700 Implement multiple data series drawing for non-stacked graph The widget can already hold and draw multiple data groups, so it makes little sense to limit drawing only to the first group when graph.stack = false, probably leading to user's confusion. Now user can call add_value(v, group) with group > 1 and the data series will be drawn (over groups with lesser id-s) with graph.stack_colors[group] color, if stack_colors is set, or with graph.color, if stack_colors isn't set. Thus nothing visibly changes for all current users of non-stacked graphs with only single data series, but the new functionality is now there for those who need it. One can't fully emulate this functionality, e.g. with multiple graphs widgets in a wibox.layout.stack, because data series in a single widget can enjoy a common auto-scaling. commit 3e645c976184ab5f50d625271c25062db9ab7ed9 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:59:12 2021 +0700 Don't draw a stacked graph data group if there's no color for it If stack_colors[idx] is nil for the group `idx`, don't draw it at all (as opposed, e.g. to the effect of drawing it with a transparent color, or as though it had all values == 0, which both produce different results). This code is still subtly different from prior behavior, because now user can have a non-contiguous stack_colors table and turn the display of any data group on and off at will, whereas the code two commits earlier would just stop rendering as soon as ipairs(stack_colors) does, i.e. no data series were drawn past the first nil hole. This commit only looks large, but is mostly a whitespace change due to nesting from a pair of wrapping ifs. commit 8c0273786de1ade66bdd977699f98b588da9f487 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:32:42 2021 +0700 Unify stacked and non-stacked graph rendering Now stacked graphs support everything the nonstacked do, like step_width ~= 1 and custom step_shape-s. Technical notes: This code no longer calls cr:stroke() for every single value in a stacked graph, but rather paints a whole series and then cr:stroke/fill()-s it at all once with its color, improving performance considerably. Additionally the graph no longer relies on the presence of the stack_colors property to render series. It draws all data that it has, and, if there's no corresponding color in stack_colors, falls back to graph.color. This will be changed in the next commit to the prior behavior of only drawing those series for which there's a color in the stack_colors table, but that would've made this commit's diff much larger due to required nesting ifs over large code chunks. This commit also takes care to properly handle NaNs and negative values. Those are currently (almost) guaranteed to not occur in the values array due to clipping in graph:add_value(), but I intend to eventually drop the clipping, because it does nothing that draw() really relies upon, and allowing unclipped and NaN values is more flexible and less surprising, e.g. when user dynamically toggles graph.scale. Eventually I intend to implement drawing negative values in non-stacked graphs and possibly even in the stacked ones. commit 38c6589c51b2aa057d7c2f40182ecf32b5b2ff5e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 10:04:15 2021 +0700 Convert graph:draw() to use `self` commit f21f1b2550426df1c4ea8abde079b1ccef84b394 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 12:27:26 2021 +0700 Fix scaling for stacked graphs Stacked graphs have cumulative heights, values from all groups that end up in the same graph bar are effectively summed. Previous code didn't take that into account when calculating min/max_value for the purposes of rescaling, but simply examined every value separately. This code adds up all values up front and finds min/max among those, to determine proper scaling that would fit the graph exactly. commit 1ce4c7bf63bdd6c99c195d668347bec49355e921 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 09:49:35 2021 +0700 Draw shaped graph bars only up to the baseline Prior to this commit, every step_shape bar was, for some reason, drawn with height = full widget's height, regardless of the value it represents. So for the most bars their lower end was far outside the box and therefore clipped off. So e.g. one couldn't see the lower round edges of rounded rectangles. This commit fixes that by appropriately varying the height that is passed into step_shape(), so that lower edges of the bars always exactly touch the lower edge of the graph. If the user wants to restore the prior behavior, they can simply ignore the passed-in height in step_shape() and just always draw their shape with height = full widget height. But they couldn't replicate the effect of this commit in their rc.lua without essentially writing their own graph widget. commit e25185b08da23745b5815f85f6e9e43222ed2294 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 08:42:54 2021 +0700 Avoid incremental transform shifting during graph bar rendering Instead of letting addition errors and cognitive load accumulate simply use the same offset calculation that is used for lines and reset the transform back to the top-left corner after every drawn bar. It's conceptually simpler, and will be useful later for stacked graphs and more. It also fixes a discrepancy between plain and step_shape rendering. Prior to this commit, bar coloring occurred with a disturbed transform, which was centered at the last drawn bar, wherever it happened to be. This meant that nontrivial colors, like gradients and images, were arbitrarily offset when step_shape was used. commit f3e98866d8583c35d0b557c09f529299edc9ba2c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:45:29 2021 +0700 Refactor out graph bar height expression into a variable commit f54528d6892f5aaa4595917853c9e2130f743fdb Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:20:25 2021 +0700 Draw graph bars with lines only when step_width == 1 Before this commit graph bars of step_width < 1 were drawn with lines of width 1, but that's a bit unfortunate, because it led to much overpainting, while still ending up with a graphically incorrect result. Let's draw such bars with rectangles and let cairo do things with subpixels, getting more graphically accurate result, while still keeping the common case of step_width == 1 (supposedly) fast-drawn with lines. commit b6515dca67c18b78eb5657378131a62ab43d4cfc Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:54:07 2021 +0700 Fix unsharp edges when drawing graph bars with rectangles The 0.5 offset is only helpful for 1-width strokes, not for fills. commit 7f3db8f07e82f821bcd1dc418d66564f537609d6 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:21:01 2021 +0700 Do coordinate transformations even if the value isn't drawn The graph presently doesn't draw values < 0, but there was a discrepancy between drawing with and without step_shape. The default line/rectangle drawing reserves an empty place where the skipped value should've been, but no such empty place is reserved, when step_shape is used. This commit fixes that. Skipped values always have their empty space now. commit 7b731ebfe96af7a9dba9c57371da6761cf22c00c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:03:30 2021 +0700 Fix graph bar scaling when min_value is not 0 Previous calculation lead to underscaling of values so that even value = max_value wasn't drawn to full height. commit dca59fd8b7fff0e7e646f2dfc6bd92047028e5a8 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:18:40 2021 +0700 Calculate the horizontal graph bar offset only when needed commit 940ddf4b9825e9c3a91d276208c089d8c74e49db Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:17:23 2021 +0700 Call cr:move_to() only when drawing the graph with lines It's redundant otherwise and might even interfere with user's drawing in step_shape(). commit b5d5cd10ca29fb29cd751faaa95137c2fe89ff63 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:04:06 2021 +0700 Fix wrong horizontal offset calculations in widget.graph Indices in the loop are zero-based, so things like i-1 and i>1 aren't necessary and actually wouldn't have been correct even in a 1-based loop. step_shape = gears.shape.rectangle and step_shape = nil, which is supposed to default to rectangle according to docs, were drawn differently due to mismatched offset calculations, which led to problems especially noticeable when step_spacing is not zero, e.g: 1) whole graph shifted left too much, drawn partially off-screen. 2) whole graph shifted right too much, and the first bar placed closer to the rest than others. commit dc369fa198281e000afc1bd17326f3a264adf7d1 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 02:09:21 2021 +0700 Fix the "border_width affects graph bar width" bug Graph bars were drawn with lines of border_width thickness instead of 1, i.e. possibly too fat to be readable. commit 1ba73378cf29ed27a6ecd18716394779ceb543d9 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 00:13:03 2021 +0700 Simplify internal values representation in graph even more New values in graph._private.values were pushed to the end, and old values were removed from the beginning of the table. In draw() however newer values are drawn first, at the left edge of the graph, necessitating annoying idx = #values - idx conversions everywhere. In particular in stacked graph that means that values that are drawn together may have different indices. One has to shift the values array in add_value() either way, so one might as well insert new values at the beginning of the array and simplify the rest of the code. This doesn't seem like much yet, but it will prove to be much more convenient and less error-prone in what I'm going to do later. commit f70523ee68dbd6b67050ace4b82903a9b22440c7 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 13:35:10 2021 +0700 Simplify internal values representation in graph widget graph._private.values was either a table of values or a table of tables of values, depending on whether "stack" property is enabled, ... or a mix of both if user forgot to pass the second "group" parameter to graph:add_value(). This resulted in pretty fragile and confusing behavior, e.g. leaving user to wonder, why they see nothing more after setting stack=true for a graph in a previously working rc.lua, and demanding a very careful use of add_value() from the user, or else some values or even entire stacks would mysteriously vanish and errors in draw() get triggered, sometimes after a period of seemingly normal functioning. Now `values` is always a table of tables and the group parameter in add_value() defaults to 1, thus making the non-stacked graph a trivial variant of the stacked graph.
2021-05-20 17:14:03 +02:00
end
2016-05-23 07:30:44 +02:00
graph: complete widget overhaul and bug fixes Squashed commit of the following: commit 69821f51fe1e8652715658543e50631ece495090 Author: Alex Belykh <albel727@ngs.ru> Date: Mon May 10 19:17:55 2021 +0700 Refactor property handling in wibox.widget.graph In addition, baseline_value, step_width and step_spacing properties are all set to their default non-nil numeric values during instance creation now. commit 842eb429bf5df4b2ba46b2e6f7646afe89b1a3c3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 9 06:18:45 2021 +0700 Make graph:draw_values() private There's no documentation nor confidence that it's a good API, so I'm hiding it for now. commit 720746780574e4bad14a71fc4174955139c2eb50 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 04:04:13 2021 +0700 Document graph_should_draw_data_group() for posterity commit 7c73f3754ce6ba86b118f1170a6f9e406ab4841d Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:59:21 2021 +0700 Make graph:should_draw_data_group() private There's no confidence whether it's a good API, so I'm hiding it for now. commit b3539e20adb6423118af726ae4f8751ef864b5d2 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:36:44 2021 +0700 Make graph:preprocess_values() private There's no confidence whether it's a good API, so I'm hiding it for now. commit 2df4400e780a6260f8d0ab1c60734fbb11bf3c4f Author: Alex Belykh <albel727@ngs.ru> Date: Thu May 6 10:05:23 2021 +0700 Add spec/wibox/widget/graph_spec.lua commit ce01a9771ab439b8df841f1565aa4aa8a44a5e0d Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 08:16:18 2021 +0700 Improve graph.capacity documentation commit e28fade8f655802f5bf89154169f68d256b8e332 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 07:36:01 2021 +0700 Document graph:compute_drawn_values_num() commit dd68332292465422d08b48e9f0229f143beccbc6 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 05:24:23 2021 +0700 Document graph:pick_data_group_color() method commit ca6dc55f77aca88a6e9cc90bb6fd0045739f41f5 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 04:13:58 2021 +0700 Guard against setting data groups sparsely in graph:add_value() commit cdea5f126f7984e72924f6dffbd312c4fe3b032f Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 02:36:12 2021 +0700 Refactor scaling and baseline choice out of graph:draw() commit 10135deafe8a9390ba66ab657ebb1fb41d549e8c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 21:34:48 2021 +0700 Document graph.nan_color and graph.stack properties commit 0d73bb84ac66b87e16aee472bd1f299549f66a1c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 20:49:24 2021 +0700 Use a better example in docs for the graph.stack property commit c27d160a0826d033d49d6141608c40aa8a0642fa Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 16:05:38 2021 +0700 Document graph.baseline_value property commit 459bc8176c13d4ea32af0dcdc2399d3b929b1c6a Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:53:47 2021 +0700 Document graph.clamp_bars property commit e732ee0b3b007d213e689b98cc00ae7cb141ae08 Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:34 2021 +0700 Improve graph:add_value() and :clear() documentation commit ae73e3aa9f8d60409cbe4cb4da9612f31af4f38e Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:11 2021 +0700 Improve graph min_value/max_value/scale documentation commit a7350996a9def773b91b1631e6c0507e33d8f93b Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:27:36 2021 +0700 Cleanup graph examples to show only relevant things in docs commit 30740c26f4f41c16f4c8a3c339bcb7bd299e14cd Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 14:04:36 2021 +0700 Rename graph.stack_colors property to `group_colors` The colors are used for stacked and non-stacked graphs alike, so the name didn't make sense. Also document and clarify some color stuff. commit 1ea01bfd410971cf0d0c25fd74cd764d5eb7279d Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 21:25:41 2021 +0700 Add an example file for code coverage of some fringe graph cases commit eae68e39553f88a6275a0c65bf040b5d4c262b54 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 20:23:32 2021 +0700 Add step_shape to the graph/nan_color example for code coverage commit 8027147bc2eab34a97b49a428fb8cef27f122bed Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:55:18 2021 +0700 Add example files for graph.nan_color/nan_indication properties commit 89a25a165c52b2c15b6c4d95ecb777a9067e66f9 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:33:07 2021 +0700 Make widget graph:add_value(v) default v to NaN 0 as a default makes no sense and silences the error that is failing to specify a value, to which the user should rather be alerted. The graph widget has sane NaN handling now and will do just that. commit 2835d552440be48d356a7f7b1d64963696dd550b Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:20:48 2021 +0700 Implement graph.nan_color and nan_indication properties Now, whenever there's a NaN among values and graph.nan_indication is set, the corresponding area, where the value bar should have been drawn, is filled with the nan_color from top to bottom. The painting is done after all data is rendered, to make sure that it won't be overpainted and go unnoticed. NaNs among graph's values are inevitably a sign of some error and it is arguably not a sane default behavior to simply not render them at all. The user should take immediate note of any errors by default, instead of wondering at a mysteriously empty graph widget, which is why nan_indication is set to true by default. If the user wants to silence NaNs, they can always simply set it to false. nan_color if unset, defaults to a noticeable yellow-black pattern. As a direct and planned consequence, stacked graphs with negative values in them trigger the NaN handling too. But silencing NaN handling for those graphs is also possible and semi-sane, because then NaNs and negatives are ignored and the graph behaves (almost) like they were zeros. commit 9c1f8f08b400e01e19c534f3810c9c9ab485760c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 26 08:10:19 2021 +0700 Refactor out graph_map_value_to_widget_coordinates() function commit 1905991ddecabdae3e15ffb8ee5b18a1bee1a711 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 28 13:39:29 2021 +0700 Add an example file for graph.baseline_value property commit b31b39d66f4c037a9484b71a3f910703ae01fac0 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 07:03:49 2021 +0700 Add an example file for graph.clamp_bars=false/true comparison commit a1fc850e3047f9ee2c9ddeffacfbabf3ded34171 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 04:06:45 2021 +0700 Add an example file for graphs with negative values and shapes commit 2f012f8ff1bb5094c14e724a61e59a2a3a296bc8 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 01:06:05 2021 +0700 Add an example file for a graph displaying negative values commit 7b14fbd59a4c4c51962fd3110a1a900dd5f4eb98 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 11:18:26 2021 +0700 Add an example file for graphs with step_width < 1 commit dfd9a54544b637e2fbe3d1325f9cc24b3f4d2313 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 10:17:38 2021 +0700 Add an example file for graph.stack=false/true comparison Should also demonstrate that multiple data groups are possible for non-stacked graphs too. commit 5b532106c3fd255f9b66a66cfb88d977b489ab5d Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 09:34:14 2021 +0700 Add an example file for disabling graph data groups with nil colors commit ac6f1083c02fcb428f089a616f9b0ebaa5496b6c Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:57:25 2021 +0700 Add an example file for step_shape/width/spacing on stacked graphs commit b3703db6a5139e73e72c1cfd198ece2cd9be7df2 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:43:56 2021 +0700 Add an example file for scale=false/true on stacked graph commit b000aed4a12cb44fb501e1c8432a71a14b2f8f3f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 20 08:22:19 2021 +0700 Document deprecation of graph's width and height commit cb0ad617bd6c83a499896797318e3b0b588c3566 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 07:53:37 2021 +0700 Deprecate width and height properties of wibox.widget.graph commit 8c64e14ad91a074b784166a7054e53444cccb9d2 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 04:37:59 2021 +0700 Refactor graph:draw() to separate concerns Firstly, summation of group values for stacked graphs is extracted into graph:preprocess_values(). This makes graph:draw() smaller and exposes the fact, that stacked graphs are almost entirely just a form of a more general concept: pre-render data manipulation. It's something a user might want to customize, and now they can do it by monkey-patching the method. Secondly, the logic for deciding, if and with which color a data group should be drawn, is extracted into graph:should_draw_data_group() and get_data_group_color() methods respectively. This makes it customizable too and makes more obvious the fact, that it is a general thing that has nothing to do with stacked graphs in particular. With this there are only 5 lines remaining in graph:draw(), that could be said to be specific to stacked graphs, all pertaining to the `prev_y` variable, which merely preserves the bar coordinates from a previous data group. commit 2a32b6305d893f413303728a0a3da1dcd9a5591e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 22:52:37 2021 +0700 Pointlessly optimize graph:add_value() 1) Purge old values first, then insert the new one, if needed. Since Lua grows arrays in power of two steps, and our guess_capacity() also takes care to return even integers, it would be a shame to overstep the capacity by one by inserting first and thus getting the table size doubled only to never use the most of it. 2) Use a removal procedure that doesn't call the slow length operator at all. Previous code called it twice (once in table.remove) for every removed value. commit 680ea8f5d81e9040afb4662b3229e937e4b89e02 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:23:38 2021 +0700 Set graph data series color before the painting loop commit b970f0f221ae76250f2a94433ff79d653caa6e21 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:05:37 2021 +0700 Drop a large no longer necessary `if` in graph:draw_values() This commit is almost purely a whitespace change. commit 6b4f0541c70ec99d7f6e276bae64939f337120d6 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:00:50 2021 +0700 Extract values rendering code into graph:draw_values() method This is to avoid ungodly if nesting levels, which are needed to bail out of drawing values early and skip to drawing the border. The next commit will get rid of the if. commit e0950e4f99d1ea1c4b8cea2cc67629fa40030c2a Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 14:50:20 2021 +0700 Track graph draw() usage to guess necessary array capacity The last step of divorcing add_values() from width calculations, while ensuring that user doesn't observe a lack of data to draw. The widget now tracks an approximate number of values that it has been asked to draw() in _private.last_drawn_values_num, and truncates its values array based on that (if not overridden by the capacity property). commit 2e3d542282f35e7c0fed454da0f2eada0fcf3b78 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 13:35:12 2021 +0700 Add `capacity` property to graph widget This is the second step of divorcing add_values() from calculations involving widths. How many values are stored and how many values are drawn are distinct concepts. There isn't even a single definitive "how many values are drawn" because the widget could be rendered in several places with distinct widths simultaneously, and in every such place the data must be drawn appropriately, e.g. with auto-scaling, that takes only the visible data points into account. And the draw() method can do that now. The default capacity value of nil uses heuristics based on widget's _private.width to guess some capacity that would tolerate some dynamic widget resizing. If the user finds the heuristic failing them, they can set the capacity property to the desired maximum number of values that will be kept in the _private.values array (in each data group). commit 08d4a80686f7fe363551c35589a460839f60853e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 03:27:18 2021 +0700 Extract values size computation into compute_drawn_values_num() This is the first step of making add_values() not concern itself (directly) with widget dimensions. The user can monkey-patch the method to override how many values are (logically) drawn, because it affects graph auto-scaling and can be useful for rendering of step_shape-s that paint outside of their step_width. I didn't make it a full-blown function-valued property, because it seems a fringe functionality, which I'm too lazy to document. commit 3c5617c98ab1147618ed22e8414a77fc56b45f6e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 02:32:29 2021 +0700 Copy graph widget prototype methods over in bulk commit 169538837ece41a03d02ba8fbc0e4097062b67a3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun Apr 25 05:58:01 2021 +0700 Remove graph.baseline_y property After giving it some thought I've concluded, that it is unlikely to be useful as it is now. I'd better reserve the name for some more sensible functionality, like fixing the baseline_value defined axis at the given widget coordinate, adjusting the scaling accordingly, so min_value/max_value won't generally correspond to widget top and bottom anymore. commit 202ffaa9a0b820c7c471e1121d01b91f8f76cd38 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 12:06:55 2021 +0700 Add baseline_y and baseline_value properties to graph widget Now values in graph have a *baseline*, tunable with baseline_y and baseline_value properties. It is the (for now invisible) horizontal line from which all bars in the graph grow. I call it a "baseline" instead of "axis", because it doesn't actually have to correspond to the 0 value. But it can, and actually does now by default. baseline_y and baseline_value properties determine the vertical placement of the baseline in the widget. baseline_y is the position of the baseline in normalized widget coordinates, e.g. 0 corresponds to widget's top, 1 - to widget's bottom, and 0.5 - to widget's middle. So if e.g. one sets baseline_y = 0, then no matter how the widget is resized and how its values are scaled, bars in it will be drawn starting from the top. baseline_value is the position of the baseline in value coordinates. So if one sets baseline_value = 10, then no matter what happens, bars for values greater than 10 will be drawn as growing up from this line, and bars for values smaller as growing down. Where and if it will be seen in the widget, depends on the scaling. baseline_value = 0 is the familiar zero axis. baseline_y is unset by default and has priority, i.e. if it's set, then baseline_value is ignored. baseline_value defaults to 0. Prior to this commit, bars were always drawn from the bottom edge of the widget, or, in the new terms, the widget had baseline_y = 1. But it was indistinguishable from baseline_value = 0, because the widget clamped all negative values to 0, so the difference between the two could never be observed. baseline_value = 0 is clearly more useful and familiar to people, so now it is in force by default. commit df7e19abaebf0a871b5ca217b4cc3365533267a6 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 11:18:12 2021 +0700 Move value clamping from graph:add_value() into draw() Previously all values were forcibly clamped between 0 (not tunable at all) and max_value (1 by default). Thus graphs with negative values were not an option. NaN values were conflated with zeros, even though it may be useful to maintain the distinction and render them differently. And if user happened to dynamically change the max_value property, they discovered, that their data got truncated. Preserving user data as it was given is more flexible and makes things easier to debug. This commit does exactly that. So what happens now to the newly allowed values during draw()? There are 3 new cases to consider: 1) NaNs 2) Negatives 3) Values outside of min_value..max_value range NaNs are presently simply not drawn. An empty place is reserved where the value should've been, but nothing else is done. This is distinct from how a zero is handled, e.g. if step_shape is set, it is called with the height parameter = 0, which can still end up drawing something, depending on the shape. One could also consider addition of nan_color/nan_shape properties which could be drawn to alert user to a gap in their data. Negative values necessitate introduction of the concept of the zero axis, from which they will be drawn DOWN, in contrast to positive values. Where should this axis be drawn? How should step_shape-s with inherent direction, e.g. arrows, be drawn for negatives? This will be addressed in the next commit. Values outside of min/max_value range can imply shapes that go beyond widget dimensions. An arrow shape that goes beyond the roof is visibly different from an arrow that saturates and merely touches the widget top, and the user may reasonably desire either behavior. Therefore this commit introduces a boolean clamp_bars property, which defaults to true for reasons of robustness and backward compatibility with the behaviour prior to this commit. The property of course affects only the display of the values, the actual user data stays intact. commit c9c24fb1ab741cd6bb8074971d568e0646c51532 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:29:01 2021 +0700 Use rectangles for drawing graph bars of all sizes I have benchmarked various bar drawing strategies and graph sizes and found out that drawing graph bars with rectangles is as fast or even marginally faster than using fat lines. Thus the code that tries to special-case 1px bars as lines seems to be not worth the additional complexity it introduces, and this commit removes it. Incidentally, I've also established, that rectangles are more robust. If one draws graph bars and feeds cairo very large coordinates (~2^62), then corresponding rectangles fail to render, but all the other rectangles in the same series get rendered successfully, whereas a single large coordinate in a move_to/line_to() sequence is enough to cause all lines in the batch to fail to render. commit bc7f4be5e06405b5aa7fb443a5ace2b9b9575b1f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:01:42 2021 +0700 Cache cairo methods used in the inner loop of graph:draw() I have benchmarked the draw() method, both on cairo xcb and on argb32 image surfaces, and have discovered that cairo context method calls are unnaturally slow, to the point, that almost nothing else in the function matters. For example, drawing bars with vertical lines made the whole draw() function almost 2 times slower than doing the same with rectangles, merely because the former requires two method calls (move_to/line_to), and the latter only one. Simply caching hotpath cairo methods in local variables, reduced the difference between the drawing methods to a negligible one and made the whole function 4 times faster on a typical 100-bar graph, (1.4ms to 0.33ms on my laptop) thus bringing closer my secret dream of drawing live audio data with awesome graphs (kidding). I guess, LGI does something not very optimal there, like gobject-introspecting the method every time, or something. There might be some untapped optimization potential, possibly in upstream, like a method cache, from which everything in awesome could benefit, even though I imagine not many things are so call-intensive as the graph widget, to really feel the difference. commit 538df25bdf02b53d0bbe81809eef2e6fdb05ff6f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 07:32:37 2021 +0700 Use 1-based index in the inner graph draw loop It's used there only once for a calculation and then several times to index some tables, and index 0 is bad for lua tables performance-wise. commit 7412546916cd0a0a490130a120255ffb719398b8 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:55:12 2021 +0700 Leave graph.max_value uninitialized at construction This is a user-visible change, but the previous behavior was probably not what user expected anyway, namely with graph.scale set the graph mysteriously couldn't zoom in onto values that are smaller than 1. commit eb81d7929a161e79c13262cfa73ac2e6a820e8ed Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:48:29 2021 +0700 Allow graph.max_value be nil This makes its behave symmetrically to min_value. Now it defaults to 1, when graph.scale is not set, and to the maximum value in the graph data, when graph.scale is set. commit f622a8430cbfde7007b58d7f4c9c76b08c91e997 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 12:01:13 2021 +0700 Calculate max values num in graph:add_value() more precisely At most so many values were kept in the widget values array, as there are pixels of usable widget width (i.e. sans border). This is exactly right for the surely most common case of graphs with 1px-wide bars, but is more than needed for graphs with step_width+step_spacing>1, and not enough for graphs with subpixel bars. Moreover this made graph autoscaling behave weirdly in graphs with thicker bars, because old values survived longer in the array and kept affecting min_value/max_value calculations even after they were shifted off-screen. The maximum number of kept values is now calculated exactly as the number of values that can be displayed at once, taking step_width and step_spacing into account. It might be worth adding a max_values_num property to the graph to let user override this calculation, in case they draw shapes in step_shape() outside of its supposed rectangle, so our assumptions of visibility are wrong, or the widget often gets resized, resulting in the annoyance of visibly truncated series. commit 0aa63249a18641251a1bf77423739164f0eab39f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 02:30:13 2021 +0700 Fix border_color/border_width bugs in graph widget add_value() wrongly assumed that border_width is always set, when border_color is, so if user set only the latter, an error occurred. add_value() assumed that border_width is always < width which led to awesome hanging up in an infinite loop, should that fail to be the case. The graph border_width property was driven by the border_color property, instead of the other way round. If border_color wasn't set, the border wasn't drawn all, even if user sets non-zero border_width, which is a confusing behavior, in contrast to all other widgets, which simply use a plethora of fallback colors to draw the border, when needed. This also meant that, despite being documented, the beautiful.graph_border_color fallback color could not and was not ever used. If the border_color was set though, an unset border_width was immediately assumed to be 1 instead of 0, also contrary to what other widgets do. Theme colors shouldn't influence widget layout, so this commit fixes this behavior to: 1) border_width always defaults to 0, if unset. 2) border_color falls back to beautiful.graph_border_color or white. Maybe one should also add a beautiful.graph_border_width theme variable, like some widgets do. commit dad4a3253b8fef971fb82720d2a407fcc0a879ad Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 03:11:27 2021 +0700 Implement multiple data series drawing for non-stacked graph The widget can already hold and draw multiple data groups, so it makes little sense to limit drawing only to the first group when graph.stack = false, probably leading to user's confusion. Now user can call add_value(v, group) with group > 1 and the data series will be drawn (over groups with lesser id-s) with graph.stack_colors[group] color, if stack_colors is set, or with graph.color, if stack_colors isn't set. Thus nothing visibly changes for all current users of non-stacked graphs with only single data series, but the new functionality is now there for those who need it. One can't fully emulate this functionality, e.g. with multiple graphs widgets in a wibox.layout.stack, because data series in a single widget can enjoy a common auto-scaling. commit 3e645c976184ab5f50d625271c25062db9ab7ed9 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:59:12 2021 +0700 Don't draw a stacked graph data group if there's no color for it If stack_colors[idx] is nil for the group `idx`, don't draw it at all (as opposed, e.g. to the effect of drawing it with a transparent color, or as though it had all values == 0, which both produce different results). This code is still subtly different from prior behavior, because now user can have a non-contiguous stack_colors table and turn the display of any data group on and off at will, whereas the code two commits earlier would just stop rendering as soon as ipairs(stack_colors) does, i.e. no data series were drawn past the first nil hole. This commit only looks large, but is mostly a whitespace change due to nesting from a pair of wrapping ifs. commit 8c0273786de1ade66bdd977699f98b588da9f487 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:32:42 2021 +0700 Unify stacked and non-stacked graph rendering Now stacked graphs support everything the nonstacked do, like step_width ~= 1 and custom step_shape-s. Technical notes: This code no longer calls cr:stroke() for every single value in a stacked graph, but rather paints a whole series and then cr:stroke/fill()-s it at all once with its color, improving performance considerably. Additionally the graph no longer relies on the presence of the stack_colors property to render series. It draws all data that it has, and, if there's no corresponding color in stack_colors, falls back to graph.color. This will be changed in the next commit to the prior behavior of only drawing those series for which there's a color in the stack_colors table, but that would've made this commit's diff much larger due to required nesting ifs over large code chunks. This commit also takes care to properly handle NaNs and negative values. Those are currently (almost) guaranteed to not occur in the values array due to clipping in graph:add_value(), but I intend to eventually drop the clipping, because it does nothing that draw() really relies upon, and allowing unclipped and NaN values is more flexible and less surprising, e.g. when user dynamically toggles graph.scale. Eventually I intend to implement drawing negative values in non-stacked graphs and possibly even in the stacked ones. commit 38c6589c51b2aa057d7c2f40182ecf32b5b2ff5e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 10:04:15 2021 +0700 Convert graph:draw() to use `self` commit f21f1b2550426df1c4ea8abde079b1ccef84b394 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 12:27:26 2021 +0700 Fix scaling for stacked graphs Stacked graphs have cumulative heights, values from all groups that end up in the same graph bar are effectively summed. Previous code didn't take that into account when calculating min/max_value for the purposes of rescaling, but simply examined every value separately. This code adds up all values up front and finds min/max among those, to determine proper scaling that would fit the graph exactly. commit 1ce4c7bf63bdd6c99c195d668347bec49355e921 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 09:49:35 2021 +0700 Draw shaped graph bars only up to the baseline Prior to this commit, every step_shape bar was, for some reason, drawn with height = full widget's height, regardless of the value it represents. So for the most bars their lower end was far outside the box and therefore clipped off. So e.g. one couldn't see the lower round edges of rounded rectangles. This commit fixes that by appropriately varying the height that is passed into step_shape(), so that lower edges of the bars always exactly touch the lower edge of the graph. If the user wants to restore the prior behavior, they can simply ignore the passed-in height in step_shape() and just always draw their shape with height = full widget height. But they couldn't replicate the effect of this commit in their rc.lua without essentially writing their own graph widget. commit e25185b08da23745b5815f85f6e9e43222ed2294 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 08:42:54 2021 +0700 Avoid incremental transform shifting during graph bar rendering Instead of letting addition errors and cognitive load accumulate simply use the same offset calculation that is used for lines and reset the transform back to the top-left corner after every drawn bar. It's conceptually simpler, and will be useful later for stacked graphs and more. It also fixes a discrepancy between plain and step_shape rendering. Prior to this commit, bar coloring occurred with a disturbed transform, which was centered at the last drawn bar, wherever it happened to be. This meant that nontrivial colors, like gradients and images, were arbitrarily offset when step_shape was used. commit f3e98866d8583c35d0b557c09f529299edc9ba2c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:45:29 2021 +0700 Refactor out graph bar height expression into a variable commit f54528d6892f5aaa4595917853c9e2130f743fdb Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:20:25 2021 +0700 Draw graph bars with lines only when step_width == 1 Before this commit graph bars of step_width < 1 were drawn with lines of width 1, but that's a bit unfortunate, because it led to much overpainting, while still ending up with a graphically incorrect result. Let's draw such bars with rectangles and let cairo do things with subpixels, getting more graphically accurate result, while still keeping the common case of step_width == 1 (supposedly) fast-drawn with lines. commit b6515dca67c18b78eb5657378131a62ab43d4cfc Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:54:07 2021 +0700 Fix unsharp edges when drawing graph bars with rectangles The 0.5 offset is only helpful for 1-width strokes, not for fills. commit 7f3db8f07e82f821bcd1dc418d66564f537609d6 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:21:01 2021 +0700 Do coordinate transformations even if the value isn't drawn The graph presently doesn't draw values < 0, but there was a discrepancy between drawing with and without step_shape. The default line/rectangle drawing reserves an empty place where the skipped value should've been, but no such empty place is reserved, when step_shape is used. This commit fixes that. Skipped values always have their empty space now. commit 7b731ebfe96af7a9dba9c57371da6761cf22c00c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:03:30 2021 +0700 Fix graph bar scaling when min_value is not 0 Previous calculation lead to underscaling of values so that even value = max_value wasn't drawn to full height. commit dca59fd8b7fff0e7e646f2dfc6bd92047028e5a8 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:18:40 2021 +0700 Calculate the horizontal graph bar offset only when needed commit 940ddf4b9825e9c3a91d276208c089d8c74e49db Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:17:23 2021 +0700 Call cr:move_to() only when drawing the graph with lines It's redundant otherwise and might even interfere with user's drawing in step_shape(). commit b5d5cd10ca29fb29cd751faaa95137c2fe89ff63 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:04:06 2021 +0700 Fix wrong horizontal offset calculations in widget.graph Indices in the loop are zero-based, so things like i-1 and i>1 aren't necessary and actually wouldn't have been correct even in a 1-based loop. step_shape = gears.shape.rectangle and step_shape = nil, which is supposed to default to rectangle according to docs, were drawn differently due to mismatched offset calculations, which led to problems especially noticeable when step_spacing is not zero, e.g: 1) whole graph shifted left too much, drawn partially off-screen. 2) whole graph shifted right too much, and the first bar placed closer to the rest than others. commit dc369fa198281e000afc1bd17326f3a264adf7d1 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 02:09:21 2021 +0700 Fix the "border_width affects graph bar width" bug Graph bars were drawn with lines of border_width thickness instead of 1, i.e. possibly too fat to be readable. commit 1ba73378cf29ed27a6ecd18716394779ceb543d9 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 00:13:03 2021 +0700 Simplify internal values representation in graph even more New values in graph._private.values were pushed to the end, and old values were removed from the beginning of the table. In draw() however newer values are drawn first, at the left edge of the graph, necessitating annoying idx = #values - idx conversions everywhere. In particular in stacked graph that means that values that are drawn together may have different indices. One has to shift the values array in add_value() either way, so one might as well insert new values at the beginning of the array and simplify the rest of the code. This doesn't seem like much yet, but it will prove to be much more convenient and less error-prone in what I'm going to do later. commit f70523ee68dbd6b67050ace4b82903a9b22440c7 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 13:35:10 2021 +0700 Simplify internal values representation in graph widget graph._private.values was either a table of values or a table of tables of values, depending on whether "stack" property is enabled, ... or a mix of both if user forgot to pass the second "group" parameter to graph:add_value(). This resulted in pretty fragile and confusing behavior, e.g. leaving user to wonder, why they see nothing more after setting stack=true for a graph in a previously working rc.lua, and demanding a very careful use of add_value() from the user, or else some values or even entire stacks would mysteriously vanish and errors in draw() get triggered, sometimes after a period of seemingly normal functioning. Now `values` is always a table of tables and the group parameter in add_value() defaults to 1, thus making the non-stacked graph a trivial variant of the stacked graph.
2021-05-20 17:14:03 +02:00
function graph:draw(_, cr, width, height)
local border_width = self._private.border_width or prop_fallbacks.border_width
local drawn_values_num = self:compute_drawn_values_num(width-2*border_width)
2016-05-23 07:30:44 +02:00
graph: complete widget overhaul and bug fixes Squashed commit of the following: commit 69821f51fe1e8652715658543e50631ece495090 Author: Alex Belykh <albel727@ngs.ru> Date: Mon May 10 19:17:55 2021 +0700 Refactor property handling in wibox.widget.graph In addition, baseline_value, step_width and step_spacing properties are all set to their default non-nil numeric values during instance creation now. commit 842eb429bf5df4b2ba46b2e6f7646afe89b1a3c3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 9 06:18:45 2021 +0700 Make graph:draw_values() private There's no documentation nor confidence that it's a good API, so I'm hiding it for now. commit 720746780574e4bad14a71fc4174955139c2eb50 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 04:04:13 2021 +0700 Document graph_should_draw_data_group() for posterity commit 7c73f3754ce6ba86b118f1170a6f9e406ab4841d Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:59:21 2021 +0700 Make graph:should_draw_data_group() private There's no confidence whether it's a good API, so I'm hiding it for now. commit b3539e20adb6423118af726ae4f8751ef864b5d2 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:36:44 2021 +0700 Make graph:preprocess_values() private There's no confidence whether it's a good API, so I'm hiding it for now. commit 2df4400e780a6260f8d0ab1c60734fbb11bf3c4f Author: Alex Belykh <albel727@ngs.ru> Date: Thu May 6 10:05:23 2021 +0700 Add spec/wibox/widget/graph_spec.lua commit ce01a9771ab439b8df841f1565aa4aa8a44a5e0d Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 08:16:18 2021 +0700 Improve graph.capacity documentation commit e28fade8f655802f5bf89154169f68d256b8e332 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 07:36:01 2021 +0700 Document graph:compute_drawn_values_num() commit dd68332292465422d08b48e9f0229f143beccbc6 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 05:24:23 2021 +0700 Document graph:pick_data_group_color() method commit ca6dc55f77aca88a6e9cc90bb6fd0045739f41f5 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 04:13:58 2021 +0700 Guard against setting data groups sparsely in graph:add_value() commit cdea5f126f7984e72924f6dffbd312c4fe3b032f Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 02:36:12 2021 +0700 Refactor scaling and baseline choice out of graph:draw() commit 10135deafe8a9390ba66ab657ebb1fb41d549e8c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 21:34:48 2021 +0700 Document graph.nan_color and graph.stack properties commit 0d73bb84ac66b87e16aee472bd1f299549f66a1c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 20:49:24 2021 +0700 Use a better example in docs for the graph.stack property commit c27d160a0826d033d49d6141608c40aa8a0642fa Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 16:05:38 2021 +0700 Document graph.baseline_value property commit 459bc8176c13d4ea32af0dcdc2399d3b929b1c6a Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:53:47 2021 +0700 Document graph.clamp_bars property commit e732ee0b3b007d213e689b98cc00ae7cb141ae08 Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:34 2021 +0700 Improve graph:add_value() and :clear() documentation commit ae73e3aa9f8d60409cbe4cb4da9612f31af4f38e Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:11 2021 +0700 Improve graph min_value/max_value/scale documentation commit a7350996a9def773b91b1631e6c0507e33d8f93b Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:27:36 2021 +0700 Cleanup graph examples to show only relevant things in docs commit 30740c26f4f41c16f4c8a3c339bcb7bd299e14cd Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 14:04:36 2021 +0700 Rename graph.stack_colors property to `group_colors` The colors are used for stacked and non-stacked graphs alike, so the name didn't make sense. Also document and clarify some color stuff. commit 1ea01bfd410971cf0d0c25fd74cd764d5eb7279d Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 21:25:41 2021 +0700 Add an example file for code coverage of some fringe graph cases commit eae68e39553f88a6275a0c65bf040b5d4c262b54 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 20:23:32 2021 +0700 Add step_shape to the graph/nan_color example for code coverage commit 8027147bc2eab34a97b49a428fb8cef27f122bed Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:55:18 2021 +0700 Add example files for graph.nan_color/nan_indication properties commit 89a25a165c52b2c15b6c4d95ecb777a9067e66f9 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:33:07 2021 +0700 Make widget graph:add_value(v) default v to NaN 0 as a default makes no sense and silences the error that is failing to specify a value, to which the user should rather be alerted. The graph widget has sane NaN handling now and will do just that. commit 2835d552440be48d356a7f7b1d64963696dd550b Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:20:48 2021 +0700 Implement graph.nan_color and nan_indication properties Now, whenever there's a NaN among values and graph.nan_indication is set, the corresponding area, where the value bar should have been drawn, is filled with the nan_color from top to bottom. The painting is done after all data is rendered, to make sure that it won't be overpainted and go unnoticed. NaNs among graph's values are inevitably a sign of some error and it is arguably not a sane default behavior to simply not render them at all. The user should take immediate note of any errors by default, instead of wondering at a mysteriously empty graph widget, which is why nan_indication is set to true by default. If the user wants to silence NaNs, they can always simply set it to false. nan_color if unset, defaults to a noticeable yellow-black pattern. As a direct and planned consequence, stacked graphs with negative values in them trigger the NaN handling too. But silencing NaN handling for those graphs is also possible and semi-sane, because then NaNs and negatives are ignored and the graph behaves (almost) like they were zeros. commit 9c1f8f08b400e01e19c534f3810c9c9ab485760c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 26 08:10:19 2021 +0700 Refactor out graph_map_value_to_widget_coordinates() function commit 1905991ddecabdae3e15ffb8ee5b18a1bee1a711 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 28 13:39:29 2021 +0700 Add an example file for graph.baseline_value property commit b31b39d66f4c037a9484b71a3f910703ae01fac0 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 07:03:49 2021 +0700 Add an example file for graph.clamp_bars=false/true comparison commit a1fc850e3047f9ee2c9ddeffacfbabf3ded34171 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 04:06:45 2021 +0700 Add an example file for graphs with negative values and shapes commit 2f012f8ff1bb5094c14e724a61e59a2a3a296bc8 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 01:06:05 2021 +0700 Add an example file for a graph displaying negative values commit 7b14fbd59a4c4c51962fd3110a1a900dd5f4eb98 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 11:18:26 2021 +0700 Add an example file for graphs with step_width < 1 commit dfd9a54544b637e2fbe3d1325f9cc24b3f4d2313 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 10:17:38 2021 +0700 Add an example file for graph.stack=false/true comparison Should also demonstrate that multiple data groups are possible for non-stacked graphs too. commit 5b532106c3fd255f9b66a66cfb88d977b489ab5d Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 09:34:14 2021 +0700 Add an example file for disabling graph data groups with nil colors commit ac6f1083c02fcb428f089a616f9b0ebaa5496b6c Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:57:25 2021 +0700 Add an example file for step_shape/width/spacing on stacked graphs commit b3703db6a5139e73e72c1cfd198ece2cd9be7df2 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:43:56 2021 +0700 Add an example file for scale=false/true on stacked graph commit b000aed4a12cb44fb501e1c8432a71a14b2f8f3f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 20 08:22:19 2021 +0700 Document deprecation of graph's width and height commit cb0ad617bd6c83a499896797318e3b0b588c3566 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 07:53:37 2021 +0700 Deprecate width and height properties of wibox.widget.graph commit 8c64e14ad91a074b784166a7054e53444cccb9d2 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 04:37:59 2021 +0700 Refactor graph:draw() to separate concerns Firstly, summation of group values for stacked graphs is extracted into graph:preprocess_values(). This makes graph:draw() smaller and exposes the fact, that stacked graphs are almost entirely just a form of a more general concept: pre-render data manipulation. It's something a user might want to customize, and now they can do it by monkey-patching the method. Secondly, the logic for deciding, if and with which color a data group should be drawn, is extracted into graph:should_draw_data_group() and get_data_group_color() methods respectively. This makes it customizable too and makes more obvious the fact, that it is a general thing that has nothing to do with stacked graphs in particular. With this there are only 5 lines remaining in graph:draw(), that could be said to be specific to stacked graphs, all pertaining to the `prev_y` variable, which merely preserves the bar coordinates from a previous data group. commit 2a32b6305d893f413303728a0a3da1dcd9a5591e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 22:52:37 2021 +0700 Pointlessly optimize graph:add_value() 1) Purge old values first, then insert the new one, if needed. Since Lua grows arrays in power of two steps, and our guess_capacity() also takes care to return even integers, it would be a shame to overstep the capacity by one by inserting first and thus getting the table size doubled only to never use the most of it. 2) Use a removal procedure that doesn't call the slow length operator at all. Previous code called it twice (once in table.remove) for every removed value. commit 680ea8f5d81e9040afb4662b3229e937e4b89e02 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:23:38 2021 +0700 Set graph data series color before the painting loop commit b970f0f221ae76250f2a94433ff79d653caa6e21 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:05:37 2021 +0700 Drop a large no longer necessary `if` in graph:draw_values() This commit is almost purely a whitespace change. commit 6b4f0541c70ec99d7f6e276bae64939f337120d6 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:00:50 2021 +0700 Extract values rendering code into graph:draw_values() method This is to avoid ungodly if nesting levels, which are needed to bail out of drawing values early and skip to drawing the border. The next commit will get rid of the if. commit e0950e4f99d1ea1c4b8cea2cc67629fa40030c2a Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 14:50:20 2021 +0700 Track graph draw() usage to guess necessary array capacity The last step of divorcing add_values() from width calculations, while ensuring that user doesn't observe a lack of data to draw. The widget now tracks an approximate number of values that it has been asked to draw() in _private.last_drawn_values_num, and truncates its values array based on that (if not overridden by the capacity property). commit 2e3d542282f35e7c0fed454da0f2eada0fcf3b78 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 13:35:12 2021 +0700 Add `capacity` property to graph widget This is the second step of divorcing add_values() from calculations involving widths. How many values are stored and how many values are drawn are distinct concepts. There isn't even a single definitive "how many values are drawn" because the widget could be rendered in several places with distinct widths simultaneously, and in every such place the data must be drawn appropriately, e.g. with auto-scaling, that takes only the visible data points into account. And the draw() method can do that now. The default capacity value of nil uses heuristics based on widget's _private.width to guess some capacity that would tolerate some dynamic widget resizing. If the user finds the heuristic failing them, they can set the capacity property to the desired maximum number of values that will be kept in the _private.values array (in each data group). commit 08d4a80686f7fe363551c35589a460839f60853e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 03:27:18 2021 +0700 Extract values size computation into compute_drawn_values_num() This is the first step of making add_values() not concern itself (directly) with widget dimensions. The user can monkey-patch the method to override how many values are (logically) drawn, because it affects graph auto-scaling and can be useful for rendering of step_shape-s that paint outside of their step_width. I didn't make it a full-blown function-valued property, because it seems a fringe functionality, which I'm too lazy to document. commit 3c5617c98ab1147618ed22e8414a77fc56b45f6e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 02:32:29 2021 +0700 Copy graph widget prototype methods over in bulk commit 169538837ece41a03d02ba8fbc0e4097062b67a3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun Apr 25 05:58:01 2021 +0700 Remove graph.baseline_y property After giving it some thought I've concluded, that it is unlikely to be useful as it is now. I'd better reserve the name for some more sensible functionality, like fixing the baseline_value defined axis at the given widget coordinate, adjusting the scaling accordingly, so min_value/max_value won't generally correspond to widget top and bottom anymore. commit 202ffaa9a0b820c7c471e1121d01b91f8f76cd38 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 12:06:55 2021 +0700 Add baseline_y and baseline_value properties to graph widget Now values in graph have a *baseline*, tunable with baseline_y and baseline_value properties. It is the (for now invisible) horizontal line from which all bars in the graph grow. I call it a "baseline" instead of "axis", because it doesn't actually have to correspond to the 0 value. But it can, and actually does now by default. baseline_y and baseline_value properties determine the vertical placement of the baseline in the widget. baseline_y is the position of the baseline in normalized widget coordinates, e.g. 0 corresponds to widget's top, 1 - to widget's bottom, and 0.5 - to widget's middle. So if e.g. one sets baseline_y = 0, then no matter how the widget is resized and how its values are scaled, bars in it will be drawn starting from the top. baseline_value is the position of the baseline in value coordinates. So if one sets baseline_value = 10, then no matter what happens, bars for values greater than 10 will be drawn as growing up from this line, and bars for values smaller as growing down. Where and if it will be seen in the widget, depends on the scaling. baseline_value = 0 is the familiar zero axis. baseline_y is unset by default and has priority, i.e. if it's set, then baseline_value is ignored. baseline_value defaults to 0. Prior to this commit, bars were always drawn from the bottom edge of the widget, or, in the new terms, the widget had baseline_y = 1. But it was indistinguishable from baseline_value = 0, because the widget clamped all negative values to 0, so the difference between the two could never be observed. baseline_value = 0 is clearly more useful and familiar to people, so now it is in force by default. commit df7e19abaebf0a871b5ca217b4cc3365533267a6 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 11:18:12 2021 +0700 Move value clamping from graph:add_value() into draw() Previously all values were forcibly clamped between 0 (not tunable at all) and max_value (1 by default). Thus graphs with negative values were not an option. NaN values were conflated with zeros, even though it may be useful to maintain the distinction and render them differently. And if user happened to dynamically change the max_value property, they discovered, that their data got truncated. Preserving user data as it was given is more flexible and makes things easier to debug. This commit does exactly that. So what happens now to the newly allowed values during draw()? There are 3 new cases to consider: 1) NaNs 2) Negatives 3) Values outside of min_value..max_value range NaNs are presently simply not drawn. An empty place is reserved where the value should've been, but nothing else is done. This is distinct from how a zero is handled, e.g. if step_shape is set, it is called with the height parameter = 0, which can still end up drawing something, depending on the shape. One could also consider addition of nan_color/nan_shape properties which could be drawn to alert user to a gap in their data. Negative values necessitate introduction of the concept of the zero axis, from which they will be drawn DOWN, in contrast to positive values. Where should this axis be drawn? How should step_shape-s with inherent direction, e.g. arrows, be drawn for negatives? This will be addressed in the next commit. Values outside of min/max_value range can imply shapes that go beyond widget dimensions. An arrow shape that goes beyond the roof is visibly different from an arrow that saturates and merely touches the widget top, and the user may reasonably desire either behavior. Therefore this commit introduces a boolean clamp_bars property, which defaults to true for reasons of robustness and backward compatibility with the behaviour prior to this commit. The property of course affects only the display of the values, the actual user data stays intact. commit c9c24fb1ab741cd6bb8074971d568e0646c51532 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:29:01 2021 +0700 Use rectangles for drawing graph bars of all sizes I have benchmarked various bar drawing strategies and graph sizes and found out that drawing graph bars with rectangles is as fast or even marginally faster than using fat lines. Thus the code that tries to special-case 1px bars as lines seems to be not worth the additional complexity it introduces, and this commit removes it. Incidentally, I've also established, that rectangles are more robust. If one draws graph bars and feeds cairo very large coordinates (~2^62), then corresponding rectangles fail to render, but all the other rectangles in the same series get rendered successfully, whereas a single large coordinate in a move_to/line_to() sequence is enough to cause all lines in the batch to fail to render. commit bc7f4be5e06405b5aa7fb443a5ace2b9b9575b1f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:01:42 2021 +0700 Cache cairo methods used in the inner loop of graph:draw() I have benchmarked the draw() method, both on cairo xcb and on argb32 image surfaces, and have discovered that cairo context method calls are unnaturally slow, to the point, that almost nothing else in the function matters. For example, drawing bars with vertical lines made the whole draw() function almost 2 times slower than doing the same with rectangles, merely because the former requires two method calls (move_to/line_to), and the latter only one. Simply caching hotpath cairo methods in local variables, reduced the difference between the drawing methods to a negligible one and made the whole function 4 times faster on a typical 100-bar graph, (1.4ms to 0.33ms on my laptop) thus bringing closer my secret dream of drawing live audio data with awesome graphs (kidding). I guess, LGI does something not very optimal there, like gobject-introspecting the method every time, or something. There might be some untapped optimization potential, possibly in upstream, like a method cache, from which everything in awesome could benefit, even though I imagine not many things are so call-intensive as the graph widget, to really feel the difference. commit 538df25bdf02b53d0bbe81809eef2e6fdb05ff6f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 07:32:37 2021 +0700 Use 1-based index in the inner graph draw loop It's used there only once for a calculation and then several times to index some tables, and index 0 is bad for lua tables performance-wise. commit 7412546916cd0a0a490130a120255ffb719398b8 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:55:12 2021 +0700 Leave graph.max_value uninitialized at construction This is a user-visible change, but the previous behavior was probably not what user expected anyway, namely with graph.scale set the graph mysteriously couldn't zoom in onto values that are smaller than 1. commit eb81d7929a161e79c13262cfa73ac2e6a820e8ed Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:48:29 2021 +0700 Allow graph.max_value be nil This makes its behave symmetrically to min_value. Now it defaults to 1, when graph.scale is not set, and to the maximum value in the graph data, when graph.scale is set. commit f622a8430cbfde7007b58d7f4c9c76b08c91e997 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 12:01:13 2021 +0700 Calculate max values num in graph:add_value() more precisely At most so many values were kept in the widget values array, as there are pixels of usable widget width (i.e. sans border). This is exactly right for the surely most common case of graphs with 1px-wide bars, but is more than needed for graphs with step_width+step_spacing>1, and not enough for graphs with subpixel bars. Moreover this made graph autoscaling behave weirdly in graphs with thicker bars, because old values survived longer in the array and kept affecting min_value/max_value calculations even after they were shifted off-screen. The maximum number of kept values is now calculated exactly as the number of values that can be displayed at once, taking step_width and step_spacing into account. It might be worth adding a max_values_num property to the graph to let user override this calculation, in case they draw shapes in step_shape() outside of its supposed rectangle, so our assumptions of visibility are wrong, or the widget often gets resized, resulting in the annoyance of visibly truncated series. commit 0aa63249a18641251a1bf77423739164f0eab39f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 02:30:13 2021 +0700 Fix border_color/border_width bugs in graph widget add_value() wrongly assumed that border_width is always set, when border_color is, so if user set only the latter, an error occurred. add_value() assumed that border_width is always < width which led to awesome hanging up in an infinite loop, should that fail to be the case. The graph border_width property was driven by the border_color property, instead of the other way round. If border_color wasn't set, the border wasn't drawn all, even if user sets non-zero border_width, which is a confusing behavior, in contrast to all other widgets, which simply use a plethora of fallback colors to draw the border, when needed. This also meant that, despite being documented, the beautiful.graph_border_color fallback color could not and was not ever used. If the border_color was set though, an unset border_width was immediately assumed to be 1 instead of 0, also contrary to what other widgets do. Theme colors shouldn't influence widget layout, so this commit fixes this behavior to: 1) border_width always defaults to 0, if unset. 2) border_color falls back to beautiful.graph_border_color or white. Maybe one should also add a beautiful.graph_border_width theme variable, like some widgets do. commit dad4a3253b8fef971fb82720d2a407fcc0a879ad Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 03:11:27 2021 +0700 Implement multiple data series drawing for non-stacked graph The widget can already hold and draw multiple data groups, so it makes little sense to limit drawing only to the first group when graph.stack = false, probably leading to user's confusion. Now user can call add_value(v, group) with group > 1 and the data series will be drawn (over groups with lesser id-s) with graph.stack_colors[group] color, if stack_colors is set, or with graph.color, if stack_colors isn't set. Thus nothing visibly changes for all current users of non-stacked graphs with only single data series, but the new functionality is now there for those who need it. One can't fully emulate this functionality, e.g. with multiple graphs widgets in a wibox.layout.stack, because data series in a single widget can enjoy a common auto-scaling. commit 3e645c976184ab5f50d625271c25062db9ab7ed9 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:59:12 2021 +0700 Don't draw a stacked graph data group if there's no color for it If stack_colors[idx] is nil for the group `idx`, don't draw it at all (as opposed, e.g. to the effect of drawing it with a transparent color, or as though it had all values == 0, which both produce different results). This code is still subtly different from prior behavior, because now user can have a non-contiguous stack_colors table and turn the display of any data group on and off at will, whereas the code two commits earlier would just stop rendering as soon as ipairs(stack_colors) does, i.e. no data series were drawn past the first nil hole. This commit only looks large, but is mostly a whitespace change due to nesting from a pair of wrapping ifs. commit 8c0273786de1ade66bdd977699f98b588da9f487 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:32:42 2021 +0700 Unify stacked and non-stacked graph rendering Now stacked graphs support everything the nonstacked do, like step_width ~= 1 and custom step_shape-s. Technical notes: This code no longer calls cr:stroke() for every single value in a stacked graph, but rather paints a whole series and then cr:stroke/fill()-s it at all once with its color, improving performance considerably. Additionally the graph no longer relies on the presence of the stack_colors property to render series. It draws all data that it has, and, if there's no corresponding color in stack_colors, falls back to graph.color. This will be changed in the next commit to the prior behavior of only drawing those series for which there's a color in the stack_colors table, but that would've made this commit's diff much larger due to required nesting ifs over large code chunks. This commit also takes care to properly handle NaNs and negative values. Those are currently (almost) guaranteed to not occur in the values array due to clipping in graph:add_value(), but I intend to eventually drop the clipping, because it does nothing that draw() really relies upon, and allowing unclipped and NaN values is more flexible and less surprising, e.g. when user dynamically toggles graph.scale. Eventually I intend to implement drawing negative values in non-stacked graphs and possibly even in the stacked ones. commit 38c6589c51b2aa057d7c2f40182ecf32b5b2ff5e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 10:04:15 2021 +0700 Convert graph:draw() to use `self` commit f21f1b2550426df1c4ea8abde079b1ccef84b394 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 12:27:26 2021 +0700 Fix scaling for stacked graphs Stacked graphs have cumulative heights, values from all groups that end up in the same graph bar are effectively summed. Previous code didn't take that into account when calculating min/max_value for the purposes of rescaling, but simply examined every value separately. This code adds up all values up front and finds min/max among those, to determine proper scaling that would fit the graph exactly. commit 1ce4c7bf63bdd6c99c195d668347bec49355e921 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 09:49:35 2021 +0700 Draw shaped graph bars only up to the baseline Prior to this commit, every step_shape bar was, for some reason, drawn with height = full widget's height, regardless of the value it represents. So for the most bars their lower end was far outside the box and therefore clipped off. So e.g. one couldn't see the lower round edges of rounded rectangles. This commit fixes that by appropriately varying the height that is passed into step_shape(), so that lower edges of the bars always exactly touch the lower edge of the graph. If the user wants to restore the prior behavior, they can simply ignore the passed-in height in step_shape() and just always draw their shape with height = full widget height. But they couldn't replicate the effect of this commit in their rc.lua without essentially writing their own graph widget. commit e25185b08da23745b5815f85f6e9e43222ed2294 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 08:42:54 2021 +0700 Avoid incremental transform shifting during graph bar rendering Instead of letting addition errors and cognitive load accumulate simply use the same offset calculation that is used for lines and reset the transform back to the top-left corner after every drawn bar. It's conceptually simpler, and will be useful later for stacked graphs and more. It also fixes a discrepancy between plain and step_shape rendering. Prior to this commit, bar coloring occurred with a disturbed transform, which was centered at the last drawn bar, wherever it happened to be. This meant that nontrivial colors, like gradients and images, were arbitrarily offset when step_shape was used. commit f3e98866d8583c35d0b557c09f529299edc9ba2c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:45:29 2021 +0700 Refactor out graph bar height expression into a variable commit f54528d6892f5aaa4595917853c9e2130f743fdb Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:20:25 2021 +0700 Draw graph bars with lines only when step_width == 1 Before this commit graph bars of step_width < 1 were drawn with lines of width 1, but that's a bit unfortunate, because it led to much overpainting, while still ending up with a graphically incorrect result. Let's draw such bars with rectangles and let cairo do things with subpixels, getting more graphically accurate result, while still keeping the common case of step_width == 1 (supposedly) fast-drawn with lines. commit b6515dca67c18b78eb5657378131a62ab43d4cfc Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:54:07 2021 +0700 Fix unsharp edges when drawing graph bars with rectangles The 0.5 offset is only helpful for 1-width strokes, not for fills. commit 7f3db8f07e82f821bcd1dc418d66564f537609d6 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:21:01 2021 +0700 Do coordinate transformations even if the value isn't drawn The graph presently doesn't draw values < 0, but there was a discrepancy between drawing with and without step_shape. The default line/rectangle drawing reserves an empty place where the skipped value should've been, but no such empty place is reserved, when step_shape is used. This commit fixes that. Skipped values always have their empty space now. commit 7b731ebfe96af7a9dba9c57371da6761cf22c00c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:03:30 2021 +0700 Fix graph bar scaling when min_value is not 0 Previous calculation lead to underscaling of values so that even value = max_value wasn't drawn to full height. commit dca59fd8b7fff0e7e646f2dfc6bd92047028e5a8 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:18:40 2021 +0700 Calculate the horizontal graph bar offset only when needed commit 940ddf4b9825e9c3a91d276208c089d8c74e49db Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:17:23 2021 +0700 Call cr:move_to() only when drawing the graph with lines It's redundant otherwise and might even interfere with user's drawing in step_shape(). commit b5d5cd10ca29fb29cd751faaa95137c2fe89ff63 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:04:06 2021 +0700 Fix wrong horizontal offset calculations in widget.graph Indices in the loop are zero-based, so things like i-1 and i>1 aren't necessary and actually wouldn't have been correct even in a 1-based loop. step_shape = gears.shape.rectangle and step_shape = nil, which is supposed to default to rectangle according to docs, were drawn differently due to mismatched offset calculations, which led to problems especially noticeable when step_spacing is not zero, e.g: 1) whole graph shifted left too much, drawn partially off-screen. 2) whole graph shifted right too much, and the first bar placed closer to the rest than others. commit dc369fa198281e000afc1bd17326f3a264adf7d1 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 02:09:21 2021 +0700 Fix the "border_width affects graph bar width" bug Graph bars were drawn with lines of border_width thickness instead of 1, i.e. possibly too fat to be readable. commit 1ba73378cf29ed27a6ecd18716394779ceb543d9 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 00:13:03 2021 +0700 Simplify internal values representation in graph even more New values in graph._private.values were pushed to the end, and old values were removed from the beginning of the table. In draw() however newer values are drawn first, at the left edge of the graph, necessitating annoying idx = #values - idx conversions everywhere. In particular in stacked graph that means that values that are drawn together may have different indices. One has to shift the values array in add_value() either way, so one might as well insert new values at the beginning of the array and simplify the rest of the code. This doesn't seem like much yet, but it will prove to be much more convenient and less error-prone in what I'm going to do later. commit f70523ee68dbd6b67050ace4b82903a9b22440c7 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 13:35:10 2021 +0700 Simplify internal values representation in graph widget graph._private.values was either a table of values or a table of tables of values, depending on whether "stack" property is enabled, ... or a mix of both if user forgot to pass the second "group" parameter to graph:add_value(). This resulted in pretty fragile and confusing behavior, e.g. leaving user to wonder, why they see nothing more after setting stack=true for a graph in a previously working rc.lua, and demanding a very careful use of add_value() from the user, or else some values or even entire stacks would mysteriously vanish and errors in draw() get triggered, sometimes after a period of seemingly normal functioning. Now `values` is always a table of tables and the group parameter in add_value() defaults to 1, thus making the non-stacked graph a trivial variant of the stacked graph.
2021-05-20 17:14:03 +02:00
-- Track our usage to help us guess the necessary values array capacity
graph_gather_drawn_values_num_stats(self, drawn_values_num)
-- Draw the background first
cr:set_source(color(self._private.background_color or prop_fallbacks.background_color))
cr:paint()
-- Draw the values
if drawn_values_num > 0 then
cr:save()
-- Account for the border width
if border_width > 0 then
cr:translate(border_width, border_width)
end
local values_width = width - 2*border_width
local values_height = height - 2*border_width
2016-05-23 07:30:44 +02:00
graph: complete widget overhaul and bug fixes Squashed commit of the following: commit 69821f51fe1e8652715658543e50631ece495090 Author: Alex Belykh <albel727@ngs.ru> Date: Mon May 10 19:17:55 2021 +0700 Refactor property handling in wibox.widget.graph In addition, baseline_value, step_width and step_spacing properties are all set to their default non-nil numeric values during instance creation now. commit 842eb429bf5df4b2ba46b2e6f7646afe89b1a3c3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 9 06:18:45 2021 +0700 Make graph:draw_values() private There's no documentation nor confidence that it's a good API, so I'm hiding it for now. commit 720746780574e4bad14a71fc4174955139c2eb50 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 04:04:13 2021 +0700 Document graph_should_draw_data_group() for posterity commit 7c73f3754ce6ba86b118f1170a6f9e406ab4841d Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:59:21 2021 +0700 Make graph:should_draw_data_group() private There's no confidence whether it's a good API, so I'm hiding it for now. commit b3539e20adb6423118af726ae4f8751ef864b5d2 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:36:44 2021 +0700 Make graph:preprocess_values() private There's no confidence whether it's a good API, so I'm hiding it for now. commit 2df4400e780a6260f8d0ab1c60734fbb11bf3c4f Author: Alex Belykh <albel727@ngs.ru> Date: Thu May 6 10:05:23 2021 +0700 Add spec/wibox/widget/graph_spec.lua commit ce01a9771ab439b8df841f1565aa4aa8a44a5e0d Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 08:16:18 2021 +0700 Improve graph.capacity documentation commit e28fade8f655802f5bf89154169f68d256b8e332 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 07:36:01 2021 +0700 Document graph:compute_drawn_values_num() commit dd68332292465422d08b48e9f0229f143beccbc6 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 05:24:23 2021 +0700 Document graph:pick_data_group_color() method commit ca6dc55f77aca88a6e9cc90bb6fd0045739f41f5 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 04:13:58 2021 +0700 Guard against setting data groups sparsely in graph:add_value() commit cdea5f126f7984e72924f6dffbd312c4fe3b032f Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 02:36:12 2021 +0700 Refactor scaling and baseline choice out of graph:draw() commit 10135deafe8a9390ba66ab657ebb1fb41d549e8c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 21:34:48 2021 +0700 Document graph.nan_color and graph.stack properties commit 0d73bb84ac66b87e16aee472bd1f299549f66a1c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 20:49:24 2021 +0700 Use a better example in docs for the graph.stack property commit c27d160a0826d033d49d6141608c40aa8a0642fa Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 16:05:38 2021 +0700 Document graph.baseline_value property commit 459bc8176c13d4ea32af0dcdc2399d3b929b1c6a Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:53:47 2021 +0700 Document graph.clamp_bars property commit e732ee0b3b007d213e689b98cc00ae7cb141ae08 Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:34 2021 +0700 Improve graph:add_value() and :clear() documentation commit ae73e3aa9f8d60409cbe4cb4da9612f31af4f38e Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:11 2021 +0700 Improve graph min_value/max_value/scale documentation commit a7350996a9def773b91b1631e6c0507e33d8f93b Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:27:36 2021 +0700 Cleanup graph examples to show only relevant things in docs commit 30740c26f4f41c16f4c8a3c339bcb7bd299e14cd Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 14:04:36 2021 +0700 Rename graph.stack_colors property to `group_colors` The colors are used for stacked and non-stacked graphs alike, so the name didn't make sense. Also document and clarify some color stuff. commit 1ea01bfd410971cf0d0c25fd74cd764d5eb7279d Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 21:25:41 2021 +0700 Add an example file for code coverage of some fringe graph cases commit eae68e39553f88a6275a0c65bf040b5d4c262b54 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 20:23:32 2021 +0700 Add step_shape to the graph/nan_color example for code coverage commit 8027147bc2eab34a97b49a428fb8cef27f122bed Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:55:18 2021 +0700 Add example files for graph.nan_color/nan_indication properties commit 89a25a165c52b2c15b6c4d95ecb777a9067e66f9 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:33:07 2021 +0700 Make widget graph:add_value(v) default v to NaN 0 as a default makes no sense and silences the error that is failing to specify a value, to which the user should rather be alerted. The graph widget has sane NaN handling now and will do just that. commit 2835d552440be48d356a7f7b1d64963696dd550b Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:20:48 2021 +0700 Implement graph.nan_color and nan_indication properties Now, whenever there's a NaN among values and graph.nan_indication is set, the corresponding area, where the value bar should have been drawn, is filled with the nan_color from top to bottom. The painting is done after all data is rendered, to make sure that it won't be overpainted and go unnoticed. NaNs among graph's values are inevitably a sign of some error and it is arguably not a sane default behavior to simply not render them at all. The user should take immediate note of any errors by default, instead of wondering at a mysteriously empty graph widget, which is why nan_indication is set to true by default. If the user wants to silence NaNs, they can always simply set it to false. nan_color if unset, defaults to a noticeable yellow-black pattern. As a direct and planned consequence, stacked graphs with negative values in them trigger the NaN handling too. But silencing NaN handling for those graphs is also possible and semi-sane, because then NaNs and negatives are ignored and the graph behaves (almost) like they were zeros. commit 9c1f8f08b400e01e19c534f3810c9c9ab485760c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 26 08:10:19 2021 +0700 Refactor out graph_map_value_to_widget_coordinates() function commit 1905991ddecabdae3e15ffb8ee5b18a1bee1a711 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 28 13:39:29 2021 +0700 Add an example file for graph.baseline_value property commit b31b39d66f4c037a9484b71a3f910703ae01fac0 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 07:03:49 2021 +0700 Add an example file for graph.clamp_bars=false/true comparison commit a1fc850e3047f9ee2c9ddeffacfbabf3ded34171 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 04:06:45 2021 +0700 Add an example file for graphs with negative values and shapes commit 2f012f8ff1bb5094c14e724a61e59a2a3a296bc8 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 01:06:05 2021 +0700 Add an example file for a graph displaying negative values commit 7b14fbd59a4c4c51962fd3110a1a900dd5f4eb98 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 11:18:26 2021 +0700 Add an example file for graphs with step_width < 1 commit dfd9a54544b637e2fbe3d1325f9cc24b3f4d2313 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 10:17:38 2021 +0700 Add an example file for graph.stack=false/true comparison Should also demonstrate that multiple data groups are possible for non-stacked graphs too. commit 5b532106c3fd255f9b66a66cfb88d977b489ab5d Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 09:34:14 2021 +0700 Add an example file for disabling graph data groups with nil colors commit ac6f1083c02fcb428f089a616f9b0ebaa5496b6c Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:57:25 2021 +0700 Add an example file for step_shape/width/spacing on stacked graphs commit b3703db6a5139e73e72c1cfd198ece2cd9be7df2 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:43:56 2021 +0700 Add an example file for scale=false/true on stacked graph commit b000aed4a12cb44fb501e1c8432a71a14b2f8f3f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 20 08:22:19 2021 +0700 Document deprecation of graph's width and height commit cb0ad617bd6c83a499896797318e3b0b588c3566 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 07:53:37 2021 +0700 Deprecate width and height properties of wibox.widget.graph commit 8c64e14ad91a074b784166a7054e53444cccb9d2 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 04:37:59 2021 +0700 Refactor graph:draw() to separate concerns Firstly, summation of group values for stacked graphs is extracted into graph:preprocess_values(). This makes graph:draw() smaller and exposes the fact, that stacked graphs are almost entirely just a form of a more general concept: pre-render data manipulation. It's something a user might want to customize, and now they can do it by monkey-patching the method. Secondly, the logic for deciding, if and with which color a data group should be drawn, is extracted into graph:should_draw_data_group() and get_data_group_color() methods respectively. This makes it customizable too and makes more obvious the fact, that it is a general thing that has nothing to do with stacked graphs in particular. With this there are only 5 lines remaining in graph:draw(), that could be said to be specific to stacked graphs, all pertaining to the `prev_y` variable, which merely preserves the bar coordinates from a previous data group. commit 2a32b6305d893f413303728a0a3da1dcd9a5591e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 22:52:37 2021 +0700 Pointlessly optimize graph:add_value() 1) Purge old values first, then insert the new one, if needed. Since Lua grows arrays in power of two steps, and our guess_capacity() also takes care to return even integers, it would be a shame to overstep the capacity by one by inserting first and thus getting the table size doubled only to never use the most of it. 2) Use a removal procedure that doesn't call the slow length operator at all. Previous code called it twice (once in table.remove) for every removed value. commit 680ea8f5d81e9040afb4662b3229e937e4b89e02 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:23:38 2021 +0700 Set graph data series color before the painting loop commit b970f0f221ae76250f2a94433ff79d653caa6e21 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:05:37 2021 +0700 Drop a large no longer necessary `if` in graph:draw_values() This commit is almost purely a whitespace change. commit 6b4f0541c70ec99d7f6e276bae64939f337120d6 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:00:50 2021 +0700 Extract values rendering code into graph:draw_values() method This is to avoid ungodly if nesting levels, which are needed to bail out of drawing values early and skip to drawing the border. The next commit will get rid of the if. commit e0950e4f99d1ea1c4b8cea2cc67629fa40030c2a Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 14:50:20 2021 +0700 Track graph draw() usage to guess necessary array capacity The last step of divorcing add_values() from width calculations, while ensuring that user doesn't observe a lack of data to draw. The widget now tracks an approximate number of values that it has been asked to draw() in _private.last_drawn_values_num, and truncates its values array based on that (if not overridden by the capacity property). commit 2e3d542282f35e7c0fed454da0f2eada0fcf3b78 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 13:35:12 2021 +0700 Add `capacity` property to graph widget This is the second step of divorcing add_values() from calculations involving widths. How many values are stored and how many values are drawn are distinct concepts. There isn't even a single definitive "how many values are drawn" because the widget could be rendered in several places with distinct widths simultaneously, and in every such place the data must be drawn appropriately, e.g. with auto-scaling, that takes only the visible data points into account. And the draw() method can do that now. The default capacity value of nil uses heuristics based on widget's _private.width to guess some capacity that would tolerate some dynamic widget resizing. If the user finds the heuristic failing them, they can set the capacity property to the desired maximum number of values that will be kept in the _private.values array (in each data group). commit 08d4a80686f7fe363551c35589a460839f60853e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 03:27:18 2021 +0700 Extract values size computation into compute_drawn_values_num() This is the first step of making add_values() not concern itself (directly) with widget dimensions. The user can monkey-patch the method to override how many values are (logically) drawn, because it affects graph auto-scaling and can be useful for rendering of step_shape-s that paint outside of their step_width. I didn't make it a full-blown function-valued property, because it seems a fringe functionality, which I'm too lazy to document. commit 3c5617c98ab1147618ed22e8414a77fc56b45f6e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 02:32:29 2021 +0700 Copy graph widget prototype methods over in bulk commit 169538837ece41a03d02ba8fbc0e4097062b67a3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun Apr 25 05:58:01 2021 +0700 Remove graph.baseline_y property After giving it some thought I've concluded, that it is unlikely to be useful as it is now. I'd better reserve the name for some more sensible functionality, like fixing the baseline_value defined axis at the given widget coordinate, adjusting the scaling accordingly, so min_value/max_value won't generally correspond to widget top and bottom anymore. commit 202ffaa9a0b820c7c471e1121d01b91f8f76cd38 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 12:06:55 2021 +0700 Add baseline_y and baseline_value properties to graph widget Now values in graph have a *baseline*, tunable with baseline_y and baseline_value properties. It is the (for now invisible) horizontal line from which all bars in the graph grow. I call it a "baseline" instead of "axis", because it doesn't actually have to correspond to the 0 value. But it can, and actually does now by default. baseline_y and baseline_value properties determine the vertical placement of the baseline in the widget. baseline_y is the position of the baseline in normalized widget coordinates, e.g. 0 corresponds to widget's top, 1 - to widget's bottom, and 0.5 - to widget's middle. So if e.g. one sets baseline_y = 0, then no matter how the widget is resized and how its values are scaled, bars in it will be drawn starting from the top. baseline_value is the position of the baseline in value coordinates. So if one sets baseline_value = 10, then no matter what happens, bars for values greater than 10 will be drawn as growing up from this line, and bars for values smaller as growing down. Where and if it will be seen in the widget, depends on the scaling. baseline_value = 0 is the familiar zero axis. baseline_y is unset by default and has priority, i.e. if it's set, then baseline_value is ignored. baseline_value defaults to 0. Prior to this commit, bars were always drawn from the bottom edge of the widget, or, in the new terms, the widget had baseline_y = 1. But it was indistinguishable from baseline_value = 0, because the widget clamped all negative values to 0, so the difference between the two could never be observed. baseline_value = 0 is clearly more useful and familiar to people, so now it is in force by default. commit df7e19abaebf0a871b5ca217b4cc3365533267a6 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 11:18:12 2021 +0700 Move value clamping from graph:add_value() into draw() Previously all values were forcibly clamped between 0 (not tunable at all) and max_value (1 by default). Thus graphs with negative values were not an option. NaN values were conflated with zeros, even though it may be useful to maintain the distinction and render them differently. And if user happened to dynamically change the max_value property, they discovered, that their data got truncated. Preserving user data as it was given is more flexible and makes things easier to debug. This commit does exactly that. So what happens now to the newly allowed values during draw()? There are 3 new cases to consider: 1) NaNs 2) Negatives 3) Values outside of min_value..max_value range NaNs are presently simply not drawn. An empty place is reserved where the value should've been, but nothing else is done. This is distinct from how a zero is handled, e.g. if step_shape is set, it is called with the height parameter = 0, which can still end up drawing something, depending on the shape. One could also consider addition of nan_color/nan_shape properties which could be drawn to alert user to a gap in their data. Negative values necessitate introduction of the concept of the zero axis, from which they will be drawn DOWN, in contrast to positive values. Where should this axis be drawn? How should step_shape-s with inherent direction, e.g. arrows, be drawn for negatives? This will be addressed in the next commit. Values outside of min/max_value range can imply shapes that go beyond widget dimensions. An arrow shape that goes beyond the roof is visibly different from an arrow that saturates and merely touches the widget top, and the user may reasonably desire either behavior. Therefore this commit introduces a boolean clamp_bars property, which defaults to true for reasons of robustness and backward compatibility with the behaviour prior to this commit. The property of course affects only the display of the values, the actual user data stays intact. commit c9c24fb1ab741cd6bb8074971d568e0646c51532 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:29:01 2021 +0700 Use rectangles for drawing graph bars of all sizes I have benchmarked various bar drawing strategies and graph sizes and found out that drawing graph bars with rectangles is as fast or even marginally faster than using fat lines. Thus the code that tries to special-case 1px bars as lines seems to be not worth the additional complexity it introduces, and this commit removes it. Incidentally, I've also established, that rectangles are more robust. If one draws graph bars and feeds cairo very large coordinates (~2^62), then corresponding rectangles fail to render, but all the other rectangles in the same series get rendered successfully, whereas a single large coordinate in a move_to/line_to() sequence is enough to cause all lines in the batch to fail to render. commit bc7f4be5e06405b5aa7fb443a5ace2b9b9575b1f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:01:42 2021 +0700 Cache cairo methods used in the inner loop of graph:draw() I have benchmarked the draw() method, both on cairo xcb and on argb32 image surfaces, and have discovered that cairo context method calls are unnaturally slow, to the point, that almost nothing else in the function matters. For example, drawing bars with vertical lines made the whole draw() function almost 2 times slower than doing the same with rectangles, merely because the former requires two method calls (move_to/line_to), and the latter only one. Simply caching hotpath cairo methods in local variables, reduced the difference between the drawing methods to a negligible one and made the whole function 4 times faster on a typical 100-bar graph, (1.4ms to 0.33ms on my laptop) thus bringing closer my secret dream of drawing live audio data with awesome graphs (kidding). I guess, LGI does something not very optimal there, like gobject-introspecting the method every time, or something. There might be some untapped optimization potential, possibly in upstream, like a method cache, from which everything in awesome could benefit, even though I imagine not many things are so call-intensive as the graph widget, to really feel the difference. commit 538df25bdf02b53d0bbe81809eef2e6fdb05ff6f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 07:32:37 2021 +0700 Use 1-based index in the inner graph draw loop It's used there only once for a calculation and then several times to index some tables, and index 0 is bad for lua tables performance-wise. commit 7412546916cd0a0a490130a120255ffb719398b8 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:55:12 2021 +0700 Leave graph.max_value uninitialized at construction This is a user-visible change, but the previous behavior was probably not what user expected anyway, namely with graph.scale set the graph mysteriously couldn't zoom in onto values that are smaller than 1. commit eb81d7929a161e79c13262cfa73ac2e6a820e8ed Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:48:29 2021 +0700 Allow graph.max_value be nil This makes its behave symmetrically to min_value. Now it defaults to 1, when graph.scale is not set, and to the maximum value in the graph data, when graph.scale is set. commit f622a8430cbfde7007b58d7f4c9c76b08c91e997 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 12:01:13 2021 +0700 Calculate max values num in graph:add_value() more precisely At most so many values were kept in the widget values array, as there are pixels of usable widget width (i.e. sans border). This is exactly right for the surely most common case of graphs with 1px-wide bars, but is more than needed for graphs with step_width+step_spacing>1, and not enough for graphs with subpixel bars. Moreover this made graph autoscaling behave weirdly in graphs with thicker bars, because old values survived longer in the array and kept affecting min_value/max_value calculations even after they were shifted off-screen. The maximum number of kept values is now calculated exactly as the number of values that can be displayed at once, taking step_width and step_spacing into account. It might be worth adding a max_values_num property to the graph to let user override this calculation, in case they draw shapes in step_shape() outside of its supposed rectangle, so our assumptions of visibility are wrong, or the widget often gets resized, resulting in the annoyance of visibly truncated series. commit 0aa63249a18641251a1bf77423739164f0eab39f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 02:30:13 2021 +0700 Fix border_color/border_width bugs in graph widget add_value() wrongly assumed that border_width is always set, when border_color is, so if user set only the latter, an error occurred. add_value() assumed that border_width is always < width which led to awesome hanging up in an infinite loop, should that fail to be the case. The graph border_width property was driven by the border_color property, instead of the other way round. If border_color wasn't set, the border wasn't drawn all, even if user sets non-zero border_width, which is a confusing behavior, in contrast to all other widgets, which simply use a plethora of fallback colors to draw the border, when needed. This also meant that, despite being documented, the beautiful.graph_border_color fallback color could not and was not ever used. If the border_color was set though, an unset border_width was immediately assumed to be 1 instead of 0, also contrary to what other widgets do. Theme colors shouldn't influence widget layout, so this commit fixes this behavior to: 1) border_width always defaults to 0, if unset. 2) border_color falls back to beautiful.graph_border_color or white. Maybe one should also add a beautiful.graph_border_width theme variable, like some widgets do. commit dad4a3253b8fef971fb82720d2a407fcc0a879ad Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 03:11:27 2021 +0700 Implement multiple data series drawing for non-stacked graph The widget can already hold and draw multiple data groups, so it makes little sense to limit drawing only to the first group when graph.stack = false, probably leading to user's confusion. Now user can call add_value(v, group) with group > 1 and the data series will be drawn (over groups with lesser id-s) with graph.stack_colors[group] color, if stack_colors is set, or with graph.color, if stack_colors isn't set. Thus nothing visibly changes for all current users of non-stacked graphs with only single data series, but the new functionality is now there for those who need it. One can't fully emulate this functionality, e.g. with multiple graphs widgets in a wibox.layout.stack, because data series in a single widget can enjoy a common auto-scaling. commit 3e645c976184ab5f50d625271c25062db9ab7ed9 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:59:12 2021 +0700 Don't draw a stacked graph data group if there's no color for it If stack_colors[idx] is nil for the group `idx`, don't draw it at all (as opposed, e.g. to the effect of drawing it with a transparent color, or as though it had all values == 0, which both produce different results). This code is still subtly different from prior behavior, because now user can have a non-contiguous stack_colors table and turn the display of any data group on and off at will, whereas the code two commits earlier would just stop rendering as soon as ipairs(stack_colors) does, i.e. no data series were drawn past the first nil hole. This commit only looks large, but is mostly a whitespace change due to nesting from a pair of wrapping ifs. commit 8c0273786de1ade66bdd977699f98b588da9f487 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:32:42 2021 +0700 Unify stacked and non-stacked graph rendering Now stacked graphs support everything the nonstacked do, like step_width ~= 1 and custom step_shape-s. Technical notes: This code no longer calls cr:stroke() for every single value in a stacked graph, but rather paints a whole series and then cr:stroke/fill()-s it at all once with its color, improving performance considerably. Additionally the graph no longer relies on the presence of the stack_colors property to render series. It draws all data that it has, and, if there's no corresponding color in stack_colors, falls back to graph.color. This will be changed in the next commit to the prior behavior of only drawing those series for which there's a color in the stack_colors table, but that would've made this commit's diff much larger due to required nesting ifs over large code chunks. This commit also takes care to properly handle NaNs and negative values. Those are currently (almost) guaranteed to not occur in the values array due to clipping in graph:add_value(), but I intend to eventually drop the clipping, because it does nothing that draw() really relies upon, and allowing unclipped and NaN values is more flexible and less surprising, e.g. when user dynamically toggles graph.scale. Eventually I intend to implement drawing negative values in non-stacked graphs and possibly even in the stacked ones. commit 38c6589c51b2aa057d7c2f40182ecf32b5b2ff5e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 10:04:15 2021 +0700 Convert graph:draw() to use `self` commit f21f1b2550426df1c4ea8abde079b1ccef84b394 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 12:27:26 2021 +0700 Fix scaling for stacked graphs Stacked graphs have cumulative heights, values from all groups that end up in the same graph bar are effectively summed. Previous code didn't take that into account when calculating min/max_value for the purposes of rescaling, but simply examined every value separately. This code adds up all values up front and finds min/max among those, to determine proper scaling that would fit the graph exactly. commit 1ce4c7bf63bdd6c99c195d668347bec49355e921 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 09:49:35 2021 +0700 Draw shaped graph bars only up to the baseline Prior to this commit, every step_shape bar was, for some reason, drawn with height = full widget's height, regardless of the value it represents. So for the most bars their lower end was far outside the box and therefore clipped off. So e.g. one couldn't see the lower round edges of rounded rectangles. This commit fixes that by appropriately varying the height that is passed into step_shape(), so that lower edges of the bars always exactly touch the lower edge of the graph. If the user wants to restore the prior behavior, they can simply ignore the passed-in height in step_shape() and just always draw their shape with height = full widget height. But they couldn't replicate the effect of this commit in their rc.lua without essentially writing their own graph widget. commit e25185b08da23745b5815f85f6e9e43222ed2294 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 08:42:54 2021 +0700 Avoid incremental transform shifting during graph bar rendering Instead of letting addition errors and cognitive load accumulate simply use the same offset calculation that is used for lines and reset the transform back to the top-left corner after every drawn bar. It's conceptually simpler, and will be useful later for stacked graphs and more. It also fixes a discrepancy between plain and step_shape rendering. Prior to this commit, bar coloring occurred with a disturbed transform, which was centered at the last drawn bar, wherever it happened to be. This meant that nontrivial colors, like gradients and images, were arbitrarily offset when step_shape was used. commit f3e98866d8583c35d0b557c09f529299edc9ba2c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:45:29 2021 +0700 Refactor out graph bar height expression into a variable commit f54528d6892f5aaa4595917853c9e2130f743fdb Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:20:25 2021 +0700 Draw graph bars with lines only when step_width == 1 Before this commit graph bars of step_width < 1 were drawn with lines of width 1, but that's a bit unfortunate, because it led to much overpainting, while still ending up with a graphically incorrect result. Let's draw such bars with rectangles and let cairo do things with subpixels, getting more graphically accurate result, while still keeping the common case of step_width == 1 (supposedly) fast-drawn with lines. commit b6515dca67c18b78eb5657378131a62ab43d4cfc Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:54:07 2021 +0700 Fix unsharp edges when drawing graph bars with rectangles The 0.5 offset is only helpful for 1-width strokes, not for fills. commit 7f3db8f07e82f821bcd1dc418d66564f537609d6 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:21:01 2021 +0700 Do coordinate transformations even if the value isn't drawn The graph presently doesn't draw values < 0, but there was a discrepancy between drawing with and without step_shape. The default line/rectangle drawing reserves an empty place where the skipped value should've been, but no such empty place is reserved, when step_shape is used. This commit fixes that. Skipped values always have their empty space now. commit 7b731ebfe96af7a9dba9c57371da6761cf22c00c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:03:30 2021 +0700 Fix graph bar scaling when min_value is not 0 Previous calculation lead to underscaling of values so that even value = max_value wasn't drawn to full height. commit dca59fd8b7fff0e7e646f2dfc6bd92047028e5a8 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:18:40 2021 +0700 Calculate the horizontal graph bar offset only when needed commit 940ddf4b9825e9c3a91d276208c089d8c74e49db Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:17:23 2021 +0700 Call cr:move_to() only when drawing the graph with lines It's redundant otherwise and might even interfere with user's drawing in step_shape(). commit b5d5cd10ca29fb29cd751faaa95137c2fe89ff63 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:04:06 2021 +0700 Fix wrong horizontal offset calculations in widget.graph Indices in the loop are zero-based, so things like i-1 and i>1 aren't necessary and actually wouldn't have been correct even in a 1-based loop. step_shape = gears.shape.rectangle and step_shape = nil, which is supposed to default to rectangle according to docs, were drawn differently due to mismatched offset calculations, which led to problems especially noticeable when step_spacing is not zero, e.g: 1) whole graph shifted left too much, drawn partially off-screen. 2) whole graph shifted right too much, and the first bar placed closer to the rest than others. commit dc369fa198281e000afc1bd17326f3a264adf7d1 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 02:09:21 2021 +0700 Fix the "border_width affects graph bar width" bug Graph bars were drawn with lines of border_width thickness instead of 1, i.e. possibly too fat to be readable. commit 1ba73378cf29ed27a6ecd18716394779ceb543d9 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 00:13:03 2021 +0700 Simplify internal values representation in graph even more New values in graph._private.values were pushed to the end, and old values were removed from the beginning of the table. In draw() however newer values are drawn first, at the left edge of the graph, necessitating annoying idx = #values - idx conversions everywhere. In particular in stacked graph that means that values that are drawn together may have different indices. One has to shift the values array in add_value() either way, so one might as well insert new values at the beginning of the array and simplify the rest of the code. This doesn't seem like much yet, but it will prove to be much more convenient and less error-prone in what I'm going to do later. commit f70523ee68dbd6b67050ace4b82903a9b22440c7 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 13:35:10 2021 +0700 Simplify internal values representation in graph widget graph._private.values was either a table of values or a table of tables of values, depending on whether "stack" property is enabled, ... or a mix of both if user forgot to pass the second "group" parameter to graph:add_value(). This resulted in pretty fragile and confusing behavior, e.g. leaving user to wonder, why they see nothing more after setting stack=true for a graph in a previously working rc.lua, and demanding a very careful use of add_value() from the user, or else some values or even entire stacks would mysteriously vanish and errors in draw() get triggered, sometimes after a period of seemingly normal functioning. Now `values` is always a table of tables and the group parameter in add_value() defaults to 1, thus making the non-stacked graph a trivial variant of the stacked graph.
2021-05-20 17:14:03 +02:00
graph_draw_values(self, cr, values_width, values_height, drawn_values_num)
-- Undo the cr:translate() for the border and step shapes
cr:restore()
end
-- Draw the border last so that it overlaps already drawn values
if border_width > 0 then
cr:set_line_width(border_width)
cr:rectangle(border_width/2, border_width/2, width - border_width, height - border_width)
cr:set_source(color(self._private.border_color or prop_fallbacks.border_color))
2016-05-23 07:30:44 +02:00
cr:stroke()
end
end
graph: complete widget overhaul and bug fixes Squashed commit of the following: commit 69821f51fe1e8652715658543e50631ece495090 Author: Alex Belykh <albel727@ngs.ru> Date: Mon May 10 19:17:55 2021 +0700 Refactor property handling in wibox.widget.graph In addition, baseline_value, step_width and step_spacing properties are all set to their default non-nil numeric values during instance creation now. commit 842eb429bf5df4b2ba46b2e6f7646afe89b1a3c3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 9 06:18:45 2021 +0700 Make graph:draw_values() private There's no documentation nor confidence that it's a good API, so I'm hiding it for now. commit 720746780574e4bad14a71fc4174955139c2eb50 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 04:04:13 2021 +0700 Document graph_should_draw_data_group() for posterity commit 7c73f3754ce6ba86b118f1170a6f9e406ab4841d Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:59:21 2021 +0700 Make graph:should_draw_data_group() private There's no confidence whether it's a good API, so I'm hiding it for now. commit b3539e20adb6423118af726ae4f8751ef864b5d2 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:36:44 2021 +0700 Make graph:preprocess_values() private There's no confidence whether it's a good API, so I'm hiding it for now. commit 2df4400e780a6260f8d0ab1c60734fbb11bf3c4f Author: Alex Belykh <albel727@ngs.ru> Date: Thu May 6 10:05:23 2021 +0700 Add spec/wibox/widget/graph_spec.lua commit ce01a9771ab439b8df841f1565aa4aa8a44a5e0d Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 08:16:18 2021 +0700 Improve graph.capacity documentation commit e28fade8f655802f5bf89154169f68d256b8e332 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 07:36:01 2021 +0700 Document graph:compute_drawn_values_num() commit dd68332292465422d08b48e9f0229f143beccbc6 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 05:24:23 2021 +0700 Document graph:pick_data_group_color() method commit ca6dc55f77aca88a6e9cc90bb6fd0045739f41f5 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 04:13:58 2021 +0700 Guard against setting data groups sparsely in graph:add_value() commit cdea5f126f7984e72924f6dffbd312c4fe3b032f Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 02:36:12 2021 +0700 Refactor scaling and baseline choice out of graph:draw() commit 10135deafe8a9390ba66ab657ebb1fb41d549e8c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 21:34:48 2021 +0700 Document graph.nan_color and graph.stack properties commit 0d73bb84ac66b87e16aee472bd1f299549f66a1c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 20:49:24 2021 +0700 Use a better example in docs for the graph.stack property commit c27d160a0826d033d49d6141608c40aa8a0642fa Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 16:05:38 2021 +0700 Document graph.baseline_value property commit 459bc8176c13d4ea32af0dcdc2399d3b929b1c6a Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:53:47 2021 +0700 Document graph.clamp_bars property commit e732ee0b3b007d213e689b98cc00ae7cb141ae08 Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:34 2021 +0700 Improve graph:add_value() and :clear() documentation commit ae73e3aa9f8d60409cbe4cb4da9612f31af4f38e Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:11 2021 +0700 Improve graph min_value/max_value/scale documentation commit a7350996a9def773b91b1631e6c0507e33d8f93b Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:27:36 2021 +0700 Cleanup graph examples to show only relevant things in docs commit 30740c26f4f41c16f4c8a3c339bcb7bd299e14cd Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 14:04:36 2021 +0700 Rename graph.stack_colors property to `group_colors` The colors are used for stacked and non-stacked graphs alike, so the name didn't make sense. Also document and clarify some color stuff. commit 1ea01bfd410971cf0d0c25fd74cd764d5eb7279d Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 21:25:41 2021 +0700 Add an example file for code coverage of some fringe graph cases commit eae68e39553f88a6275a0c65bf040b5d4c262b54 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 20:23:32 2021 +0700 Add step_shape to the graph/nan_color example for code coverage commit 8027147bc2eab34a97b49a428fb8cef27f122bed Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:55:18 2021 +0700 Add example files for graph.nan_color/nan_indication properties commit 89a25a165c52b2c15b6c4d95ecb777a9067e66f9 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:33:07 2021 +0700 Make widget graph:add_value(v) default v to NaN 0 as a default makes no sense and silences the error that is failing to specify a value, to which the user should rather be alerted. The graph widget has sane NaN handling now and will do just that. commit 2835d552440be48d356a7f7b1d64963696dd550b Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:20:48 2021 +0700 Implement graph.nan_color and nan_indication properties Now, whenever there's a NaN among values and graph.nan_indication is set, the corresponding area, where the value bar should have been drawn, is filled with the nan_color from top to bottom. The painting is done after all data is rendered, to make sure that it won't be overpainted and go unnoticed. NaNs among graph's values are inevitably a sign of some error and it is arguably not a sane default behavior to simply not render them at all. The user should take immediate note of any errors by default, instead of wondering at a mysteriously empty graph widget, which is why nan_indication is set to true by default. If the user wants to silence NaNs, they can always simply set it to false. nan_color if unset, defaults to a noticeable yellow-black pattern. As a direct and planned consequence, stacked graphs with negative values in them trigger the NaN handling too. But silencing NaN handling for those graphs is also possible and semi-sane, because then NaNs and negatives are ignored and the graph behaves (almost) like they were zeros. commit 9c1f8f08b400e01e19c534f3810c9c9ab485760c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 26 08:10:19 2021 +0700 Refactor out graph_map_value_to_widget_coordinates() function commit 1905991ddecabdae3e15ffb8ee5b18a1bee1a711 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 28 13:39:29 2021 +0700 Add an example file for graph.baseline_value property commit b31b39d66f4c037a9484b71a3f910703ae01fac0 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 07:03:49 2021 +0700 Add an example file for graph.clamp_bars=false/true comparison commit a1fc850e3047f9ee2c9ddeffacfbabf3ded34171 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 04:06:45 2021 +0700 Add an example file for graphs with negative values and shapes commit 2f012f8ff1bb5094c14e724a61e59a2a3a296bc8 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 01:06:05 2021 +0700 Add an example file for a graph displaying negative values commit 7b14fbd59a4c4c51962fd3110a1a900dd5f4eb98 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 11:18:26 2021 +0700 Add an example file for graphs with step_width < 1 commit dfd9a54544b637e2fbe3d1325f9cc24b3f4d2313 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 10:17:38 2021 +0700 Add an example file for graph.stack=false/true comparison Should also demonstrate that multiple data groups are possible for non-stacked graphs too. commit 5b532106c3fd255f9b66a66cfb88d977b489ab5d Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 09:34:14 2021 +0700 Add an example file for disabling graph data groups with nil colors commit ac6f1083c02fcb428f089a616f9b0ebaa5496b6c Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:57:25 2021 +0700 Add an example file for step_shape/width/spacing on stacked graphs commit b3703db6a5139e73e72c1cfd198ece2cd9be7df2 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:43:56 2021 +0700 Add an example file for scale=false/true on stacked graph commit b000aed4a12cb44fb501e1c8432a71a14b2f8f3f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 20 08:22:19 2021 +0700 Document deprecation of graph's width and height commit cb0ad617bd6c83a499896797318e3b0b588c3566 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 07:53:37 2021 +0700 Deprecate width and height properties of wibox.widget.graph commit 8c64e14ad91a074b784166a7054e53444cccb9d2 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 04:37:59 2021 +0700 Refactor graph:draw() to separate concerns Firstly, summation of group values for stacked graphs is extracted into graph:preprocess_values(). This makes graph:draw() smaller and exposes the fact, that stacked graphs are almost entirely just a form of a more general concept: pre-render data manipulation. It's something a user might want to customize, and now they can do it by monkey-patching the method. Secondly, the logic for deciding, if and with which color a data group should be drawn, is extracted into graph:should_draw_data_group() and get_data_group_color() methods respectively. This makes it customizable too and makes more obvious the fact, that it is a general thing that has nothing to do with stacked graphs in particular. With this there are only 5 lines remaining in graph:draw(), that could be said to be specific to stacked graphs, all pertaining to the `prev_y` variable, which merely preserves the bar coordinates from a previous data group. commit 2a32b6305d893f413303728a0a3da1dcd9a5591e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 22:52:37 2021 +0700 Pointlessly optimize graph:add_value() 1) Purge old values first, then insert the new one, if needed. Since Lua grows arrays in power of two steps, and our guess_capacity() also takes care to return even integers, it would be a shame to overstep the capacity by one by inserting first and thus getting the table size doubled only to never use the most of it. 2) Use a removal procedure that doesn't call the slow length operator at all. Previous code called it twice (once in table.remove) for every removed value. commit 680ea8f5d81e9040afb4662b3229e937e4b89e02 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:23:38 2021 +0700 Set graph data series color before the painting loop commit b970f0f221ae76250f2a94433ff79d653caa6e21 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:05:37 2021 +0700 Drop a large no longer necessary `if` in graph:draw_values() This commit is almost purely a whitespace change. commit 6b4f0541c70ec99d7f6e276bae64939f337120d6 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:00:50 2021 +0700 Extract values rendering code into graph:draw_values() method This is to avoid ungodly if nesting levels, which are needed to bail out of drawing values early and skip to drawing the border. The next commit will get rid of the if. commit e0950e4f99d1ea1c4b8cea2cc67629fa40030c2a Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 14:50:20 2021 +0700 Track graph draw() usage to guess necessary array capacity The last step of divorcing add_values() from width calculations, while ensuring that user doesn't observe a lack of data to draw. The widget now tracks an approximate number of values that it has been asked to draw() in _private.last_drawn_values_num, and truncates its values array based on that (if not overridden by the capacity property). commit 2e3d542282f35e7c0fed454da0f2eada0fcf3b78 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 13:35:12 2021 +0700 Add `capacity` property to graph widget This is the second step of divorcing add_values() from calculations involving widths. How many values are stored and how many values are drawn are distinct concepts. There isn't even a single definitive "how many values are drawn" because the widget could be rendered in several places with distinct widths simultaneously, and in every such place the data must be drawn appropriately, e.g. with auto-scaling, that takes only the visible data points into account. And the draw() method can do that now. The default capacity value of nil uses heuristics based on widget's _private.width to guess some capacity that would tolerate some dynamic widget resizing. If the user finds the heuristic failing them, they can set the capacity property to the desired maximum number of values that will be kept in the _private.values array (in each data group). commit 08d4a80686f7fe363551c35589a460839f60853e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 03:27:18 2021 +0700 Extract values size computation into compute_drawn_values_num() This is the first step of making add_values() not concern itself (directly) with widget dimensions. The user can monkey-patch the method to override how many values are (logically) drawn, because it affects graph auto-scaling and can be useful for rendering of step_shape-s that paint outside of their step_width. I didn't make it a full-blown function-valued property, because it seems a fringe functionality, which I'm too lazy to document. commit 3c5617c98ab1147618ed22e8414a77fc56b45f6e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 02:32:29 2021 +0700 Copy graph widget prototype methods over in bulk commit 169538837ece41a03d02ba8fbc0e4097062b67a3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun Apr 25 05:58:01 2021 +0700 Remove graph.baseline_y property After giving it some thought I've concluded, that it is unlikely to be useful as it is now. I'd better reserve the name for some more sensible functionality, like fixing the baseline_value defined axis at the given widget coordinate, adjusting the scaling accordingly, so min_value/max_value won't generally correspond to widget top and bottom anymore. commit 202ffaa9a0b820c7c471e1121d01b91f8f76cd38 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 12:06:55 2021 +0700 Add baseline_y and baseline_value properties to graph widget Now values in graph have a *baseline*, tunable with baseline_y and baseline_value properties. It is the (for now invisible) horizontal line from which all bars in the graph grow. I call it a "baseline" instead of "axis", because it doesn't actually have to correspond to the 0 value. But it can, and actually does now by default. baseline_y and baseline_value properties determine the vertical placement of the baseline in the widget. baseline_y is the position of the baseline in normalized widget coordinates, e.g. 0 corresponds to widget's top, 1 - to widget's bottom, and 0.5 - to widget's middle. So if e.g. one sets baseline_y = 0, then no matter how the widget is resized and how its values are scaled, bars in it will be drawn starting from the top. baseline_value is the position of the baseline in value coordinates. So if one sets baseline_value = 10, then no matter what happens, bars for values greater than 10 will be drawn as growing up from this line, and bars for values smaller as growing down. Where and if it will be seen in the widget, depends on the scaling. baseline_value = 0 is the familiar zero axis. baseline_y is unset by default and has priority, i.e. if it's set, then baseline_value is ignored. baseline_value defaults to 0. Prior to this commit, bars were always drawn from the bottom edge of the widget, or, in the new terms, the widget had baseline_y = 1. But it was indistinguishable from baseline_value = 0, because the widget clamped all negative values to 0, so the difference between the two could never be observed. baseline_value = 0 is clearly more useful and familiar to people, so now it is in force by default. commit df7e19abaebf0a871b5ca217b4cc3365533267a6 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 11:18:12 2021 +0700 Move value clamping from graph:add_value() into draw() Previously all values were forcibly clamped between 0 (not tunable at all) and max_value (1 by default). Thus graphs with negative values were not an option. NaN values were conflated with zeros, even though it may be useful to maintain the distinction and render them differently. And if user happened to dynamically change the max_value property, they discovered, that their data got truncated. Preserving user data as it was given is more flexible and makes things easier to debug. This commit does exactly that. So what happens now to the newly allowed values during draw()? There are 3 new cases to consider: 1) NaNs 2) Negatives 3) Values outside of min_value..max_value range NaNs are presently simply not drawn. An empty place is reserved where the value should've been, but nothing else is done. This is distinct from how a zero is handled, e.g. if step_shape is set, it is called with the height parameter = 0, which can still end up drawing something, depending on the shape. One could also consider addition of nan_color/nan_shape properties which could be drawn to alert user to a gap in their data. Negative values necessitate introduction of the concept of the zero axis, from which they will be drawn DOWN, in contrast to positive values. Where should this axis be drawn? How should step_shape-s with inherent direction, e.g. arrows, be drawn for negatives? This will be addressed in the next commit. Values outside of min/max_value range can imply shapes that go beyond widget dimensions. An arrow shape that goes beyond the roof is visibly different from an arrow that saturates and merely touches the widget top, and the user may reasonably desire either behavior. Therefore this commit introduces a boolean clamp_bars property, which defaults to true for reasons of robustness and backward compatibility with the behaviour prior to this commit. The property of course affects only the display of the values, the actual user data stays intact. commit c9c24fb1ab741cd6bb8074971d568e0646c51532 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:29:01 2021 +0700 Use rectangles for drawing graph bars of all sizes I have benchmarked various bar drawing strategies and graph sizes and found out that drawing graph bars with rectangles is as fast or even marginally faster than using fat lines. Thus the code that tries to special-case 1px bars as lines seems to be not worth the additional complexity it introduces, and this commit removes it. Incidentally, I've also established, that rectangles are more robust. If one draws graph bars and feeds cairo very large coordinates (~2^62), then corresponding rectangles fail to render, but all the other rectangles in the same series get rendered successfully, whereas a single large coordinate in a move_to/line_to() sequence is enough to cause all lines in the batch to fail to render. commit bc7f4be5e06405b5aa7fb443a5ace2b9b9575b1f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:01:42 2021 +0700 Cache cairo methods used in the inner loop of graph:draw() I have benchmarked the draw() method, both on cairo xcb and on argb32 image surfaces, and have discovered that cairo context method calls are unnaturally slow, to the point, that almost nothing else in the function matters. For example, drawing bars with vertical lines made the whole draw() function almost 2 times slower than doing the same with rectangles, merely because the former requires two method calls (move_to/line_to), and the latter only one. Simply caching hotpath cairo methods in local variables, reduced the difference between the drawing methods to a negligible one and made the whole function 4 times faster on a typical 100-bar graph, (1.4ms to 0.33ms on my laptop) thus bringing closer my secret dream of drawing live audio data with awesome graphs (kidding). I guess, LGI does something not very optimal there, like gobject-introspecting the method every time, or something. There might be some untapped optimization potential, possibly in upstream, like a method cache, from which everything in awesome could benefit, even though I imagine not many things are so call-intensive as the graph widget, to really feel the difference. commit 538df25bdf02b53d0bbe81809eef2e6fdb05ff6f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 07:32:37 2021 +0700 Use 1-based index in the inner graph draw loop It's used there only once for a calculation and then several times to index some tables, and index 0 is bad for lua tables performance-wise. commit 7412546916cd0a0a490130a120255ffb719398b8 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:55:12 2021 +0700 Leave graph.max_value uninitialized at construction This is a user-visible change, but the previous behavior was probably not what user expected anyway, namely with graph.scale set the graph mysteriously couldn't zoom in onto values that are smaller than 1. commit eb81d7929a161e79c13262cfa73ac2e6a820e8ed Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:48:29 2021 +0700 Allow graph.max_value be nil This makes its behave symmetrically to min_value. Now it defaults to 1, when graph.scale is not set, and to the maximum value in the graph data, when graph.scale is set. commit f622a8430cbfde7007b58d7f4c9c76b08c91e997 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 12:01:13 2021 +0700 Calculate max values num in graph:add_value() more precisely At most so many values were kept in the widget values array, as there are pixels of usable widget width (i.e. sans border). This is exactly right for the surely most common case of graphs with 1px-wide bars, but is more than needed for graphs with step_width+step_spacing>1, and not enough for graphs with subpixel bars. Moreover this made graph autoscaling behave weirdly in graphs with thicker bars, because old values survived longer in the array and kept affecting min_value/max_value calculations even after they were shifted off-screen. The maximum number of kept values is now calculated exactly as the number of values that can be displayed at once, taking step_width and step_spacing into account. It might be worth adding a max_values_num property to the graph to let user override this calculation, in case they draw shapes in step_shape() outside of its supposed rectangle, so our assumptions of visibility are wrong, or the widget often gets resized, resulting in the annoyance of visibly truncated series. commit 0aa63249a18641251a1bf77423739164f0eab39f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 02:30:13 2021 +0700 Fix border_color/border_width bugs in graph widget add_value() wrongly assumed that border_width is always set, when border_color is, so if user set only the latter, an error occurred. add_value() assumed that border_width is always < width which led to awesome hanging up in an infinite loop, should that fail to be the case. The graph border_width property was driven by the border_color property, instead of the other way round. If border_color wasn't set, the border wasn't drawn all, even if user sets non-zero border_width, which is a confusing behavior, in contrast to all other widgets, which simply use a plethora of fallback colors to draw the border, when needed. This also meant that, despite being documented, the beautiful.graph_border_color fallback color could not and was not ever used. If the border_color was set though, an unset border_width was immediately assumed to be 1 instead of 0, also contrary to what other widgets do. Theme colors shouldn't influence widget layout, so this commit fixes this behavior to: 1) border_width always defaults to 0, if unset. 2) border_color falls back to beautiful.graph_border_color or white. Maybe one should also add a beautiful.graph_border_width theme variable, like some widgets do. commit dad4a3253b8fef971fb82720d2a407fcc0a879ad Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 03:11:27 2021 +0700 Implement multiple data series drawing for non-stacked graph The widget can already hold and draw multiple data groups, so it makes little sense to limit drawing only to the first group when graph.stack = false, probably leading to user's confusion. Now user can call add_value(v, group) with group > 1 and the data series will be drawn (over groups with lesser id-s) with graph.stack_colors[group] color, if stack_colors is set, or with graph.color, if stack_colors isn't set. Thus nothing visibly changes for all current users of non-stacked graphs with only single data series, but the new functionality is now there for those who need it. One can't fully emulate this functionality, e.g. with multiple graphs widgets in a wibox.layout.stack, because data series in a single widget can enjoy a common auto-scaling. commit 3e645c976184ab5f50d625271c25062db9ab7ed9 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:59:12 2021 +0700 Don't draw a stacked graph data group if there's no color for it If stack_colors[idx] is nil for the group `idx`, don't draw it at all (as opposed, e.g. to the effect of drawing it with a transparent color, or as though it had all values == 0, which both produce different results). This code is still subtly different from prior behavior, because now user can have a non-contiguous stack_colors table and turn the display of any data group on and off at will, whereas the code two commits earlier would just stop rendering as soon as ipairs(stack_colors) does, i.e. no data series were drawn past the first nil hole. This commit only looks large, but is mostly a whitespace change due to nesting from a pair of wrapping ifs. commit 8c0273786de1ade66bdd977699f98b588da9f487 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:32:42 2021 +0700 Unify stacked and non-stacked graph rendering Now stacked graphs support everything the nonstacked do, like step_width ~= 1 and custom step_shape-s. Technical notes: This code no longer calls cr:stroke() for every single value in a stacked graph, but rather paints a whole series and then cr:stroke/fill()-s it at all once with its color, improving performance considerably. Additionally the graph no longer relies on the presence of the stack_colors property to render series. It draws all data that it has, and, if there's no corresponding color in stack_colors, falls back to graph.color. This will be changed in the next commit to the prior behavior of only drawing those series for which there's a color in the stack_colors table, but that would've made this commit's diff much larger due to required nesting ifs over large code chunks. This commit also takes care to properly handle NaNs and negative values. Those are currently (almost) guaranteed to not occur in the values array due to clipping in graph:add_value(), but I intend to eventually drop the clipping, because it does nothing that draw() really relies upon, and allowing unclipped and NaN values is more flexible and less surprising, e.g. when user dynamically toggles graph.scale. Eventually I intend to implement drawing negative values in non-stacked graphs and possibly even in the stacked ones. commit 38c6589c51b2aa057d7c2f40182ecf32b5b2ff5e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 10:04:15 2021 +0700 Convert graph:draw() to use `self` commit f21f1b2550426df1c4ea8abde079b1ccef84b394 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 12:27:26 2021 +0700 Fix scaling for stacked graphs Stacked graphs have cumulative heights, values from all groups that end up in the same graph bar are effectively summed. Previous code didn't take that into account when calculating min/max_value for the purposes of rescaling, but simply examined every value separately. This code adds up all values up front and finds min/max among those, to determine proper scaling that would fit the graph exactly. commit 1ce4c7bf63bdd6c99c195d668347bec49355e921 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 09:49:35 2021 +0700 Draw shaped graph bars only up to the baseline Prior to this commit, every step_shape bar was, for some reason, drawn with height = full widget's height, regardless of the value it represents. So for the most bars their lower end was far outside the box and therefore clipped off. So e.g. one couldn't see the lower round edges of rounded rectangles. This commit fixes that by appropriately varying the height that is passed into step_shape(), so that lower edges of the bars always exactly touch the lower edge of the graph. If the user wants to restore the prior behavior, they can simply ignore the passed-in height in step_shape() and just always draw their shape with height = full widget height. But they couldn't replicate the effect of this commit in their rc.lua without essentially writing their own graph widget. commit e25185b08da23745b5815f85f6e9e43222ed2294 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 08:42:54 2021 +0700 Avoid incremental transform shifting during graph bar rendering Instead of letting addition errors and cognitive load accumulate simply use the same offset calculation that is used for lines and reset the transform back to the top-left corner after every drawn bar. It's conceptually simpler, and will be useful later for stacked graphs and more. It also fixes a discrepancy between plain and step_shape rendering. Prior to this commit, bar coloring occurred with a disturbed transform, which was centered at the last drawn bar, wherever it happened to be. This meant that nontrivial colors, like gradients and images, were arbitrarily offset when step_shape was used. commit f3e98866d8583c35d0b557c09f529299edc9ba2c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:45:29 2021 +0700 Refactor out graph bar height expression into a variable commit f54528d6892f5aaa4595917853c9e2130f743fdb Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:20:25 2021 +0700 Draw graph bars with lines only when step_width == 1 Before this commit graph bars of step_width < 1 were drawn with lines of width 1, but that's a bit unfortunate, because it led to much overpainting, while still ending up with a graphically incorrect result. Let's draw such bars with rectangles and let cairo do things with subpixels, getting more graphically accurate result, while still keeping the common case of step_width == 1 (supposedly) fast-drawn with lines. commit b6515dca67c18b78eb5657378131a62ab43d4cfc Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:54:07 2021 +0700 Fix unsharp edges when drawing graph bars with rectangles The 0.5 offset is only helpful for 1-width strokes, not for fills. commit 7f3db8f07e82f821bcd1dc418d66564f537609d6 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:21:01 2021 +0700 Do coordinate transformations even if the value isn't drawn The graph presently doesn't draw values < 0, but there was a discrepancy between drawing with and without step_shape. The default line/rectangle drawing reserves an empty place where the skipped value should've been, but no such empty place is reserved, when step_shape is used. This commit fixes that. Skipped values always have their empty space now. commit 7b731ebfe96af7a9dba9c57371da6761cf22c00c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:03:30 2021 +0700 Fix graph bar scaling when min_value is not 0 Previous calculation lead to underscaling of values so that even value = max_value wasn't drawn to full height. commit dca59fd8b7fff0e7e646f2dfc6bd92047028e5a8 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:18:40 2021 +0700 Calculate the horizontal graph bar offset only when needed commit 940ddf4b9825e9c3a91d276208c089d8c74e49db Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:17:23 2021 +0700 Call cr:move_to() only when drawing the graph with lines It's redundant otherwise and might even interfere with user's drawing in step_shape(). commit b5d5cd10ca29fb29cd751faaa95137c2fe89ff63 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:04:06 2021 +0700 Fix wrong horizontal offset calculations in widget.graph Indices in the loop are zero-based, so things like i-1 and i>1 aren't necessary and actually wouldn't have been correct even in a 1-based loop. step_shape = gears.shape.rectangle and step_shape = nil, which is supposed to default to rectangle according to docs, were drawn differently due to mismatched offset calculations, which led to problems especially noticeable when step_spacing is not zero, e.g: 1) whole graph shifted left too much, drawn partially off-screen. 2) whole graph shifted right too much, and the first bar placed closer to the rest than others. commit dc369fa198281e000afc1bd17326f3a264adf7d1 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 02:09:21 2021 +0700 Fix the "border_width affects graph bar width" bug Graph bars were drawn with lines of border_width thickness instead of 1, i.e. possibly too fat to be readable. commit 1ba73378cf29ed27a6ecd18716394779ceb543d9 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 00:13:03 2021 +0700 Simplify internal values representation in graph even more New values in graph._private.values were pushed to the end, and old values were removed from the beginning of the table. In draw() however newer values are drawn first, at the left edge of the graph, necessitating annoying idx = #values - idx conversions everywhere. In particular in stacked graph that means that values that are drawn together may have different indices. One has to shift the values array in add_value() either way, so one might as well insert new values at the beginning of the array and simplify the rest of the code. This doesn't seem like much yet, but it will prove to be much more convenient and less error-prone in what I'm going to do later. commit f70523ee68dbd6b67050ace4b82903a9b22440c7 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 13:35:10 2021 +0700 Simplify internal values representation in graph widget graph._private.values was either a table of values or a table of tables of values, depending on whether "stack" property is enabled, ... or a mix of both if user forgot to pass the second "group" parameter to graph:add_value(). This resulted in pretty fragile and confusing behavior, e.g. leaving user to wonder, why they see nothing more after setting stack=true for a graph in a previously working rc.lua, and demanding a very careful use of add_value() from the user, or else some values or even entire stacks would mysteriously vanish and errors in draw() get triggered, sometimes after a period of seemingly normal functioning. Now `values` is always a table of tables and the group parameter in add_value() defaults to 1, thus making the non-stacked graph a trivial variant of the stacked graph.
2021-05-20 17:14:03 +02:00
function graph:fit(_, width, height)
return width, height
2016-05-23 07:30:44 +02:00
end
graph: complete widget overhaul and bug fixes Squashed commit of the following: commit 69821f51fe1e8652715658543e50631ece495090 Author: Alex Belykh <albel727@ngs.ru> Date: Mon May 10 19:17:55 2021 +0700 Refactor property handling in wibox.widget.graph In addition, baseline_value, step_width and step_spacing properties are all set to their default non-nil numeric values during instance creation now. commit 842eb429bf5df4b2ba46b2e6f7646afe89b1a3c3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 9 06:18:45 2021 +0700 Make graph:draw_values() private There's no documentation nor confidence that it's a good API, so I'm hiding it for now. commit 720746780574e4bad14a71fc4174955139c2eb50 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 04:04:13 2021 +0700 Document graph_should_draw_data_group() for posterity commit 7c73f3754ce6ba86b118f1170a6f9e406ab4841d Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:59:21 2021 +0700 Make graph:should_draw_data_group() private There's no confidence whether it's a good API, so I'm hiding it for now. commit b3539e20adb6423118af726ae4f8751ef864b5d2 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:36:44 2021 +0700 Make graph:preprocess_values() private There's no confidence whether it's a good API, so I'm hiding it for now. commit 2df4400e780a6260f8d0ab1c60734fbb11bf3c4f Author: Alex Belykh <albel727@ngs.ru> Date: Thu May 6 10:05:23 2021 +0700 Add spec/wibox/widget/graph_spec.lua commit ce01a9771ab439b8df841f1565aa4aa8a44a5e0d Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 08:16:18 2021 +0700 Improve graph.capacity documentation commit e28fade8f655802f5bf89154169f68d256b8e332 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 07:36:01 2021 +0700 Document graph:compute_drawn_values_num() commit dd68332292465422d08b48e9f0229f143beccbc6 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 05:24:23 2021 +0700 Document graph:pick_data_group_color() method commit ca6dc55f77aca88a6e9cc90bb6fd0045739f41f5 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 04:13:58 2021 +0700 Guard against setting data groups sparsely in graph:add_value() commit cdea5f126f7984e72924f6dffbd312c4fe3b032f Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 02:36:12 2021 +0700 Refactor scaling and baseline choice out of graph:draw() commit 10135deafe8a9390ba66ab657ebb1fb41d549e8c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 21:34:48 2021 +0700 Document graph.nan_color and graph.stack properties commit 0d73bb84ac66b87e16aee472bd1f299549f66a1c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 20:49:24 2021 +0700 Use a better example in docs for the graph.stack property commit c27d160a0826d033d49d6141608c40aa8a0642fa Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 16:05:38 2021 +0700 Document graph.baseline_value property commit 459bc8176c13d4ea32af0dcdc2399d3b929b1c6a Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:53:47 2021 +0700 Document graph.clamp_bars property commit e732ee0b3b007d213e689b98cc00ae7cb141ae08 Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:34 2021 +0700 Improve graph:add_value() and :clear() documentation commit ae73e3aa9f8d60409cbe4cb4da9612f31af4f38e Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:11 2021 +0700 Improve graph min_value/max_value/scale documentation commit a7350996a9def773b91b1631e6c0507e33d8f93b Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:27:36 2021 +0700 Cleanup graph examples to show only relevant things in docs commit 30740c26f4f41c16f4c8a3c339bcb7bd299e14cd Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 14:04:36 2021 +0700 Rename graph.stack_colors property to `group_colors` The colors are used for stacked and non-stacked graphs alike, so the name didn't make sense. Also document and clarify some color stuff. commit 1ea01bfd410971cf0d0c25fd74cd764d5eb7279d Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 21:25:41 2021 +0700 Add an example file for code coverage of some fringe graph cases commit eae68e39553f88a6275a0c65bf040b5d4c262b54 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 20:23:32 2021 +0700 Add step_shape to the graph/nan_color example for code coverage commit 8027147bc2eab34a97b49a428fb8cef27f122bed Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:55:18 2021 +0700 Add example files for graph.nan_color/nan_indication properties commit 89a25a165c52b2c15b6c4d95ecb777a9067e66f9 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:33:07 2021 +0700 Make widget graph:add_value(v) default v to NaN 0 as a default makes no sense and silences the error that is failing to specify a value, to which the user should rather be alerted. The graph widget has sane NaN handling now and will do just that. commit 2835d552440be48d356a7f7b1d64963696dd550b Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:20:48 2021 +0700 Implement graph.nan_color and nan_indication properties Now, whenever there's a NaN among values and graph.nan_indication is set, the corresponding area, where the value bar should have been drawn, is filled with the nan_color from top to bottom. The painting is done after all data is rendered, to make sure that it won't be overpainted and go unnoticed. NaNs among graph's values are inevitably a sign of some error and it is arguably not a sane default behavior to simply not render them at all. The user should take immediate note of any errors by default, instead of wondering at a mysteriously empty graph widget, which is why nan_indication is set to true by default. If the user wants to silence NaNs, they can always simply set it to false. nan_color if unset, defaults to a noticeable yellow-black pattern. As a direct and planned consequence, stacked graphs with negative values in them trigger the NaN handling too. But silencing NaN handling for those graphs is also possible and semi-sane, because then NaNs and negatives are ignored and the graph behaves (almost) like they were zeros. commit 9c1f8f08b400e01e19c534f3810c9c9ab485760c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 26 08:10:19 2021 +0700 Refactor out graph_map_value_to_widget_coordinates() function commit 1905991ddecabdae3e15ffb8ee5b18a1bee1a711 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 28 13:39:29 2021 +0700 Add an example file for graph.baseline_value property commit b31b39d66f4c037a9484b71a3f910703ae01fac0 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 07:03:49 2021 +0700 Add an example file for graph.clamp_bars=false/true comparison commit a1fc850e3047f9ee2c9ddeffacfbabf3ded34171 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 04:06:45 2021 +0700 Add an example file for graphs with negative values and shapes commit 2f012f8ff1bb5094c14e724a61e59a2a3a296bc8 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 01:06:05 2021 +0700 Add an example file for a graph displaying negative values commit 7b14fbd59a4c4c51962fd3110a1a900dd5f4eb98 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 11:18:26 2021 +0700 Add an example file for graphs with step_width < 1 commit dfd9a54544b637e2fbe3d1325f9cc24b3f4d2313 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 10:17:38 2021 +0700 Add an example file for graph.stack=false/true comparison Should also demonstrate that multiple data groups are possible for non-stacked graphs too. commit 5b532106c3fd255f9b66a66cfb88d977b489ab5d Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 09:34:14 2021 +0700 Add an example file for disabling graph data groups with nil colors commit ac6f1083c02fcb428f089a616f9b0ebaa5496b6c Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:57:25 2021 +0700 Add an example file for step_shape/width/spacing on stacked graphs commit b3703db6a5139e73e72c1cfd198ece2cd9be7df2 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:43:56 2021 +0700 Add an example file for scale=false/true on stacked graph commit b000aed4a12cb44fb501e1c8432a71a14b2f8f3f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 20 08:22:19 2021 +0700 Document deprecation of graph's width and height commit cb0ad617bd6c83a499896797318e3b0b588c3566 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 07:53:37 2021 +0700 Deprecate width and height properties of wibox.widget.graph commit 8c64e14ad91a074b784166a7054e53444cccb9d2 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 04:37:59 2021 +0700 Refactor graph:draw() to separate concerns Firstly, summation of group values for stacked graphs is extracted into graph:preprocess_values(). This makes graph:draw() smaller and exposes the fact, that stacked graphs are almost entirely just a form of a more general concept: pre-render data manipulation. It's something a user might want to customize, and now they can do it by monkey-patching the method. Secondly, the logic for deciding, if and with which color a data group should be drawn, is extracted into graph:should_draw_data_group() and get_data_group_color() methods respectively. This makes it customizable too and makes more obvious the fact, that it is a general thing that has nothing to do with stacked graphs in particular. With this there are only 5 lines remaining in graph:draw(), that could be said to be specific to stacked graphs, all pertaining to the `prev_y` variable, which merely preserves the bar coordinates from a previous data group. commit 2a32b6305d893f413303728a0a3da1dcd9a5591e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 22:52:37 2021 +0700 Pointlessly optimize graph:add_value() 1) Purge old values first, then insert the new one, if needed. Since Lua grows arrays in power of two steps, and our guess_capacity() also takes care to return even integers, it would be a shame to overstep the capacity by one by inserting first and thus getting the table size doubled only to never use the most of it. 2) Use a removal procedure that doesn't call the slow length operator at all. Previous code called it twice (once in table.remove) for every removed value. commit 680ea8f5d81e9040afb4662b3229e937e4b89e02 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:23:38 2021 +0700 Set graph data series color before the painting loop commit b970f0f221ae76250f2a94433ff79d653caa6e21 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:05:37 2021 +0700 Drop a large no longer necessary `if` in graph:draw_values() This commit is almost purely a whitespace change. commit 6b4f0541c70ec99d7f6e276bae64939f337120d6 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:00:50 2021 +0700 Extract values rendering code into graph:draw_values() method This is to avoid ungodly if nesting levels, which are needed to bail out of drawing values early and skip to drawing the border. The next commit will get rid of the if. commit e0950e4f99d1ea1c4b8cea2cc67629fa40030c2a Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 14:50:20 2021 +0700 Track graph draw() usage to guess necessary array capacity The last step of divorcing add_values() from width calculations, while ensuring that user doesn't observe a lack of data to draw. The widget now tracks an approximate number of values that it has been asked to draw() in _private.last_drawn_values_num, and truncates its values array based on that (if not overridden by the capacity property). commit 2e3d542282f35e7c0fed454da0f2eada0fcf3b78 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 13:35:12 2021 +0700 Add `capacity` property to graph widget This is the second step of divorcing add_values() from calculations involving widths. How many values are stored and how many values are drawn are distinct concepts. There isn't even a single definitive "how many values are drawn" because the widget could be rendered in several places with distinct widths simultaneously, and in every such place the data must be drawn appropriately, e.g. with auto-scaling, that takes only the visible data points into account. And the draw() method can do that now. The default capacity value of nil uses heuristics based on widget's _private.width to guess some capacity that would tolerate some dynamic widget resizing. If the user finds the heuristic failing them, they can set the capacity property to the desired maximum number of values that will be kept in the _private.values array (in each data group). commit 08d4a80686f7fe363551c35589a460839f60853e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 03:27:18 2021 +0700 Extract values size computation into compute_drawn_values_num() This is the first step of making add_values() not concern itself (directly) with widget dimensions. The user can monkey-patch the method to override how many values are (logically) drawn, because it affects graph auto-scaling and can be useful for rendering of step_shape-s that paint outside of their step_width. I didn't make it a full-blown function-valued property, because it seems a fringe functionality, which I'm too lazy to document. commit 3c5617c98ab1147618ed22e8414a77fc56b45f6e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 02:32:29 2021 +0700 Copy graph widget prototype methods over in bulk commit 169538837ece41a03d02ba8fbc0e4097062b67a3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun Apr 25 05:58:01 2021 +0700 Remove graph.baseline_y property After giving it some thought I've concluded, that it is unlikely to be useful as it is now. I'd better reserve the name for some more sensible functionality, like fixing the baseline_value defined axis at the given widget coordinate, adjusting the scaling accordingly, so min_value/max_value won't generally correspond to widget top and bottom anymore. commit 202ffaa9a0b820c7c471e1121d01b91f8f76cd38 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 12:06:55 2021 +0700 Add baseline_y and baseline_value properties to graph widget Now values in graph have a *baseline*, tunable with baseline_y and baseline_value properties. It is the (for now invisible) horizontal line from which all bars in the graph grow. I call it a "baseline" instead of "axis", because it doesn't actually have to correspond to the 0 value. But it can, and actually does now by default. baseline_y and baseline_value properties determine the vertical placement of the baseline in the widget. baseline_y is the position of the baseline in normalized widget coordinates, e.g. 0 corresponds to widget's top, 1 - to widget's bottom, and 0.5 - to widget's middle. So if e.g. one sets baseline_y = 0, then no matter how the widget is resized and how its values are scaled, bars in it will be drawn starting from the top. baseline_value is the position of the baseline in value coordinates. So if one sets baseline_value = 10, then no matter what happens, bars for values greater than 10 will be drawn as growing up from this line, and bars for values smaller as growing down. Where and if it will be seen in the widget, depends on the scaling. baseline_value = 0 is the familiar zero axis. baseline_y is unset by default and has priority, i.e. if it's set, then baseline_value is ignored. baseline_value defaults to 0. Prior to this commit, bars were always drawn from the bottom edge of the widget, or, in the new terms, the widget had baseline_y = 1. But it was indistinguishable from baseline_value = 0, because the widget clamped all negative values to 0, so the difference between the two could never be observed. baseline_value = 0 is clearly more useful and familiar to people, so now it is in force by default. commit df7e19abaebf0a871b5ca217b4cc3365533267a6 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 11:18:12 2021 +0700 Move value clamping from graph:add_value() into draw() Previously all values were forcibly clamped between 0 (not tunable at all) and max_value (1 by default). Thus graphs with negative values were not an option. NaN values were conflated with zeros, even though it may be useful to maintain the distinction and render them differently. And if user happened to dynamically change the max_value property, they discovered, that their data got truncated. Preserving user data as it was given is more flexible and makes things easier to debug. This commit does exactly that. So what happens now to the newly allowed values during draw()? There are 3 new cases to consider: 1) NaNs 2) Negatives 3) Values outside of min_value..max_value range NaNs are presently simply not drawn. An empty place is reserved where the value should've been, but nothing else is done. This is distinct from how a zero is handled, e.g. if step_shape is set, it is called with the height parameter = 0, which can still end up drawing something, depending on the shape. One could also consider addition of nan_color/nan_shape properties which could be drawn to alert user to a gap in their data. Negative values necessitate introduction of the concept of the zero axis, from which they will be drawn DOWN, in contrast to positive values. Where should this axis be drawn? How should step_shape-s with inherent direction, e.g. arrows, be drawn for negatives? This will be addressed in the next commit. Values outside of min/max_value range can imply shapes that go beyond widget dimensions. An arrow shape that goes beyond the roof is visibly different from an arrow that saturates and merely touches the widget top, and the user may reasonably desire either behavior. Therefore this commit introduces a boolean clamp_bars property, which defaults to true for reasons of robustness and backward compatibility with the behaviour prior to this commit. The property of course affects only the display of the values, the actual user data stays intact. commit c9c24fb1ab741cd6bb8074971d568e0646c51532 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:29:01 2021 +0700 Use rectangles for drawing graph bars of all sizes I have benchmarked various bar drawing strategies and graph sizes and found out that drawing graph bars with rectangles is as fast or even marginally faster than using fat lines. Thus the code that tries to special-case 1px bars as lines seems to be not worth the additional complexity it introduces, and this commit removes it. Incidentally, I've also established, that rectangles are more robust. If one draws graph bars and feeds cairo very large coordinates (~2^62), then corresponding rectangles fail to render, but all the other rectangles in the same series get rendered successfully, whereas a single large coordinate in a move_to/line_to() sequence is enough to cause all lines in the batch to fail to render. commit bc7f4be5e06405b5aa7fb443a5ace2b9b9575b1f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:01:42 2021 +0700 Cache cairo methods used in the inner loop of graph:draw() I have benchmarked the draw() method, both on cairo xcb and on argb32 image surfaces, and have discovered that cairo context method calls are unnaturally slow, to the point, that almost nothing else in the function matters. For example, drawing bars with vertical lines made the whole draw() function almost 2 times slower than doing the same with rectangles, merely because the former requires two method calls (move_to/line_to), and the latter only one. Simply caching hotpath cairo methods in local variables, reduced the difference between the drawing methods to a negligible one and made the whole function 4 times faster on a typical 100-bar graph, (1.4ms to 0.33ms on my laptop) thus bringing closer my secret dream of drawing live audio data with awesome graphs (kidding). I guess, LGI does something not very optimal there, like gobject-introspecting the method every time, or something. There might be some untapped optimization potential, possibly in upstream, like a method cache, from which everything in awesome could benefit, even though I imagine not many things are so call-intensive as the graph widget, to really feel the difference. commit 538df25bdf02b53d0bbe81809eef2e6fdb05ff6f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 07:32:37 2021 +0700 Use 1-based index in the inner graph draw loop It's used there only once for a calculation and then several times to index some tables, and index 0 is bad for lua tables performance-wise. commit 7412546916cd0a0a490130a120255ffb719398b8 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:55:12 2021 +0700 Leave graph.max_value uninitialized at construction This is a user-visible change, but the previous behavior was probably not what user expected anyway, namely with graph.scale set the graph mysteriously couldn't zoom in onto values that are smaller than 1. commit eb81d7929a161e79c13262cfa73ac2e6a820e8ed Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:48:29 2021 +0700 Allow graph.max_value be nil This makes its behave symmetrically to min_value. Now it defaults to 1, when graph.scale is not set, and to the maximum value in the graph data, when graph.scale is set. commit f622a8430cbfde7007b58d7f4c9c76b08c91e997 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 12:01:13 2021 +0700 Calculate max values num in graph:add_value() more precisely At most so many values were kept in the widget values array, as there are pixels of usable widget width (i.e. sans border). This is exactly right for the surely most common case of graphs with 1px-wide bars, but is more than needed for graphs with step_width+step_spacing>1, and not enough for graphs with subpixel bars. Moreover this made graph autoscaling behave weirdly in graphs with thicker bars, because old values survived longer in the array and kept affecting min_value/max_value calculations even after they were shifted off-screen. The maximum number of kept values is now calculated exactly as the number of values that can be displayed at once, taking step_width and step_spacing into account. It might be worth adding a max_values_num property to the graph to let user override this calculation, in case they draw shapes in step_shape() outside of its supposed rectangle, so our assumptions of visibility are wrong, or the widget often gets resized, resulting in the annoyance of visibly truncated series. commit 0aa63249a18641251a1bf77423739164f0eab39f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 02:30:13 2021 +0700 Fix border_color/border_width bugs in graph widget add_value() wrongly assumed that border_width is always set, when border_color is, so if user set only the latter, an error occurred. add_value() assumed that border_width is always < width which led to awesome hanging up in an infinite loop, should that fail to be the case. The graph border_width property was driven by the border_color property, instead of the other way round. If border_color wasn't set, the border wasn't drawn all, even if user sets non-zero border_width, which is a confusing behavior, in contrast to all other widgets, which simply use a plethora of fallback colors to draw the border, when needed. This also meant that, despite being documented, the beautiful.graph_border_color fallback color could not and was not ever used. If the border_color was set though, an unset border_width was immediately assumed to be 1 instead of 0, also contrary to what other widgets do. Theme colors shouldn't influence widget layout, so this commit fixes this behavior to: 1) border_width always defaults to 0, if unset. 2) border_color falls back to beautiful.graph_border_color or white. Maybe one should also add a beautiful.graph_border_width theme variable, like some widgets do. commit dad4a3253b8fef971fb82720d2a407fcc0a879ad Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 03:11:27 2021 +0700 Implement multiple data series drawing for non-stacked graph The widget can already hold and draw multiple data groups, so it makes little sense to limit drawing only to the first group when graph.stack = false, probably leading to user's confusion. Now user can call add_value(v, group) with group > 1 and the data series will be drawn (over groups with lesser id-s) with graph.stack_colors[group] color, if stack_colors is set, or with graph.color, if stack_colors isn't set. Thus nothing visibly changes for all current users of non-stacked graphs with only single data series, but the new functionality is now there for those who need it. One can't fully emulate this functionality, e.g. with multiple graphs widgets in a wibox.layout.stack, because data series in a single widget can enjoy a common auto-scaling. commit 3e645c976184ab5f50d625271c25062db9ab7ed9 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:59:12 2021 +0700 Don't draw a stacked graph data group if there's no color for it If stack_colors[idx] is nil for the group `idx`, don't draw it at all (as opposed, e.g. to the effect of drawing it with a transparent color, or as though it had all values == 0, which both produce different results). This code is still subtly different from prior behavior, because now user can have a non-contiguous stack_colors table and turn the display of any data group on and off at will, whereas the code two commits earlier would just stop rendering as soon as ipairs(stack_colors) does, i.e. no data series were drawn past the first nil hole. This commit only looks large, but is mostly a whitespace change due to nesting from a pair of wrapping ifs. commit 8c0273786de1ade66bdd977699f98b588da9f487 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:32:42 2021 +0700 Unify stacked and non-stacked graph rendering Now stacked graphs support everything the nonstacked do, like step_width ~= 1 and custom step_shape-s. Technical notes: This code no longer calls cr:stroke() for every single value in a stacked graph, but rather paints a whole series and then cr:stroke/fill()-s it at all once with its color, improving performance considerably. Additionally the graph no longer relies on the presence of the stack_colors property to render series. It draws all data that it has, and, if there's no corresponding color in stack_colors, falls back to graph.color. This will be changed in the next commit to the prior behavior of only drawing those series for which there's a color in the stack_colors table, but that would've made this commit's diff much larger due to required nesting ifs over large code chunks. This commit also takes care to properly handle NaNs and negative values. Those are currently (almost) guaranteed to not occur in the values array due to clipping in graph:add_value(), but I intend to eventually drop the clipping, because it does nothing that draw() really relies upon, and allowing unclipped and NaN values is more flexible and less surprising, e.g. when user dynamically toggles graph.scale. Eventually I intend to implement drawing negative values in non-stacked graphs and possibly even in the stacked ones. commit 38c6589c51b2aa057d7c2f40182ecf32b5b2ff5e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 10:04:15 2021 +0700 Convert graph:draw() to use `self` commit f21f1b2550426df1c4ea8abde079b1ccef84b394 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 12:27:26 2021 +0700 Fix scaling for stacked graphs Stacked graphs have cumulative heights, values from all groups that end up in the same graph bar are effectively summed. Previous code didn't take that into account when calculating min/max_value for the purposes of rescaling, but simply examined every value separately. This code adds up all values up front and finds min/max among those, to determine proper scaling that would fit the graph exactly. commit 1ce4c7bf63bdd6c99c195d668347bec49355e921 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 09:49:35 2021 +0700 Draw shaped graph bars only up to the baseline Prior to this commit, every step_shape bar was, for some reason, drawn with height = full widget's height, regardless of the value it represents. So for the most bars their lower end was far outside the box and therefore clipped off. So e.g. one couldn't see the lower round edges of rounded rectangles. This commit fixes that by appropriately varying the height that is passed into step_shape(), so that lower edges of the bars always exactly touch the lower edge of the graph. If the user wants to restore the prior behavior, they can simply ignore the passed-in height in step_shape() and just always draw their shape with height = full widget height. But they couldn't replicate the effect of this commit in their rc.lua without essentially writing their own graph widget. commit e25185b08da23745b5815f85f6e9e43222ed2294 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 08:42:54 2021 +0700 Avoid incremental transform shifting during graph bar rendering Instead of letting addition errors and cognitive load accumulate simply use the same offset calculation that is used for lines and reset the transform back to the top-left corner after every drawn bar. It's conceptually simpler, and will be useful later for stacked graphs and more. It also fixes a discrepancy between plain and step_shape rendering. Prior to this commit, bar coloring occurred with a disturbed transform, which was centered at the last drawn bar, wherever it happened to be. This meant that nontrivial colors, like gradients and images, were arbitrarily offset when step_shape was used. commit f3e98866d8583c35d0b557c09f529299edc9ba2c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:45:29 2021 +0700 Refactor out graph bar height expression into a variable commit f54528d6892f5aaa4595917853c9e2130f743fdb Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:20:25 2021 +0700 Draw graph bars with lines only when step_width == 1 Before this commit graph bars of step_width < 1 were drawn with lines of width 1, but that's a bit unfortunate, because it led to much overpainting, while still ending up with a graphically incorrect result. Let's draw such bars with rectangles and let cairo do things with subpixels, getting more graphically accurate result, while still keeping the common case of step_width == 1 (supposedly) fast-drawn with lines. commit b6515dca67c18b78eb5657378131a62ab43d4cfc Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:54:07 2021 +0700 Fix unsharp edges when drawing graph bars with rectangles The 0.5 offset is only helpful for 1-width strokes, not for fills. commit 7f3db8f07e82f821bcd1dc418d66564f537609d6 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:21:01 2021 +0700 Do coordinate transformations even if the value isn't drawn The graph presently doesn't draw values < 0, but there was a discrepancy between drawing with and without step_shape. The default line/rectangle drawing reserves an empty place where the skipped value should've been, but no such empty place is reserved, when step_shape is used. This commit fixes that. Skipped values always have their empty space now. commit 7b731ebfe96af7a9dba9c57371da6761cf22c00c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:03:30 2021 +0700 Fix graph bar scaling when min_value is not 0 Previous calculation lead to underscaling of values so that even value = max_value wasn't drawn to full height. commit dca59fd8b7fff0e7e646f2dfc6bd92047028e5a8 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:18:40 2021 +0700 Calculate the horizontal graph bar offset only when needed commit 940ddf4b9825e9c3a91d276208c089d8c74e49db Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:17:23 2021 +0700 Call cr:move_to() only when drawing the graph with lines It's redundant otherwise and might even interfere with user's drawing in step_shape(). commit b5d5cd10ca29fb29cd751faaa95137c2fe89ff63 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:04:06 2021 +0700 Fix wrong horizontal offset calculations in widget.graph Indices in the loop are zero-based, so things like i-1 and i>1 aren't necessary and actually wouldn't have been correct even in a 1-based loop. step_shape = gears.shape.rectangle and step_shape = nil, which is supposed to default to rectangle according to docs, were drawn differently due to mismatched offset calculations, which led to problems especially noticeable when step_spacing is not zero, e.g: 1) whole graph shifted left too much, drawn partially off-screen. 2) whole graph shifted right too much, and the first bar placed closer to the rest than others. commit dc369fa198281e000afc1bd17326f3a264adf7d1 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 02:09:21 2021 +0700 Fix the "border_width affects graph bar width" bug Graph bars were drawn with lines of border_width thickness instead of 1, i.e. possibly too fat to be readable. commit 1ba73378cf29ed27a6ecd18716394779ceb543d9 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 00:13:03 2021 +0700 Simplify internal values representation in graph even more New values in graph._private.values were pushed to the end, and old values were removed from the beginning of the table. In draw() however newer values are drawn first, at the left edge of the graph, necessitating annoying idx = #values - idx conversions everywhere. In particular in stacked graph that means that values that are drawn together may have different indices. One has to shift the values array in add_value() either way, so one might as well insert new values at the beginning of the array and simplify the rest of the code. This doesn't seem like much yet, but it will prove to be much more convenient and less error-prone in what I'm going to do later. commit f70523ee68dbd6b67050ace4b82903a9b22440c7 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 13:35:10 2021 +0700 Simplify internal values representation in graph widget graph._private.values was either a table of values or a table of tables of values, depending on whether "stack" property is enabled, ... or a mix of both if user forgot to pass the second "group" parameter to graph:add_value(). This resulted in pretty fragile and confusing behavior, e.g. leaving user to wonder, why they see nothing more after setting stack=true for a graph in a previously working rc.lua, and demanding a very careful use of add_value() from the user, or else some values or even entire stacks would mysteriously vanish and errors in draw() get triggered, sometimes after a period of seemingly normal functioning. Now `values` is always a table of tables and the group parameter in add_value() defaults to 1, thus making the non-stacked graph a trivial variant of the stacked graph.
2021-05-20 17:14:03 +02:00
--- Determine how many values should be drawn for a given widget width.
--
-- The graph uses this method to determine the upper bound on the
-- number of values that will be drawn from each data group. This affects,
-- among other things, how many values will be considered for autoscaling,
-- when `scale` is true, and, indirectly, how many values will be kept in
-- the backing array, when `capacity` is unset.
--
-- The default implementation computes the minimum number that is enough
-- to completely cover the given width with `step_width` + `step_spacing`
-- intervals. The graph calls this method on every redraw and the width
-- passed is the width of the value drawing area, i.e the graph borders
-- are subtracted (2\*`border_width`).
--
-- @method compute_drawn_values_num
-- @tparam number usable_width
-- @treturn number The number of values.
graph: complete widget overhaul and bug fixes Squashed commit of the following: commit 69821f51fe1e8652715658543e50631ece495090 Author: Alex Belykh <albel727@ngs.ru> Date: Mon May 10 19:17:55 2021 +0700 Refactor property handling in wibox.widget.graph In addition, baseline_value, step_width and step_spacing properties are all set to their default non-nil numeric values during instance creation now. commit 842eb429bf5df4b2ba46b2e6f7646afe89b1a3c3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 9 06:18:45 2021 +0700 Make graph:draw_values() private There's no documentation nor confidence that it's a good API, so I'm hiding it for now. commit 720746780574e4bad14a71fc4174955139c2eb50 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 04:04:13 2021 +0700 Document graph_should_draw_data_group() for posterity commit 7c73f3754ce6ba86b118f1170a6f9e406ab4841d Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:59:21 2021 +0700 Make graph:should_draw_data_group() private There's no confidence whether it's a good API, so I'm hiding it for now. commit b3539e20adb6423118af726ae4f8751ef864b5d2 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:36:44 2021 +0700 Make graph:preprocess_values() private There's no confidence whether it's a good API, so I'm hiding it for now. commit 2df4400e780a6260f8d0ab1c60734fbb11bf3c4f Author: Alex Belykh <albel727@ngs.ru> Date: Thu May 6 10:05:23 2021 +0700 Add spec/wibox/widget/graph_spec.lua commit ce01a9771ab439b8df841f1565aa4aa8a44a5e0d Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 08:16:18 2021 +0700 Improve graph.capacity documentation commit e28fade8f655802f5bf89154169f68d256b8e332 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 07:36:01 2021 +0700 Document graph:compute_drawn_values_num() commit dd68332292465422d08b48e9f0229f143beccbc6 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 05:24:23 2021 +0700 Document graph:pick_data_group_color() method commit ca6dc55f77aca88a6e9cc90bb6fd0045739f41f5 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 04:13:58 2021 +0700 Guard against setting data groups sparsely in graph:add_value() commit cdea5f126f7984e72924f6dffbd312c4fe3b032f Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 02:36:12 2021 +0700 Refactor scaling and baseline choice out of graph:draw() commit 10135deafe8a9390ba66ab657ebb1fb41d549e8c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 21:34:48 2021 +0700 Document graph.nan_color and graph.stack properties commit 0d73bb84ac66b87e16aee472bd1f299549f66a1c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 20:49:24 2021 +0700 Use a better example in docs for the graph.stack property commit c27d160a0826d033d49d6141608c40aa8a0642fa Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 16:05:38 2021 +0700 Document graph.baseline_value property commit 459bc8176c13d4ea32af0dcdc2399d3b929b1c6a Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:53:47 2021 +0700 Document graph.clamp_bars property commit e732ee0b3b007d213e689b98cc00ae7cb141ae08 Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:34 2021 +0700 Improve graph:add_value() and :clear() documentation commit ae73e3aa9f8d60409cbe4cb4da9612f31af4f38e Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:11 2021 +0700 Improve graph min_value/max_value/scale documentation commit a7350996a9def773b91b1631e6c0507e33d8f93b Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:27:36 2021 +0700 Cleanup graph examples to show only relevant things in docs commit 30740c26f4f41c16f4c8a3c339bcb7bd299e14cd Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 14:04:36 2021 +0700 Rename graph.stack_colors property to `group_colors` The colors are used for stacked and non-stacked graphs alike, so the name didn't make sense. Also document and clarify some color stuff. commit 1ea01bfd410971cf0d0c25fd74cd764d5eb7279d Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 21:25:41 2021 +0700 Add an example file for code coverage of some fringe graph cases commit eae68e39553f88a6275a0c65bf040b5d4c262b54 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 20:23:32 2021 +0700 Add step_shape to the graph/nan_color example for code coverage commit 8027147bc2eab34a97b49a428fb8cef27f122bed Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:55:18 2021 +0700 Add example files for graph.nan_color/nan_indication properties commit 89a25a165c52b2c15b6c4d95ecb777a9067e66f9 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:33:07 2021 +0700 Make widget graph:add_value(v) default v to NaN 0 as a default makes no sense and silences the error that is failing to specify a value, to which the user should rather be alerted. The graph widget has sane NaN handling now and will do just that. commit 2835d552440be48d356a7f7b1d64963696dd550b Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:20:48 2021 +0700 Implement graph.nan_color and nan_indication properties Now, whenever there's a NaN among values and graph.nan_indication is set, the corresponding area, where the value bar should have been drawn, is filled with the nan_color from top to bottom. The painting is done after all data is rendered, to make sure that it won't be overpainted and go unnoticed. NaNs among graph's values are inevitably a sign of some error and it is arguably not a sane default behavior to simply not render them at all. The user should take immediate note of any errors by default, instead of wondering at a mysteriously empty graph widget, which is why nan_indication is set to true by default. If the user wants to silence NaNs, they can always simply set it to false. nan_color if unset, defaults to a noticeable yellow-black pattern. As a direct and planned consequence, stacked graphs with negative values in them trigger the NaN handling too. But silencing NaN handling for those graphs is also possible and semi-sane, because then NaNs and negatives are ignored and the graph behaves (almost) like they were zeros. commit 9c1f8f08b400e01e19c534f3810c9c9ab485760c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 26 08:10:19 2021 +0700 Refactor out graph_map_value_to_widget_coordinates() function commit 1905991ddecabdae3e15ffb8ee5b18a1bee1a711 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 28 13:39:29 2021 +0700 Add an example file for graph.baseline_value property commit b31b39d66f4c037a9484b71a3f910703ae01fac0 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 07:03:49 2021 +0700 Add an example file for graph.clamp_bars=false/true comparison commit a1fc850e3047f9ee2c9ddeffacfbabf3ded34171 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 04:06:45 2021 +0700 Add an example file for graphs with negative values and shapes commit 2f012f8ff1bb5094c14e724a61e59a2a3a296bc8 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 01:06:05 2021 +0700 Add an example file for a graph displaying negative values commit 7b14fbd59a4c4c51962fd3110a1a900dd5f4eb98 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 11:18:26 2021 +0700 Add an example file for graphs with step_width < 1 commit dfd9a54544b637e2fbe3d1325f9cc24b3f4d2313 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 10:17:38 2021 +0700 Add an example file for graph.stack=false/true comparison Should also demonstrate that multiple data groups are possible for non-stacked graphs too. commit 5b532106c3fd255f9b66a66cfb88d977b489ab5d Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 09:34:14 2021 +0700 Add an example file for disabling graph data groups with nil colors commit ac6f1083c02fcb428f089a616f9b0ebaa5496b6c Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:57:25 2021 +0700 Add an example file for step_shape/width/spacing on stacked graphs commit b3703db6a5139e73e72c1cfd198ece2cd9be7df2 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:43:56 2021 +0700 Add an example file for scale=false/true on stacked graph commit b000aed4a12cb44fb501e1c8432a71a14b2f8f3f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 20 08:22:19 2021 +0700 Document deprecation of graph's width and height commit cb0ad617bd6c83a499896797318e3b0b588c3566 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 07:53:37 2021 +0700 Deprecate width and height properties of wibox.widget.graph commit 8c64e14ad91a074b784166a7054e53444cccb9d2 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 04:37:59 2021 +0700 Refactor graph:draw() to separate concerns Firstly, summation of group values for stacked graphs is extracted into graph:preprocess_values(). This makes graph:draw() smaller and exposes the fact, that stacked graphs are almost entirely just a form of a more general concept: pre-render data manipulation. It's something a user might want to customize, and now they can do it by monkey-patching the method. Secondly, the logic for deciding, if and with which color a data group should be drawn, is extracted into graph:should_draw_data_group() and get_data_group_color() methods respectively. This makes it customizable too and makes more obvious the fact, that it is a general thing that has nothing to do with stacked graphs in particular. With this there are only 5 lines remaining in graph:draw(), that could be said to be specific to stacked graphs, all pertaining to the `prev_y` variable, which merely preserves the bar coordinates from a previous data group. commit 2a32b6305d893f413303728a0a3da1dcd9a5591e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 22:52:37 2021 +0700 Pointlessly optimize graph:add_value() 1) Purge old values first, then insert the new one, if needed. Since Lua grows arrays in power of two steps, and our guess_capacity() also takes care to return even integers, it would be a shame to overstep the capacity by one by inserting first and thus getting the table size doubled only to never use the most of it. 2) Use a removal procedure that doesn't call the slow length operator at all. Previous code called it twice (once in table.remove) for every removed value. commit 680ea8f5d81e9040afb4662b3229e937e4b89e02 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:23:38 2021 +0700 Set graph data series color before the painting loop commit b970f0f221ae76250f2a94433ff79d653caa6e21 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:05:37 2021 +0700 Drop a large no longer necessary `if` in graph:draw_values() This commit is almost purely a whitespace change. commit 6b4f0541c70ec99d7f6e276bae64939f337120d6 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:00:50 2021 +0700 Extract values rendering code into graph:draw_values() method This is to avoid ungodly if nesting levels, which are needed to bail out of drawing values early and skip to drawing the border. The next commit will get rid of the if. commit e0950e4f99d1ea1c4b8cea2cc67629fa40030c2a Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 14:50:20 2021 +0700 Track graph draw() usage to guess necessary array capacity The last step of divorcing add_values() from width calculations, while ensuring that user doesn't observe a lack of data to draw. The widget now tracks an approximate number of values that it has been asked to draw() in _private.last_drawn_values_num, and truncates its values array based on that (if not overridden by the capacity property). commit 2e3d542282f35e7c0fed454da0f2eada0fcf3b78 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 13:35:12 2021 +0700 Add `capacity` property to graph widget This is the second step of divorcing add_values() from calculations involving widths. How many values are stored and how many values are drawn are distinct concepts. There isn't even a single definitive "how many values are drawn" because the widget could be rendered in several places with distinct widths simultaneously, and in every such place the data must be drawn appropriately, e.g. with auto-scaling, that takes only the visible data points into account. And the draw() method can do that now. The default capacity value of nil uses heuristics based on widget's _private.width to guess some capacity that would tolerate some dynamic widget resizing. If the user finds the heuristic failing them, they can set the capacity property to the desired maximum number of values that will be kept in the _private.values array (in each data group). commit 08d4a80686f7fe363551c35589a460839f60853e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 03:27:18 2021 +0700 Extract values size computation into compute_drawn_values_num() This is the first step of making add_values() not concern itself (directly) with widget dimensions. The user can monkey-patch the method to override how many values are (logically) drawn, because it affects graph auto-scaling and can be useful for rendering of step_shape-s that paint outside of their step_width. I didn't make it a full-blown function-valued property, because it seems a fringe functionality, which I'm too lazy to document. commit 3c5617c98ab1147618ed22e8414a77fc56b45f6e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 02:32:29 2021 +0700 Copy graph widget prototype methods over in bulk commit 169538837ece41a03d02ba8fbc0e4097062b67a3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun Apr 25 05:58:01 2021 +0700 Remove graph.baseline_y property After giving it some thought I've concluded, that it is unlikely to be useful as it is now. I'd better reserve the name for some more sensible functionality, like fixing the baseline_value defined axis at the given widget coordinate, adjusting the scaling accordingly, so min_value/max_value won't generally correspond to widget top and bottom anymore. commit 202ffaa9a0b820c7c471e1121d01b91f8f76cd38 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 12:06:55 2021 +0700 Add baseline_y and baseline_value properties to graph widget Now values in graph have a *baseline*, tunable with baseline_y and baseline_value properties. It is the (for now invisible) horizontal line from which all bars in the graph grow. I call it a "baseline" instead of "axis", because it doesn't actually have to correspond to the 0 value. But it can, and actually does now by default. baseline_y and baseline_value properties determine the vertical placement of the baseline in the widget. baseline_y is the position of the baseline in normalized widget coordinates, e.g. 0 corresponds to widget's top, 1 - to widget's bottom, and 0.5 - to widget's middle. So if e.g. one sets baseline_y = 0, then no matter how the widget is resized and how its values are scaled, bars in it will be drawn starting from the top. baseline_value is the position of the baseline in value coordinates. So if one sets baseline_value = 10, then no matter what happens, bars for values greater than 10 will be drawn as growing up from this line, and bars for values smaller as growing down. Where and if it will be seen in the widget, depends on the scaling. baseline_value = 0 is the familiar zero axis. baseline_y is unset by default and has priority, i.e. if it's set, then baseline_value is ignored. baseline_value defaults to 0. Prior to this commit, bars were always drawn from the bottom edge of the widget, or, in the new terms, the widget had baseline_y = 1. But it was indistinguishable from baseline_value = 0, because the widget clamped all negative values to 0, so the difference between the two could never be observed. baseline_value = 0 is clearly more useful and familiar to people, so now it is in force by default. commit df7e19abaebf0a871b5ca217b4cc3365533267a6 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 11:18:12 2021 +0700 Move value clamping from graph:add_value() into draw() Previously all values were forcibly clamped between 0 (not tunable at all) and max_value (1 by default). Thus graphs with negative values were not an option. NaN values were conflated with zeros, even though it may be useful to maintain the distinction and render them differently. And if user happened to dynamically change the max_value property, they discovered, that their data got truncated. Preserving user data as it was given is more flexible and makes things easier to debug. This commit does exactly that. So what happens now to the newly allowed values during draw()? There are 3 new cases to consider: 1) NaNs 2) Negatives 3) Values outside of min_value..max_value range NaNs are presently simply not drawn. An empty place is reserved where the value should've been, but nothing else is done. This is distinct from how a zero is handled, e.g. if step_shape is set, it is called with the height parameter = 0, which can still end up drawing something, depending on the shape. One could also consider addition of nan_color/nan_shape properties which could be drawn to alert user to a gap in their data. Negative values necessitate introduction of the concept of the zero axis, from which they will be drawn DOWN, in contrast to positive values. Where should this axis be drawn? How should step_shape-s with inherent direction, e.g. arrows, be drawn for negatives? This will be addressed in the next commit. Values outside of min/max_value range can imply shapes that go beyond widget dimensions. An arrow shape that goes beyond the roof is visibly different from an arrow that saturates and merely touches the widget top, and the user may reasonably desire either behavior. Therefore this commit introduces a boolean clamp_bars property, which defaults to true for reasons of robustness and backward compatibility with the behaviour prior to this commit. The property of course affects only the display of the values, the actual user data stays intact. commit c9c24fb1ab741cd6bb8074971d568e0646c51532 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:29:01 2021 +0700 Use rectangles for drawing graph bars of all sizes I have benchmarked various bar drawing strategies and graph sizes and found out that drawing graph bars with rectangles is as fast or even marginally faster than using fat lines. Thus the code that tries to special-case 1px bars as lines seems to be not worth the additional complexity it introduces, and this commit removes it. Incidentally, I've also established, that rectangles are more robust. If one draws graph bars and feeds cairo very large coordinates (~2^62), then corresponding rectangles fail to render, but all the other rectangles in the same series get rendered successfully, whereas a single large coordinate in a move_to/line_to() sequence is enough to cause all lines in the batch to fail to render. commit bc7f4be5e06405b5aa7fb443a5ace2b9b9575b1f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:01:42 2021 +0700 Cache cairo methods used in the inner loop of graph:draw() I have benchmarked the draw() method, both on cairo xcb and on argb32 image surfaces, and have discovered that cairo context method calls are unnaturally slow, to the point, that almost nothing else in the function matters. For example, drawing bars with vertical lines made the whole draw() function almost 2 times slower than doing the same with rectangles, merely because the former requires two method calls (move_to/line_to), and the latter only one. Simply caching hotpath cairo methods in local variables, reduced the difference between the drawing methods to a negligible one and made the whole function 4 times faster on a typical 100-bar graph, (1.4ms to 0.33ms on my laptop) thus bringing closer my secret dream of drawing live audio data with awesome graphs (kidding). I guess, LGI does something not very optimal there, like gobject-introspecting the method every time, or something. There might be some untapped optimization potential, possibly in upstream, like a method cache, from which everything in awesome could benefit, even though I imagine not many things are so call-intensive as the graph widget, to really feel the difference. commit 538df25bdf02b53d0bbe81809eef2e6fdb05ff6f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 07:32:37 2021 +0700 Use 1-based index in the inner graph draw loop It's used there only once for a calculation and then several times to index some tables, and index 0 is bad for lua tables performance-wise. commit 7412546916cd0a0a490130a120255ffb719398b8 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:55:12 2021 +0700 Leave graph.max_value uninitialized at construction This is a user-visible change, but the previous behavior was probably not what user expected anyway, namely with graph.scale set the graph mysteriously couldn't zoom in onto values that are smaller than 1. commit eb81d7929a161e79c13262cfa73ac2e6a820e8ed Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:48:29 2021 +0700 Allow graph.max_value be nil This makes its behave symmetrically to min_value. Now it defaults to 1, when graph.scale is not set, and to the maximum value in the graph data, when graph.scale is set. commit f622a8430cbfde7007b58d7f4c9c76b08c91e997 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 12:01:13 2021 +0700 Calculate max values num in graph:add_value() more precisely At most so many values were kept in the widget values array, as there are pixels of usable widget width (i.e. sans border). This is exactly right for the surely most common case of graphs with 1px-wide bars, but is more than needed for graphs with step_width+step_spacing>1, and not enough for graphs with subpixel bars. Moreover this made graph autoscaling behave weirdly in graphs with thicker bars, because old values survived longer in the array and kept affecting min_value/max_value calculations even after they were shifted off-screen. The maximum number of kept values is now calculated exactly as the number of values that can be displayed at once, taking step_width and step_spacing into account. It might be worth adding a max_values_num property to the graph to let user override this calculation, in case they draw shapes in step_shape() outside of its supposed rectangle, so our assumptions of visibility are wrong, or the widget often gets resized, resulting in the annoyance of visibly truncated series. commit 0aa63249a18641251a1bf77423739164f0eab39f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 02:30:13 2021 +0700 Fix border_color/border_width bugs in graph widget add_value() wrongly assumed that border_width is always set, when border_color is, so if user set only the latter, an error occurred. add_value() assumed that border_width is always < width which led to awesome hanging up in an infinite loop, should that fail to be the case. The graph border_width property was driven by the border_color property, instead of the other way round. If border_color wasn't set, the border wasn't drawn all, even if user sets non-zero border_width, which is a confusing behavior, in contrast to all other widgets, which simply use a plethora of fallback colors to draw the border, when needed. This also meant that, despite being documented, the beautiful.graph_border_color fallback color could not and was not ever used. If the border_color was set though, an unset border_width was immediately assumed to be 1 instead of 0, also contrary to what other widgets do. Theme colors shouldn't influence widget layout, so this commit fixes this behavior to: 1) border_width always defaults to 0, if unset. 2) border_color falls back to beautiful.graph_border_color or white. Maybe one should also add a beautiful.graph_border_width theme variable, like some widgets do. commit dad4a3253b8fef971fb82720d2a407fcc0a879ad Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 03:11:27 2021 +0700 Implement multiple data series drawing for non-stacked graph The widget can already hold and draw multiple data groups, so it makes little sense to limit drawing only to the first group when graph.stack = false, probably leading to user's confusion. Now user can call add_value(v, group) with group > 1 and the data series will be drawn (over groups with lesser id-s) with graph.stack_colors[group] color, if stack_colors is set, or with graph.color, if stack_colors isn't set. Thus nothing visibly changes for all current users of non-stacked graphs with only single data series, but the new functionality is now there for those who need it. One can't fully emulate this functionality, e.g. with multiple graphs widgets in a wibox.layout.stack, because data series in a single widget can enjoy a common auto-scaling. commit 3e645c976184ab5f50d625271c25062db9ab7ed9 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:59:12 2021 +0700 Don't draw a stacked graph data group if there's no color for it If stack_colors[idx] is nil for the group `idx`, don't draw it at all (as opposed, e.g. to the effect of drawing it with a transparent color, or as though it had all values == 0, which both produce different results). This code is still subtly different from prior behavior, because now user can have a non-contiguous stack_colors table and turn the display of any data group on and off at will, whereas the code two commits earlier would just stop rendering as soon as ipairs(stack_colors) does, i.e. no data series were drawn past the first nil hole. This commit only looks large, but is mostly a whitespace change due to nesting from a pair of wrapping ifs. commit 8c0273786de1ade66bdd977699f98b588da9f487 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:32:42 2021 +0700 Unify stacked and non-stacked graph rendering Now stacked graphs support everything the nonstacked do, like step_width ~= 1 and custom step_shape-s. Technical notes: This code no longer calls cr:stroke() for every single value in a stacked graph, but rather paints a whole series and then cr:stroke/fill()-s it at all once with its color, improving performance considerably. Additionally the graph no longer relies on the presence of the stack_colors property to render series. It draws all data that it has, and, if there's no corresponding color in stack_colors, falls back to graph.color. This will be changed in the next commit to the prior behavior of only drawing those series for which there's a color in the stack_colors table, but that would've made this commit's diff much larger due to required nesting ifs over large code chunks. This commit also takes care to properly handle NaNs and negative values. Those are currently (almost) guaranteed to not occur in the values array due to clipping in graph:add_value(), but I intend to eventually drop the clipping, because it does nothing that draw() really relies upon, and allowing unclipped and NaN values is more flexible and less surprising, e.g. when user dynamically toggles graph.scale. Eventually I intend to implement drawing negative values in non-stacked graphs and possibly even in the stacked ones. commit 38c6589c51b2aa057d7c2f40182ecf32b5b2ff5e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 10:04:15 2021 +0700 Convert graph:draw() to use `self` commit f21f1b2550426df1c4ea8abde079b1ccef84b394 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 12:27:26 2021 +0700 Fix scaling for stacked graphs Stacked graphs have cumulative heights, values from all groups that end up in the same graph bar are effectively summed. Previous code didn't take that into account when calculating min/max_value for the purposes of rescaling, but simply examined every value separately. This code adds up all values up front and finds min/max among those, to determine proper scaling that would fit the graph exactly. commit 1ce4c7bf63bdd6c99c195d668347bec49355e921 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 09:49:35 2021 +0700 Draw shaped graph bars only up to the baseline Prior to this commit, every step_shape bar was, for some reason, drawn with height = full widget's height, regardless of the value it represents. So for the most bars their lower end was far outside the box and therefore clipped off. So e.g. one couldn't see the lower round edges of rounded rectangles. This commit fixes that by appropriately varying the height that is passed into step_shape(), so that lower edges of the bars always exactly touch the lower edge of the graph. If the user wants to restore the prior behavior, they can simply ignore the passed-in height in step_shape() and just always draw their shape with height = full widget height. But they couldn't replicate the effect of this commit in their rc.lua without essentially writing their own graph widget. commit e25185b08da23745b5815f85f6e9e43222ed2294 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 08:42:54 2021 +0700 Avoid incremental transform shifting during graph bar rendering Instead of letting addition errors and cognitive load accumulate simply use the same offset calculation that is used for lines and reset the transform back to the top-left corner after every drawn bar. It's conceptually simpler, and will be useful later for stacked graphs and more. It also fixes a discrepancy between plain and step_shape rendering. Prior to this commit, bar coloring occurred with a disturbed transform, which was centered at the last drawn bar, wherever it happened to be. This meant that nontrivial colors, like gradients and images, were arbitrarily offset when step_shape was used. commit f3e98866d8583c35d0b557c09f529299edc9ba2c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:45:29 2021 +0700 Refactor out graph bar height expression into a variable commit f54528d6892f5aaa4595917853c9e2130f743fdb Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:20:25 2021 +0700 Draw graph bars with lines only when step_width == 1 Before this commit graph bars of step_width < 1 were drawn with lines of width 1, but that's a bit unfortunate, because it led to much overpainting, while still ending up with a graphically incorrect result. Let's draw such bars with rectangles and let cairo do things with subpixels, getting more graphically accurate result, while still keeping the common case of step_width == 1 (supposedly) fast-drawn with lines. commit b6515dca67c18b78eb5657378131a62ab43d4cfc Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:54:07 2021 +0700 Fix unsharp edges when drawing graph bars with rectangles The 0.5 offset is only helpful for 1-width strokes, not for fills. commit 7f3db8f07e82f821bcd1dc418d66564f537609d6 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:21:01 2021 +0700 Do coordinate transformations even if the value isn't drawn The graph presently doesn't draw values < 0, but there was a discrepancy between drawing with and without step_shape. The default line/rectangle drawing reserves an empty place where the skipped value should've been, but no such empty place is reserved, when step_shape is used. This commit fixes that. Skipped values always have their empty space now. commit 7b731ebfe96af7a9dba9c57371da6761cf22c00c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:03:30 2021 +0700 Fix graph bar scaling when min_value is not 0 Previous calculation lead to underscaling of values so that even value = max_value wasn't drawn to full height. commit dca59fd8b7fff0e7e646f2dfc6bd92047028e5a8 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:18:40 2021 +0700 Calculate the horizontal graph bar offset only when needed commit 940ddf4b9825e9c3a91d276208c089d8c74e49db Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:17:23 2021 +0700 Call cr:move_to() only when drawing the graph with lines It's redundant otherwise and might even interfere with user's drawing in step_shape(). commit b5d5cd10ca29fb29cd751faaa95137c2fe89ff63 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:04:06 2021 +0700 Fix wrong horizontal offset calculations in widget.graph Indices in the loop are zero-based, so things like i-1 and i>1 aren't necessary and actually wouldn't have been correct even in a 1-based loop. step_shape = gears.shape.rectangle and step_shape = nil, which is supposed to default to rectangle according to docs, were drawn differently due to mismatched offset calculations, which led to problems especially noticeable when step_spacing is not zero, e.g: 1) whole graph shifted left too much, drawn partially off-screen. 2) whole graph shifted right too much, and the first bar placed closer to the rest than others. commit dc369fa198281e000afc1bd17326f3a264adf7d1 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 02:09:21 2021 +0700 Fix the "border_width affects graph bar width" bug Graph bars were drawn with lines of border_width thickness instead of 1, i.e. possibly too fat to be readable. commit 1ba73378cf29ed27a6ecd18716394779ceb543d9 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 00:13:03 2021 +0700 Simplify internal values representation in graph even more New values in graph._private.values were pushed to the end, and old values were removed from the beginning of the table. In draw() however newer values are drawn first, at the left edge of the graph, necessitating annoying idx = #values - idx conversions everywhere. In particular in stacked graph that means that values that are drawn together may have different indices. One has to shift the values array in add_value() either way, so one might as well insert new values at the beginning of the array and simplify the rest of the code. This doesn't seem like much yet, but it will prove to be much more convenient and less error-prone in what I'm going to do later. commit f70523ee68dbd6b67050ace4b82903a9b22440c7 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 13:35:10 2021 +0700 Simplify internal values representation in graph widget graph._private.values was either a table of values or a table of tables of values, depending on whether "stack" property is enabled, ... or a mix of both if user forgot to pass the second "group" parameter to graph:add_value(). This resulted in pretty fragile and confusing behavior, e.g. leaving user to wonder, why they see nothing more after setting stack=true for a graph in a previously working rc.lua, and demanding a very careful use of add_value() from the user, or else some values or even entire stacks would mysteriously vanish and errors in draw() get triggered, sometimes after a period of seemingly normal functioning. Now `values` is always a table of tables and the group parameter in add_value() defaults to 1, thus making the non-stacked graph a trivial variant of the stacked graph.
2021-05-20 17:14:03 +02:00
function graph:compute_drawn_values_num(usable_width)
if usable_width <= 0 then
return 0
end
local step_width = self._private.step_width or prop_fallbacks.step_width
local step_spacing = self._private.step_spacing or prop_fallbacks.step_spacing
return math.ceil(usable_width / (step_width + step_spacing))
end
local function guess_capacity(self)
local capacity = self._private.capacity
if capacity then
-- Ensure it's integer, no matter what the user sets.
return math.ceil(capacity)
end
local ldwn = self._private.last_drawn_values_num
if not ldwn then
-- We haven't been drawn even once yet,
-- maybe the user will push a ton of values now.
-- Our widget is 8K-display-ready.
return 8192
end
-- Calculate an appropriate capacity from drawn values num
-- with some wiggle room for widget resizes
return math.ceil(ldwn/64 + 1)*64
end
--- Add a value to the graph.
--
-- The graph widget keeps its values grouped in _data groups_. Each data group
-- is drawn with its own set of bars, starting with the latest value
-- in the data group at the left edge of the graph.
--
-- Simply calling this method with a particular data group index is the only
-- thing necessary and sufficient for creating a data group.
-- Any natural integer as a group number is ok, but the user is advised to keep
-- the group numbers low and consecutive for performance reasons.
--
-- There are no constraints on the value parameter, other than it should
-- be a number.
2016-05-23 07:30:44 +02:00
--
-- @method add_value
graph: complete widget overhaul and bug fixes Squashed commit of the following: commit 69821f51fe1e8652715658543e50631ece495090 Author: Alex Belykh <albel727@ngs.ru> Date: Mon May 10 19:17:55 2021 +0700 Refactor property handling in wibox.widget.graph In addition, baseline_value, step_width and step_spacing properties are all set to their default non-nil numeric values during instance creation now. commit 842eb429bf5df4b2ba46b2e6f7646afe89b1a3c3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 9 06:18:45 2021 +0700 Make graph:draw_values() private There's no documentation nor confidence that it's a good API, so I'm hiding it for now. commit 720746780574e4bad14a71fc4174955139c2eb50 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 04:04:13 2021 +0700 Document graph_should_draw_data_group() for posterity commit 7c73f3754ce6ba86b118f1170a6f9e406ab4841d Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:59:21 2021 +0700 Make graph:should_draw_data_group() private There's no confidence whether it's a good API, so I'm hiding it for now. commit b3539e20adb6423118af726ae4f8751ef864b5d2 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:36:44 2021 +0700 Make graph:preprocess_values() private There's no confidence whether it's a good API, so I'm hiding it for now. commit 2df4400e780a6260f8d0ab1c60734fbb11bf3c4f Author: Alex Belykh <albel727@ngs.ru> Date: Thu May 6 10:05:23 2021 +0700 Add spec/wibox/widget/graph_spec.lua commit ce01a9771ab439b8df841f1565aa4aa8a44a5e0d Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 08:16:18 2021 +0700 Improve graph.capacity documentation commit e28fade8f655802f5bf89154169f68d256b8e332 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 07:36:01 2021 +0700 Document graph:compute_drawn_values_num() commit dd68332292465422d08b48e9f0229f143beccbc6 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 05:24:23 2021 +0700 Document graph:pick_data_group_color() method commit ca6dc55f77aca88a6e9cc90bb6fd0045739f41f5 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 04:13:58 2021 +0700 Guard against setting data groups sparsely in graph:add_value() commit cdea5f126f7984e72924f6dffbd312c4fe3b032f Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 02:36:12 2021 +0700 Refactor scaling and baseline choice out of graph:draw() commit 10135deafe8a9390ba66ab657ebb1fb41d549e8c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 21:34:48 2021 +0700 Document graph.nan_color and graph.stack properties commit 0d73bb84ac66b87e16aee472bd1f299549f66a1c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 20:49:24 2021 +0700 Use a better example in docs for the graph.stack property commit c27d160a0826d033d49d6141608c40aa8a0642fa Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 16:05:38 2021 +0700 Document graph.baseline_value property commit 459bc8176c13d4ea32af0dcdc2399d3b929b1c6a Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:53:47 2021 +0700 Document graph.clamp_bars property commit e732ee0b3b007d213e689b98cc00ae7cb141ae08 Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:34 2021 +0700 Improve graph:add_value() and :clear() documentation commit ae73e3aa9f8d60409cbe4cb4da9612f31af4f38e Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:11 2021 +0700 Improve graph min_value/max_value/scale documentation commit a7350996a9def773b91b1631e6c0507e33d8f93b Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:27:36 2021 +0700 Cleanup graph examples to show only relevant things in docs commit 30740c26f4f41c16f4c8a3c339bcb7bd299e14cd Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 14:04:36 2021 +0700 Rename graph.stack_colors property to `group_colors` The colors are used for stacked and non-stacked graphs alike, so the name didn't make sense. Also document and clarify some color stuff. commit 1ea01bfd410971cf0d0c25fd74cd764d5eb7279d Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 21:25:41 2021 +0700 Add an example file for code coverage of some fringe graph cases commit eae68e39553f88a6275a0c65bf040b5d4c262b54 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 20:23:32 2021 +0700 Add step_shape to the graph/nan_color example for code coverage commit 8027147bc2eab34a97b49a428fb8cef27f122bed Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:55:18 2021 +0700 Add example files for graph.nan_color/nan_indication properties commit 89a25a165c52b2c15b6c4d95ecb777a9067e66f9 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:33:07 2021 +0700 Make widget graph:add_value(v) default v to NaN 0 as a default makes no sense and silences the error that is failing to specify a value, to which the user should rather be alerted. The graph widget has sane NaN handling now and will do just that. commit 2835d552440be48d356a7f7b1d64963696dd550b Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:20:48 2021 +0700 Implement graph.nan_color and nan_indication properties Now, whenever there's a NaN among values and graph.nan_indication is set, the corresponding area, where the value bar should have been drawn, is filled with the nan_color from top to bottom. The painting is done after all data is rendered, to make sure that it won't be overpainted and go unnoticed. NaNs among graph's values are inevitably a sign of some error and it is arguably not a sane default behavior to simply not render them at all. The user should take immediate note of any errors by default, instead of wondering at a mysteriously empty graph widget, which is why nan_indication is set to true by default. If the user wants to silence NaNs, they can always simply set it to false. nan_color if unset, defaults to a noticeable yellow-black pattern. As a direct and planned consequence, stacked graphs with negative values in them trigger the NaN handling too. But silencing NaN handling for those graphs is also possible and semi-sane, because then NaNs and negatives are ignored and the graph behaves (almost) like they were zeros. commit 9c1f8f08b400e01e19c534f3810c9c9ab485760c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 26 08:10:19 2021 +0700 Refactor out graph_map_value_to_widget_coordinates() function commit 1905991ddecabdae3e15ffb8ee5b18a1bee1a711 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 28 13:39:29 2021 +0700 Add an example file for graph.baseline_value property commit b31b39d66f4c037a9484b71a3f910703ae01fac0 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 07:03:49 2021 +0700 Add an example file for graph.clamp_bars=false/true comparison commit a1fc850e3047f9ee2c9ddeffacfbabf3ded34171 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 04:06:45 2021 +0700 Add an example file for graphs with negative values and shapes commit 2f012f8ff1bb5094c14e724a61e59a2a3a296bc8 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 01:06:05 2021 +0700 Add an example file for a graph displaying negative values commit 7b14fbd59a4c4c51962fd3110a1a900dd5f4eb98 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 11:18:26 2021 +0700 Add an example file for graphs with step_width < 1 commit dfd9a54544b637e2fbe3d1325f9cc24b3f4d2313 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 10:17:38 2021 +0700 Add an example file for graph.stack=false/true comparison Should also demonstrate that multiple data groups are possible for non-stacked graphs too. commit 5b532106c3fd255f9b66a66cfb88d977b489ab5d Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 09:34:14 2021 +0700 Add an example file for disabling graph data groups with nil colors commit ac6f1083c02fcb428f089a616f9b0ebaa5496b6c Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:57:25 2021 +0700 Add an example file for step_shape/width/spacing on stacked graphs commit b3703db6a5139e73e72c1cfd198ece2cd9be7df2 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:43:56 2021 +0700 Add an example file for scale=false/true on stacked graph commit b000aed4a12cb44fb501e1c8432a71a14b2f8f3f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 20 08:22:19 2021 +0700 Document deprecation of graph's width and height commit cb0ad617bd6c83a499896797318e3b0b588c3566 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 07:53:37 2021 +0700 Deprecate width and height properties of wibox.widget.graph commit 8c64e14ad91a074b784166a7054e53444cccb9d2 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 04:37:59 2021 +0700 Refactor graph:draw() to separate concerns Firstly, summation of group values for stacked graphs is extracted into graph:preprocess_values(). This makes graph:draw() smaller and exposes the fact, that stacked graphs are almost entirely just a form of a more general concept: pre-render data manipulation. It's something a user might want to customize, and now they can do it by monkey-patching the method. Secondly, the logic for deciding, if and with which color a data group should be drawn, is extracted into graph:should_draw_data_group() and get_data_group_color() methods respectively. This makes it customizable too and makes more obvious the fact, that it is a general thing that has nothing to do with stacked graphs in particular. With this there are only 5 lines remaining in graph:draw(), that could be said to be specific to stacked graphs, all pertaining to the `prev_y` variable, which merely preserves the bar coordinates from a previous data group. commit 2a32b6305d893f413303728a0a3da1dcd9a5591e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 22:52:37 2021 +0700 Pointlessly optimize graph:add_value() 1) Purge old values first, then insert the new one, if needed. Since Lua grows arrays in power of two steps, and our guess_capacity() also takes care to return even integers, it would be a shame to overstep the capacity by one by inserting first and thus getting the table size doubled only to never use the most of it. 2) Use a removal procedure that doesn't call the slow length operator at all. Previous code called it twice (once in table.remove) for every removed value. commit 680ea8f5d81e9040afb4662b3229e937e4b89e02 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:23:38 2021 +0700 Set graph data series color before the painting loop commit b970f0f221ae76250f2a94433ff79d653caa6e21 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:05:37 2021 +0700 Drop a large no longer necessary `if` in graph:draw_values() This commit is almost purely a whitespace change. commit 6b4f0541c70ec99d7f6e276bae64939f337120d6 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:00:50 2021 +0700 Extract values rendering code into graph:draw_values() method This is to avoid ungodly if nesting levels, which are needed to bail out of drawing values early and skip to drawing the border. The next commit will get rid of the if. commit e0950e4f99d1ea1c4b8cea2cc67629fa40030c2a Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 14:50:20 2021 +0700 Track graph draw() usage to guess necessary array capacity The last step of divorcing add_values() from width calculations, while ensuring that user doesn't observe a lack of data to draw. The widget now tracks an approximate number of values that it has been asked to draw() in _private.last_drawn_values_num, and truncates its values array based on that (if not overridden by the capacity property). commit 2e3d542282f35e7c0fed454da0f2eada0fcf3b78 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 13:35:12 2021 +0700 Add `capacity` property to graph widget This is the second step of divorcing add_values() from calculations involving widths. How many values are stored and how many values are drawn are distinct concepts. There isn't even a single definitive "how many values are drawn" because the widget could be rendered in several places with distinct widths simultaneously, and in every such place the data must be drawn appropriately, e.g. with auto-scaling, that takes only the visible data points into account. And the draw() method can do that now. The default capacity value of nil uses heuristics based on widget's _private.width to guess some capacity that would tolerate some dynamic widget resizing. If the user finds the heuristic failing them, they can set the capacity property to the desired maximum number of values that will be kept in the _private.values array (in each data group). commit 08d4a80686f7fe363551c35589a460839f60853e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 03:27:18 2021 +0700 Extract values size computation into compute_drawn_values_num() This is the first step of making add_values() not concern itself (directly) with widget dimensions. The user can monkey-patch the method to override how many values are (logically) drawn, because it affects graph auto-scaling and can be useful for rendering of step_shape-s that paint outside of their step_width. I didn't make it a full-blown function-valued property, because it seems a fringe functionality, which I'm too lazy to document. commit 3c5617c98ab1147618ed22e8414a77fc56b45f6e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 02:32:29 2021 +0700 Copy graph widget prototype methods over in bulk commit 169538837ece41a03d02ba8fbc0e4097062b67a3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun Apr 25 05:58:01 2021 +0700 Remove graph.baseline_y property After giving it some thought I've concluded, that it is unlikely to be useful as it is now. I'd better reserve the name for some more sensible functionality, like fixing the baseline_value defined axis at the given widget coordinate, adjusting the scaling accordingly, so min_value/max_value won't generally correspond to widget top and bottom anymore. commit 202ffaa9a0b820c7c471e1121d01b91f8f76cd38 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 12:06:55 2021 +0700 Add baseline_y and baseline_value properties to graph widget Now values in graph have a *baseline*, tunable with baseline_y and baseline_value properties. It is the (for now invisible) horizontal line from which all bars in the graph grow. I call it a "baseline" instead of "axis", because it doesn't actually have to correspond to the 0 value. But it can, and actually does now by default. baseline_y and baseline_value properties determine the vertical placement of the baseline in the widget. baseline_y is the position of the baseline in normalized widget coordinates, e.g. 0 corresponds to widget's top, 1 - to widget's bottom, and 0.5 - to widget's middle. So if e.g. one sets baseline_y = 0, then no matter how the widget is resized and how its values are scaled, bars in it will be drawn starting from the top. baseline_value is the position of the baseline in value coordinates. So if one sets baseline_value = 10, then no matter what happens, bars for values greater than 10 will be drawn as growing up from this line, and bars for values smaller as growing down. Where and if it will be seen in the widget, depends on the scaling. baseline_value = 0 is the familiar zero axis. baseline_y is unset by default and has priority, i.e. if it's set, then baseline_value is ignored. baseline_value defaults to 0. Prior to this commit, bars were always drawn from the bottom edge of the widget, or, in the new terms, the widget had baseline_y = 1. But it was indistinguishable from baseline_value = 0, because the widget clamped all negative values to 0, so the difference between the two could never be observed. baseline_value = 0 is clearly more useful and familiar to people, so now it is in force by default. commit df7e19abaebf0a871b5ca217b4cc3365533267a6 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 11:18:12 2021 +0700 Move value clamping from graph:add_value() into draw() Previously all values were forcibly clamped between 0 (not tunable at all) and max_value (1 by default). Thus graphs with negative values were not an option. NaN values were conflated with zeros, even though it may be useful to maintain the distinction and render them differently. And if user happened to dynamically change the max_value property, they discovered, that their data got truncated. Preserving user data as it was given is more flexible and makes things easier to debug. This commit does exactly that. So what happens now to the newly allowed values during draw()? There are 3 new cases to consider: 1) NaNs 2) Negatives 3) Values outside of min_value..max_value range NaNs are presently simply not drawn. An empty place is reserved where the value should've been, but nothing else is done. This is distinct from how a zero is handled, e.g. if step_shape is set, it is called with the height parameter = 0, which can still end up drawing something, depending on the shape. One could also consider addition of nan_color/nan_shape properties which could be drawn to alert user to a gap in their data. Negative values necessitate introduction of the concept of the zero axis, from which they will be drawn DOWN, in contrast to positive values. Where should this axis be drawn? How should step_shape-s with inherent direction, e.g. arrows, be drawn for negatives? This will be addressed in the next commit. Values outside of min/max_value range can imply shapes that go beyond widget dimensions. An arrow shape that goes beyond the roof is visibly different from an arrow that saturates and merely touches the widget top, and the user may reasonably desire either behavior. Therefore this commit introduces a boolean clamp_bars property, which defaults to true for reasons of robustness and backward compatibility with the behaviour prior to this commit. The property of course affects only the display of the values, the actual user data stays intact. commit c9c24fb1ab741cd6bb8074971d568e0646c51532 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:29:01 2021 +0700 Use rectangles for drawing graph bars of all sizes I have benchmarked various bar drawing strategies and graph sizes and found out that drawing graph bars with rectangles is as fast or even marginally faster than using fat lines. Thus the code that tries to special-case 1px bars as lines seems to be not worth the additional complexity it introduces, and this commit removes it. Incidentally, I've also established, that rectangles are more robust. If one draws graph bars and feeds cairo very large coordinates (~2^62), then corresponding rectangles fail to render, but all the other rectangles in the same series get rendered successfully, whereas a single large coordinate in a move_to/line_to() sequence is enough to cause all lines in the batch to fail to render. commit bc7f4be5e06405b5aa7fb443a5ace2b9b9575b1f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:01:42 2021 +0700 Cache cairo methods used in the inner loop of graph:draw() I have benchmarked the draw() method, both on cairo xcb and on argb32 image surfaces, and have discovered that cairo context method calls are unnaturally slow, to the point, that almost nothing else in the function matters. For example, drawing bars with vertical lines made the whole draw() function almost 2 times slower than doing the same with rectangles, merely because the former requires two method calls (move_to/line_to), and the latter only one. Simply caching hotpath cairo methods in local variables, reduced the difference between the drawing methods to a negligible one and made the whole function 4 times faster on a typical 100-bar graph, (1.4ms to 0.33ms on my laptop) thus bringing closer my secret dream of drawing live audio data with awesome graphs (kidding). I guess, LGI does something not very optimal there, like gobject-introspecting the method every time, or something. There might be some untapped optimization potential, possibly in upstream, like a method cache, from which everything in awesome could benefit, even though I imagine not many things are so call-intensive as the graph widget, to really feel the difference. commit 538df25bdf02b53d0bbe81809eef2e6fdb05ff6f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 07:32:37 2021 +0700 Use 1-based index in the inner graph draw loop It's used there only once for a calculation and then several times to index some tables, and index 0 is bad for lua tables performance-wise. commit 7412546916cd0a0a490130a120255ffb719398b8 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:55:12 2021 +0700 Leave graph.max_value uninitialized at construction This is a user-visible change, but the previous behavior was probably not what user expected anyway, namely with graph.scale set the graph mysteriously couldn't zoom in onto values that are smaller than 1. commit eb81d7929a161e79c13262cfa73ac2e6a820e8ed Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:48:29 2021 +0700 Allow graph.max_value be nil This makes its behave symmetrically to min_value. Now it defaults to 1, when graph.scale is not set, and to the maximum value in the graph data, when graph.scale is set. commit f622a8430cbfde7007b58d7f4c9c76b08c91e997 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 12:01:13 2021 +0700 Calculate max values num in graph:add_value() more precisely At most so many values were kept in the widget values array, as there are pixels of usable widget width (i.e. sans border). This is exactly right for the surely most common case of graphs with 1px-wide bars, but is more than needed for graphs with step_width+step_spacing>1, and not enough for graphs with subpixel bars. Moreover this made graph autoscaling behave weirdly in graphs with thicker bars, because old values survived longer in the array and kept affecting min_value/max_value calculations even after they were shifted off-screen. The maximum number of kept values is now calculated exactly as the number of values that can be displayed at once, taking step_width and step_spacing into account. It might be worth adding a max_values_num property to the graph to let user override this calculation, in case they draw shapes in step_shape() outside of its supposed rectangle, so our assumptions of visibility are wrong, or the widget often gets resized, resulting in the annoyance of visibly truncated series. commit 0aa63249a18641251a1bf77423739164f0eab39f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 02:30:13 2021 +0700 Fix border_color/border_width bugs in graph widget add_value() wrongly assumed that border_width is always set, when border_color is, so if user set only the latter, an error occurred. add_value() assumed that border_width is always < width which led to awesome hanging up in an infinite loop, should that fail to be the case. The graph border_width property was driven by the border_color property, instead of the other way round. If border_color wasn't set, the border wasn't drawn all, even if user sets non-zero border_width, which is a confusing behavior, in contrast to all other widgets, which simply use a plethora of fallback colors to draw the border, when needed. This also meant that, despite being documented, the beautiful.graph_border_color fallback color could not and was not ever used. If the border_color was set though, an unset border_width was immediately assumed to be 1 instead of 0, also contrary to what other widgets do. Theme colors shouldn't influence widget layout, so this commit fixes this behavior to: 1) border_width always defaults to 0, if unset. 2) border_color falls back to beautiful.graph_border_color or white. Maybe one should also add a beautiful.graph_border_width theme variable, like some widgets do. commit dad4a3253b8fef971fb82720d2a407fcc0a879ad Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 03:11:27 2021 +0700 Implement multiple data series drawing for non-stacked graph The widget can already hold and draw multiple data groups, so it makes little sense to limit drawing only to the first group when graph.stack = false, probably leading to user's confusion. Now user can call add_value(v, group) with group > 1 and the data series will be drawn (over groups with lesser id-s) with graph.stack_colors[group] color, if stack_colors is set, or with graph.color, if stack_colors isn't set. Thus nothing visibly changes for all current users of non-stacked graphs with only single data series, but the new functionality is now there for those who need it. One can't fully emulate this functionality, e.g. with multiple graphs widgets in a wibox.layout.stack, because data series in a single widget can enjoy a common auto-scaling. commit 3e645c976184ab5f50d625271c25062db9ab7ed9 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:59:12 2021 +0700 Don't draw a stacked graph data group if there's no color for it If stack_colors[idx] is nil for the group `idx`, don't draw it at all (as opposed, e.g. to the effect of drawing it with a transparent color, or as though it had all values == 0, which both produce different results). This code is still subtly different from prior behavior, because now user can have a non-contiguous stack_colors table and turn the display of any data group on and off at will, whereas the code two commits earlier would just stop rendering as soon as ipairs(stack_colors) does, i.e. no data series were drawn past the first nil hole. This commit only looks large, but is mostly a whitespace change due to nesting from a pair of wrapping ifs. commit 8c0273786de1ade66bdd977699f98b588da9f487 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:32:42 2021 +0700 Unify stacked and non-stacked graph rendering Now stacked graphs support everything the nonstacked do, like step_width ~= 1 and custom step_shape-s. Technical notes: This code no longer calls cr:stroke() for every single value in a stacked graph, but rather paints a whole series and then cr:stroke/fill()-s it at all once with its color, improving performance considerably. Additionally the graph no longer relies on the presence of the stack_colors property to render series. It draws all data that it has, and, if there's no corresponding color in stack_colors, falls back to graph.color. This will be changed in the next commit to the prior behavior of only drawing those series for which there's a color in the stack_colors table, but that would've made this commit's diff much larger due to required nesting ifs over large code chunks. This commit also takes care to properly handle NaNs and negative values. Those are currently (almost) guaranteed to not occur in the values array due to clipping in graph:add_value(), but I intend to eventually drop the clipping, because it does nothing that draw() really relies upon, and allowing unclipped and NaN values is more flexible and less surprising, e.g. when user dynamically toggles graph.scale. Eventually I intend to implement drawing negative values in non-stacked graphs and possibly even in the stacked ones. commit 38c6589c51b2aa057d7c2f40182ecf32b5b2ff5e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 10:04:15 2021 +0700 Convert graph:draw() to use `self` commit f21f1b2550426df1c4ea8abde079b1ccef84b394 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 12:27:26 2021 +0700 Fix scaling for stacked graphs Stacked graphs have cumulative heights, values from all groups that end up in the same graph bar are effectively summed. Previous code didn't take that into account when calculating min/max_value for the purposes of rescaling, but simply examined every value separately. This code adds up all values up front and finds min/max among those, to determine proper scaling that would fit the graph exactly. commit 1ce4c7bf63bdd6c99c195d668347bec49355e921 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 09:49:35 2021 +0700 Draw shaped graph bars only up to the baseline Prior to this commit, every step_shape bar was, for some reason, drawn with height = full widget's height, regardless of the value it represents. So for the most bars their lower end was far outside the box and therefore clipped off. So e.g. one couldn't see the lower round edges of rounded rectangles. This commit fixes that by appropriately varying the height that is passed into step_shape(), so that lower edges of the bars always exactly touch the lower edge of the graph. If the user wants to restore the prior behavior, they can simply ignore the passed-in height in step_shape() and just always draw their shape with height = full widget height. But they couldn't replicate the effect of this commit in their rc.lua without essentially writing their own graph widget. commit e25185b08da23745b5815f85f6e9e43222ed2294 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 08:42:54 2021 +0700 Avoid incremental transform shifting during graph bar rendering Instead of letting addition errors and cognitive load accumulate simply use the same offset calculation that is used for lines and reset the transform back to the top-left corner after every drawn bar. It's conceptually simpler, and will be useful later for stacked graphs and more. It also fixes a discrepancy between plain and step_shape rendering. Prior to this commit, bar coloring occurred with a disturbed transform, which was centered at the last drawn bar, wherever it happened to be. This meant that nontrivial colors, like gradients and images, were arbitrarily offset when step_shape was used. commit f3e98866d8583c35d0b557c09f529299edc9ba2c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:45:29 2021 +0700 Refactor out graph bar height expression into a variable commit f54528d6892f5aaa4595917853c9e2130f743fdb Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:20:25 2021 +0700 Draw graph bars with lines only when step_width == 1 Before this commit graph bars of step_width < 1 were drawn with lines of width 1, but that's a bit unfortunate, because it led to much overpainting, while still ending up with a graphically incorrect result. Let's draw such bars with rectangles and let cairo do things with subpixels, getting more graphically accurate result, while still keeping the common case of step_width == 1 (supposedly) fast-drawn with lines. commit b6515dca67c18b78eb5657378131a62ab43d4cfc Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:54:07 2021 +0700 Fix unsharp edges when drawing graph bars with rectangles The 0.5 offset is only helpful for 1-width strokes, not for fills. commit 7f3db8f07e82f821bcd1dc418d66564f537609d6 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:21:01 2021 +0700 Do coordinate transformations even if the value isn't drawn The graph presently doesn't draw values < 0, but there was a discrepancy between drawing with and without step_shape. The default line/rectangle drawing reserves an empty place where the skipped value should've been, but no such empty place is reserved, when step_shape is used. This commit fixes that. Skipped values always have their empty space now. commit 7b731ebfe96af7a9dba9c57371da6761cf22c00c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:03:30 2021 +0700 Fix graph bar scaling when min_value is not 0 Previous calculation lead to underscaling of values so that even value = max_value wasn't drawn to full height. commit dca59fd8b7fff0e7e646f2dfc6bd92047028e5a8 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:18:40 2021 +0700 Calculate the horizontal graph bar offset only when needed commit 940ddf4b9825e9c3a91d276208c089d8c74e49db Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:17:23 2021 +0700 Call cr:move_to() only when drawing the graph with lines It's redundant otherwise and might even interfere with user's drawing in step_shape(). commit b5d5cd10ca29fb29cd751faaa95137c2fe89ff63 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:04:06 2021 +0700 Fix wrong horizontal offset calculations in widget.graph Indices in the loop are zero-based, so things like i-1 and i>1 aren't necessary and actually wouldn't have been correct even in a 1-based loop. step_shape = gears.shape.rectangle and step_shape = nil, which is supposed to default to rectangle according to docs, were drawn differently due to mismatched offset calculations, which led to problems especially noticeable when step_spacing is not zero, e.g: 1) whole graph shifted left too much, drawn partially off-screen. 2) whole graph shifted right too much, and the first bar placed closer to the rest than others. commit dc369fa198281e000afc1bd17326f3a264adf7d1 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 02:09:21 2021 +0700 Fix the "border_width affects graph bar width" bug Graph bars were drawn with lines of border_width thickness instead of 1, i.e. possibly too fat to be readable. commit 1ba73378cf29ed27a6ecd18716394779ceb543d9 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 00:13:03 2021 +0700 Simplify internal values representation in graph even more New values in graph._private.values were pushed to the end, and old values were removed from the beginning of the table. In draw() however newer values are drawn first, at the left edge of the graph, necessitating annoying idx = #values - idx conversions everywhere. In particular in stacked graph that means that values that are drawn together may have different indices. One has to shift the values array in add_value() either way, so one might as well insert new values at the beginning of the array and simplify the rest of the code. This doesn't seem like much yet, but it will prove to be much more convenient and less error-prone in what I'm going to do later. commit f70523ee68dbd6b67050ace4b82903a9b22440c7 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 13:35:10 2021 +0700 Simplify internal values representation in graph widget graph._private.values was either a table of values or a table of tables of values, depending on whether "stack" property is enabled, ... or a mix of both if user forgot to pass the second "group" parameter to graph:add_value(). This resulted in pretty fragile and confusing behavior, e.g. leaving user to wonder, why they see nothing more after setting stack=true for a graph in a previously working rc.lua, and demanding a very careful use of add_value() from the user, or else some values or even entire stacks would mysteriously vanish and errors in draw() get triggered, sometimes after a period of seemingly normal functioning. Now `values` is always a table of tables and the group parameter in add_value() defaults to 1, thus making the non-stacked graph a trivial variant of the stacked graph.
2021-05-20 17:14:03 +02:00
-- @tparam[opt=NaN] number value The value to be added to a graph's data group.
-- @tparam[opt=1] integer group The index of the data group.
-- @noreturn Note that it actually returns something, but that's better undocumented.
2016-05-23 07:30:44 +02:00
function graph:add_value(value, group)
graph: complete widget overhaul and bug fixes Squashed commit of the following: commit 69821f51fe1e8652715658543e50631ece495090 Author: Alex Belykh <albel727@ngs.ru> Date: Mon May 10 19:17:55 2021 +0700 Refactor property handling in wibox.widget.graph In addition, baseline_value, step_width and step_spacing properties are all set to their default non-nil numeric values during instance creation now. commit 842eb429bf5df4b2ba46b2e6f7646afe89b1a3c3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 9 06:18:45 2021 +0700 Make graph:draw_values() private There's no documentation nor confidence that it's a good API, so I'm hiding it for now. commit 720746780574e4bad14a71fc4174955139c2eb50 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 04:04:13 2021 +0700 Document graph_should_draw_data_group() for posterity commit 7c73f3754ce6ba86b118f1170a6f9e406ab4841d Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:59:21 2021 +0700 Make graph:should_draw_data_group() private There's no confidence whether it's a good API, so I'm hiding it for now. commit b3539e20adb6423118af726ae4f8751ef864b5d2 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:36:44 2021 +0700 Make graph:preprocess_values() private There's no confidence whether it's a good API, so I'm hiding it for now. commit 2df4400e780a6260f8d0ab1c60734fbb11bf3c4f Author: Alex Belykh <albel727@ngs.ru> Date: Thu May 6 10:05:23 2021 +0700 Add spec/wibox/widget/graph_spec.lua commit ce01a9771ab439b8df841f1565aa4aa8a44a5e0d Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 08:16:18 2021 +0700 Improve graph.capacity documentation commit e28fade8f655802f5bf89154169f68d256b8e332 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 07:36:01 2021 +0700 Document graph:compute_drawn_values_num() commit dd68332292465422d08b48e9f0229f143beccbc6 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 05:24:23 2021 +0700 Document graph:pick_data_group_color() method commit ca6dc55f77aca88a6e9cc90bb6fd0045739f41f5 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 04:13:58 2021 +0700 Guard against setting data groups sparsely in graph:add_value() commit cdea5f126f7984e72924f6dffbd312c4fe3b032f Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 02:36:12 2021 +0700 Refactor scaling and baseline choice out of graph:draw() commit 10135deafe8a9390ba66ab657ebb1fb41d549e8c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 21:34:48 2021 +0700 Document graph.nan_color and graph.stack properties commit 0d73bb84ac66b87e16aee472bd1f299549f66a1c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 20:49:24 2021 +0700 Use a better example in docs for the graph.stack property commit c27d160a0826d033d49d6141608c40aa8a0642fa Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 16:05:38 2021 +0700 Document graph.baseline_value property commit 459bc8176c13d4ea32af0dcdc2399d3b929b1c6a Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:53:47 2021 +0700 Document graph.clamp_bars property commit e732ee0b3b007d213e689b98cc00ae7cb141ae08 Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:34 2021 +0700 Improve graph:add_value() and :clear() documentation commit ae73e3aa9f8d60409cbe4cb4da9612f31af4f38e Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:11 2021 +0700 Improve graph min_value/max_value/scale documentation commit a7350996a9def773b91b1631e6c0507e33d8f93b Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:27:36 2021 +0700 Cleanup graph examples to show only relevant things in docs commit 30740c26f4f41c16f4c8a3c339bcb7bd299e14cd Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 14:04:36 2021 +0700 Rename graph.stack_colors property to `group_colors` The colors are used for stacked and non-stacked graphs alike, so the name didn't make sense. Also document and clarify some color stuff. commit 1ea01bfd410971cf0d0c25fd74cd764d5eb7279d Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 21:25:41 2021 +0700 Add an example file for code coverage of some fringe graph cases commit eae68e39553f88a6275a0c65bf040b5d4c262b54 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 20:23:32 2021 +0700 Add step_shape to the graph/nan_color example for code coverage commit 8027147bc2eab34a97b49a428fb8cef27f122bed Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:55:18 2021 +0700 Add example files for graph.nan_color/nan_indication properties commit 89a25a165c52b2c15b6c4d95ecb777a9067e66f9 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:33:07 2021 +0700 Make widget graph:add_value(v) default v to NaN 0 as a default makes no sense and silences the error that is failing to specify a value, to which the user should rather be alerted. The graph widget has sane NaN handling now and will do just that. commit 2835d552440be48d356a7f7b1d64963696dd550b Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:20:48 2021 +0700 Implement graph.nan_color and nan_indication properties Now, whenever there's a NaN among values and graph.nan_indication is set, the corresponding area, where the value bar should have been drawn, is filled with the nan_color from top to bottom. The painting is done after all data is rendered, to make sure that it won't be overpainted and go unnoticed. NaNs among graph's values are inevitably a sign of some error and it is arguably not a sane default behavior to simply not render them at all. The user should take immediate note of any errors by default, instead of wondering at a mysteriously empty graph widget, which is why nan_indication is set to true by default. If the user wants to silence NaNs, they can always simply set it to false. nan_color if unset, defaults to a noticeable yellow-black pattern. As a direct and planned consequence, stacked graphs with negative values in them trigger the NaN handling too. But silencing NaN handling for those graphs is also possible and semi-sane, because then NaNs and negatives are ignored and the graph behaves (almost) like they were zeros. commit 9c1f8f08b400e01e19c534f3810c9c9ab485760c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 26 08:10:19 2021 +0700 Refactor out graph_map_value_to_widget_coordinates() function commit 1905991ddecabdae3e15ffb8ee5b18a1bee1a711 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 28 13:39:29 2021 +0700 Add an example file for graph.baseline_value property commit b31b39d66f4c037a9484b71a3f910703ae01fac0 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 07:03:49 2021 +0700 Add an example file for graph.clamp_bars=false/true comparison commit a1fc850e3047f9ee2c9ddeffacfbabf3ded34171 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 04:06:45 2021 +0700 Add an example file for graphs with negative values and shapes commit 2f012f8ff1bb5094c14e724a61e59a2a3a296bc8 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 01:06:05 2021 +0700 Add an example file for a graph displaying negative values commit 7b14fbd59a4c4c51962fd3110a1a900dd5f4eb98 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 11:18:26 2021 +0700 Add an example file for graphs with step_width < 1 commit dfd9a54544b637e2fbe3d1325f9cc24b3f4d2313 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 10:17:38 2021 +0700 Add an example file for graph.stack=false/true comparison Should also demonstrate that multiple data groups are possible for non-stacked graphs too. commit 5b532106c3fd255f9b66a66cfb88d977b489ab5d Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 09:34:14 2021 +0700 Add an example file for disabling graph data groups with nil colors commit ac6f1083c02fcb428f089a616f9b0ebaa5496b6c Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:57:25 2021 +0700 Add an example file for step_shape/width/spacing on stacked graphs commit b3703db6a5139e73e72c1cfd198ece2cd9be7df2 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:43:56 2021 +0700 Add an example file for scale=false/true on stacked graph commit b000aed4a12cb44fb501e1c8432a71a14b2f8f3f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 20 08:22:19 2021 +0700 Document deprecation of graph's width and height commit cb0ad617bd6c83a499896797318e3b0b588c3566 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 07:53:37 2021 +0700 Deprecate width and height properties of wibox.widget.graph commit 8c64e14ad91a074b784166a7054e53444cccb9d2 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 04:37:59 2021 +0700 Refactor graph:draw() to separate concerns Firstly, summation of group values for stacked graphs is extracted into graph:preprocess_values(). This makes graph:draw() smaller and exposes the fact, that stacked graphs are almost entirely just a form of a more general concept: pre-render data manipulation. It's something a user might want to customize, and now they can do it by monkey-patching the method. Secondly, the logic for deciding, if and with which color a data group should be drawn, is extracted into graph:should_draw_data_group() and get_data_group_color() methods respectively. This makes it customizable too and makes more obvious the fact, that it is a general thing that has nothing to do with stacked graphs in particular. With this there are only 5 lines remaining in graph:draw(), that could be said to be specific to stacked graphs, all pertaining to the `prev_y` variable, which merely preserves the bar coordinates from a previous data group. commit 2a32b6305d893f413303728a0a3da1dcd9a5591e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 22:52:37 2021 +0700 Pointlessly optimize graph:add_value() 1) Purge old values first, then insert the new one, if needed. Since Lua grows arrays in power of two steps, and our guess_capacity() also takes care to return even integers, it would be a shame to overstep the capacity by one by inserting first and thus getting the table size doubled only to never use the most of it. 2) Use a removal procedure that doesn't call the slow length operator at all. Previous code called it twice (once in table.remove) for every removed value. commit 680ea8f5d81e9040afb4662b3229e937e4b89e02 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:23:38 2021 +0700 Set graph data series color before the painting loop commit b970f0f221ae76250f2a94433ff79d653caa6e21 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:05:37 2021 +0700 Drop a large no longer necessary `if` in graph:draw_values() This commit is almost purely a whitespace change. commit 6b4f0541c70ec99d7f6e276bae64939f337120d6 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:00:50 2021 +0700 Extract values rendering code into graph:draw_values() method This is to avoid ungodly if nesting levels, which are needed to bail out of drawing values early and skip to drawing the border. The next commit will get rid of the if. commit e0950e4f99d1ea1c4b8cea2cc67629fa40030c2a Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 14:50:20 2021 +0700 Track graph draw() usage to guess necessary array capacity The last step of divorcing add_values() from width calculations, while ensuring that user doesn't observe a lack of data to draw. The widget now tracks an approximate number of values that it has been asked to draw() in _private.last_drawn_values_num, and truncates its values array based on that (if not overridden by the capacity property). commit 2e3d542282f35e7c0fed454da0f2eada0fcf3b78 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 13:35:12 2021 +0700 Add `capacity` property to graph widget This is the second step of divorcing add_values() from calculations involving widths. How many values are stored and how many values are drawn are distinct concepts. There isn't even a single definitive "how many values are drawn" because the widget could be rendered in several places with distinct widths simultaneously, and in every such place the data must be drawn appropriately, e.g. with auto-scaling, that takes only the visible data points into account. And the draw() method can do that now. The default capacity value of nil uses heuristics based on widget's _private.width to guess some capacity that would tolerate some dynamic widget resizing. If the user finds the heuristic failing them, they can set the capacity property to the desired maximum number of values that will be kept in the _private.values array (in each data group). commit 08d4a80686f7fe363551c35589a460839f60853e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 03:27:18 2021 +0700 Extract values size computation into compute_drawn_values_num() This is the first step of making add_values() not concern itself (directly) with widget dimensions. The user can monkey-patch the method to override how many values are (logically) drawn, because it affects graph auto-scaling and can be useful for rendering of step_shape-s that paint outside of their step_width. I didn't make it a full-blown function-valued property, because it seems a fringe functionality, which I'm too lazy to document. commit 3c5617c98ab1147618ed22e8414a77fc56b45f6e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 02:32:29 2021 +0700 Copy graph widget prototype methods over in bulk commit 169538837ece41a03d02ba8fbc0e4097062b67a3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun Apr 25 05:58:01 2021 +0700 Remove graph.baseline_y property After giving it some thought I've concluded, that it is unlikely to be useful as it is now. I'd better reserve the name for some more sensible functionality, like fixing the baseline_value defined axis at the given widget coordinate, adjusting the scaling accordingly, so min_value/max_value won't generally correspond to widget top and bottom anymore. commit 202ffaa9a0b820c7c471e1121d01b91f8f76cd38 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 12:06:55 2021 +0700 Add baseline_y and baseline_value properties to graph widget Now values in graph have a *baseline*, tunable with baseline_y and baseline_value properties. It is the (for now invisible) horizontal line from which all bars in the graph grow. I call it a "baseline" instead of "axis", because it doesn't actually have to correspond to the 0 value. But it can, and actually does now by default. baseline_y and baseline_value properties determine the vertical placement of the baseline in the widget. baseline_y is the position of the baseline in normalized widget coordinates, e.g. 0 corresponds to widget's top, 1 - to widget's bottom, and 0.5 - to widget's middle. So if e.g. one sets baseline_y = 0, then no matter how the widget is resized and how its values are scaled, bars in it will be drawn starting from the top. baseline_value is the position of the baseline in value coordinates. So if one sets baseline_value = 10, then no matter what happens, bars for values greater than 10 will be drawn as growing up from this line, and bars for values smaller as growing down. Where and if it will be seen in the widget, depends on the scaling. baseline_value = 0 is the familiar zero axis. baseline_y is unset by default and has priority, i.e. if it's set, then baseline_value is ignored. baseline_value defaults to 0. Prior to this commit, bars were always drawn from the bottom edge of the widget, or, in the new terms, the widget had baseline_y = 1. But it was indistinguishable from baseline_value = 0, because the widget clamped all negative values to 0, so the difference between the two could never be observed. baseline_value = 0 is clearly more useful and familiar to people, so now it is in force by default. commit df7e19abaebf0a871b5ca217b4cc3365533267a6 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 11:18:12 2021 +0700 Move value clamping from graph:add_value() into draw() Previously all values were forcibly clamped between 0 (not tunable at all) and max_value (1 by default). Thus graphs with negative values were not an option. NaN values were conflated with zeros, even though it may be useful to maintain the distinction and render them differently. And if user happened to dynamically change the max_value property, they discovered, that their data got truncated. Preserving user data as it was given is more flexible and makes things easier to debug. This commit does exactly that. So what happens now to the newly allowed values during draw()? There are 3 new cases to consider: 1) NaNs 2) Negatives 3) Values outside of min_value..max_value range NaNs are presently simply not drawn. An empty place is reserved where the value should've been, but nothing else is done. This is distinct from how a zero is handled, e.g. if step_shape is set, it is called with the height parameter = 0, which can still end up drawing something, depending on the shape. One could also consider addition of nan_color/nan_shape properties which could be drawn to alert user to a gap in their data. Negative values necessitate introduction of the concept of the zero axis, from which they will be drawn DOWN, in contrast to positive values. Where should this axis be drawn? How should step_shape-s with inherent direction, e.g. arrows, be drawn for negatives? This will be addressed in the next commit. Values outside of min/max_value range can imply shapes that go beyond widget dimensions. An arrow shape that goes beyond the roof is visibly different from an arrow that saturates and merely touches the widget top, and the user may reasonably desire either behavior. Therefore this commit introduces a boolean clamp_bars property, which defaults to true for reasons of robustness and backward compatibility with the behaviour prior to this commit. The property of course affects only the display of the values, the actual user data stays intact. commit c9c24fb1ab741cd6bb8074971d568e0646c51532 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:29:01 2021 +0700 Use rectangles for drawing graph bars of all sizes I have benchmarked various bar drawing strategies and graph sizes and found out that drawing graph bars with rectangles is as fast or even marginally faster than using fat lines. Thus the code that tries to special-case 1px bars as lines seems to be not worth the additional complexity it introduces, and this commit removes it. Incidentally, I've also established, that rectangles are more robust. If one draws graph bars and feeds cairo very large coordinates (~2^62), then corresponding rectangles fail to render, but all the other rectangles in the same series get rendered successfully, whereas a single large coordinate in a move_to/line_to() sequence is enough to cause all lines in the batch to fail to render. commit bc7f4be5e06405b5aa7fb443a5ace2b9b9575b1f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:01:42 2021 +0700 Cache cairo methods used in the inner loop of graph:draw() I have benchmarked the draw() method, both on cairo xcb and on argb32 image surfaces, and have discovered that cairo context method calls are unnaturally slow, to the point, that almost nothing else in the function matters. For example, drawing bars with vertical lines made the whole draw() function almost 2 times slower than doing the same with rectangles, merely because the former requires two method calls (move_to/line_to), and the latter only one. Simply caching hotpath cairo methods in local variables, reduced the difference between the drawing methods to a negligible one and made the whole function 4 times faster on a typical 100-bar graph, (1.4ms to 0.33ms on my laptop) thus bringing closer my secret dream of drawing live audio data with awesome graphs (kidding). I guess, LGI does something not very optimal there, like gobject-introspecting the method every time, or something. There might be some untapped optimization potential, possibly in upstream, like a method cache, from which everything in awesome could benefit, even though I imagine not many things are so call-intensive as the graph widget, to really feel the difference. commit 538df25bdf02b53d0bbe81809eef2e6fdb05ff6f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 07:32:37 2021 +0700 Use 1-based index in the inner graph draw loop It's used there only once for a calculation and then several times to index some tables, and index 0 is bad for lua tables performance-wise. commit 7412546916cd0a0a490130a120255ffb719398b8 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:55:12 2021 +0700 Leave graph.max_value uninitialized at construction This is a user-visible change, but the previous behavior was probably not what user expected anyway, namely with graph.scale set the graph mysteriously couldn't zoom in onto values that are smaller than 1. commit eb81d7929a161e79c13262cfa73ac2e6a820e8ed Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:48:29 2021 +0700 Allow graph.max_value be nil This makes its behave symmetrically to min_value. Now it defaults to 1, when graph.scale is not set, and to the maximum value in the graph data, when graph.scale is set. commit f622a8430cbfde7007b58d7f4c9c76b08c91e997 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 12:01:13 2021 +0700 Calculate max values num in graph:add_value() more precisely At most so many values were kept in the widget values array, as there are pixels of usable widget width (i.e. sans border). This is exactly right for the surely most common case of graphs with 1px-wide bars, but is more than needed for graphs with step_width+step_spacing>1, and not enough for graphs with subpixel bars. Moreover this made graph autoscaling behave weirdly in graphs with thicker bars, because old values survived longer in the array and kept affecting min_value/max_value calculations even after they were shifted off-screen. The maximum number of kept values is now calculated exactly as the number of values that can be displayed at once, taking step_width and step_spacing into account. It might be worth adding a max_values_num property to the graph to let user override this calculation, in case they draw shapes in step_shape() outside of its supposed rectangle, so our assumptions of visibility are wrong, or the widget often gets resized, resulting in the annoyance of visibly truncated series. commit 0aa63249a18641251a1bf77423739164f0eab39f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 02:30:13 2021 +0700 Fix border_color/border_width bugs in graph widget add_value() wrongly assumed that border_width is always set, when border_color is, so if user set only the latter, an error occurred. add_value() assumed that border_width is always < width which led to awesome hanging up in an infinite loop, should that fail to be the case. The graph border_width property was driven by the border_color property, instead of the other way round. If border_color wasn't set, the border wasn't drawn all, even if user sets non-zero border_width, which is a confusing behavior, in contrast to all other widgets, which simply use a plethora of fallback colors to draw the border, when needed. This also meant that, despite being documented, the beautiful.graph_border_color fallback color could not and was not ever used. If the border_color was set though, an unset border_width was immediately assumed to be 1 instead of 0, also contrary to what other widgets do. Theme colors shouldn't influence widget layout, so this commit fixes this behavior to: 1) border_width always defaults to 0, if unset. 2) border_color falls back to beautiful.graph_border_color or white. Maybe one should also add a beautiful.graph_border_width theme variable, like some widgets do. commit dad4a3253b8fef971fb82720d2a407fcc0a879ad Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 03:11:27 2021 +0700 Implement multiple data series drawing for non-stacked graph The widget can already hold and draw multiple data groups, so it makes little sense to limit drawing only to the first group when graph.stack = false, probably leading to user's confusion. Now user can call add_value(v, group) with group > 1 and the data series will be drawn (over groups with lesser id-s) with graph.stack_colors[group] color, if stack_colors is set, or with graph.color, if stack_colors isn't set. Thus nothing visibly changes for all current users of non-stacked graphs with only single data series, but the new functionality is now there for those who need it. One can't fully emulate this functionality, e.g. with multiple graphs widgets in a wibox.layout.stack, because data series in a single widget can enjoy a common auto-scaling. commit 3e645c976184ab5f50d625271c25062db9ab7ed9 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:59:12 2021 +0700 Don't draw a stacked graph data group if there's no color for it If stack_colors[idx] is nil for the group `idx`, don't draw it at all (as opposed, e.g. to the effect of drawing it with a transparent color, or as though it had all values == 0, which both produce different results). This code is still subtly different from prior behavior, because now user can have a non-contiguous stack_colors table and turn the display of any data group on and off at will, whereas the code two commits earlier would just stop rendering as soon as ipairs(stack_colors) does, i.e. no data series were drawn past the first nil hole. This commit only looks large, but is mostly a whitespace change due to nesting from a pair of wrapping ifs. commit 8c0273786de1ade66bdd977699f98b588da9f487 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:32:42 2021 +0700 Unify stacked and non-stacked graph rendering Now stacked graphs support everything the nonstacked do, like step_width ~= 1 and custom step_shape-s. Technical notes: This code no longer calls cr:stroke() for every single value in a stacked graph, but rather paints a whole series and then cr:stroke/fill()-s it at all once with its color, improving performance considerably. Additionally the graph no longer relies on the presence of the stack_colors property to render series. It draws all data that it has, and, if there's no corresponding color in stack_colors, falls back to graph.color. This will be changed in the next commit to the prior behavior of only drawing those series for which there's a color in the stack_colors table, but that would've made this commit's diff much larger due to required nesting ifs over large code chunks. This commit also takes care to properly handle NaNs and negative values. Those are currently (almost) guaranteed to not occur in the values array due to clipping in graph:add_value(), but I intend to eventually drop the clipping, because it does nothing that draw() really relies upon, and allowing unclipped and NaN values is more flexible and less surprising, e.g. when user dynamically toggles graph.scale. Eventually I intend to implement drawing negative values in non-stacked graphs and possibly even in the stacked ones. commit 38c6589c51b2aa057d7c2f40182ecf32b5b2ff5e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 10:04:15 2021 +0700 Convert graph:draw() to use `self` commit f21f1b2550426df1c4ea8abde079b1ccef84b394 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 12:27:26 2021 +0700 Fix scaling for stacked graphs Stacked graphs have cumulative heights, values from all groups that end up in the same graph bar are effectively summed. Previous code didn't take that into account when calculating min/max_value for the purposes of rescaling, but simply examined every value separately. This code adds up all values up front and finds min/max among those, to determine proper scaling that would fit the graph exactly. commit 1ce4c7bf63bdd6c99c195d668347bec49355e921 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 09:49:35 2021 +0700 Draw shaped graph bars only up to the baseline Prior to this commit, every step_shape bar was, for some reason, drawn with height = full widget's height, regardless of the value it represents. So for the most bars their lower end was far outside the box and therefore clipped off. So e.g. one couldn't see the lower round edges of rounded rectangles. This commit fixes that by appropriately varying the height that is passed into step_shape(), so that lower edges of the bars always exactly touch the lower edge of the graph. If the user wants to restore the prior behavior, they can simply ignore the passed-in height in step_shape() and just always draw their shape with height = full widget height. But they couldn't replicate the effect of this commit in their rc.lua without essentially writing their own graph widget. commit e25185b08da23745b5815f85f6e9e43222ed2294 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 08:42:54 2021 +0700 Avoid incremental transform shifting during graph bar rendering Instead of letting addition errors and cognitive load accumulate simply use the same offset calculation that is used for lines and reset the transform back to the top-left corner after every drawn bar. It's conceptually simpler, and will be useful later for stacked graphs and more. It also fixes a discrepancy between plain and step_shape rendering. Prior to this commit, bar coloring occurred with a disturbed transform, which was centered at the last drawn bar, wherever it happened to be. This meant that nontrivial colors, like gradients and images, were arbitrarily offset when step_shape was used. commit f3e98866d8583c35d0b557c09f529299edc9ba2c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:45:29 2021 +0700 Refactor out graph bar height expression into a variable commit f54528d6892f5aaa4595917853c9e2130f743fdb Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:20:25 2021 +0700 Draw graph bars with lines only when step_width == 1 Before this commit graph bars of step_width < 1 were drawn with lines of width 1, but that's a bit unfortunate, because it led to much overpainting, while still ending up with a graphically incorrect result. Let's draw such bars with rectangles and let cairo do things with subpixels, getting more graphically accurate result, while still keeping the common case of step_width == 1 (supposedly) fast-drawn with lines. commit b6515dca67c18b78eb5657378131a62ab43d4cfc Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:54:07 2021 +0700 Fix unsharp edges when drawing graph bars with rectangles The 0.5 offset is only helpful for 1-width strokes, not for fills. commit 7f3db8f07e82f821bcd1dc418d66564f537609d6 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:21:01 2021 +0700 Do coordinate transformations even if the value isn't drawn The graph presently doesn't draw values < 0, but there was a discrepancy between drawing with and without step_shape. The default line/rectangle drawing reserves an empty place where the skipped value should've been, but no such empty place is reserved, when step_shape is used. This commit fixes that. Skipped values always have their empty space now. commit 7b731ebfe96af7a9dba9c57371da6761cf22c00c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:03:30 2021 +0700 Fix graph bar scaling when min_value is not 0 Previous calculation lead to underscaling of values so that even value = max_value wasn't drawn to full height. commit dca59fd8b7fff0e7e646f2dfc6bd92047028e5a8 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:18:40 2021 +0700 Calculate the horizontal graph bar offset only when needed commit 940ddf4b9825e9c3a91d276208c089d8c74e49db Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:17:23 2021 +0700 Call cr:move_to() only when drawing the graph with lines It's redundant otherwise and might even interfere with user's drawing in step_shape(). commit b5d5cd10ca29fb29cd751faaa95137c2fe89ff63 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:04:06 2021 +0700 Fix wrong horizontal offset calculations in widget.graph Indices in the loop are zero-based, so things like i-1 and i>1 aren't necessary and actually wouldn't have been correct even in a 1-based loop. step_shape = gears.shape.rectangle and step_shape = nil, which is supposed to default to rectangle according to docs, were drawn differently due to mismatched offset calculations, which led to problems especially noticeable when step_spacing is not zero, e.g: 1) whole graph shifted left too much, drawn partially off-screen. 2) whole graph shifted right too much, and the first bar placed closer to the rest than others. commit dc369fa198281e000afc1bd17326f3a264adf7d1 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 02:09:21 2021 +0700 Fix the "border_width affects graph bar width" bug Graph bars were drawn with lines of border_width thickness instead of 1, i.e. possibly too fat to be readable. commit 1ba73378cf29ed27a6ecd18716394779ceb543d9 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 00:13:03 2021 +0700 Simplify internal values representation in graph even more New values in graph._private.values were pushed to the end, and old values were removed from the beginning of the table. In draw() however newer values are drawn first, at the left edge of the graph, necessitating annoying idx = #values - idx conversions everywhere. In particular in stacked graph that means that values that are drawn together may have different indices. One has to shift the values array in add_value() either way, so one might as well insert new values at the beginning of the array and simplify the rest of the code. This doesn't seem like much yet, but it will prove to be much more convenient and less error-prone in what I'm going to do later. commit f70523ee68dbd6b67050ace4b82903a9b22440c7 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 13:35:10 2021 +0700 Simplify internal values representation in graph widget graph._private.values was either a table of values or a table of tables of values, depending on whether "stack" property is enabled, ... or a mix of both if user forgot to pass the second "group" parameter to graph:add_value(). This resulted in pretty fragile and confusing behavior, e.g. leaving user to wonder, why they see nothing more after setting stack=true for a graph in a previously working rc.lua, and demanding a very careful use of add_value() from the user, or else some values or even entire stacks would mysteriously vanish and errors in draw() get triggered, sometimes after a period of seemingly normal functioning. Now `values` is always a table of tables and the group parameter in add_value() defaults to 1, thus making the non-stacked graph a trivial variant of the stacked graph.
2021-05-20 17:14:03 +02:00
value = value or 0/0 -- default to NaN
group = group or 1
2016-05-23 07:30:44 +02:00
graph: complete widget overhaul and bug fixes Squashed commit of the following: commit 69821f51fe1e8652715658543e50631ece495090 Author: Alex Belykh <albel727@ngs.ru> Date: Mon May 10 19:17:55 2021 +0700 Refactor property handling in wibox.widget.graph In addition, baseline_value, step_width and step_spacing properties are all set to their default non-nil numeric values during instance creation now. commit 842eb429bf5df4b2ba46b2e6f7646afe89b1a3c3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 9 06:18:45 2021 +0700 Make graph:draw_values() private There's no documentation nor confidence that it's a good API, so I'm hiding it for now. commit 720746780574e4bad14a71fc4174955139c2eb50 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 04:04:13 2021 +0700 Document graph_should_draw_data_group() for posterity commit 7c73f3754ce6ba86b118f1170a6f9e406ab4841d Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:59:21 2021 +0700 Make graph:should_draw_data_group() private There's no confidence whether it's a good API, so I'm hiding it for now. commit b3539e20adb6423118af726ae4f8751ef864b5d2 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:36:44 2021 +0700 Make graph:preprocess_values() private There's no confidence whether it's a good API, so I'm hiding it for now. commit 2df4400e780a6260f8d0ab1c60734fbb11bf3c4f Author: Alex Belykh <albel727@ngs.ru> Date: Thu May 6 10:05:23 2021 +0700 Add spec/wibox/widget/graph_spec.lua commit ce01a9771ab439b8df841f1565aa4aa8a44a5e0d Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 08:16:18 2021 +0700 Improve graph.capacity documentation commit e28fade8f655802f5bf89154169f68d256b8e332 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 07:36:01 2021 +0700 Document graph:compute_drawn_values_num() commit dd68332292465422d08b48e9f0229f143beccbc6 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 05:24:23 2021 +0700 Document graph:pick_data_group_color() method commit ca6dc55f77aca88a6e9cc90bb6fd0045739f41f5 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 04:13:58 2021 +0700 Guard against setting data groups sparsely in graph:add_value() commit cdea5f126f7984e72924f6dffbd312c4fe3b032f Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 02:36:12 2021 +0700 Refactor scaling and baseline choice out of graph:draw() commit 10135deafe8a9390ba66ab657ebb1fb41d549e8c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 21:34:48 2021 +0700 Document graph.nan_color and graph.stack properties commit 0d73bb84ac66b87e16aee472bd1f299549f66a1c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 20:49:24 2021 +0700 Use a better example in docs for the graph.stack property commit c27d160a0826d033d49d6141608c40aa8a0642fa Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 16:05:38 2021 +0700 Document graph.baseline_value property commit 459bc8176c13d4ea32af0dcdc2399d3b929b1c6a Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:53:47 2021 +0700 Document graph.clamp_bars property commit e732ee0b3b007d213e689b98cc00ae7cb141ae08 Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:34 2021 +0700 Improve graph:add_value() and :clear() documentation commit ae73e3aa9f8d60409cbe4cb4da9612f31af4f38e Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:11 2021 +0700 Improve graph min_value/max_value/scale documentation commit a7350996a9def773b91b1631e6c0507e33d8f93b Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:27:36 2021 +0700 Cleanup graph examples to show only relevant things in docs commit 30740c26f4f41c16f4c8a3c339bcb7bd299e14cd Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 14:04:36 2021 +0700 Rename graph.stack_colors property to `group_colors` The colors are used for stacked and non-stacked graphs alike, so the name didn't make sense. Also document and clarify some color stuff. commit 1ea01bfd410971cf0d0c25fd74cd764d5eb7279d Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 21:25:41 2021 +0700 Add an example file for code coverage of some fringe graph cases commit eae68e39553f88a6275a0c65bf040b5d4c262b54 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 20:23:32 2021 +0700 Add step_shape to the graph/nan_color example for code coverage commit 8027147bc2eab34a97b49a428fb8cef27f122bed Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:55:18 2021 +0700 Add example files for graph.nan_color/nan_indication properties commit 89a25a165c52b2c15b6c4d95ecb777a9067e66f9 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:33:07 2021 +0700 Make widget graph:add_value(v) default v to NaN 0 as a default makes no sense and silences the error that is failing to specify a value, to which the user should rather be alerted. The graph widget has sane NaN handling now and will do just that. commit 2835d552440be48d356a7f7b1d64963696dd550b Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:20:48 2021 +0700 Implement graph.nan_color and nan_indication properties Now, whenever there's a NaN among values and graph.nan_indication is set, the corresponding area, where the value bar should have been drawn, is filled with the nan_color from top to bottom. The painting is done after all data is rendered, to make sure that it won't be overpainted and go unnoticed. NaNs among graph's values are inevitably a sign of some error and it is arguably not a sane default behavior to simply not render them at all. The user should take immediate note of any errors by default, instead of wondering at a mysteriously empty graph widget, which is why nan_indication is set to true by default. If the user wants to silence NaNs, they can always simply set it to false. nan_color if unset, defaults to a noticeable yellow-black pattern. As a direct and planned consequence, stacked graphs with negative values in them trigger the NaN handling too. But silencing NaN handling for those graphs is also possible and semi-sane, because then NaNs and negatives are ignored and the graph behaves (almost) like they were zeros. commit 9c1f8f08b400e01e19c534f3810c9c9ab485760c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 26 08:10:19 2021 +0700 Refactor out graph_map_value_to_widget_coordinates() function commit 1905991ddecabdae3e15ffb8ee5b18a1bee1a711 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 28 13:39:29 2021 +0700 Add an example file for graph.baseline_value property commit b31b39d66f4c037a9484b71a3f910703ae01fac0 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 07:03:49 2021 +0700 Add an example file for graph.clamp_bars=false/true comparison commit a1fc850e3047f9ee2c9ddeffacfbabf3ded34171 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 04:06:45 2021 +0700 Add an example file for graphs with negative values and shapes commit 2f012f8ff1bb5094c14e724a61e59a2a3a296bc8 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 01:06:05 2021 +0700 Add an example file for a graph displaying negative values commit 7b14fbd59a4c4c51962fd3110a1a900dd5f4eb98 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 11:18:26 2021 +0700 Add an example file for graphs with step_width < 1 commit dfd9a54544b637e2fbe3d1325f9cc24b3f4d2313 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 10:17:38 2021 +0700 Add an example file for graph.stack=false/true comparison Should also demonstrate that multiple data groups are possible for non-stacked graphs too. commit 5b532106c3fd255f9b66a66cfb88d977b489ab5d Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 09:34:14 2021 +0700 Add an example file for disabling graph data groups with nil colors commit ac6f1083c02fcb428f089a616f9b0ebaa5496b6c Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:57:25 2021 +0700 Add an example file for step_shape/width/spacing on stacked graphs commit b3703db6a5139e73e72c1cfd198ece2cd9be7df2 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:43:56 2021 +0700 Add an example file for scale=false/true on stacked graph commit b000aed4a12cb44fb501e1c8432a71a14b2f8f3f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 20 08:22:19 2021 +0700 Document deprecation of graph's width and height commit cb0ad617bd6c83a499896797318e3b0b588c3566 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 07:53:37 2021 +0700 Deprecate width and height properties of wibox.widget.graph commit 8c64e14ad91a074b784166a7054e53444cccb9d2 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 04:37:59 2021 +0700 Refactor graph:draw() to separate concerns Firstly, summation of group values for stacked graphs is extracted into graph:preprocess_values(). This makes graph:draw() smaller and exposes the fact, that stacked graphs are almost entirely just a form of a more general concept: pre-render data manipulation. It's something a user might want to customize, and now they can do it by monkey-patching the method. Secondly, the logic for deciding, if and with which color a data group should be drawn, is extracted into graph:should_draw_data_group() and get_data_group_color() methods respectively. This makes it customizable too and makes more obvious the fact, that it is a general thing that has nothing to do with stacked graphs in particular. With this there are only 5 lines remaining in graph:draw(), that could be said to be specific to stacked graphs, all pertaining to the `prev_y` variable, which merely preserves the bar coordinates from a previous data group. commit 2a32b6305d893f413303728a0a3da1dcd9a5591e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 22:52:37 2021 +0700 Pointlessly optimize graph:add_value() 1) Purge old values first, then insert the new one, if needed. Since Lua grows arrays in power of two steps, and our guess_capacity() also takes care to return even integers, it would be a shame to overstep the capacity by one by inserting first and thus getting the table size doubled only to never use the most of it. 2) Use a removal procedure that doesn't call the slow length operator at all. Previous code called it twice (once in table.remove) for every removed value. commit 680ea8f5d81e9040afb4662b3229e937e4b89e02 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:23:38 2021 +0700 Set graph data series color before the painting loop commit b970f0f221ae76250f2a94433ff79d653caa6e21 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:05:37 2021 +0700 Drop a large no longer necessary `if` in graph:draw_values() This commit is almost purely a whitespace change. commit 6b4f0541c70ec99d7f6e276bae64939f337120d6 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:00:50 2021 +0700 Extract values rendering code into graph:draw_values() method This is to avoid ungodly if nesting levels, which are needed to bail out of drawing values early and skip to drawing the border. The next commit will get rid of the if. commit e0950e4f99d1ea1c4b8cea2cc67629fa40030c2a Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 14:50:20 2021 +0700 Track graph draw() usage to guess necessary array capacity The last step of divorcing add_values() from width calculations, while ensuring that user doesn't observe a lack of data to draw. The widget now tracks an approximate number of values that it has been asked to draw() in _private.last_drawn_values_num, and truncates its values array based on that (if not overridden by the capacity property). commit 2e3d542282f35e7c0fed454da0f2eada0fcf3b78 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 13:35:12 2021 +0700 Add `capacity` property to graph widget This is the second step of divorcing add_values() from calculations involving widths. How many values are stored and how many values are drawn are distinct concepts. There isn't even a single definitive "how many values are drawn" because the widget could be rendered in several places with distinct widths simultaneously, and in every such place the data must be drawn appropriately, e.g. with auto-scaling, that takes only the visible data points into account. And the draw() method can do that now. The default capacity value of nil uses heuristics based on widget's _private.width to guess some capacity that would tolerate some dynamic widget resizing. If the user finds the heuristic failing them, they can set the capacity property to the desired maximum number of values that will be kept in the _private.values array (in each data group). commit 08d4a80686f7fe363551c35589a460839f60853e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 03:27:18 2021 +0700 Extract values size computation into compute_drawn_values_num() This is the first step of making add_values() not concern itself (directly) with widget dimensions. The user can monkey-patch the method to override how many values are (logically) drawn, because it affects graph auto-scaling and can be useful for rendering of step_shape-s that paint outside of their step_width. I didn't make it a full-blown function-valued property, because it seems a fringe functionality, which I'm too lazy to document. commit 3c5617c98ab1147618ed22e8414a77fc56b45f6e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 02:32:29 2021 +0700 Copy graph widget prototype methods over in bulk commit 169538837ece41a03d02ba8fbc0e4097062b67a3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun Apr 25 05:58:01 2021 +0700 Remove graph.baseline_y property After giving it some thought I've concluded, that it is unlikely to be useful as it is now. I'd better reserve the name for some more sensible functionality, like fixing the baseline_value defined axis at the given widget coordinate, adjusting the scaling accordingly, so min_value/max_value won't generally correspond to widget top and bottom anymore. commit 202ffaa9a0b820c7c471e1121d01b91f8f76cd38 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 12:06:55 2021 +0700 Add baseline_y and baseline_value properties to graph widget Now values in graph have a *baseline*, tunable with baseline_y and baseline_value properties. It is the (for now invisible) horizontal line from which all bars in the graph grow. I call it a "baseline" instead of "axis", because it doesn't actually have to correspond to the 0 value. But it can, and actually does now by default. baseline_y and baseline_value properties determine the vertical placement of the baseline in the widget. baseline_y is the position of the baseline in normalized widget coordinates, e.g. 0 corresponds to widget's top, 1 - to widget's bottom, and 0.5 - to widget's middle. So if e.g. one sets baseline_y = 0, then no matter how the widget is resized and how its values are scaled, bars in it will be drawn starting from the top. baseline_value is the position of the baseline in value coordinates. So if one sets baseline_value = 10, then no matter what happens, bars for values greater than 10 will be drawn as growing up from this line, and bars for values smaller as growing down. Where and if it will be seen in the widget, depends on the scaling. baseline_value = 0 is the familiar zero axis. baseline_y is unset by default and has priority, i.e. if it's set, then baseline_value is ignored. baseline_value defaults to 0. Prior to this commit, bars were always drawn from the bottom edge of the widget, or, in the new terms, the widget had baseline_y = 1. But it was indistinguishable from baseline_value = 0, because the widget clamped all negative values to 0, so the difference between the two could never be observed. baseline_value = 0 is clearly more useful and familiar to people, so now it is in force by default. commit df7e19abaebf0a871b5ca217b4cc3365533267a6 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 11:18:12 2021 +0700 Move value clamping from graph:add_value() into draw() Previously all values were forcibly clamped between 0 (not tunable at all) and max_value (1 by default). Thus graphs with negative values were not an option. NaN values were conflated with zeros, even though it may be useful to maintain the distinction and render them differently. And if user happened to dynamically change the max_value property, they discovered, that their data got truncated. Preserving user data as it was given is more flexible and makes things easier to debug. This commit does exactly that. So what happens now to the newly allowed values during draw()? There are 3 new cases to consider: 1) NaNs 2) Negatives 3) Values outside of min_value..max_value range NaNs are presently simply not drawn. An empty place is reserved where the value should've been, but nothing else is done. This is distinct from how a zero is handled, e.g. if step_shape is set, it is called with the height parameter = 0, which can still end up drawing something, depending on the shape. One could also consider addition of nan_color/nan_shape properties which could be drawn to alert user to a gap in their data. Negative values necessitate introduction of the concept of the zero axis, from which they will be drawn DOWN, in contrast to positive values. Where should this axis be drawn? How should step_shape-s with inherent direction, e.g. arrows, be drawn for negatives? This will be addressed in the next commit. Values outside of min/max_value range can imply shapes that go beyond widget dimensions. An arrow shape that goes beyond the roof is visibly different from an arrow that saturates and merely touches the widget top, and the user may reasonably desire either behavior. Therefore this commit introduces a boolean clamp_bars property, which defaults to true for reasons of robustness and backward compatibility with the behaviour prior to this commit. The property of course affects only the display of the values, the actual user data stays intact. commit c9c24fb1ab741cd6bb8074971d568e0646c51532 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:29:01 2021 +0700 Use rectangles for drawing graph bars of all sizes I have benchmarked various bar drawing strategies and graph sizes and found out that drawing graph bars with rectangles is as fast or even marginally faster than using fat lines. Thus the code that tries to special-case 1px bars as lines seems to be not worth the additional complexity it introduces, and this commit removes it. Incidentally, I've also established, that rectangles are more robust. If one draws graph bars and feeds cairo very large coordinates (~2^62), then corresponding rectangles fail to render, but all the other rectangles in the same series get rendered successfully, whereas a single large coordinate in a move_to/line_to() sequence is enough to cause all lines in the batch to fail to render. commit bc7f4be5e06405b5aa7fb443a5ace2b9b9575b1f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:01:42 2021 +0700 Cache cairo methods used in the inner loop of graph:draw() I have benchmarked the draw() method, both on cairo xcb and on argb32 image surfaces, and have discovered that cairo context method calls are unnaturally slow, to the point, that almost nothing else in the function matters. For example, drawing bars with vertical lines made the whole draw() function almost 2 times slower than doing the same with rectangles, merely because the former requires two method calls (move_to/line_to), and the latter only one. Simply caching hotpath cairo methods in local variables, reduced the difference between the drawing methods to a negligible one and made the whole function 4 times faster on a typical 100-bar graph, (1.4ms to 0.33ms on my laptop) thus bringing closer my secret dream of drawing live audio data with awesome graphs (kidding). I guess, LGI does something not very optimal there, like gobject-introspecting the method every time, or something. There might be some untapped optimization potential, possibly in upstream, like a method cache, from which everything in awesome could benefit, even though I imagine not many things are so call-intensive as the graph widget, to really feel the difference. commit 538df25bdf02b53d0bbe81809eef2e6fdb05ff6f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 07:32:37 2021 +0700 Use 1-based index in the inner graph draw loop It's used there only once for a calculation and then several times to index some tables, and index 0 is bad for lua tables performance-wise. commit 7412546916cd0a0a490130a120255ffb719398b8 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:55:12 2021 +0700 Leave graph.max_value uninitialized at construction This is a user-visible change, but the previous behavior was probably not what user expected anyway, namely with graph.scale set the graph mysteriously couldn't zoom in onto values that are smaller than 1. commit eb81d7929a161e79c13262cfa73ac2e6a820e8ed Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:48:29 2021 +0700 Allow graph.max_value be nil This makes its behave symmetrically to min_value. Now it defaults to 1, when graph.scale is not set, and to the maximum value in the graph data, when graph.scale is set. commit f622a8430cbfde7007b58d7f4c9c76b08c91e997 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 12:01:13 2021 +0700 Calculate max values num in graph:add_value() more precisely At most so many values were kept in the widget values array, as there are pixels of usable widget width (i.e. sans border). This is exactly right for the surely most common case of graphs with 1px-wide bars, but is more than needed for graphs with step_width+step_spacing>1, and not enough for graphs with subpixel bars. Moreover this made graph autoscaling behave weirdly in graphs with thicker bars, because old values survived longer in the array and kept affecting min_value/max_value calculations even after they were shifted off-screen. The maximum number of kept values is now calculated exactly as the number of values that can be displayed at once, taking step_width and step_spacing into account. It might be worth adding a max_values_num property to the graph to let user override this calculation, in case they draw shapes in step_shape() outside of its supposed rectangle, so our assumptions of visibility are wrong, or the widget often gets resized, resulting in the annoyance of visibly truncated series. commit 0aa63249a18641251a1bf77423739164f0eab39f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 02:30:13 2021 +0700 Fix border_color/border_width bugs in graph widget add_value() wrongly assumed that border_width is always set, when border_color is, so if user set only the latter, an error occurred. add_value() assumed that border_width is always < width which led to awesome hanging up in an infinite loop, should that fail to be the case. The graph border_width property was driven by the border_color property, instead of the other way round. If border_color wasn't set, the border wasn't drawn all, even if user sets non-zero border_width, which is a confusing behavior, in contrast to all other widgets, which simply use a plethora of fallback colors to draw the border, when needed. This also meant that, despite being documented, the beautiful.graph_border_color fallback color could not and was not ever used. If the border_color was set though, an unset border_width was immediately assumed to be 1 instead of 0, also contrary to what other widgets do. Theme colors shouldn't influence widget layout, so this commit fixes this behavior to: 1) border_width always defaults to 0, if unset. 2) border_color falls back to beautiful.graph_border_color or white. Maybe one should also add a beautiful.graph_border_width theme variable, like some widgets do. commit dad4a3253b8fef971fb82720d2a407fcc0a879ad Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 03:11:27 2021 +0700 Implement multiple data series drawing for non-stacked graph The widget can already hold and draw multiple data groups, so it makes little sense to limit drawing only to the first group when graph.stack = false, probably leading to user's confusion. Now user can call add_value(v, group) with group > 1 and the data series will be drawn (over groups with lesser id-s) with graph.stack_colors[group] color, if stack_colors is set, or with graph.color, if stack_colors isn't set. Thus nothing visibly changes for all current users of non-stacked graphs with only single data series, but the new functionality is now there for those who need it. One can't fully emulate this functionality, e.g. with multiple graphs widgets in a wibox.layout.stack, because data series in a single widget can enjoy a common auto-scaling. commit 3e645c976184ab5f50d625271c25062db9ab7ed9 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:59:12 2021 +0700 Don't draw a stacked graph data group if there's no color for it If stack_colors[idx] is nil for the group `idx`, don't draw it at all (as opposed, e.g. to the effect of drawing it with a transparent color, or as though it had all values == 0, which both produce different results). This code is still subtly different from prior behavior, because now user can have a non-contiguous stack_colors table and turn the display of any data group on and off at will, whereas the code two commits earlier would just stop rendering as soon as ipairs(stack_colors) does, i.e. no data series were drawn past the first nil hole. This commit only looks large, but is mostly a whitespace change due to nesting from a pair of wrapping ifs. commit 8c0273786de1ade66bdd977699f98b588da9f487 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:32:42 2021 +0700 Unify stacked and non-stacked graph rendering Now stacked graphs support everything the nonstacked do, like step_width ~= 1 and custom step_shape-s. Technical notes: This code no longer calls cr:stroke() for every single value in a stacked graph, but rather paints a whole series and then cr:stroke/fill()-s it at all once with its color, improving performance considerably. Additionally the graph no longer relies on the presence of the stack_colors property to render series. It draws all data that it has, and, if there's no corresponding color in stack_colors, falls back to graph.color. This will be changed in the next commit to the prior behavior of only drawing those series for which there's a color in the stack_colors table, but that would've made this commit's diff much larger due to required nesting ifs over large code chunks. This commit also takes care to properly handle NaNs and negative values. Those are currently (almost) guaranteed to not occur in the values array due to clipping in graph:add_value(), but I intend to eventually drop the clipping, because it does nothing that draw() really relies upon, and allowing unclipped and NaN values is more flexible and less surprising, e.g. when user dynamically toggles graph.scale. Eventually I intend to implement drawing negative values in non-stacked graphs and possibly even in the stacked ones. commit 38c6589c51b2aa057d7c2f40182ecf32b5b2ff5e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 10:04:15 2021 +0700 Convert graph:draw() to use `self` commit f21f1b2550426df1c4ea8abde079b1ccef84b394 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 12:27:26 2021 +0700 Fix scaling for stacked graphs Stacked graphs have cumulative heights, values from all groups that end up in the same graph bar are effectively summed. Previous code didn't take that into account when calculating min/max_value for the purposes of rescaling, but simply examined every value separately. This code adds up all values up front and finds min/max among those, to determine proper scaling that would fit the graph exactly. commit 1ce4c7bf63bdd6c99c195d668347bec49355e921 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 09:49:35 2021 +0700 Draw shaped graph bars only up to the baseline Prior to this commit, every step_shape bar was, for some reason, drawn with height = full widget's height, regardless of the value it represents. So for the most bars their lower end was far outside the box and therefore clipped off. So e.g. one couldn't see the lower round edges of rounded rectangles. This commit fixes that by appropriately varying the height that is passed into step_shape(), so that lower edges of the bars always exactly touch the lower edge of the graph. If the user wants to restore the prior behavior, they can simply ignore the passed-in height in step_shape() and just always draw their shape with height = full widget height. But they couldn't replicate the effect of this commit in their rc.lua without essentially writing their own graph widget. commit e25185b08da23745b5815f85f6e9e43222ed2294 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 08:42:54 2021 +0700 Avoid incremental transform shifting during graph bar rendering Instead of letting addition errors and cognitive load accumulate simply use the same offset calculation that is used for lines and reset the transform back to the top-left corner after every drawn bar. It's conceptually simpler, and will be useful later for stacked graphs and more. It also fixes a discrepancy between plain and step_shape rendering. Prior to this commit, bar coloring occurred with a disturbed transform, which was centered at the last drawn bar, wherever it happened to be. This meant that nontrivial colors, like gradients and images, were arbitrarily offset when step_shape was used. commit f3e98866d8583c35d0b557c09f529299edc9ba2c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:45:29 2021 +0700 Refactor out graph bar height expression into a variable commit f54528d6892f5aaa4595917853c9e2130f743fdb Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:20:25 2021 +0700 Draw graph bars with lines only when step_width == 1 Before this commit graph bars of step_width < 1 were drawn with lines of width 1, but that's a bit unfortunate, because it led to much overpainting, while still ending up with a graphically incorrect result. Let's draw such bars with rectangles and let cairo do things with subpixels, getting more graphically accurate result, while still keeping the common case of step_width == 1 (supposedly) fast-drawn with lines. commit b6515dca67c18b78eb5657378131a62ab43d4cfc Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:54:07 2021 +0700 Fix unsharp edges when drawing graph bars with rectangles The 0.5 offset is only helpful for 1-width strokes, not for fills. commit 7f3db8f07e82f821bcd1dc418d66564f537609d6 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:21:01 2021 +0700 Do coordinate transformations even if the value isn't drawn The graph presently doesn't draw values < 0, but there was a discrepancy between drawing with and without step_shape. The default line/rectangle drawing reserves an empty place where the skipped value should've been, but no such empty place is reserved, when step_shape is used. This commit fixes that. Skipped values always have their empty space now. commit 7b731ebfe96af7a9dba9c57371da6761cf22c00c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:03:30 2021 +0700 Fix graph bar scaling when min_value is not 0 Previous calculation lead to underscaling of values so that even value = max_value wasn't drawn to full height. commit dca59fd8b7fff0e7e646f2dfc6bd92047028e5a8 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:18:40 2021 +0700 Calculate the horizontal graph bar offset only when needed commit 940ddf4b9825e9c3a91d276208c089d8c74e49db Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:17:23 2021 +0700 Call cr:move_to() only when drawing the graph with lines It's redundant otherwise and might even interfere with user's drawing in step_shape(). commit b5d5cd10ca29fb29cd751faaa95137c2fe89ff63 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:04:06 2021 +0700 Fix wrong horizontal offset calculations in widget.graph Indices in the loop are zero-based, so things like i-1 and i>1 aren't necessary and actually wouldn't have been correct even in a 1-based loop. step_shape = gears.shape.rectangle and step_shape = nil, which is supposed to default to rectangle according to docs, were drawn differently due to mismatched offset calculations, which led to problems especially noticeable when step_spacing is not zero, e.g: 1) whole graph shifted left too much, drawn partially off-screen. 2) whole graph shifted right too much, and the first bar placed closer to the rest than others. commit dc369fa198281e000afc1bd17326f3a264adf7d1 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 02:09:21 2021 +0700 Fix the "border_width affects graph bar width" bug Graph bars were drawn with lines of border_width thickness instead of 1, i.e. possibly too fat to be readable. commit 1ba73378cf29ed27a6ecd18716394779ceb543d9 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 00:13:03 2021 +0700 Simplify internal values representation in graph even more New values in graph._private.values were pushed to the end, and old values were removed from the beginning of the table. In draw() however newer values are drawn first, at the left edge of the graph, necessitating annoying idx = #values - idx conversions everywhere. In particular in stacked graph that means that values that are drawn together may have different indices. One has to shift the values array in add_value() either way, so one might as well insert new values at the beginning of the array and simplify the rest of the code. This doesn't seem like much yet, but it will prove to be much more convenient and less error-prone in what I'm going to do later. commit f70523ee68dbd6b67050ace4b82903a9b22440c7 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 13:35:10 2021 +0700 Simplify internal values representation in graph widget graph._private.values was either a table of values or a table of tables of values, depending on whether "stack" property is enabled, ... or a mix of both if user forgot to pass the second "group" parameter to graph:add_value(). This resulted in pretty fragile and confusing behavior, e.g. leaving user to wonder, why they see nothing more after setting stack=true for a graph in a previously working rc.lua, and demanding a very careful use of add_value() from the user, or else some values or even entire stacks would mysteriously vanish and errors in draw() get triggered, sometimes after a period of seemingly normal functioning. Now `values` is always a table of tables and the group parameter in add_value() defaults to 1, thus making the non-stacked graph a trivial variant of the stacked graph.
2021-05-20 17:14:03 +02:00
local values = self._private.values
if not values[group] then
-- Ensure that there are no gaps in the values array,
-- so that ipairs() can reach all data groups.
for i = #values+1, group do
values[i] = {}
end
-- If the above loop hasn't set it, then
-- `group` wasn't a non-negative integer.
if not values[group] then
error("Invalid data group index: " .. tostring(group))
2016-05-23 07:30:44 +02:00
end
end
graph: complete widget overhaul and bug fixes Squashed commit of the following: commit 69821f51fe1e8652715658543e50631ece495090 Author: Alex Belykh <albel727@ngs.ru> Date: Mon May 10 19:17:55 2021 +0700 Refactor property handling in wibox.widget.graph In addition, baseline_value, step_width and step_spacing properties are all set to their default non-nil numeric values during instance creation now. commit 842eb429bf5df4b2ba46b2e6f7646afe89b1a3c3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 9 06:18:45 2021 +0700 Make graph:draw_values() private There's no documentation nor confidence that it's a good API, so I'm hiding it for now. commit 720746780574e4bad14a71fc4174955139c2eb50 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 04:04:13 2021 +0700 Document graph_should_draw_data_group() for posterity commit 7c73f3754ce6ba86b118f1170a6f9e406ab4841d Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:59:21 2021 +0700 Make graph:should_draw_data_group() private There's no confidence whether it's a good API, so I'm hiding it for now. commit b3539e20adb6423118af726ae4f8751ef864b5d2 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:36:44 2021 +0700 Make graph:preprocess_values() private There's no confidence whether it's a good API, so I'm hiding it for now. commit 2df4400e780a6260f8d0ab1c60734fbb11bf3c4f Author: Alex Belykh <albel727@ngs.ru> Date: Thu May 6 10:05:23 2021 +0700 Add spec/wibox/widget/graph_spec.lua commit ce01a9771ab439b8df841f1565aa4aa8a44a5e0d Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 08:16:18 2021 +0700 Improve graph.capacity documentation commit e28fade8f655802f5bf89154169f68d256b8e332 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 07:36:01 2021 +0700 Document graph:compute_drawn_values_num() commit dd68332292465422d08b48e9f0229f143beccbc6 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 05:24:23 2021 +0700 Document graph:pick_data_group_color() method commit ca6dc55f77aca88a6e9cc90bb6fd0045739f41f5 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 04:13:58 2021 +0700 Guard against setting data groups sparsely in graph:add_value() commit cdea5f126f7984e72924f6dffbd312c4fe3b032f Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 02:36:12 2021 +0700 Refactor scaling and baseline choice out of graph:draw() commit 10135deafe8a9390ba66ab657ebb1fb41d549e8c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 21:34:48 2021 +0700 Document graph.nan_color and graph.stack properties commit 0d73bb84ac66b87e16aee472bd1f299549f66a1c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 20:49:24 2021 +0700 Use a better example in docs for the graph.stack property commit c27d160a0826d033d49d6141608c40aa8a0642fa Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 16:05:38 2021 +0700 Document graph.baseline_value property commit 459bc8176c13d4ea32af0dcdc2399d3b929b1c6a Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:53:47 2021 +0700 Document graph.clamp_bars property commit e732ee0b3b007d213e689b98cc00ae7cb141ae08 Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:34 2021 +0700 Improve graph:add_value() and :clear() documentation commit ae73e3aa9f8d60409cbe4cb4da9612f31af4f38e Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:11 2021 +0700 Improve graph min_value/max_value/scale documentation commit a7350996a9def773b91b1631e6c0507e33d8f93b Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:27:36 2021 +0700 Cleanup graph examples to show only relevant things in docs commit 30740c26f4f41c16f4c8a3c339bcb7bd299e14cd Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 14:04:36 2021 +0700 Rename graph.stack_colors property to `group_colors` The colors are used for stacked and non-stacked graphs alike, so the name didn't make sense. Also document and clarify some color stuff. commit 1ea01bfd410971cf0d0c25fd74cd764d5eb7279d Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 21:25:41 2021 +0700 Add an example file for code coverage of some fringe graph cases commit eae68e39553f88a6275a0c65bf040b5d4c262b54 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 20:23:32 2021 +0700 Add step_shape to the graph/nan_color example for code coverage commit 8027147bc2eab34a97b49a428fb8cef27f122bed Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:55:18 2021 +0700 Add example files for graph.nan_color/nan_indication properties commit 89a25a165c52b2c15b6c4d95ecb777a9067e66f9 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:33:07 2021 +0700 Make widget graph:add_value(v) default v to NaN 0 as a default makes no sense and silences the error that is failing to specify a value, to which the user should rather be alerted. The graph widget has sane NaN handling now and will do just that. commit 2835d552440be48d356a7f7b1d64963696dd550b Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:20:48 2021 +0700 Implement graph.nan_color and nan_indication properties Now, whenever there's a NaN among values and graph.nan_indication is set, the corresponding area, where the value bar should have been drawn, is filled with the nan_color from top to bottom. The painting is done after all data is rendered, to make sure that it won't be overpainted and go unnoticed. NaNs among graph's values are inevitably a sign of some error and it is arguably not a sane default behavior to simply not render them at all. The user should take immediate note of any errors by default, instead of wondering at a mysteriously empty graph widget, which is why nan_indication is set to true by default. If the user wants to silence NaNs, they can always simply set it to false. nan_color if unset, defaults to a noticeable yellow-black pattern. As a direct and planned consequence, stacked graphs with negative values in them trigger the NaN handling too. But silencing NaN handling for those graphs is also possible and semi-sane, because then NaNs and negatives are ignored and the graph behaves (almost) like they were zeros. commit 9c1f8f08b400e01e19c534f3810c9c9ab485760c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 26 08:10:19 2021 +0700 Refactor out graph_map_value_to_widget_coordinates() function commit 1905991ddecabdae3e15ffb8ee5b18a1bee1a711 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 28 13:39:29 2021 +0700 Add an example file for graph.baseline_value property commit b31b39d66f4c037a9484b71a3f910703ae01fac0 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 07:03:49 2021 +0700 Add an example file for graph.clamp_bars=false/true comparison commit a1fc850e3047f9ee2c9ddeffacfbabf3ded34171 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 04:06:45 2021 +0700 Add an example file for graphs with negative values and shapes commit 2f012f8ff1bb5094c14e724a61e59a2a3a296bc8 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 01:06:05 2021 +0700 Add an example file for a graph displaying negative values commit 7b14fbd59a4c4c51962fd3110a1a900dd5f4eb98 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 11:18:26 2021 +0700 Add an example file for graphs with step_width < 1 commit dfd9a54544b637e2fbe3d1325f9cc24b3f4d2313 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 10:17:38 2021 +0700 Add an example file for graph.stack=false/true comparison Should also demonstrate that multiple data groups are possible for non-stacked graphs too. commit 5b532106c3fd255f9b66a66cfb88d977b489ab5d Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 09:34:14 2021 +0700 Add an example file for disabling graph data groups with nil colors commit ac6f1083c02fcb428f089a616f9b0ebaa5496b6c Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:57:25 2021 +0700 Add an example file for step_shape/width/spacing on stacked graphs commit b3703db6a5139e73e72c1cfd198ece2cd9be7df2 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:43:56 2021 +0700 Add an example file for scale=false/true on stacked graph commit b000aed4a12cb44fb501e1c8432a71a14b2f8f3f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 20 08:22:19 2021 +0700 Document deprecation of graph's width and height commit cb0ad617bd6c83a499896797318e3b0b588c3566 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 07:53:37 2021 +0700 Deprecate width and height properties of wibox.widget.graph commit 8c64e14ad91a074b784166a7054e53444cccb9d2 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 04:37:59 2021 +0700 Refactor graph:draw() to separate concerns Firstly, summation of group values for stacked graphs is extracted into graph:preprocess_values(). This makes graph:draw() smaller and exposes the fact, that stacked graphs are almost entirely just a form of a more general concept: pre-render data manipulation. It's something a user might want to customize, and now they can do it by monkey-patching the method. Secondly, the logic for deciding, if and with which color a data group should be drawn, is extracted into graph:should_draw_data_group() and get_data_group_color() methods respectively. This makes it customizable too and makes more obvious the fact, that it is a general thing that has nothing to do with stacked graphs in particular. With this there are only 5 lines remaining in graph:draw(), that could be said to be specific to stacked graphs, all pertaining to the `prev_y` variable, which merely preserves the bar coordinates from a previous data group. commit 2a32b6305d893f413303728a0a3da1dcd9a5591e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 22:52:37 2021 +0700 Pointlessly optimize graph:add_value() 1) Purge old values first, then insert the new one, if needed. Since Lua grows arrays in power of two steps, and our guess_capacity() also takes care to return even integers, it would be a shame to overstep the capacity by one by inserting first and thus getting the table size doubled only to never use the most of it. 2) Use a removal procedure that doesn't call the slow length operator at all. Previous code called it twice (once in table.remove) for every removed value. commit 680ea8f5d81e9040afb4662b3229e937e4b89e02 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:23:38 2021 +0700 Set graph data series color before the painting loop commit b970f0f221ae76250f2a94433ff79d653caa6e21 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:05:37 2021 +0700 Drop a large no longer necessary `if` in graph:draw_values() This commit is almost purely a whitespace change. commit 6b4f0541c70ec99d7f6e276bae64939f337120d6 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:00:50 2021 +0700 Extract values rendering code into graph:draw_values() method This is to avoid ungodly if nesting levels, which are needed to bail out of drawing values early and skip to drawing the border. The next commit will get rid of the if. commit e0950e4f99d1ea1c4b8cea2cc67629fa40030c2a Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 14:50:20 2021 +0700 Track graph draw() usage to guess necessary array capacity The last step of divorcing add_values() from width calculations, while ensuring that user doesn't observe a lack of data to draw. The widget now tracks an approximate number of values that it has been asked to draw() in _private.last_drawn_values_num, and truncates its values array based on that (if not overridden by the capacity property). commit 2e3d542282f35e7c0fed454da0f2eada0fcf3b78 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 13:35:12 2021 +0700 Add `capacity` property to graph widget This is the second step of divorcing add_values() from calculations involving widths. How many values are stored and how many values are drawn are distinct concepts. There isn't even a single definitive "how many values are drawn" because the widget could be rendered in several places with distinct widths simultaneously, and in every such place the data must be drawn appropriately, e.g. with auto-scaling, that takes only the visible data points into account. And the draw() method can do that now. The default capacity value of nil uses heuristics based on widget's _private.width to guess some capacity that would tolerate some dynamic widget resizing. If the user finds the heuristic failing them, they can set the capacity property to the desired maximum number of values that will be kept in the _private.values array (in each data group). commit 08d4a80686f7fe363551c35589a460839f60853e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 03:27:18 2021 +0700 Extract values size computation into compute_drawn_values_num() This is the first step of making add_values() not concern itself (directly) with widget dimensions. The user can monkey-patch the method to override how many values are (logically) drawn, because it affects graph auto-scaling and can be useful for rendering of step_shape-s that paint outside of their step_width. I didn't make it a full-blown function-valued property, because it seems a fringe functionality, which I'm too lazy to document. commit 3c5617c98ab1147618ed22e8414a77fc56b45f6e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 02:32:29 2021 +0700 Copy graph widget prototype methods over in bulk commit 169538837ece41a03d02ba8fbc0e4097062b67a3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun Apr 25 05:58:01 2021 +0700 Remove graph.baseline_y property After giving it some thought I've concluded, that it is unlikely to be useful as it is now. I'd better reserve the name for some more sensible functionality, like fixing the baseline_value defined axis at the given widget coordinate, adjusting the scaling accordingly, so min_value/max_value won't generally correspond to widget top and bottom anymore. commit 202ffaa9a0b820c7c471e1121d01b91f8f76cd38 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 12:06:55 2021 +0700 Add baseline_y and baseline_value properties to graph widget Now values in graph have a *baseline*, tunable with baseline_y and baseline_value properties. It is the (for now invisible) horizontal line from which all bars in the graph grow. I call it a "baseline" instead of "axis", because it doesn't actually have to correspond to the 0 value. But it can, and actually does now by default. baseline_y and baseline_value properties determine the vertical placement of the baseline in the widget. baseline_y is the position of the baseline in normalized widget coordinates, e.g. 0 corresponds to widget's top, 1 - to widget's bottom, and 0.5 - to widget's middle. So if e.g. one sets baseline_y = 0, then no matter how the widget is resized and how its values are scaled, bars in it will be drawn starting from the top. baseline_value is the position of the baseline in value coordinates. So if one sets baseline_value = 10, then no matter what happens, bars for values greater than 10 will be drawn as growing up from this line, and bars for values smaller as growing down. Where and if it will be seen in the widget, depends on the scaling. baseline_value = 0 is the familiar zero axis. baseline_y is unset by default and has priority, i.e. if it's set, then baseline_value is ignored. baseline_value defaults to 0. Prior to this commit, bars were always drawn from the bottom edge of the widget, or, in the new terms, the widget had baseline_y = 1. But it was indistinguishable from baseline_value = 0, because the widget clamped all negative values to 0, so the difference between the two could never be observed. baseline_value = 0 is clearly more useful and familiar to people, so now it is in force by default. commit df7e19abaebf0a871b5ca217b4cc3365533267a6 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 11:18:12 2021 +0700 Move value clamping from graph:add_value() into draw() Previously all values were forcibly clamped between 0 (not tunable at all) and max_value (1 by default). Thus graphs with negative values were not an option. NaN values were conflated with zeros, even though it may be useful to maintain the distinction and render them differently. And if user happened to dynamically change the max_value property, they discovered, that their data got truncated. Preserving user data as it was given is more flexible and makes things easier to debug. This commit does exactly that. So what happens now to the newly allowed values during draw()? There are 3 new cases to consider: 1) NaNs 2) Negatives 3) Values outside of min_value..max_value range NaNs are presently simply not drawn. An empty place is reserved where the value should've been, but nothing else is done. This is distinct from how a zero is handled, e.g. if step_shape is set, it is called with the height parameter = 0, which can still end up drawing something, depending on the shape. One could also consider addition of nan_color/nan_shape properties which could be drawn to alert user to a gap in their data. Negative values necessitate introduction of the concept of the zero axis, from which they will be drawn DOWN, in contrast to positive values. Where should this axis be drawn? How should step_shape-s with inherent direction, e.g. arrows, be drawn for negatives? This will be addressed in the next commit. Values outside of min/max_value range can imply shapes that go beyond widget dimensions. An arrow shape that goes beyond the roof is visibly different from an arrow that saturates and merely touches the widget top, and the user may reasonably desire either behavior. Therefore this commit introduces a boolean clamp_bars property, which defaults to true for reasons of robustness and backward compatibility with the behaviour prior to this commit. The property of course affects only the display of the values, the actual user data stays intact. commit c9c24fb1ab741cd6bb8074971d568e0646c51532 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:29:01 2021 +0700 Use rectangles for drawing graph bars of all sizes I have benchmarked various bar drawing strategies and graph sizes and found out that drawing graph bars with rectangles is as fast or even marginally faster than using fat lines. Thus the code that tries to special-case 1px bars as lines seems to be not worth the additional complexity it introduces, and this commit removes it. Incidentally, I've also established, that rectangles are more robust. If one draws graph bars and feeds cairo very large coordinates (~2^62), then corresponding rectangles fail to render, but all the other rectangles in the same series get rendered successfully, whereas a single large coordinate in a move_to/line_to() sequence is enough to cause all lines in the batch to fail to render. commit bc7f4be5e06405b5aa7fb443a5ace2b9b9575b1f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:01:42 2021 +0700 Cache cairo methods used in the inner loop of graph:draw() I have benchmarked the draw() method, both on cairo xcb and on argb32 image surfaces, and have discovered that cairo context method calls are unnaturally slow, to the point, that almost nothing else in the function matters. For example, drawing bars with vertical lines made the whole draw() function almost 2 times slower than doing the same with rectangles, merely because the former requires two method calls (move_to/line_to), and the latter only one. Simply caching hotpath cairo methods in local variables, reduced the difference between the drawing methods to a negligible one and made the whole function 4 times faster on a typical 100-bar graph, (1.4ms to 0.33ms on my laptop) thus bringing closer my secret dream of drawing live audio data with awesome graphs (kidding). I guess, LGI does something not very optimal there, like gobject-introspecting the method every time, or something. There might be some untapped optimization potential, possibly in upstream, like a method cache, from which everything in awesome could benefit, even though I imagine not many things are so call-intensive as the graph widget, to really feel the difference. commit 538df25bdf02b53d0bbe81809eef2e6fdb05ff6f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 07:32:37 2021 +0700 Use 1-based index in the inner graph draw loop It's used there only once for a calculation and then several times to index some tables, and index 0 is bad for lua tables performance-wise. commit 7412546916cd0a0a490130a120255ffb719398b8 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:55:12 2021 +0700 Leave graph.max_value uninitialized at construction This is a user-visible change, but the previous behavior was probably not what user expected anyway, namely with graph.scale set the graph mysteriously couldn't zoom in onto values that are smaller than 1. commit eb81d7929a161e79c13262cfa73ac2e6a820e8ed Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:48:29 2021 +0700 Allow graph.max_value be nil This makes its behave symmetrically to min_value. Now it defaults to 1, when graph.scale is not set, and to the maximum value in the graph data, when graph.scale is set. commit f622a8430cbfde7007b58d7f4c9c76b08c91e997 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 12:01:13 2021 +0700 Calculate max values num in graph:add_value() more precisely At most so many values were kept in the widget values array, as there are pixels of usable widget width (i.e. sans border). This is exactly right for the surely most common case of graphs with 1px-wide bars, but is more than needed for graphs with step_width+step_spacing>1, and not enough for graphs with subpixel bars. Moreover this made graph autoscaling behave weirdly in graphs with thicker bars, because old values survived longer in the array and kept affecting min_value/max_value calculations even after they were shifted off-screen. The maximum number of kept values is now calculated exactly as the number of values that can be displayed at once, taking step_width and step_spacing into account. It might be worth adding a max_values_num property to the graph to let user override this calculation, in case they draw shapes in step_shape() outside of its supposed rectangle, so our assumptions of visibility are wrong, or the widget often gets resized, resulting in the annoyance of visibly truncated series. commit 0aa63249a18641251a1bf77423739164f0eab39f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 02:30:13 2021 +0700 Fix border_color/border_width bugs in graph widget add_value() wrongly assumed that border_width is always set, when border_color is, so if user set only the latter, an error occurred. add_value() assumed that border_width is always < width which led to awesome hanging up in an infinite loop, should that fail to be the case. The graph border_width property was driven by the border_color property, instead of the other way round. If border_color wasn't set, the border wasn't drawn all, even if user sets non-zero border_width, which is a confusing behavior, in contrast to all other widgets, which simply use a plethora of fallback colors to draw the border, when needed. This also meant that, despite being documented, the beautiful.graph_border_color fallback color could not and was not ever used. If the border_color was set though, an unset border_width was immediately assumed to be 1 instead of 0, also contrary to what other widgets do. Theme colors shouldn't influence widget layout, so this commit fixes this behavior to: 1) border_width always defaults to 0, if unset. 2) border_color falls back to beautiful.graph_border_color or white. Maybe one should also add a beautiful.graph_border_width theme variable, like some widgets do. commit dad4a3253b8fef971fb82720d2a407fcc0a879ad Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 03:11:27 2021 +0700 Implement multiple data series drawing for non-stacked graph The widget can already hold and draw multiple data groups, so it makes little sense to limit drawing only to the first group when graph.stack = false, probably leading to user's confusion. Now user can call add_value(v, group) with group > 1 and the data series will be drawn (over groups with lesser id-s) with graph.stack_colors[group] color, if stack_colors is set, or with graph.color, if stack_colors isn't set. Thus nothing visibly changes for all current users of non-stacked graphs with only single data series, but the new functionality is now there for those who need it. One can't fully emulate this functionality, e.g. with multiple graphs widgets in a wibox.layout.stack, because data series in a single widget can enjoy a common auto-scaling. commit 3e645c976184ab5f50d625271c25062db9ab7ed9 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:59:12 2021 +0700 Don't draw a stacked graph data group if there's no color for it If stack_colors[idx] is nil for the group `idx`, don't draw it at all (as opposed, e.g. to the effect of drawing it with a transparent color, or as though it had all values == 0, which both produce different results). This code is still subtly different from prior behavior, because now user can have a non-contiguous stack_colors table and turn the display of any data group on and off at will, whereas the code two commits earlier would just stop rendering as soon as ipairs(stack_colors) does, i.e. no data series were drawn past the first nil hole. This commit only looks large, but is mostly a whitespace change due to nesting from a pair of wrapping ifs. commit 8c0273786de1ade66bdd977699f98b588da9f487 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:32:42 2021 +0700 Unify stacked and non-stacked graph rendering Now stacked graphs support everything the nonstacked do, like step_width ~= 1 and custom step_shape-s. Technical notes: This code no longer calls cr:stroke() for every single value in a stacked graph, but rather paints a whole series and then cr:stroke/fill()-s it at all once with its color, improving performance considerably. Additionally the graph no longer relies on the presence of the stack_colors property to render series. It draws all data that it has, and, if there's no corresponding color in stack_colors, falls back to graph.color. This will be changed in the next commit to the prior behavior of only drawing those series for which there's a color in the stack_colors table, but that would've made this commit's diff much larger due to required nesting ifs over large code chunks. This commit also takes care to properly handle NaNs and negative values. Those are currently (almost) guaranteed to not occur in the values array due to clipping in graph:add_value(), but I intend to eventually drop the clipping, because it does nothing that draw() really relies upon, and allowing unclipped and NaN values is more flexible and less surprising, e.g. when user dynamically toggles graph.scale. Eventually I intend to implement drawing negative values in non-stacked graphs and possibly even in the stacked ones. commit 38c6589c51b2aa057d7c2f40182ecf32b5b2ff5e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 10:04:15 2021 +0700 Convert graph:draw() to use `self` commit f21f1b2550426df1c4ea8abde079b1ccef84b394 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 12:27:26 2021 +0700 Fix scaling for stacked graphs Stacked graphs have cumulative heights, values from all groups that end up in the same graph bar are effectively summed. Previous code didn't take that into account when calculating min/max_value for the purposes of rescaling, but simply examined every value separately. This code adds up all values up front and finds min/max among those, to determine proper scaling that would fit the graph exactly. commit 1ce4c7bf63bdd6c99c195d668347bec49355e921 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 09:49:35 2021 +0700 Draw shaped graph bars only up to the baseline Prior to this commit, every step_shape bar was, for some reason, drawn with height = full widget's height, regardless of the value it represents. So for the most bars their lower end was far outside the box and therefore clipped off. So e.g. one couldn't see the lower round edges of rounded rectangles. This commit fixes that by appropriately varying the height that is passed into step_shape(), so that lower edges of the bars always exactly touch the lower edge of the graph. If the user wants to restore the prior behavior, they can simply ignore the passed-in height in step_shape() and just always draw their shape with height = full widget height. But they couldn't replicate the effect of this commit in their rc.lua without essentially writing their own graph widget. commit e25185b08da23745b5815f85f6e9e43222ed2294 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 08:42:54 2021 +0700 Avoid incremental transform shifting during graph bar rendering Instead of letting addition errors and cognitive load accumulate simply use the same offset calculation that is used for lines and reset the transform back to the top-left corner after every drawn bar. It's conceptually simpler, and will be useful later for stacked graphs and more. It also fixes a discrepancy between plain and step_shape rendering. Prior to this commit, bar coloring occurred with a disturbed transform, which was centered at the last drawn bar, wherever it happened to be. This meant that nontrivial colors, like gradients and images, were arbitrarily offset when step_shape was used. commit f3e98866d8583c35d0b557c09f529299edc9ba2c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:45:29 2021 +0700 Refactor out graph bar height expression into a variable commit f54528d6892f5aaa4595917853c9e2130f743fdb Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:20:25 2021 +0700 Draw graph bars with lines only when step_width == 1 Before this commit graph bars of step_width < 1 were drawn with lines of width 1, but that's a bit unfortunate, because it led to much overpainting, while still ending up with a graphically incorrect result. Let's draw such bars with rectangles and let cairo do things with subpixels, getting more graphically accurate result, while still keeping the common case of step_width == 1 (supposedly) fast-drawn with lines. commit b6515dca67c18b78eb5657378131a62ab43d4cfc Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:54:07 2021 +0700 Fix unsharp edges when drawing graph bars with rectangles The 0.5 offset is only helpful for 1-width strokes, not for fills. commit 7f3db8f07e82f821bcd1dc418d66564f537609d6 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:21:01 2021 +0700 Do coordinate transformations even if the value isn't drawn The graph presently doesn't draw values < 0, but there was a discrepancy between drawing with and without step_shape. The default line/rectangle drawing reserves an empty place where the skipped value should've been, but no such empty place is reserved, when step_shape is used. This commit fixes that. Skipped values always have their empty space now. commit 7b731ebfe96af7a9dba9c57371da6761cf22c00c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:03:30 2021 +0700 Fix graph bar scaling when min_value is not 0 Previous calculation lead to underscaling of values so that even value = max_value wasn't drawn to full height. commit dca59fd8b7fff0e7e646f2dfc6bd92047028e5a8 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:18:40 2021 +0700 Calculate the horizontal graph bar offset only when needed commit 940ddf4b9825e9c3a91d276208c089d8c74e49db Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:17:23 2021 +0700 Call cr:move_to() only when drawing the graph with lines It's redundant otherwise and might even interfere with user's drawing in step_shape(). commit b5d5cd10ca29fb29cd751faaa95137c2fe89ff63 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:04:06 2021 +0700 Fix wrong horizontal offset calculations in widget.graph Indices in the loop are zero-based, so things like i-1 and i>1 aren't necessary and actually wouldn't have been correct even in a 1-based loop. step_shape = gears.shape.rectangle and step_shape = nil, which is supposed to default to rectangle according to docs, were drawn differently due to mismatched offset calculations, which led to problems especially noticeable when step_spacing is not zero, e.g: 1) whole graph shifted left too much, drawn partially off-screen. 2) whole graph shifted right too much, and the first bar placed closer to the rest than others. commit dc369fa198281e000afc1bd17326f3a264adf7d1 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 02:09:21 2021 +0700 Fix the "border_width affects graph bar width" bug Graph bars were drawn with lines of border_width thickness instead of 1, i.e. possibly too fat to be readable. commit 1ba73378cf29ed27a6ecd18716394779ceb543d9 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 00:13:03 2021 +0700 Simplify internal values representation in graph even more New values in graph._private.values were pushed to the end, and old values were removed from the beginning of the table. In draw() however newer values are drawn first, at the left edge of the graph, necessitating annoying idx = #values - idx conversions everywhere. In particular in stacked graph that means that values that are drawn together may have different indices. One has to shift the values array in add_value() either way, so one might as well insert new values at the beginning of the array and simplify the rest of the code. This doesn't seem like much yet, but it will prove to be much more convenient and less error-prone in what I'm going to do later. commit f70523ee68dbd6b67050ace4b82903a9b22440c7 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 13:35:10 2021 +0700 Simplify internal values representation in graph widget graph._private.values was either a table of values or a table of tables of values, depending on whether "stack" property is enabled, ... or a mix of both if user forgot to pass the second "group" parameter to graph:add_value(). This resulted in pretty fragile and confusing behavior, e.g. leaving user to wonder, why they see nothing more after setting stack=true for a graph in a previously working rc.lua, and demanding a very careful use of add_value() from the user, or else some values or even entire stacks would mysteriously vanish and errors in draw() get triggered, sometimes after a period of seemingly normal functioning. Now `values` is always a table of tables and the group parameter in add_value() defaults to 1, thus making the non-stacked graph a trivial variant of the stacked graph.
2021-05-20 17:14:03 +02:00
values = values[group]
local capacity = guess_capacity(self)
-- Map negatives, NaNs and zero to nil
capacity = (capacity >= 1) and capacity
-- Remove old values over capacity
-- Invalid capacity means "remove everything"
local i = capacity or 1
while values[i] do
values[i] = nil
i = i + 1
end
2016-05-23 07:30:44 +02:00
graph: complete widget overhaul and bug fixes Squashed commit of the following: commit 69821f51fe1e8652715658543e50631ece495090 Author: Alex Belykh <albel727@ngs.ru> Date: Mon May 10 19:17:55 2021 +0700 Refactor property handling in wibox.widget.graph In addition, baseline_value, step_width and step_spacing properties are all set to their default non-nil numeric values during instance creation now. commit 842eb429bf5df4b2ba46b2e6f7646afe89b1a3c3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 9 06:18:45 2021 +0700 Make graph:draw_values() private There's no documentation nor confidence that it's a good API, so I'm hiding it for now. commit 720746780574e4bad14a71fc4174955139c2eb50 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 04:04:13 2021 +0700 Document graph_should_draw_data_group() for posterity commit 7c73f3754ce6ba86b118f1170a6f9e406ab4841d Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:59:21 2021 +0700 Make graph:should_draw_data_group() private There's no confidence whether it's a good API, so I'm hiding it for now. commit b3539e20adb6423118af726ae4f8751ef864b5d2 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:36:44 2021 +0700 Make graph:preprocess_values() private There's no confidence whether it's a good API, so I'm hiding it for now. commit 2df4400e780a6260f8d0ab1c60734fbb11bf3c4f Author: Alex Belykh <albel727@ngs.ru> Date: Thu May 6 10:05:23 2021 +0700 Add spec/wibox/widget/graph_spec.lua commit ce01a9771ab439b8df841f1565aa4aa8a44a5e0d Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 08:16:18 2021 +0700 Improve graph.capacity documentation commit e28fade8f655802f5bf89154169f68d256b8e332 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 07:36:01 2021 +0700 Document graph:compute_drawn_values_num() commit dd68332292465422d08b48e9f0229f143beccbc6 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 05:24:23 2021 +0700 Document graph:pick_data_group_color() method commit ca6dc55f77aca88a6e9cc90bb6fd0045739f41f5 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 04:13:58 2021 +0700 Guard against setting data groups sparsely in graph:add_value() commit cdea5f126f7984e72924f6dffbd312c4fe3b032f Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 02:36:12 2021 +0700 Refactor scaling and baseline choice out of graph:draw() commit 10135deafe8a9390ba66ab657ebb1fb41d549e8c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 21:34:48 2021 +0700 Document graph.nan_color and graph.stack properties commit 0d73bb84ac66b87e16aee472bd1f299549f66a1c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 20:49:24 2021 +0700 Use a better example in docs for the graph.stack property commit c27d160a0826d033d49d6141608c40aa8a0642fa Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 16:05:38 2021 +0700 Document graph.baseline_value property commit 459bc8176c13d4ea32af0dcdc2399d3b929b1c6a Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:53:47 2021 +0700 Document graph.clamp_bars property commit e732ee0b3b007d213e689b98cc00ae7cb141ae08 Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:34 2021 +0700 Improve graph:add_value() and :clear() documentation commit ae73e3aa9f8d60409cbe4cb4da9612f31af4f38e Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:11 2021 +0700 Improve graph min_value/max_value/scale documentation commit a7350996a9def773b91b1631e6c0507e33d8f93b Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:27:36 2021 +0700 Cleanup graph examples to show only relevant things in docs commit 30740c26f4f41c16f4c8a3c339bcb7bd299e14cd Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 14:04:36 2021 +0700 Rename graph.stack_colors property to `group_colors` The colors are used for stacked and non-stacked graphs alike, so the name didn't make sense. Also document and clarify some color stuff. commit 1ea01bfd410971cf0d0c25fd74cd764d5eb7279d Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 21:25:41 2021 +0700 Add an example file for code coverage of some fringe graph cases commit eae68e39553f88a6275a0c65bf040b5d4c262b54 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 20:23:32 2021 +0700 Add step_shape to the graph/nan_color example for code coverage commit 8027147bc2eab34a97b49a428fb8cef27f122bed Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:55:18 2021 +0700 Add example files for graph.nan_color/nan_indication properties commit 89a25a165c52b2c15b6c4d95ecb777a9067e66f9 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:33:07 2021 +0700 Make widget graph:add_value(v) default v to NaN 0 as a default makes no sense and silences the error that is failing to specify a value, to which the user should rather be alerted. The graph widget has sane NaN handling now and will do just that. commit 2835d552440be48d356a7f7b1d64963696dd550b Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:20:48 2021 +0700 Implement graph.nan_color and nan_indication properties Now, whenever there's a NaN among values and graph.nan_indication is set, the corresponding area, where the value bar should have been drawn, is filled with the nan_color from top to bottom. The painting is done after all data is rendered, to make sure that it won't be overpainted and go unnoticed. NaNs among graph's values are inevitably a sign of some error and it is arguably not a sane default behavior to simply not render them at all. The user should take immediate note of any errors by default, instead of wondering at a mysteriously empty graph widget, which is why nan_indication is set to true by default. If the user wants to silence NaNs, they can always simply set it to false. nan_color if unset, defaults to a noticeable yellow-black pattern. As a direct and planned consequence, stacked graphs with negative values in them trigger the NaN handling too. But silencing NaN handling for those graphs is also possible and semi-sane, because then NaNs and negatives are ignored and the graph behaves (almost) like they were zeros. commit 9c1f8f08b400e01e19c534f3810c9c9ab485760c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 26 08:10:19 2021 +0700 Refactor out graph_map_value_to_widget_coordinates() function commit 1905991ddecabdae3e15ffb8ee5b18a1bee1a711 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 28 13:39:29 2021 +0700 Add an example file for graph.baseline_value property commit b31b39d66f4c037a9484b71a3f910703ae01fac0 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 07:03:49 2021 +0700 Add an example file for graph.clamp_bars=false/true comparison commit a1fc850e3047f9ee2c9ddeffacfbabf3ded34171 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 04:06:45 2021 +0700 Add an example file for graphs with negative values and shapes commit 2f012f8ff1bb5094c14e724a61e59a2a3a296bc8 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 01:06:05 2021 +0700 Add an example file for a graph displaying negative values commit 7b14fbd59a4c4c51962fd3110a1a900dd5f4eb98 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 11:18:26 2021 +0700 Add an example file for graphs with step_width < 1 commit dfd9a54544b637e2fbe3d1325f9cc24b3f4d2313 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 10:17:38 2021 +0700 Add an example file for graph.stack=false/true comparison Should also demonstrate that multiple data groups are possible for non-stacked graphs too. commit 5b532106c3fd255f9b66a66cfb88d977b489ab5d Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 09:34:14 2021 +0700 Add an example file for disabling graph data groups with nil colors commit ac6f1083c02fcb428f089a616f9b0ebaa5496b6c Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:57:25 2021 +0700 Add an example file for step_shape/width/spacing on stacked graphs commit b3703db6a5139e73e72c1cfd198ece2cd9be7df2 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:43:56 2021 +0700 Add an example file for scale=false/true on stacked graph commit b000aed4a12cb44fb501e1c8432a71a14b2f8f3f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 20 08:22:19 2021 +0700 Document deprecation of graph's width and height commit cb0ad617bd6c83a499896797318e3b0b588c3566 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 07:53:37 2021 +0700 Deprecate width and height properties of wibox.widget.graph commit 8c64e14ad91a074b784166a7054e53444cccb9d2 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 04:37:59 2021 +0700 Refactor graph:draw() to separate concerns Firstly, summation of group values for stacked graphs is extracted into graph:preprocess_values(). This makes graph:draw() smaller and exposes the fact, that stacked graphs are almost entirely just a form of a more general concept: pre-render data manipulation. It's something a user might want to customize, and now they can do it by monkey-patching the method. Secondly, the logic for deciding, if and with which color a data group should be drawn, is extracted into graph:should_draw_data_group() and get_data_group_color() methods respectively. This makes it customizable too and makes more obvious the fact, that it is a general thing that has nothing to do with stacked graphs in particular. With this there are only 5 lines remaining in graph:draw(), that could be said to be specific to stacked graphs, all pertaining to the `prev_y` variable, which merely preserves the bar coordinates from a previous data group. commit 2a32b6305d893f413303728a0a3da1dcd9a5591e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 22:52:37 2021 +0700 Pointlessly optimize graph:add_value() 1) Purge old values first, then insert the new one, if needed. Since Lua grows arrays in power of two steps, and our guess_capacity() also takes care to return even integers, it would be a shame to overstep the capacity by one by inserting first and thus getting the table size doubled only to never use the most of it. 2) Use a removal procedure that doesn't call the slow length operator at all. Previous code called it twice (once in table.remove) for every removed value. commit 680ea8f5d81e9040afb4662b3229e937e4b89e02 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:23:38 2021 +0700 Set graph data series color before the painting loop commit b970f0f221ae76250f2a94433ff79d653caa6e21 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:05:37 2021 +0700 Drop a large no longer necessary `if` in graph:draw_values() This commit is almost purely a whitespace change. commit 6b4f0541c70ec99d7f6e276bae64939f337120d6 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:00:50 2021 +0700 Extract values rendering code into graph:draw_values() method This is to avoid ungodly if nesting levels, which are needed to bail out of drawing values early and skip to drawing the border. The next commit will get rid of the if. commit e0950e4f99d1ea1c4b8cea2cc67629fa40030c2a Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 14:50:20 2021 +0700 Track graph draw() usage to guess necessary array capacity The last step of divorcing add_values() from width calculations, while ensuring that user doesn't observe a lack of data to draw. The widget now tracks an approximate number of values that it has been asked to draw() in _private.last_drawn_values_num, and truncates its values array based on that (if not overridden by the capacity property). commit 2e3d542282f35e7c0fed454da0f2eada0fcf3b78 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 13:35:12 2021 +0700 Add `capacity` property to graph widget This is the second step of divorcing add_values() from calculations involving widths. How many values are stored and how many values are drawn are distinct concepts. There isn't even a single definitive "how many values are drawn" because the widget could be rendered in several places with distinct widths simultaneously, and in every such place the data must be drawn appropriately, e.g. with auto-scaling, that takes only the visible data points into account. And the draw() method can do that now. The default capacity value of nil uses heuristics based on widget's _private.width to guess some capacity that would tolerate some dynamic widget resizing. If the user finds the heuristic failing them, they can set the capacity property to the desired maximum number of values that will be kept in the _private.values array (in each data group). commit 08d4a80686f7fe363551c35589a460839f60853e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 03:27:18 2021 +0700 Extract values size computation into compute_drawn_values_num() This is the first step of making add_values() not concern itself (directly) with widget dimensions. The user can monkey-patch the method to override how many values are (logically) drawn, because it affects graph auto-scaling and can be useful for rendering of step_shape-s that paint outside of their step_width. I didn't make it a full-blown function-valued property, because it seems a fringe functionality, which I'm too lazy to document. commit 3c5617c98ab1147618ed22e8414a77fc56b45f6e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 02:32:29 2021 +0700 Copy graph widget prototype methods over in bulk commit 169538837ece41a03d02ba8fbc0e4097062b67a3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun Apr 25 05:58:01 2021 +0700 Remove graph.baseline_y property After giving it some thought I've concluded, that it is unlikely to be useful as it is now. I'd better reserve the name for some more sensible functionality, like fixing the baseline_value defined axis at the given widget coordinate, adjusting the scaling accordingly, so min_value/max_value won't generally correspond to widget top and bottom anymore. commit 202ffaa9a0b820c7c471e1121d01b91f8f76cd38 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 12:06:55 2021 +0700 Add baseline_y and baseline_value properties to graph widget Now values in graph have a *baseline*, tunable with baseline_y and baseline_value properties. It is the (for now invisible) horizontal line from which all bars in the graph grow. I call it a "baseline" instead of "axis", because it doesn't actually have to correspond to the 0 value. But it can, and actually does now by default. baseline_y and baseline_value properties determine the vertical placement of the baseline in the widget. baseline_y is the position of the baseline in normalized widget coordinates, e.g. 0 corresponds to widget's top, 1 - to widget's bottom, and 0.5 - to widget's middle. So if e.g. one sets baseline_y = 0, then no matter how the widget is resized and how its values are scaled, bars in it will be drawn starting from the top. baseline_value is the position of the baseline in value coordinates. So if one sets baseline_value = 10, then no matter what happens, bars for values greater than 10 will be drawn as growing up from this line, and bars for values smaller as growing down. Where and if it will be seen in the widget, depends on the scaling. baseline_value = 0 is the familiar zero axis. baseline_y is unset by default and has priority, i.e. if it's set, then baseline_value is ignored. baseline_value defaults to 0. Prior to this commit, bars were always drawn from the bottom edge of the widget, or, in the new terms, the widget had baseline_y = 1. But it was indistinguishable from baseline_value = 0, because the widget clamped all negative values to 0, so the difference between the two could never be observed. baseline_value = 0 is clearly more useful and familiar to people, so now it is in force by default. commit df7e19abaebf0a871b5ca217b4cc3365533267a6 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 11:18:12 2021 +0700 Move value clamping from graph:add_value() into draw() Previously all values were forcibly clamped between 0 (not tunable at all) and max_value (1 by default). Thus graphs with negative values were not an option. NaN values were conflated with zeros, even though it may be useful to maintain the distinction and render them differently. And if user happened to dynamically change the max_value property, they discovered, that their data got truncated. Preserving user data as it was given is more flexible and makes things easier to debug. This commit does exactly that. So what happens now to the newly allowed values during draw()? There are 3 new cases to consider: 1) NaNs 2) Negatives 3) Values outside of min_value..max_value range NaNs are presently simply not drawn. An empty place is reserved where the value should've been, but nothing else is done. This is distinct from how a zero is handled, e.g. if step_shape is set, it is called with the height parameter = 0, which can still end up drawing something, depending on the shape. One could also consider addition of nan_color/nan_shape properties which could be drawn to alert user to a gap in their data. Negative values necessitate introduction of the concept of the zero axis, from which they will be drawn DOWN, in contrast to positive values. Where should this axis be drawn? How should step_shape-s with inherent direction, e.g. arrows, be drawn for negatives? This will be addressed in the next commit. Values outside of min/max_value range can imply shapes that go beyond widget dimensions. An arrow shape that goes beyond the roof is visibly different from an arrow that saturates and merely touches the widget top, and the user may reasonably desire either behavior. Therefore this commit introduces a boolean clamp_bars property, which defaults to true for reasons of robustness and backward compatibility with the behaviour prior to this commit. The property of course affects only the display of the values, the actual user data stays intact. commit c9c24fb1ab741cd6bb8074971d568e0646c51532 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:29:01 2021 +0700 Use rectangles for drawing graph bars of all sizes I have benchmarked various bar drawing strategies and graph sizes and found out that drawing graph bars with rectangles is as fast or even marginally faster than using fat lines. Thus the code that tries to special-case 1px bars as lines seems to be not worth the additional complexity it introduces, and this commit removes it. Incidentally, I've also established, that rectangles are more robust. If one draws graph bars and feeds cairo very large coordinates (~2^62), then corresponding rectangles fail to render, but all the other rectangles in the same series get rendered successfully, whereas a single large coordinate in a move_to/line_to() sequence is enough to cause all lines in the batch to fail to render. commit bc7f4be5e06405b5aa7fb443a5ace2b9b9575b1f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:01:42 2021 +0700 Cache cairo methods used in the inner loop of graph:draw() I have benchmarked the draw() method, both on cairo xcb and on argb32 image surfaces, and have discovered that cairo context method calls are unnaturally slow, to the point, that almost nothing else in the function matters. For example, drawing bars with vertical lines made the whole draw() function almost 2 times slower than doing the same with rectangles, merely because the former requires two method calls (move_to/line_to), and the latter only one. Simply caching hotpath cairo methods in local variables, reduced the difference between the drawing methods to a negligible one and made the whole function 4 times faster on a typical 100-bar graph, (1.4ms to 0.33ms on my laptop) thus bringing closer my secret dream of drawing live audio data with awesome graphs (kidding). I guess, LGI does something not very optimal there, like gobject-introspecting the method every time, or something. There might be some untapped optimization potential, possibly in upstream, like a method cache, from which everything in awesome could benefit, even though I imagine not many things are so call-intensive as the graph widget, to really feel the difference. commit 538df25bdf02b53d0bbe81809eef2e6fdb05ff6f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 07:32:37 2021 +0700 Use 1-based index in the inner graph draw loop It's used there only once for a calculation and then several times to index some tables, and index 0 is bad for lua tables performance-wise. commit 7412546916cd0a0a490130a120255ffb719398b8 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:55:12 2021 +0700 Leave graph.max_value uninitialized at construction This is a user-visible change, but the previous behavior was probably not what user expected anyway, namely with graph.scale set the graph mysteriously couldn't zoom in onto values that are smaller than 1. commit eb81d7929a161e79c13262cfa73ac2e6a820e8ed Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:48:29 2021 +0700 Allow graph.max_value be nil This makes its behave symmetrically to min_value. Now it defaults to 1, when graph.scale is not set, and to the maximum value in the graph data, when graph.scale is set. commit f622a8430cbfde7007b58d7f4c9c76b08c91e997 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 12:01:13 2021 +0700 Calculate max values num in graph:add_value() more precisely At most so many values were kept in the widget values array, as there are pixels of usable widget width (i.e. sans border). This is exactly right for the surely most common case of graphs with 1px-wide bars, but is more than needed for graphs with step_width+step_spacing>1, and not enough for graphs with subpixel bars. Moreover this made graph autoscaling behave weirdly in graphs with thicker bars, because old values survived longer in the array and kept affecting min_value/max_value calculations even after they were shifted off-screen. The maximum number of kept values is now calculated exactly as the number of values that can be displayed at once, taking step_width and step_spacing into account. It might be worth adding a max_values_num property to the graph to let user override this calculation, in case they draw shapes in step_shape() outside of its supposed rectangle, so our assumptions of visibility are wrong, or the widget often gets resized, resulting in the annoyance of visibly truncated series. commit 0aa63249a18641251a1bf77423739164f0eab39f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 02:30:13 2021 +0700 Fix border_color/border_width bugs in graph widget add_value() wrongly assumed that border_width is always set, when border_color is, so if user set only the latter, an error occurred. add_value() assumed that border_width is always < width which led to awesome hanging up in an infinite loop, should that fail to be the case. The graph border_width property was driven by the border_color property, instead of the other way round. If border_color wasn't set, the border wasn't drawn all, even if user sets non-zero border_width, which is a confusing behavior, in contrast to all other widgets, which simply use a plethora of fallback colors to draw the border, when needed. This also meant that, despite being documented, the beautiful.graph_border_color fallback color could not and was not ever used. If the border_color was set though, an unset border_width was immediately assumed to be 1 instead of 0, also contrary to what other widgets do. Theme colors shouldn't influence widget layout, so this commit fixes this behavior to: 1) border_width always defaults to 0, if unset. 2) border_color falls back to beautiful.graph_border_color or white. Maybe one should also add a beautiful.graph_border_width theme variable, like some widgets do. commit dad4a3253b8fef971fb82720d2a407fcc0a879ad Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 03:11:27 2021 +0700 Implement multiple data series drawing for non-stacked graph The widget can already hold and draw multiple data groups, so it makes little sense to limit drawing only to the first group when graph.stack = false, probably leading to user's confusion. Now user can call add_value(v, group) with group > 1 and the data series will be drawn (over groups with lesser id-s) with graph.stack_colors[group] color, if stack_colors is set, or with graph.color, if stack_colors isn't set. Thus nothing visibly changes for all current users of non-stacked graphs with only single data series, but the new functionality is now there for those who need it. One can't fully emulate this functionality, e.g. with multiple graphs widgets in a wibox.layout.stack, because data series in a single widget can enjoy a common auto-scaling. commit 3e645c976184ab5f50d625271c25062db9ab7ed9 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:59:12 2021 +0700 Don't draw a stacked graph data group if there's no color for it If stack_colors[idx] is nil for the group `idx`, don't draw it at all (as opposed, e.g. to the effect of drawing it with a transparent color, or as though it had all values == 0, which both produce different results). This code is still subtly different from prior behavior, because now user can have a non-contiguous stack_colors table and turn the display of any data group on and off at will, whereas the code two commits earlier would just stop rendering as soon as ipairs(stack_colors) does, i.e. no data series were drawn past the first nil hole. This commit only looks large, but is mostly a whitespace change due to nesting from a pair of wrapping ifs. commit 8c0273786de1ade66bdd977699f98b588da9f487 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:32:42 2021 +0700 Unify stacked and non-stacked graph rendering Now stacked graphs support everything the nonstacked do, like step_width ~= 1 and custom step_shape-s. Technical notes: This code no longer calls cr:stroke() for every single value in a stacked graph, but rather paints a whole series and then cr:stroke/fill()-s it at all once with its color, improving performance considerably. Additionally the graph no longer relies on the presence of the stack_colors property to render series. It draws all data that it has, and, if there's no corresponding color in stack_colors, falls back to graph.color. This will be changed in the next commit to the prior behavior of only drawing those series for which there's a color in the stack_colors table, but that would've made this commit's diff much larger due to required nesting ifs over large code chunks. This commit also takes care to properly handle NaNs and negative values. Those are currently (almost) guaranteed to not occur in the values array due to clipping in graph:add_value(), but I intend to eventually drop the clipping, because it does nothing that draw() really relies upon, and allowing unclipped and NaN values is more flexible and less surprising, e.g. when user dynamically toggles graph.scale. Eventually I intend to implement drawing negative values in non-stacked graphs and possibly even in the stacked ones. commit 38c6589c51b2aa057d7c2f40182ecf32b5b2ff5e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 10:04:15 2021 +0700 Convert graph:draw() to use `self` commit f21f1b2550426df1c4ea8abde079b1ccef84b394 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 12:27:26 2021 +0700 Fix scaling for stacked graphs Stacked graphs have cumulative heights, values from all groups that end up in the same graph bar are effectively summed. Previous code didn't take that into account when calculating min/max_value for the purposes of rescaling, but simply examined every value separately. This code adds up all values up front and finds min/max among those, to determine proper scaling that would fit the graph exactly. commit 1ce4c7bf63bdd6c99c195d668347bec49355e921 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 09:49:35 2021 +0700 Draw shaped graph bars only up to the baseline Prior to this commit, every step_shape bar was, for some reason, drawn with height = full widget's height, regardless of the value it represents. So for the most bars their lower end was far outside the box and therefore clipped off. So e.g. one couldn't see the lower round edges of rounded rectangles. This commit fixes that by appropriately varying the height that is passed into step_shape(), so that lower edges of the bars always exactly touch the lower edge of the graph. If the user wants to restore the prior behavior, they can simply ignore the passed-in height in step_shape() and just always draw their shape with height = full widget height. But they couldn't replicate the effect of this commit in their rc.lua without essentially writing their own graph widget. commit e25185b08da23745b5815f85f6e9e43222ed2294 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 08:42:54 2021 +0700 Avoid incremental transform shifting during graph bar rendering Instead of letting addition errors and cognitive load accumulate simply use the same offset calculation that is used for lines and reset the transform back to the top-left corner after every drawn bar. It's conceptually simpler, and will be useful later for stacked graphs and more. It also fixes a discrepancy between plain and step_shape rendering. Prior to this commit, bar coloring occurred with a disturbed transform, which was centered at the last drawn bar, wherever it happened to be. This meant that nontrivial colors, like gradients and images, were arbitrarily offset when step_shape was used. commit f3e98866d8583c35d0b557c09f529299edc9ba2c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:45:29 2021 +0700 Refactor out graph bar height expression into a variable commit f54528d6892f5aaa4595917853c9e2130f743fdb Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:20:25 2021 +0700 Draw graph bars with lines only when step_width == 1 Before this commit graph bars of step_width < 1 were drawn with lines of width 1, but that's a bit unfortunate, because it led to much overpainting, while still ending up with a graphically incorrect result. Let's draw such bars with rectangles and let cairo do things with subpixels, getting more graphically accurate result, while still keeping the common case of step_width == 1 (supposedly) fast-drawn with lines. commit b6515dca67c18b78eb5657378131a62ab43d4cfc Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:54:07 2021 +0700 Fix unsharp edges when drawing graph bars with rectangles The 0.5 offset is only helpful for 1-width strokes, not for fills. commit 7f3db8f07e82f821bcd1dc418d66564f537609d6 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:21:01 2021 +0700 Do coordinate transformations even if the value isn't drawn The graph presently doesn't draw values < 0, but there was a discrepancy between drawing with and without step_shape. The default line/rectangle drawing reserves an empty place where the skipped value should've been, but no such empty place is reserved, when step_shape is used. This commit fixes that. Skipped values always have their empty space now. commit 7b731ebfe96af7a9dba9c57371da6761cf22c00c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:03:30 2021 +0700 Fix graph bar scaling when min_value is not 0 Previous calculation lead to underscaling of values so that even value = max_value wasn't drawn to full height. commit dca59fd8b7fff0e7e646f2dfc6bd92047028e5a8 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:18:40 2021 +0700 Calculate the horizontal graph bar offset only when needed commit 940ddf4b9825e9c3a91d276208c089d8c74e49db Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:17:23 2021 +0700 Call cr:move_to() only when drawing the graph with lines It's redundant otherwise and might even interfere with user's drawing in step_shape(). commit b5d5cd10ca29fb29cd751faaa95137c2fe89ff63 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:04:06 2021 +0700 Fix wrong horizontal offset calculations in widget.graph Indices in the loop are zero-based, so things like i-1 and i>1 aren't necessary and actually wouldn't have been correct even in a 1-based loop. step_shape = gears.shape.rectangle and step_shape = nil, which is supposed to default to rectangle according to docs, were drawn differently due to mismatched offset calculations, which led to problems especially noticeable when step_spacing is not zero, e.g: 1) whole graph shifted left too much, drawn partially off-screen. 2) whole graph shifted right too much, and the first bar placed closer to the rest than others. commit dc369fa198281e000afc1bd17326f3a264adf7d1 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 02:09:21 2021 +0700 Fix the "border_width affects graph bar width" bug Graph bars were drawn with lines of border_width thickness instead of 1, i.e. possibly too fat to be readable. commit 1ba73378cf29ed27a6ecd18716394779ceb543d9 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 00:13:03 2021 +0700 Simplify internal values representation in graph even more New values in graph._private.values were pushed to the end, and old values were removed from the beginning of the table. In draw() however newer values are drawn first, at the left edge of the graph, necessitating annoying idx = #values - idx conversions everywhere. In particular in stacked graph that means that values that are drawn together may have different indices. One has to shift the values array in add_value() either way, so one might as well insert new values at the beginning of the array and simplify the rest of the code. This doesn't seem like much yet, but it will prove to be much more convenient and less error-prone in what I'm going to do later. commit f70523ee68dbd6b67050ace4b82903a9b22440c7 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 13:35:10 2021 +0700 Simplify internal values representation in graph widget graph._private.values was either a table of values or a table of tables of values, depending on whether "stack" property is enabled, ... or a mix of both if user forgot to pass the second "group" parameter to graph:add_value(). This resulted in pretty fragile and confusing behavior, e.g. leaving user to wonder, why they see nothing more after setting stack=true for a graph in a previously working rc.lua, and demanding a very careful use of add_value() from the user, or else some values or even entire stacks would mysteriously vanish and errors in draw() get triggered, sometimes after a period of seemingly normal functioning. Now `values` is always a table of tables and the group parameter in add_value() defaults to 1, thus making the non-stacked graph a trivial variant of the stacked graph.
2021-05-20 17:14:03 +02:00
if capacity then
table.insert(values, 1, value)
2016-05-23 07:30:44 +02:00
end
self:emit_signal("widget::redraw_needed")
return self
end
--- Clear the graph.
--
graph: complete widget overhaul and bug fixes Squashed commit of the following: commit 69821f51fe1e8652715658543e50631ece495090 Author: Alex Belykh <albel727@ngs.ru> Date: Mon May 10 19:17:55 2021 +0700 Refactor property handling in wibox.widget.graph In addition, baseline_value, step_width and step_spacing properties are all set to their default non-nil numeric values during instance creation now. commit 842eb429bf5df4b2ba46b2e6f7646afe89b1a3c3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 9 06:18:45 2021 +0700 Make graph:draw_values() private There's no documentation nor confidence that it's a good API, so I'm hiding it for now. commit 720746780574e4bad14a71fc4174955139c2eb50 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 04:04:13 2021 +0700 Document graph_should_draw_data_group() for posterity commit 7c73f3754ce6ba86b118f1170a6f9e406ab4841d Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:59:21 2021 +0700 Make graph:should_draw_data_group() private There's no confidence whether it's a good API, so I'm hiding it for now. commit b3539e20adb6423118af726ae4f8751ef864b5d2 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:36:44 2021 +0700 Make graph:preprocess_values() private There's no confidence whether it's a good API, so I'm hiding it for now. commit 2df4400e780a6260f8d0ab1c60734fbb11bf3c4f Author: Alex Belykh <albel727@ngs.ru> Date: Thu May 6 10:05:23 2021 +0700 Add spec/wibox/widget/graph_spec.lua commit ce01a9771ab439b8df841f1565aa4aa8a44a5e0d Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 08:16:18 2021 +0700 Improve graph.capacity documentation commit e28fade8f655802f5bf89154169f68d256b8e332 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 07:36:01 2021 +0700 Document graph:compute_drawn_values_num() commit dd68332292465422d08b48e9f0229f143beccbc6 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 05:24:23 2021 +0700 Document graph:pick_data_group_color() method commit ca6dc55f77aca88a6e9cc90bb6fd0045739f41f5 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 04:13:58 2021 +0700 Guard against setting data groups sparsely in graph:add_value() commit cdea5f126f7984e72924f6dffbd312c4fe3b032f Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 02:36:12 2021 +0700 Refactor scaling and baseline choice out of graph:draw() commit 10135deafe8a9390ba66ab657ebb1fb41d549e8c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 21:34:48 2021 +0700 Document graph.nan_color and graph.stack properties commit 0d73bb84ac66b87e16aee472bd1f299549f66a1c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 20:49:24 2021 +0700 Use a better example in docs for the graph.stack property commit c27d160a0826d033d49d6141608c40aa8a0642fa Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 16:05:38 2021 +0700 Document graph.baseline_value property commit 459bc8176c13d4ea32af0dcdc2399d3b929b1c6a Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:53:47 2021 +0700 Document graph.clamp_bars property commit e732ee0b3b007d213e689b98cc00ae7cb141ae08 Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:34 2021 +0700 Improve graph:add_value() and :clear() documentation commit ae73e3aa9f8d60409cbe4cb4da9612f31af4f38e Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:11 2021 +0700 Improve graph min_value/max_value/scale documentation commit a7350996a9def773b91b1631e6c0507e33d8f93b Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:27:36 2021 +0700 Cleanup graph examples to show only relevant things in docs commit 30740c26f4f41c16f4c8a3c339bcb7bd299e14cd Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 14:04:36 2021 +0700 Rename graph.stack_colors property to `group_colors` The colors are used for stacked and non-stacked graphs alike, so the name didn't make sense. Also document and clarify some color stuff. commit 1ea01bfd410971cf0d0c25fd74cd764d5eb7279d Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 21:25:41 2021 +0700 Add an example file for code coverage of some fringe graph cases commit eae68e39553f88a6275a0c65bf040b5d4c262b54 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 20:23:32 2021 +0700 Add step_shape to the graph/nan_color example for code coverage commit 8027147bc2eab34a97b49a428fb8cef27f122bed Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:55:18 2021 +0700 Add example files for graph.nan_color/nan_indication properties commit 89a25a165c52b2c15b6c4d95ecb777a9067e66f9 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:33:07 2021 +0700 Make widget graph:add_value(v) default v to NaN 0 as a default makes no sense and silences the error that is failing to specify a value, to which the user should rather be alerted. The graph widget has sane NaN handling now and will do just that. commit 2835d552440be48d356a7f7b1d64963696dd550b Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:20:48 2021 +0700 Implement graph.nan_color and nan_indication properties Now, whenever there's a NaN among values and graph.nan_indication is set, the corresponding area, where the value bar should have been drawn, is filled with the nan_color from top to bottom. The painting is done after all data is rendered, to make sure that it won't be overpainted and go unnoticed. NaNs among graph's values are inevitably a sign of some error and it is arguably not a sane default behavior to simply not render them at all. The user should take immediate note of any errors by default, instead of wondering at a mysteriously empty graph widget, which is why nan_indication is set to true by default. If the user wants to silence NaNs, they can always simply set it to false. nan_color if unset, defaults to a noticeable yellow-black pattern. As a direct and planned consequence, stacked graphs with negative values in them trigger the NaN handling too. But silencing NaN handling for those graphs is also possible and semi-sane, because then NaNs and negatives are ignored and the graph behaves (almost) like they were zeros. commit 9c1f8f08b400e01e19c534f3810c9c9ab485760c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 26 08:10:19 2021 +0700 Refactor out graph_map_value_to_widget_coordinates() function commit 1905991ddecabdae3e15ffb8ee5b18a1bee1a711 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 28 13:39:29 2021 +0700 Add an example file for graph.baseline_value property commit b31b39d66f4c037a9484b71a3f910703ae01fac0 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 07:03:49 2021 +0700 Add an example file for graph.clamp_bars=false/true comparison commit a1fc850e3047f9ee2c9ddeffacfbabf3ded34171 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 04:06:45 2021 +0700 Add an example file for graphs with negative values and shapes commit 2f012f8ff1bb5094c14e724a61e59a2a3a296bc8 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 01:06:05 2021 +0700 Add an example file for a graph displaying negative values commit 7b14fbd59a4c4c51962fd3110a1a900dd5f4eb98 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 11:18:26 2021 +0700 Add an example file for graphs with step_width < 1 commit dfd9a54544b637e2fbe3d1325f9cc24b3f4d2313 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 10:17:38 2021 +0700 Add an example file for graph.stack=false/true comparison Should also demonstrate that multiple data groups are possible for non-stacked graphs too. commit 5b532106c3fd255f9b66a66cfb88d977b489ab5d Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 09:34:14 2021 +0700 Add an example file for disabling graph data groups with nil colors commit ac6f1083c02fcb428f089a616f9b0ebaa5496b6c Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:57:25 2021 +0700 Add an example file for step_shape/width/spacing on stacked graphs commit b3703db6a5139e73e72c1cfd198ece2cd9be7df2 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:43:56 2021 +0700 Add an example file for scale=false/true on stacked graph commit b000aed4a12cb44fb501e1c8432a71a14b2f8f3f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 20 08:22:19 2021 +0700 Document deprecation of graph's width and height commit cb0ad617bd6c83a499896797318e3b0b588c3566 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 07:53:37 2021 +0700 Deprecate width and height properties of wibox.widget.graph commit 8c64e14ad91a074b784166a7054e53444cccb9d2 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 04:37:59 2021 +0700 Refactor graph:draw() to separate concerns Firstly, summation of group values for stacked graphs is extracted into graph:preprocess_values(). This makes graph:draw() smaller and exposes the fact, that stacked graphs are almost entirely just a form of a more general concept: pre-render data manipulation. It's something a user might want to customize, and now they can do it by monkey-patching the method. Secondly, the logic for deciding, if and with which color a data group should be drawn, is extracted into graph:should_draw_data_group() and get_data_group_color() methods respectively. This makes it customizable too and makes more obvious the fact, that it is a general thing that has nothing to do with stacked graphs in particular. With this there are only 5 lines remaining in graph:draw(), that could be said to be specific to stacked graphs, all pertaining to the `prev_y` variable, which merely preserves the bar coordinates from a previous data group. commit 2a32b6305d893f413303728a0a3da1dcd9a5591e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 22:52:37 2021 +0700 Pointlessly optimize graph:add_value() 1) Purge old values first, then insert the new one, if needed. Since Lua grows arrays in power of two steps, and our guess_capacity() also takes care to return even integers, it would be a shame to overstep the capacity by one by inserting first and thus getting the table size doubled only to never use the most of it. 2) Use a removal procedure that doesn't call the slow length operator at all. Previous code called it twice (once in table.remove) for every removed value. commit 680ea8f5d81e9040afb4662b3229e937e4b89e02 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:23:38 2021 +0700 Set graph data series color before the painting loop commit b970f0f221ae76250f2a94433ff79d653caa6e21 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:05:37 2021 +0700 Drop a large no longer necessary `if` in graph:draw_values() This commit is almost purely a whitespace change. commit 6b4f0541c70ec99d7f6e276bae64939f337120d6 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:00:50 2021 +0700 Extract values rendering code into graph:draw_values() method This is to avoid ungodly if nesting levels, which are needed to bail out of drawing values early and skip to drawing the border. The next commit will get rid of the if. commit e0950e4f99d1ea1c4b8cea2cc67629fa40030c2a Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 14:50:20 2021 +0700 Track graph draw() usage to guess necessary array capacity The last step of divorcing add_values() from width calculations, while ensuring that user doesn't observe a lack of data to draw. The widget now tracks an approximate number of values that it has been asked to draw() in _private.last_drawn_values_num, and truncates its values array based on that (if not overridden by the capacity property). commit 2e3d542282f35e7c0fed454da0f2eada0fcf3b78 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 13:35:12 2021 +0700 Add `capacity` property to graph widget This is the second step of divorcing add_values() from calculations involving widths. How many values are stored and how many values are drawn are distinct concepts. There isn't even a single definitive "how many values are drawn" because the widget could be rendered in several places with distinct widths simultaneously, and in every such place the data must be drawn appropriately, e.g. with auto-scaling, that takes only the visible data points into account. And the draw() method can do that now. The default capacity value of nil uses heuristics based on widget's _private.width to guess some capacity that would tolerate some dynamic widget resizing. If the user finds the heuristic failing them, they can set the capacity property to the desired maximum number of values that will be kept in the _private.values array (in each data group). commit 08d4a80686f7fe363551c35589a460839f60853e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 03:27:18 2021 +0700 Extract values size computation into compute_drawn_values_num() This is the first step of making add_values() not concern itself (directly) with widget dimensions. The user can monkey-patch the method to override how many values are (logically) drawn, because it affects graph auto-scaling and can be useful for rendering of step_shape-s that paint outside of their step_width. I didn't make it a full-blown function-valued property, because it seems a fringe functionality, which I'm too lazy to document. commit 3c5617c98ab1147618ed22e8414a77fc56b45f6e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 02:32:29 2021 +0700 Copy graph widget prototype methods over in bulk commit 169538837ece41a03d02ba8fbc0e4097062b67a3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun Apr 25 05:58:01 2021 +0700 Remove graph.baseline_y property After giving it some thought I've concluded, that it is unlikely to be useful as it is now. I'd better reserve the name for some more sensible functionality, like fixing the baseline_value defined axis at the given widget coordinate, adjusting the scaling accordingly, so min_value/max_value won't generally correspond to widget top and bottom anymore. commit 202ffaa9a0b820c7c471e1121d01b91f8f76cd38 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 12:06:55 2021 +0700 Add baseline_y and baseline_value properties to graph widget Now values in graph have a *baseline*, tunable with baseline_y and baseline_value properties. It is the (for now invisible) horizontal line from which all bars in the graph grow. I call it a "baseline" instead of "axis", because it doesn't actually have to correspond to the 0 value. But it can, and actually does now by default. baseline_y and baseline_value properties determine the vertical placement of the baseline in the widget. baseline_y is the position of the baseline in normalized widget coordinates, e.g. 0 corresponds to widget's top, 1 - to widget's bottom, and 0.5 - to widget's middle. So if e.g. one sets baseline_y = 0, then no matter how the widget is resized and how its values are scaled, bars in it will be drawn starting from the top. baseline_value is the position of the baseline in value coordinates. So if one sets baseline_value = 10, then no matter what happens, bars for values greater than 10 will be drawn as growing up from this line, and bars for values smaller as growing down. Where and if it will be seen in the widget, depends on the scaling. baseline_value = 0 is the familiar zero axis. baseline_y is unset by default and has priority, i.e. if it's set, then baseline_value is ignored. baseline_value defaults to 0. Prior to this commit, bars were always drawn from the bottom edge of the widget, or, in the new terms, the widget had baseline_y = 1. But it was indistinguishable from baseline_value = 0, because the widget clamped all negative values to 0, so the difference between the two could never be observed. baseline_value = 0 is clearly more useful and familiar to people, so now it is in force by default. commit df7e19abaebf0a871b5ca217b4cc3365533267a6 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 11:18:12 2021 +0700 Move value clamping from graph:add_value() into draw() Previously all values were forcibly clamped between 0 (not tunable at all) and max_value (1 by default). Thus graphs with negative values were not an option. NaN values were conflated with zeros, even though it may be useful to maintain the distinction and render them differently. And if user happened to dynamically change the max_value property, they discovered, that their data got truncated. Preserving user data as it was given is more flexible and makes things easier to debug. This commit does exactly that. So what happens now to the newly allowed values during draw()? There are 3 new cases to consider: 1) NaNs 2) Negatives 3) Values outside of min_value..max_value range NaNs are presently simply not drawn. An empty place is reserved where the value should've been, but nothing else is done. This is distinct from how a zero is handled, e.g. if step_shape is set, it is called with the height parameter = 0, which can still end up drawing something, depending on the shape. One could also consider addition of nan_color/nan_shape properties which could be drawn to alert user to a gap in their data. Negative values necessitate introduction of the concept of the zero axis, from which they will be drawn DOWN, in contrast to positive values. Where should this axis be drawn? How should step_shape-s with inherent direction, e.g. arrows, be drawn for negatives? This will be addressed in the next commit. Values outside of min/max_value range can imply shapes that go beyond widget dimensions. An arrow shape that goes beyond the roof is visibly different from an arrow that saturates and merely touches the widget top, and the user may reasonably desire either behavior. Therefore this commit introduces a boolean clamp_bars property, which defaults to true for reasons of robustness and backward compatibility with the behaviour prior to this commit. The property of course affects only the display of the values, the actual user data stays intact. commit c9c24fb1ab741cd6bb8074971d568e0646c51532 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:29:01 2021 +0700 Use rectangles for drawing graph bars of all sizes I have benchmarked various bar drawing strategies and graph sizes and found out that drawing graph bars with rectangles is as fast or even marginally faster than using fat lines. Thus the code that tries to special-case 1px bars as lines seems to be not worth the additional complexity it introduces, and this commit removes it. Incidentally, I've also established, that rectangles are more robust. If one draws graph bars and feeds cairo very large coordinates (~2^62), then corresponding rectangles fail to render, but all the other rectangles in the same series get rendered successfully, whereas a single large coordinate in a move_to/line_to() sequence is enough to cause all lines in the batch to fail to render. commit bc7f4be5e06405b5aa7fb443a5ace2b9b9575b1f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:01:42 2021 +0700 Cache cairo methods used in the inner loop of graph:draw() I have benchmarked the draw() method, both on cairo xcb and on argb32 image surfaces, and have discovered that cairo context method calls are unnaturally slow, to the point, that almost nothing else in the function matters. For example, drawing bars with vertical lines made the whole draw() function almost 2 times slower than doing the same with rectangles, merely because the former requires two method calls (move_to/line_to), and the latter only one. Simply caching hotpath cairo methods in local variables, reduced the difference between the drawing methods to a negligible one and made the whole function 4 times faster on a typical 100-bar graph, (1.4ms to 0.33ms on my laptop) thus bringing closer my secret dream of drawing live audio data with awesome graphs (kidding). I guess, LGI does something not very optimal there, like gobject-introspecting the method every time, or something. There might be some untapped optimization potential, possibly in upstream, like a method cache, from which everything in awesome could benefit, even though I imagine not many things are so call-intensive as the graph widget, to really feel the difference. commit 538df25bdf02b53d0bbe81809eef2e6fdb05ff6f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 07:32:37 2021 +0700 Use 1-based index in the inner graph draw loop It's used there only once for a calculation and then several times to index some tables, and index 0 is bad for lua tables performance-wise. commit 7412546916cd0a0a490130a120255ffb719398b8 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:55:12 2021 +0700 Leave graph.max_value uninitialized at construction This is a user-visible change, but the previous behavior was probably not what user expected anyway, namely with graph.scale set the graph mysteriously couldn't zoom in onto values that are smaller than 1. commit eb81d7929a161e79c13262cfa73ac2e6a820e8ed Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:48:29 2021 +0700 Allow graph.max_value be nil This makes its behave symmetrically to min_value. Now it defaults to 1, when graph.scale is not set, and to the maximum value in the graph data, when graph.scale is set. commit f622a8430cbfde7007b58d7f4c9c76b08c91e997 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 12:01:13 2021 +0700 Calculate max values num in graph:add_value() more precisely At most so many values were kept in the widget values array, as there are pixels of usable widget width (i.e. sans border). This is exactly right for the surely most common case of graphs with 1px-wide bars, but is more than needed for graphs with step_width+step_spacing>1, and not enough for graphs with subpixel bars. Moreover this made graph autoscaling behave weirdly in graphs with thicker bars, because old values survived longer in the array and kept affecting min_value/max_value calculations even after they were shifted off-screen. The maximum number of kept values is now calculated exactly as the number of values that can be displayed at once, taking step_width and step_spacing into account. It might be worth adding a max_values_num property to the graph to let user override this calculation, in case they draw shapes in step_shape() outside of its supposed rectangle, so our assumptions of visibility are wrong, or the widget often gets resized, resulting in the annoyance of visibly truncated series. commit 0aa63249a18641251a1bf77423739164f0eab39f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 02:30:13 2021 +0700 Fix border_color/border_width bugs in graph widget add_value() wrongly assumed that border_width is always set, when border_color is, so if user set only the latter, an error occurred. add_value() assumed that border_width is always < width which led to awesome hanging up in an infinite loop, should that fail to be the case. The graph border_width property was driven by the border_color property, instead of the other way round. If border_color wasn't set, the border wasn't drawn all, even if user sets non-zero border_width, which is a confusing behavior, in contrast to all other widgets, which simply use a plethora of fallback colors to draw the border, when needed. This also meant that, despite being documented, the beautiful.graph_border_color fallback color could not and was not ever used. If the border_color was set though, an unset border_width was immediately assumed to be 1 instead of 0, also contrary to what other widgets do. Theme colors shouldn't influence widget layout, so this commit fixes this behavior to: 1) border_width always defaults to 0, if unset. 2) border_color falls back to beautiful.graph_border_color or white. Maybe one should also add a beautiful.graph_border_width theme variable, like some widgets do. commit dad4a3253b8fef971fb82720d2a407fcc0a879ad Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 03:11:27 2021 +0700 Implement multiple data series drawing for non-stacked graph The widget can already hold and draw multiple data groups, so it makes little sense to limit drawing only to the first group when graph.stack = false, probably leading to user's confusion. Now user can call add_value(v, group) with group > 1 and the data series will be drawn (over groups with lesser id-s) with graph.stack_colors[group] color, if stack_colors is set, or with graph.color, if stack_colors isn't set. Thus nothing visibly changes for all current users of non-stacked graphs with only single data series, but the new functionality is now there for those who need it. One can't fully emulate this functionality, e.g. with multiple graphs widgets in a wibox.layout.stack, because data series in a single widget can enjoy a common auto-scaling. commit 3e645c976184ab5f50d625271c25062db9ab7ed9 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:59:12 2021 +0700 Don't draw a stacked graph data group if there's no color for it If stack_colors[idx] is nil for the group `idx`, don't draw it at all (as opposed, e.g. to the effect of drawing it with a transparent color, or as though it had all values == 0, which both produce different results). This code is still subtly different from prior behavior, because now user can have a non-contiguous stack_colors table and turn the display of any data group on and off at will, whereas the code two commits earlier would just stop rendering as soon as ipairs(stack_colors) does, i.e. no data series were drawn past the first nil hole. This commit only looks large, but is mostly a whitespace change due to nesting from a pair of wrapping ifs. commit 8c0273786de1ade66bdd977699f98b588da9f487 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:32:42 2021 +0700 Unify stacked and non-stacked graph rendering Now stacked graphs support everything the nonstacked do, like step_width ~= 1 and custom step_shape-s. Technical notes: This code no longer calls cr:stroke() for every single value in a stacked graph, but rather paints a whole series and then cr:stroke/fill()-s it at all once with its color, improving performance considerably. Additionally the graph no longer relies on the presence of the stack_colors property to render series. It draws all data that it has, and, if there's no corresponding color in stack_colors, falls back to graph.color. This will be changed in the next commit to the prior behavior of only drawing those series for which there's a color in the stack_colors table, but that would've made this commit's diff much larger due to required nesting ifs over large code chunks. This commit also takes care to properly handle NaNs and negative values. Those are currently (almost) guaranteed to not occur in the values array due to clipping in graph:add_value(), but I intend to eventually drop the clipping, because it does nothing that draw() really relies upon, and allowing unclipped and NaN values is more flexible and less surprising, e.g. when user dynamically toggles graph.scale. Eventually I intend to implement drawing negative values in non-stacked graphs and possibly even in the stacked ones. commit 38c6589c51b2aa057d7c2f40182ecf32b5b2ff5e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 10:04:15 2021 +0700 Convert graph:draw() to use `self` commit f21f1b2550426df1c4ea8abde079b1ccef84b394 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 12:27:26 2021 +0700 Fix scaling for stacked graphs Stacked graphs have cumulative heights, values from all groups that end up in the same graph bar are effectively summed. Previous code didn't take that into account when calculating min/max_value for the purposes of rescaling, but simply examined every value separately. This code adds up all values up front and finds min/max among those, to determine proper scaling that would fit the graph exactly. commit 1ce4c7bf63bdd6c99c195d668347bec49355e921 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 09:49:35 2021 +0700 Draw shaped graph bars only up to the baseline Prior to this commit, every step_shape bar was, for some reason, drawn with height = full widget's height, regardless of the value it represents. So for the most bars their lower end was far outside the box and therefore clipped off. So e.g. one couldn't see the lower round edges of rounded rectangles. This commit fixes that by appropriately varying the height that is passed into step_shape(), so that lower edges of the bars always exactly touch the lower edge of the graph. If the user wants to restore the prior behavior, they can simply ignore the passed-in height in step_shape() and just always draw their shape with height = full widget height. But they couldn't replicate the effect of this commit in their rc.lua without essentially writing their own graph widget. commit e25185b08da23745b5815f85f6e9e43222ed2294 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 08:42:54 2021 +0700 Avoid incremental transform shifting during graph bar rendering Instead of letting addition errors and cognitive load accumulate simply use the same offset calculation that is used for lines and reset the transform back to the top-left corner after every drawn bar. It's conceptually simpler, and will be useful later for stacked graphs and more. It also fixes a discrepancy between plain and step_shape rendering. Prior to this commit, bar coloring occurred with a disturbed transform, which was centered at the last drawn bar, wherever it happened to be. This meant that nontrivial colors, like gradients and images, were arbitrarily offset when step_shape was used. commit f3e98866d8583c35d0b557c09f529299edc9ba2c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:45:29 2021 +0700 Refactor out graph bar height expression into a variable commit f54528d6892f5aaa4595917853c9e2130f743fdb Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:20:25 2021 +0700 Draw graph bars with lines only when step_width == 1 Before this commit graph bars of step_width < 1 were drawn with lines of width 1, but that's a bit unfortunate, because it led to much overpainting, while still ending up with a graphically incorrect result. Let's draw such bars with rectangles and let cairo do things with subpixels, getting more graphically accurate result, while still keeping the common case of step_width == 1 (supposedly) fast-drawn with lines. commit b6515dca67c18b78eb5657378131a62ab43d4cfc Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:54:07 2021 +0700 Fix unsharp edges when drawing graph bars with rectangles The 0.5 offset is only helpful for 1-width strokes, not for fills. commit 7f3db8f07e82f821bcd1dc418d66564f537609d6 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:21:01 2021 +0700 Do coordinate transformations even if the value isn't drawn The graph presently doesn't draw values < 0, but there was a discrepancy between drawing with and without step_shape. The default line/rectangle drawing reserves an empty place where the skipped value should've been, but no such empty place is reserved, when step_shape is used. This commit fixes that. Skipped values always have their empty space now. commit 7b731ebfe96af7a9dba9c57371da6761cf22c00c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:03:30 2021 +0700 Fix graph bar scaling when min_value is not 0 Previous calculation lead to underscaling of values so that even value = max_value wasn't drawn to full height. commit dca59fd8b7fff0e7e646f2dfc6bd92047028e5a8 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:18:40 2021 +0700 Calculate the horizontal graph bar offset only when needed commit 940ddf4b9825e9c3a91d276208c089d8c74e49db Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:17:23 2021 +0700 Call cr:move_to() only when drawing the graph with lines It's redundant otherwise and might even interfere with user's drawing in step_shape(). commit b5d5cd10ca29fb29cd751faaa95137c2fe89ff63 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:04:06 2021 +0700 Fix wrong horizontal offset calculations in widget.graph Indices in the loop are zero-based, so things like i-1 and i>1 aren't necessary and actually wouldn't have been correct even in a 1-based loop. step_shape = gears.shape.rectangle and step_shape = nil, which is supposed to default to rectangle according to docs, were drawn differently due to mismatched offset calculations, which led to problems especially noticeable when step_spacing is not zero, e.g: 1) whole graph shifted left too much, drawn partially off-screen. 2) whole graph shifted right too much, and the first bar placed closer to the rest than others. commit dc369fa198281e000afc1bd17326f3a264adf7d1 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 02:09:21 2021 +0700 Fix the "border_width affects graph bar width" bug Graph bars were drawn with lines of border_width thickness instead of 1, i.e. possibly too fat to be readable. commit 1ba73378cf29ed27a6ecd18716394779ceb543d9 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 00:13:03 2021 +0700 Simplify internal values representation in graph even more New values in graph._private.values were pushed to the end, and old values were removed from the beginning of the table. In draw() however newer values are drawn first, at the left edge of the graph, necessitating annoying idx = #values - idx conversions everywhere. In particular in stacked graph that means that values that are drawn together may have different indices. One has to shift the values array in add_value() either way, so one might as well insert new values at the beginning of the array and simplify the rest of the code. This doesn't seem like much yet, but it will prove to be much more convenient and less error-prone in what I'm going to do later. commit f70523ee68dbd6b67050ace4b82903a9b22440c7 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 13:35:10 2021 +0700 Simplify internal values representation in graph widget graph._private.values was either a table of values or a table of tables of values, depending on whether "stack" property is enabled, ... or a mix of both if user forgot to pass the second "group" parameter to graph:add_value(). This resulted in pretty fragile and confusing behavior, e.g. leaving user to wonder, why they see nothing more after setting stack=true for a graph in a previously working rc.lua, and demanding a very careful use of add_value() from the user, or else some values or even entire stacks would mysteriously vanish and errors in draw() get triggered, sometimes after a period of seemingly normal functioning. Now `values` is always a table of tables and the group parameter in add_value() defaults to 1, thus making the non-stacked graph a trivial variant of the stacked graph.
2021-05-20 17:14:03 +02:00
-- Removes all values from all data groups.
--
-- @method clear
-- @noreturn Note that it actually returns something, but that's better undocumented.
2016-05-23 07:30:44 +02:00
function graph:clear()
self._private.values = {}
2016-05-23 07:30:44 +02:00
self:emit_signal("widget::redraw_needed")
return self
end
graph: complete widget overhaul and bug fixes Squashed commit of the following: commit 69821f51fe1e8652715658543e50631ece495090 Author: Alex Belykh <albel727@ngs.ru> Date: Mon May 10 19:17:55 2021 +0700 Refactor property handling in wibox.widget.graph In addition, baseline_value, step_width and step_spacing properties are all set to their default non-nil numeric values during instance creation now. commit 842eb429bf5df4b2ba46b2e6f7646afe89b1a3c3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 9 06:18:45 2021 +0700 Make graph:draw_values() private There's no documentation nor confidence that it's a good API, so I'm hiding it for now. commit 720746780574e4bad14a71fc4174955139c2eb50 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 04:04:13 2021 +0700 Document graph_should_draw_data_group() for posterity commit 7c73f3754ce6ba86b118f1170a6f9e406ab4841d Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:59:21 2021 +0700 Make graph:should_draw_data_group() private There's no confidence whether it's a good API, so I'm hiding it for now. commit b3539e20adb6423118af726ae4f8751ef864b5d2 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:36:44 2021 +0700 Make graph:preprocess_values() private There's no confidence whether it's a good API, so I'm hiding it for now. commit 2df4400e780a6260f8d0ab1c60734fbb11bf3c4f Author: Alex Belykh <albel727@ngs.ru> Date: Thu May 6 10:05:23 2021 +0700 Add spec/wibox/widget/graph_spec.lua commit ce01a9771ab439b8df841f1565aa4aa8a44a5e0d Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 08:16:18 2021 +0700 Improve graph.capacity documentation commit e28fade8f655802f5bf89154169f68d256b8e332 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 07:36:01 2021 +0700 Document graph:compute_drawn_values_num() commit dd68332292465422d08b48e9f0229f143beccbc6 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 05:24:23 2021 +0700 Document graph:pick_data_group_color() method commit ca6dc55f77aca88a6e9cc90bb6fd0045739f41f5 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 04:13:58 2021 +0700 Guard against setting data groups sparsely in graph:add_value() commit cdea5f126f7984e72924f6dffbd312c4fe3b032f Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 02:36:12 2021 +0700 Refactor scaling and baseline choice out of graph:draw() commit 10135deafe8a9390ba66ab657ebb1fb41d549e8c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 21:34:48 2021 +0700 Document graph.nan_color and graph.stack properties commit 0d73bb84ac66b87e16aee472bd1f299549f66a1c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 20:49:24 2021 +0700 Use a better example in docs for the graph.stack property commit c27d160a0826d033d49d6141608c40aa8a0642fa Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 16:05:38 2021 +0700 Document graph.baseline_value property commit 459bc8176c13d4ea32af0dcdc2399d3b929b1c6a Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:53:47 2021 +0700 Document graph.clamp_bars property commit e732ee0b3b007d213e689b98cc00ae7cb141ae08 Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:34 2021 +0700 Improve graph:add_value() and :clear() documentation commit ae73e3aa9f8d60409cbe4cb4da9612f31af4f38e Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:11 2021 +0700 Improve graph min_value/max_value/scale documentation commit a7350996a9def773b91b1631e6c0507e33d8f93b Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:27:36 2021 +0700 Cleanup graph examples to show only relevant things in docs commit 30740c26f4f41c16f4c8a3c339bcb7bd299e14cd Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 14:04:36 2021 +0700 Rename graph.stack_colors property to `group_colors` The colors are used for stacked and non-stacked graphs alike, so the name didn't make sense. Also document and clarify some color stuff. commit 1ea01bfd410971cf0d0c25fd74cd764d5eb7279d Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 21:25:41 2021 +0700 Add an example file for code coverage of some fringe graph cases commit eae68e39553f88a6275a0c65bf040b5d4c262b54 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 20:23:32 2021 +0700 Add step_shape to the graph/nan_color example for code coverage commit 8027147bc2eab34a97b49a428fb8cef27f122bed Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:55:18 2021 +0700 Add example files for graph.nan_color/nan_indication properties commit 89a25a165c52b2c15b6c4d95ecb777a9067e66f9 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:33:07 2021 +0700 Make widget graph:add_value(v) default v to NaN 0 as a default makes no sense and silences the error that is failing to specify a value, to which the user should rather be alerted. The graph widget has sane NaN handling now and will do just that. commit 2835d552440be48d356a7f7b1d64963696dd550b Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:20:48 2021 +0700 Implement graph.nan_color and nan_indication properties Now, whenever there's a NaN among values and graph.nan_indication is set, the corresponding area, where the value bar should have been drawn, is filled with the nan_color from top to bottom. The painting is done after all data is rendered, to make sure that it won't be overpainted and go unnoticed. NaNs among graph's values are inevitably a sign of some error and it is arguably not a sane default behavior to simply not render them at all. The user should take immediate note of any errors by default, instead of wondering at a mysteriously empty graph widget, which is why nan_indication is set to true by default. If the user wants to silence NaNs, they can always simply set it to false. nan_color if unset, defaults to a noticeable yellow-black pattern. As a direct and planned consequence, stacked graphs with negative values in them trigger the NaN handling too. But silencing NaN handling for those graphs is also possible and semi-sane, because then NaNs and negatives are ignored and the graph behaves (almost) like they were zeros. commit 9c1f8f08b400e01e19c534f3810c9c9ab485760c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 26 08:10:19 2021 +0700 Refactor out graph_map_value_to_widget_coordinates() function commit 1905991ddecabdae3e15ffb8ee5b18a1bee1a711 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 28 13:39:29 2021 +0700 Add an example file for graph.baseline_value property commit b31b39d66f4c037a9484b71a3f910703ae01fac0 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 07:03:49 2021 +0700 Add an example file for graph.clamp_bars=false/true comparison commit a1fc850e3047f9ee2c9ddeffacfbabf3ded34171 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 04:06:45 2021 +0700 Add an example file for graphs with negative values and shapes commit 2f012f8ff1bb5094c14e724a61e59a2a3a296bc8 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 01:06:05 2021 +0700 Add an example file for a graph displaying negative values commit 7b14fbd59a4c4c51962fd3110a1a900dd5f4eb98 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 11:18:26 2021 +0700 Add an example file for graphs with step_width < 1 commit dfd9a54544b637e2fbe3d1325f9cc24b3f4d2313 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 10:17:38 2021 +0700 Add an example file for graph.stack=false/true comparison Should also demonstrate that multiple data groups are possible for non-stacked graphs too. commit 5b532106c3fd255f9b66a66cfb88d977b489ab5d Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 09:34:14 2021 +0700 Add an example file for disabling graph data groups with nil colors commit ac6f1083c02fcb428f089a616f9b0ebaa5496b6c Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:57:25 2021 +0700 Add an example file for step_shape/width/spacing on stacked graphs commit b3703db6a5139e73e72c1cfd198ece2cd9be7df2 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:43:56 2021 +0700 Add an example file for scale=false/true on stacked graph commit b000aed4a12cb44fb501e1c8432a71a14b2f8f3f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 20 08:22:19 2021 +0700 Document deprecation of graph's width and height commit cb0ad617bd6c83a499896797318e3b0b588c3566 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 07:53:37 2021 +0700 Deprecate width and height properties of wibox.widget.graph commit 8c64e14ad91a074b784166a7054e53444cccb9d2 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 04:37:59 2021 +0700 Refactor graph:draw() to separate concerns Firstly, summation of group values for stacked graphs is extracted into graph:preprocess_values(). This makes graph:draw() smaller and exposes the fact, that stacked graphs are almost entirely just a form of a more general concept: pre-render data manipulation. It's something a user might want to customize, and now they can do it by monkey-patching the method. Secondly, the logic for deciding, if and with which color a data group should be drawn, is extracted into graph:should_draw_data_group() and get_data_group_color() methods respectively. This makes it customizable too and makes more obvious the fact, that it is a general thing that has nothing to do with stacked graphs in particular. With this there are only 5 lines remaining in graph:draw(), that could be said to be specific to stacked graphs, all pertaining to the `prev_y` variable, which merely preserves the bar coordinates from a previous data group. commit 2a32b6305d893f413303728a0a3da1dcd9a5591e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 22:52:37 2021 +0700 Pointlessly optimize graph:add_value() 1) Purge old values first, then insert the new one, if needed. Since Lua grows arrays in power of two steps, and our guess_capacity() also takes care to return even integers, it would be a shame to overstep the capacity by one by inserting first and thus getting the table size doubled only to never use the most of it. 2) Use a removal procedure that doesn't call the slow length operator at all. Previous code called it twice (once in table.remove) for every removed value. commit 680ea8f5d81e9040afb4662b3229e937e4b89e02 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:23:38 2021 +0700 Set graph data series color before the painting loop commit b970f0f221ae76250f2a94433ff79d653caa6e21 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:05:37 2021 +0700 Drop a large no longer necessary `if` in graph:draw_values() This commit is almost purely a whitespace change. commit 6b4f0541c70ec99d7f6e276bae64939f337120d6 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:00:50 2021 +0700 Extract values rendering code into graph:draw_values() method This is to avoid ungodly if nesting levels, which are needed to bail out of drawing values early and skip to drawing the border. The next commit will get rid of the if. commit e0950e4f99d1ea1c4b8cea2cc67629fa40030c2a Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 14:50:20 2021 +0700 Track graph draw() usage to guess necessary array capacity The last step of divorcing add_values() from width calculations, while ensuring that user doesn't observe a lack of data to draw. The widget now tracks an approximate number of values that it has been asked to draw() in _private.last_drawn_values_num, and truncates its values array based on that (if not overridden by the capacity property). commit 2e3d542282f35e7c0fed454da0f2eada0fcf3b78 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 13:35:12 2021 +0700 Add `capacity` property to graph widget This is the second step of divorcing add_values() from calculations involving widths. How many values are stored and how many values are drawn are distinct concepts. There isn't even a single definitive "how many values are drawn" because the widget could be rendered in several places with distinct widths simultaneously, and in every such place the data must be drawn appropriately, e.g. with auto-scaling, that takes only the visible data points into account. And the draw() method can do that now. The default capacity value of nil uses heuristics based on widget's _private.width to guess some capacity that would tolerate some dynamic widget resizing. If the user finds the heuristic failing them, they can set the capacity property to the desired maximum number of values that will be kept in the _private.values array (in each data group). commit 08d4a80686f7fe363551c35589a460839f60853e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 03:27:18 2021 +0700 Extract values size computation into compute_drawn_values_num() This is the first step of making add_values() not concern itself (directly) with widget dimensions. The user can monkey-patch the method to override how many values are (logically) drawn, because it affects graph auto-scaling and can be useful for rendering of step_shape-s that paint outside of their step_width. I didn't make it a full-blown function-valued property, because it seems a fringe functionality, which I'm too lazy to document. commit 3c5617c98ab1147618ed22e8414a77fc56b45f6e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 02:32:29 2021 +0700 Copy graph widget prototype methods over in bulk commit 169538837ece41a03d02ba8fbc0e4097062b67a3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun Apr 25 05:58:01 2021 +0700 Remove graph.baseline_y property After giving it some thought I've concluded, that it is unlikely to be useful as it is now. I'd better reserve the name for some more sensible functionality, like fixing the baseline_value defined axis at the given widget coordinate, adjusting the scaling accordingly, so min_value/max_value won't generally correspond to widget top and bottom anymore. commit 202ffaa9a0b820c7c471e1121d01b91f8f76cd38 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 12:06:55 2021 +0700 Add baseline_y and baseline_value properties to graph widget Now values in graph have a *baseline*, tunable with baseline_y and baseline_value properties. It is the (for now invisible) horizontal line from which all bars in the graph grow. I call it a "baseline" instead of "axis", because it doesn't actually have to correspond to the 0 value. But it can, and actually does now by default. baseline_y and baseline_value properties determine the vertical placement of the baseline in the widget. baseline_y is the position of the baseline in normalized widget coordinates, e.g. 0 corresponds to widget's top, 1 - to widget's bottom, and 0.5 - to widget's middle. So if e.g. one sets baseline_y = 0, then no matter how the widget is resized and how its values are scaled, bars in it will be drawn starting from the top. baseline_value is the position of the baseline in value coordinates. So if one sets baseline_value = 10, then no matter what happens, bars for values greater than 10 will be drawn as growing up from this line, and bars for values smaller as growing down. Where and if it will be seen in the widget, depends on the scaling. baseline_value = 0 is the familiar zero axis. baseline_y is unset by default and has priority, i.e. if it's set, then baseline_value is ignored. baseline_value defaults to 0. Prior to this commit, bars were always drawn from the bottom edge of the widget, or, in the new terms, the widget had baseline_y = 1. But it was indistinguishable from baseline_value = 0, because the widget clamped all negative values to 0, so the difference between the two could never be observed. baseline_value = 0 is clearly more useful and familiar to people, so now it is in force by default. commit df7e19abaebf0a871b5ca217b4cc3365533267a6 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 11:18:12 2021 +0700 Move value clamping from graph:add_value() into draw() Previously all values were forcibly clamped between 0 (not tunable at all) and max_value (1 by default). Thus graphs with negative values were not an option. NaN values were conflated with zeros, even though it may be useful to maintain the distinction and render them differently. And if user happened to dynamically change the max_value property, they discovered, that their data got truncated. Preserving user data as it was given is more flexible and makes things easier to debug. This commit does exactly that. So what happens now to the newly allowed values during draw()? There are 3 new cases to consider: 1) NaNs 2) Negatives 3) Values outside of min_value..max_value range NaNs are presently simply not drawn. An empty place is reserved where the value should've been, but nothing else is done. This is distinct from how a zero is handled, e.g. if step_shape is set, it is called with the height parameter = 0, which can still end up drawing something, depending on the shape. One could also consider addition of nan_color/nan_shape properties which could be drawn to alert user to a gap in their data. Negative values necessitate introduction of the concept of the zero axis, from which they will be drawn DOWN, in contrast to positive values. Where should this axis be drawn? How should step_shape-s with inherent direction, e.g. arrows, be drawn for negatives? This will be addressed in the next commit. Values outside of min/max_value range can imply shapes that go beyond widget dimensions. An arrow shape that goes beyond the roof is visibly different from an arrow that saturates and merely touches the widget top, and the user may reasonably desire either behavior. Therefore this commit introduces a boolean clamp_bars property, which defaults to true for reasons of robustness and backward compatibility with the behaviour prior to this commit. The property of course affects only the display of the values, the actual user data stays intact. commit c9c24fb1ab741cd6bb8074971d568e0646c51532 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:29:01 2021 +0700 Use rectangles for drawing graph bars of all sizes I have benchmarked various bar drawing strategies and graph sizes and found out that drawing graph bars with rectangles is as fast or even marginally faster than using fat lines. Thus the code that tries to special-case 1px bars as lines seems to be not worth the additional complexity it introduces, and this commit removes it. Incidentally, I've also established, that rectangles are more robust. If one draws graph bars and feeds cairo very large coordinates (~2^62), then corresponding rectangles fail to render, but all the other rectangles in the same series get rendered successfully, whereas a single large coordinate in a move_to/line_to() sequence is enough to cause all lines in the batch to fail to render. commit bc7f4be5e06405b5aa7fb443a5ace2b9b9575b1f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:01:42 2021 +0700 Cache cairo methods used in the inner loop of graph:draw() I have benchmarked the draw() method, both on cairo xcb and on argb32 image surfaces, and have discovered that cairo context method calls are unnaturally slow, to the point, that almost nothing else in the function matters. For example, drawing bars with vertical lines made the whole draw() function almost 2 times slower than doing the same with rectangles, merely because the former requires two method calls (move_to/line_to), and the latter only one. Simply caching hotpath cairo methods in local variables, reduced the difference between the drawing methods to a negligible one and made the whole function 4 times faster on a typical 100-bar graph, (1.4ms to 0.33ms on my laptop) thus bringing closer my secret dream of drawing live audio data with awesome graphs (kidding). I guess, LGI does something not very optimal there, like gobject-introspecting the method every time, or something. There might be some untapped optimization potential, possibly in upstream, like a method cache, from which everything in awesome could benefit, even though I imagine not many things are so call-intensive as the graph widget, to really feel the difference. commit 538df25bdf02b53d0bbe81809eef2e6fdb05ff6f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 07:32:37 2021 +0700 Use 1-based index in the inner graph draw loop It's used there only once for a calculation and then several times to index some tables, and index 0 is bad for lua tables performance-wise. commit 7412546916cd0a0a490130a120255ffb719398b8 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:55:12 2021 +0700 Leave graph.max_value uninitialized at construction This is a user-visible change, but the previous behavior was probably not what user expected anyway, namely with graph.scale set the graph mysteriously couldn't zoom in onto values that are smaller than 1. commit eb81d7929a161e79c13262cfa73ac2e6a820e8ed Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:48:29 2021 +0700 Allow graph.max_value be nil This makes its behave symmetrically to min_value. Now it defaults to 1, when graph.scale is not set, and to the maximum value in the graph data, when graph.scale is set. commit f622a8430cbfde7007b58d7f4c9c76b08c91e997 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 12:01:13 2021 +0700 Calculate max values num in graph:add_value() more precisely At most so many values were kept in the widget values array, as there are pixels of usable widget width (i.e. sans border). This is exactly right for the surely most common case of graphs with 1px-wide bars, but is more than needed for graphs with step_width+step_spacing>1, and not enough for graphs with subpixel bars. Moreover this made graph autoscaling behave weirdly in graphs with thicker bars, because old values survived longer in the array and kept affecting min_value/max_value calculations even after they were shifted off-screen. The maximum number of kept values is now calculated exactly as the number of values that can be displayed at once, taking step_width and step_spacing into account. It might be worth adding a max_values_num property to the graph to let user override this calculation, in case they draw shapes in step_shape() outside of its supposed rectangle, so our assumptions of visibility are wrong, or the widget often gets resized, resulting in the annoyance of visibly truncated series. commit 0aa63249a18641251a1bf77423739164f0eab39f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 02:30:13 2021 +0700 Fix border_color/border_width bugs in graph widget add_value() wrongly assumed that border_width is always set, when border_color is, so if user set only the latter, an error occurred. add_value() assumed that border_width is always < width which led to awesome hanging up in an infinite loop, should that fail to be the case. The graph border_width property was driven by the border_color property, instead of the other way round. If border_color wasn't set, the border wasn't drawn all, even if user sets non-zero border_width, which is a confusing behavior, in contrast to all other widgets, which simply use a plethora of fallback colors to draw the border, when needed. This also meant that, despite being documented, the beautiful.graph_border_color fallback color could not and was not ever used. If the border_color was set though, an unset border_width was immediately assumed to be 1 instead of 0, also contrary to what other widgets do. Theme colors shouldn't influence widget layout, so this commit fixes this behavior to: 1) border_width always defaults to 0, if unset. 2) border_color falls back to beautiful.graph_border_color or white. Maybe one should also add a beautiful.graph_border_width theme variable, like some widgets do. commit dad4a3253b8fef971fb82720d2a407fcc0a879ad Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 03:11:27 2021 +0700 Implement multiple data series drawing for non-stacked graph The widget can already hold and draw multiple data groups, so it makes little sense to limit drawing only to the first group when graph.stack = false, probably leading to user's confusion. Now user can call add_value(v, group) with group > 1 and the data series will be drawn (over groups with lesser id-s) with graph.stack_colors[group] color, if stack_colors is set, or with graph.color, if stack_colors isn't set. Thus nothing visibly changes for all current users of non-stacked graphs with only single data series, but the new functionality is now there for those who need it. One can't fully emulate this functionality, e.g. with multiple graphs widgets in a wibox.layout.stack, because data series in a single widget can enjoy a common auto-scaling. commit 3e645c976184ab5f50d625271c25062db9ab7ed9 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:59:12 2021 +0700 Don't draw a stacked graph data group if there's no color for it If stack_colors[idx] is nil for the group `idx`, don't draw it at all (as opposed, e.g. to the effect of drawing it with a transparent color, or as though it had all values == 0, which both produce different results). This code is still subtly different from prior behavior, because now user can have a non-contiguous stack_colors table and turn the display of any data group on and off at will, whereas the code two commits earlier would just stop rendering as soon as ipairs(stack_colors) does, i.e. no data series were drawn past the first nil hole. This commit only looks large, but is mostly a whitespace change due to nesting from a pair of wrapping ifs. commit 8c0273786de1ade66bdd977699f98b588da9f487 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:32:42 2021 +0700 Unify stacked and non-stacked graph rendering Now stacked graphs support everything the nonstacked do, like step_width ~= 1 and custom step_shape-s. Technical notes: This code no longer calls cr:stroke() for every single value in a stacked graph, but rather paints a whole series and then cr:stroke/fill()-s it at all once with its color, improving performance considerably. Additionally the graph no longer relies on the presence of the stack_colors property to render series. It draws all data that it has, and, if there's no corresponding color in stack_colors, falls back to graph.color. This will be changed in the next commit to the prior behavior of only drawing those series for which there's a color in the stack_colors table, but that would've made this commit's diff much larger due to required nesting ifs over large code chunks. This commit also takes care to properly handle NaNs and negative values. Those are currently (almost) guaranteed to not occur in the values array due to clipping in graph:add_value(), but I intend to eventually drop the clipping, because it does nothing that draw() really relies upon, and allowing unclipped and NaN values is more flexible and less surprising, e.g. when user dynamically toggles graph.scale. Eventually I intend to implement drawing negative values in non-stacked graphs and possibly even in the stacked ones. commit 38c6589c51b2aa057d7c2f40182ecf32b5b2ff5e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 10:04:15 2021 +0700 Convert graph:draw() to use `self` commit f21f1b2550426df1c4ea8abde079b1ccef84b394 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 12:27:26 2021 +0700 Fix scaling for stacked graphs Stacked graphs have cumulative heights, values from all groups that end up in the same graph bar are effectively summed. Previous code didn't take that into account when calculating min/max_value for the purposes of rescaling, but simply examined every value separately. This code adds up all values up front and finds min/max among those, to determine proper scaling that would fit the graph exactly. commit 1ce4c7bf63bdd6c99c195d668347bec49355e921 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 09:49:35 2021 +0700 Draw shaped graph bars only up to the baseline Prior to this commit, every step_shape bar was, for some reason, drawn with height = full widget's height, regardless of the value it represents. So for the most bars their lower end was far outside the box and therefore clipped off. So e.g. one couldn't see the lower round edges of rounded rectangles. This commit fixes that by appropriately varying the height that is passed into step_shape(), so that lower edges of the bars always exactly touch the lower edge of the graph. If the user wants to restore the prior behavior, they can simply ignore the passed-in height in step_shape() and just always draw their shape with height = full widget height. But they couldn't replicate the effect of this commit in their rc.lua without essentially writing their own graph widget. commit e25185b08da23745b5815f85f6e9e43222ed2294 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 08:42:54 2021 +0700 Avoid incremental transform shifting during graph bar rendering Instead of letting addition errors and cognitive load accumulate simply use the same offset calculation that is used for lines and reset the transform back to the top-left corner after every drawn bar. It's conceptually simpler, and will be useful later for stacked graphs and more. It also fixes a discrepancy between plain and step_shape rendering. Prior to this commit, bar coloring occurred with a disturbed transform, which was centered at the last drawn bar, wherever it happened to be. This meant that nontrivial colors, like gradients and images, were arbitrarily offset when step_shape was used. commit f3e98866d8583c35d0b557c09f529299edc9ba2c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:45:29 2021 +0700 Refactor out graph bar height expression into a variable commit f54528d6892f5aaa4595917853c9e2130f743fdb Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:20:25 2021 +0700 Draw graph bars with lines only when step_width == 1 Before this commit graph bars of step_width < 1 were drawn with lines of width 1, but that's a bit unfortunate, because it led to much overpainting, while still ending up with a graphically incorrect result. Let's draw such bars with rectangles and let cairo do things with subpixels, getting more graphically accurate result, while still keeping the common case of step_width == 1 (supposedly) fast-drawn with lines. commit b6515dca67c18b78eb5657378131a62ab43d4cfc Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:54:07 2021 +0700 Fix unsharp edges when drawing graph bars with rectangles The 0.5 offset is only helpful for 1-width strokes, not for fills. commit 7f3db8f07e82f821bcd1dc418d66564f537609d6 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:21:01 2021 +0700 Do coordinate transformations even if the value isn't drawn The graph presently doesn't draw values < 0, but there was a discrepancy between drawing with and without step_shape. The default line/rectangle drawing reserves an empty place where the skipped value should've been, but no such empty place is reserved, when step_shape is used. This commit fixes that. Skipped values always have their empty space now. commit 7b731ebfe96af7a9dba9c57371da6761cf22c00c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:03:30 2021 +0700 Fix graph bar scaling when min_value is not 0 Previous calculation lead to underscaling of values so that even value = max_value wasn't drawn to full height. commit dca59fd8b7fff0e7e646f2dfc6bd92047028e5a8 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:18:40 2021 +0700 Calculate the horizontal graph bar offset only when needed commit 940ddf4b9825e9c3a91d276208c089d8c74e49db Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:17:23 2021 +0700 Call cr:move_to() only when drawing the graph with lines It's redundant otherwise and might even interfere with user's drawing in step_shape(). commit b5d5cd10ca29fb29cd751faaa95137c2fe89ff63 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:04:06 2021 +0700 Fix wrong horizontal offset calculations in widget.graph Indices in the loop are zero-based, so things like i-1 and i>1 aren't necessary and actually wouldn't have been correct even in a 1-based loop. step_shape = gears.shape.rectangle and step_shape = nil, which is supposed to default to rectangle according to docs, were drawn differently due to mismatched offset calculations, which led to problems especially noticeable when step_spacing is not zero, e.g: 1) whole graph shifted left too much, drawn partially off-screen. 2) whole graph shifted right too much, and the first bar placed closer to the rest than others. commit dc369fa198281e000afc1bd17326f3a264adf7d1 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 02:09:21 2021 +0700 Fix the "border_width affects graph bar width" bug Graph bars were drawn with lines of border_width thickness instead of 1, i.e. possibly too fat to be readable. commit 1ba73378cf29ed27a6ecd18716394779ceb543d9 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 00:13:03 2021 +0700 Simplify internal values representation in graph even more New values in graph._private.values were pushed to the end, and old values were removed from the beginning of the table. In draw() however newer values are drawn first, at the left edge of the graph, necessitating annoying idx = #values - idx conversions everywhere. In particular in stacked graph that means that values that are drawn together may have different indices. One has to shift the values array in add_value() either way, so one might as well insert new values at the beginning of the array and simplify the rest of the code. This doesn't seem like much yet, but it will prove to be much more convenient and less error-prone in what I'm going to do later. commit f70523ee68dbd6b67050ace4b82903a9b22440c7 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 13:35:10 2021 +0700 Simplify internal values representation in graph widget graph._private.values was either a table of values or a table of tables of values, depending on whether "stack" property is enabled, ... or a mix of both if user forgot to pass the second "group" parameter to graph:add_value(). This resulted in pretty fragile and confusing behavior, e.g. leaving user to wonder, why they see nothing more after setting stack=true for a graph in a previously working rc.lua, and demanding a very careful use of add_value() from the user, or else some values or even entire stacks would mysteriously vanish and errors in draw() get triggered, sometimes after a period of seemingly normal functioning. Now `values` is always a table of tables and the group parameter in add_value() defaults to 1, thus making the non-stacked graph a trivial variant of the stacked graph.
2021-05-20 17:14:03 +02:00
--- Set the graph capacity.
--
-- Since the typical uses of the graph widget imply that `add_value` will be
-- called an indefinite number of times, the widget needs a way to know, when
-- to start discarding old values from the backing array.
--
-- When `capacity` is set, it defines the maximum number of values to keep in
-- each data group.
--
-- When `capacity` is unset (default), the number is determined heuristically,
-- which is sufficient most of the time, unless the widget gets resized
-- too much too fast.
--
-- @property capacity
-- @tparam[opt=nil] integer|nil capacity The maximum number of values to keep
-- per data group (`nil` for automatic guess).
-- @negativeallowed false
-- @propertyunit Number of value.
graph: complete widget overhaul and bug fixes Squashed commit of the following: commit 69821f51fe1e8652715658543e50631ece495090 Author: Alex Belykh <albel727@ngs.ru> Date: Mon May 10 19:17:55 2021 +0700 Refactor property handling in wibox.widget.graph In addition, baseline_value, step_width and step_spacing properties are all set to their default non-nil numeric values during instance creation now. commit 842eb429bf5df4b2ba46b2e6f7646afe89b1a3c3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 9 06:18:45 2021 +0700 Make graph:draw_values() private There's no documentation nor confidence that it's a good API, so I'm hiding it for now. commit 720746780574e4bad14a71fc4174955139c2eb50 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 04:04:13 2021 +0700 Document graph_should_draw_data_group() for posterity commit 7c73f3754ce6ba86b118f1170a6f9e406ab4841d Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:59:21 2021 +0700 Make graph:should_draw_data_group() private There's no confidence whether it's a good API, so I'm hiding it for now. commit b3539e20adb6423118af726ae4f8751ef864b5d2 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:36:44 2021 +0700 Make graph:preprocess_values() private There's no confidence whether it's a good API, so I'm hiding it for now. commit 2df4400e780a6260f8d0ab1c60734fbb11bf3c4f Author: Alex Belykh <albel727@ngs.ru> Date: Thu May 6 10:05:23 2021 +0700 Add spec/wibox/widget/graph_spec.lua commit ce01a9771ab439b8df841f1565aa4aa8a44a5e0d Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 08:16:18 2021 +0700 Improve graph.capacity documentation commit e28fade8f655802f5bf89154169f68d256b8e332 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 07:36:01 2021 +0700 Document graph:compute_drawn_values_num() commit dd68332292465422d08b48e9f0229f143beccbc6 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 05:24:23 2021 +0700 Document graph:pick_data_group_color() method commit ca6dc55f77aca88a6e9cc90bb6fd0045739f41f5 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 04:13:58 2021 +0700 Guard against setting data groups sparsely in graph:add_value() commit cdea5f126f7984e72924f6dffbd312c4fe3b032f Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 02:36:12 2021 +0700 Refactor scaling and baseline choice out of graph:draw() commit 10135deafe8a9390ba66ab657ebb1fb41d549e8c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 21:34:48 2021 +0700 Document graph.nan_color and graph.stack properties commit 0d73bb84ac66b87e16aee472bd1f299549f66a1c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 20:49:24 2021 +0700 Use a better example in docs for the graph.stack property commit c27d160a0826d033d49d6141608c40aa8a0642fa Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 16:05:38 2021 +0700 Document graph.baseline_value property commit 459bc8176c13d4ea32af0dcdc2399d3b929b1c6a Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:53:47 2021 +0700 Document graph.clamp_bars property commit e732ee0b3b007d213e689b98cc00ae7cb141ae08 Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:34 2021 +0700 Improve graph:add_value() and :clear() documentation commit ae73e3aa9f8d60409cbe4cb4da9612f31af4f38e Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:11 2021 +0700 Improve graph min_value/max_value/scale documentation commit a7350996a9def773b91b1631e6c0507e33d8f93b Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:27:36 2021 +0700 Cleanup graph examples to show only relevant things in docs commit 30740c26f4f41c16f4c8a3c339bcb7bd299e14cd Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 14:04:36 2021 +0700 Rename graph.stack_colors property to `group_colors` The colors are used for stacked and non-stacked graphs alike, so the name didn't make sense. Also document and clarify some color stuff. commit 1ea01bfd410971cf0d0c25fd74cd764d5eb7279d Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 21:25:41 2021 +0700 Add an example file for code coverage of some fringe graph cases commit eae68e39553f88a6275a0c65bf040b5d4c262b54 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 20:23:32 2021 +0700 Add step_shape to the graph/nan_color example for code coverage commit 8027147bc2eab34a97b49a428fb8cef27f122bed Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:55:18 2021 +0700 Add example files for graph.nan_color/nan_indication properties commit 89a25a165c52b2c15b6c4d95ecb777a9067e66f9 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:33:07 2021 +0700 Make widget graph:add_value(v) default v to NaN 0 as a default makes no sense and silences the error that is failing to specify a value, to which the user should rather be alerted. The graph widget has sane NaN handling now and will do just that. commit 2835d552440be48d356a7f7b1d64963696dd550b Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:20:48 2021 +0700 Implement graph.nan_color and nan_indication properties Now, whenever there's a NaN among values and graph.nan_indication is set, the corresponding area, where the value bar should have been drawn, is filled with the nan_color from top to bottom. The painting is done after all data is rendered, to make sure that it won't be overpainted and go unnoticed. NaNs among graph's values are inevitably a sign of some error and it is arguably not a sane default behavior to simply not render them at all. The user should take immediate note of any errors by default, instead of wondering at a mysteriously empty graph widget, which is why nan_indication is set to true by default. If the user wants to silence NaNs, they can always simply set it to false. nan_color if unset, defaults to a noticeable yellow-black pattern. As a direct and planned consequence, stacked graphs with negative values in them trigger the NaN handling too. But silencing NaN handling for those graphs is also possible and semi-sane, because then NaNs and negatives are ignored and the graph behaves (almost) like they were zeros. commit 9c1f8f08b400e01e19c534f3810c9c9ab485760c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 26 08:10:19 2021 +0700 Refactor out graph_map_value_to_widget_coordinates() function commit 1905991ddecabdae3e15ffb8ee5b18a1bee1a711 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 28 13:39:29 2021 +0700 Add an example file for graph.baseline_value property commit b31b39d66f4c037a9484b71a3f910703ae01fac0 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 07:03:49 2021 +0700 Add an example file for graph.clamp_bars=false/true comparison commit a1fc850e3047f9ee2c9ddeffacfbabf3ded34171 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 04:06:45 2021 +0700 Add an example file for graphs with negative values and shapes commit 2f012f8ff1bb5094c14e724a61e59a2a3a296bc8 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 01:06:05 2021 +0700 Add an example file for a graph displaying negative values commit 7b14fbd59a4c4c51962fd3110a1a900dd5f4eb98 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 11:18:26 2021 +0700 Add an example file for graphs with step_width < 1 commit dfd9a54544b637e2fbe3d1325f9cc24b3f4d2313 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 10:17:38 2021 +0700 Add an example file for graph.stack=false/true comparison Should also demonstrate that multiple data groups are possible for non-stacked graphs too. commit 5b532106c3fd255f9b66a66cfb88d977b489ab5d Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 09:34:14 2021 +0700 Add an example file for disabling graph data groups with nil colors commit ac6f1083c02fcb428f089a616f9b0ebaa5496b6c Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:57:25 2021 +0700 Add an example file for step_shape/width/spacing on stacked graphs commit b3703db6a5139e73e72c1cfd198ece2cd9be7df2 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:43:56 2021 +0700 Add an example file for scale=false/true on stacked graph commit b000aed4a12cb44fb501e1c8432a71a14b2f8f3f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 20 08:22:19 2021 +0700 Document deprecation of graph's width and height commit cb0ad617bd6c83a499896797318e3b0b588c3566 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 07:53:37 2021 +0700 Deprecate width and height properties of wibox.widget.graph commit 8c64e14ad91a074b784166a7054e53444cccb9d2 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 04:37:59 2021 +0700 Refactor graph:draw() to separate concerns Firstly, summation of group values for stacked graphs is extracted into graph:preprocess_values(). This makes graph:draw() smaller and exposes the fact, that stacked graphs are almost entirely just a form of a more general concept: pre-render data manipulation. It's something a user might want to customize, and now they can do it by monkey-patching the method. Secondly, the logic for deciding, if and with which color a data group should be drawn, is extracted into graph:should_draw_data_group() and get_data_group_color() methods respectively. This makes it customizable too and makes more obvious the fact, that it is a general thing that has nothing to do with stacked graphs in particular. With this there are only 5 lines remaining in graph:draw(), that could be said to be specific to stacked graphs, all pertaining to the `prev_y` variable, which merely preserves the bar coordinates from a previous data group. commit 2a32b6305d893f413303728a0a3da1dcd9a5591e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 22:52:37 2021 +0700 Pointlessly optimize graph:add_value() 1) Purge old values first, then insert the new one, if needed. Since Lua grows arrays in power of two steps, and our guess_capacity() also takes care to return even integers, it would be a shame to overstep the capacity by one by inserting first and thus getting the table size doubled only to never use the most of it. 2) Use a removal procedure that doesn't call the slow length operator at all. Previous code called it twice (once in table.remove) for every removed value. commit 680ea8f5d81e9040afb4662b3229e937e4b89e02 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:23:38 2021 +0700 Set graph data series color before the painting loop commit b970f0f221ae76250f2a94433ff79d653caa6e21 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:05:37 2021 +0700 Drop a large no longer necessary `if` in graph:draw_values() This commit is almost purely a whitespace change. commit 6b4f0541c70ec99d7f6e276bae64939f337120d6 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:00:50 2021 +0700 Extract values rendering code into graph:draw_values() method This is to avoid ungodly if nesting levels, which are needed to bail out of drawing values early and skip to drawing the border. The next commit will get rid of the if. commit e0950e4f99d1ea1c4b8cea2cc67629fa40030c2a Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 14:50:20 2021 +0700 Track graph draw() usage to guess necessary array capacity The last step of divorcing add_values() from width calculations, while ensuring that user doesn't observe a lack of data to draw. The widget now tracks an approximate number of values that it has been asked to draw() in _private.last_drawn_values_num, and truncates its values array based on that (if not overridden by the capacity property). commit 2e3d542282f35e7c0fed454da0f2eada0fcf3b78 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 13:35:12 2021 +0700 Add `capacity` property to graph widget This is the second step of divorcing add_values() from calculations involving widths. How many values are stored and how many values are drawn are distinct concepts. There isn't even a single definitive "how many values are drawn" because the widget could be rendered in several places with distinct widths simultaneously, and in every such place the data must be drawn appropriately, e.g. with auto-scaling, that takes only the visible data points into account. And the draw() method can do that now. The default capacity value of nil uses heuristics based on widget's _private.width to guess some capacity that would tolerate some dynamic widget resizing. If the user finds the heuristic failing them, they can set the capacity property to the desired maximum number of values that will be kept in the _private.values array (in each data group). commit 08d4a80686f7fe363551c35589a460839f60853e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 03:27:18 2021 +0700 Extract values size computation into compute_drawn_values_num() This is the first step of making add_values() not concern itself (directly) with widget dimensions. The user can monkey-patch the method to override how many values are (logically) drawn, because it affects graph auto-scaling and can be useful for rendering of step_shape-s that paint outside of their step_width. I didn't make it a full-blown function-valued property, because it seems a fringe functionality, which I'm too lazy to document. commit 3c5617c98ab1147618ed22e8414a77fc56b45f6e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 02:32:29 2021 +0700 Copy graph widget prototype methods over in bulk commit 169538837ece41a03d02ba8fbc0e4097062b67a3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun Apr 25 05:58:01 2021 +0700 Remove graph.baseline_y property After giving it some thought I've concluded, that it is unlikely to be useful as it is now. I'd better reserve the name for some more sensible functionality, like fixing the baseline_value defined axis at the given widget coordinate, adjusting the scaling accordingly, so min_value/max_value won't generally correspond to widget top and bottom anymore. commit 202ffaa9a0b820c7c471e1121d01b91f8f76cd38 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 12:06:55 2021 +0700 Add baseline_y and baseline_value properties to graph widget Now values in graph have a *baseline*, tunable with baseline_y and baseline_value properties. It is the (for now invisible) horizontal line from which all bars in the graph grow. I call it a "baseline" instead of "axis", because it doesn't actually have to correspond to the 0 value. But it can, and actually does now by default. baseline_y and baseline_value properties determine the vertical placement of the baseline in the widget. baseline_y is the position of the baseline in normalized widget coordinates, e.g. 0 corresponds to widget's top, 1 - to widget's bottom, and 0.5 - to widget's middle. So if e.g. one sets baseline_y = 0, then no matter how the widget is resized and how its values are scaled, bars in it will be drawn starting from the top. baseline_value is the position of the baseline in value coordinates. So if one sets baseline_value = 10, then no matter what happens, bars for values greater than 10 will be drawn as growing up from this line, and bars for values smaller as growing down. Where and if it will be seen in the widget, depends on the scaling. baseline_value = 0 is the familiar zero axis. baseline_y is unset by default and has priority, i.e. if it's set, then baseline_value is ignored. baseline_value defaults to 0. Prior to this commit, bars were always drawn from the bottom edge of the widget, or, in the new terms, the widget had baseline_y = 1. But it was indistinguishable from baseline_value = 0, because the widget clamped all negative values to 0, so the difference between the two could never be observed. baseline_value = 0 is clearly more useful and familiar to people, so now it is in force by default. commit df7e19abaebf0a871b5ca217b4cc3365533267a6 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 11:18:12 2021 +0700 Move value clamping from graph:add_value() into draw() Previously all values were forcibly clamped between 0 (not tunable at all) and max_value (1 by default). Thus graphs with negative values were not an option. NaN values were conflated with zeros, even though it may be useful to maintain the distinction and render them differently. And if user happened to dynamically change the max_value property, they discovered, that their data got truncated. Preserving user data as it was given is more flexible and makes things easier to debug. This commit does exactly that. So what happens now to the newly allowed values during draw()? There are 3 new cases to consider: 1) NaNs 2) Negatives 3) Values outside of min_value..max_value range NaNs are presently simply not drawn. An empty place is reserved where the value should've been, but nothing else is done. This is distinct from how a zero is handled, e.g. if step_shape is set, it is called with the height parameter = 0, which can still end up drawing something, depending on the shape. One could also consider addition of nan_color/nan_shape properties which could be drawn to alert user to a gap in their data. Negative values necessitate introduction of the concept of the zero axis, from which they will be drawn DOWN, in contrast to positive values. Where should this axis be drawn? How should step_shape-s with inherent direction, e.g. arrows, be drawn for negatives? This will be addressed in the next commit. Values outside of min/max_value range can imply shapes that go beyond widget dimensions. An arrow shape that goes beyond the roof is visibly different from an arrow that saturates and merely touches the widget top, and the user may reasonably desire either behavior. Therefore this commit introduces a boolean clamp_bars property, which defaults to true for reasons of robustness and backward compatibility with the behaviour prior to this commit. The property of course affects only the display of the values, the actual user data stays intact. commit c9c24fb1ab741cd6bb8074971d568e0646c51532 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:29:01 2021 +0700 Use rectangles for drawing graph bars of all sizes I have benchmarked various bar drawing strategies and graph sizes and found out that drawing graph bars with rectangles is as fast or even marginally faster than using fat lines. Thus the code that tries to special-case 1px bars as lines seems to be not worth the additional complexity it introduces, and this commit removes it. Incidentally, I've also established, that rectangles are more robust. If one draws graph bars and feeds cairo very large coordinates (~2^62), then corresponding rectangles fail to render, but all the other rectangles in the same series get rendered successfully, whereas a single large coordinate in a move_to/line_to() sequence is enough to cause all lines in the batch to fail to render. commit bc7f4be5e06405b5aa7fb443a5ace2b9b9575b1f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:01:42 2021 +0700 Cache cairo methods used in the inner loop of graph:draw() I have benchmarked the draw() method, both on cairo xcb and on argb32 image surfaces, and have discovered that cairo context method calls are unnaturally slow, to the point, that almost nothing else in the function matters. For example, drawing bars with vertical lines made the whole draw() function almost 2 times slower than doing the same with rectangles, merely because the former requires two method calls (move_to/line_to), and the latter only one. Simply caching hotpath cairo methods in local variables, reduced the difference between the drawing methods to a negligible one and made the whole function 4 times faster on a typical 100-bar graph, (1.4ms to 0.33ms on my laptop) thus bringing closer my secret dream of drawing live audio data with awesome graphs (kidding). I guess, LGI does something not very optimal there, like gobject-introspecting the method every time, or something. There might be some untapped optimization potential, possibly in upstream, like a method cache, from which everything in awesome could benefit, even though I imagine not many things are so call-intensive as the graph widget, to really feel the difference. commit 538df25bdf02b53d0bbe81809eef2e6fdb05ff6f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 07:32:37 2021 +0700 Use 1-based index in the inner graph draw loop It's used there only once for a calculation and then several times to index some tables, and index 0 is bad for lua tables performance-wise. commit 7412546916cd0a0a490130a120255ffb719398b8 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:55:12 2021 +0700 Leave graph.max_value uninitialized at construction This is a user-visible change, but the previous behavior was probably not what user expected anyway, namely with graph.scale set the graph mysteriously couldn't zoom in onto values that are smaller than 1. commit eb81d7929a161e79c13262cfa73ac2e6a820e8ed Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:48:29 2021 +0700 Allow graph.max_value be nil This makes its behave symmetrically to min_value. Now it defaults to 1, when graph.scale is not set, and to the maximum value in the graph data, when graph.scale is set. commit f622a8430cbfde7007b58d7f4c9c76b08c91e997 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 12:01:13 2021 +0700 Calculate max values num in graph:add_value() more precisely At most so many values were kept in the widget values array, as there are pixels of usable widget width (i.e. sans border). This is exactly right for the surely most common case of graphs with 1px-wide bars, but is more than needed for graphs with step_width+step_spacing>1, and not enough for graphs with subpixel bars. Moreover this made graph autoscaling behave weirdly in graphs with thicker bars, because old values survived longer in the array and kept affecting min_value/max_value calculations even after they were shifted off-screen. The maximum number of kept values is now calculated exactly as the number of values that can be displayed at once, taking step_width and step_spacing into account. It might be worth adding a max_values_num property to the graph to let user override this calculation, in case they draw shapes in step_shape() outside of its supposed rectangle, so our assumptions of visibility are wrong, or the widget often gets resized, resulting in the annoyance of visibly truncated series. commit 0aa63249a18641251a1bf77423739164f0eab39f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 02:30:13 2021 +0700 Fix border_color/border_width bugs in graph widget add_value() wrongly assumed that border_width is always set, when border_color is, so if user set only the latter, an error occurred. add_value() assumed that border_width is always < width which led to awesome hanging up in an infinite loop, should that fail to be the case. The graph border_width property was driven by the border_color property, instead of the other way round. If border_color wasn't set, the border wasn't drawn all, even if user sets non-zero border_width, which is a confusing behavior, in contrast to all other widgets, which simply use a plethora of fallback colors to draw the border, when needed. This also meant that, despite being documented, the beautiful.graph_border_color fallback color could not and was not ever used. If the border_color was set though, an unset border_width was immediately assumed to be 1 instead of 0, also contrary to what other widgets do. Theme colors shouldn't influence widget layout, so this commit fixes this behavior to: 1) border_width always defaults to 0, if unset. 2) border_color falls back to beautiful.graph_border_color or white. Maybe one should also add a beautiful.graph_border_width theme variable, like some widgets do. commit dad4a3253b8fef971fb82720d2a407fcc0a879ad Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 03:11:27 2021 +0700 Implement multiple data series drawing for non-stacked graph The widget can already hold and draw multiple data groups, so it makes little sense to limit drawing only to the first group when graph.stack = false, probably leading to user's confusion. Now user can call add_value(v, group) with group > 1 and the data series will be drawn (over groups with lesser id-s) with graph.stack_colors[group] color, if stack_colors is set, or with graph.color, if stack_colors isn't set. Thus nothing visibly changes for all current users of non-stacked graphs with only single data series, but the new functionality is now there for those who need it. One can't fully emulate this functionality, e.g. with multiple graphs widgets in a wibox.layout.stack, because data series in a single widget can enjoy a common auto-scaling. commit 3e645c976184ab5f50d625271c25062db9ab7ed9 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:59:12 2021 +0700 Don't draw a stacked graph data group if there's no color for it If stack_colors[idx] is nil for the group `idx`, don't draw it at all (as opposed, e.g. to the effect of drawing it with a transparent color, or as though it had all values == 0, which both produce different results). This code is still subtly different from prior behavior, because now user can have a non-contiguous stack_colors table and turn the display of any data group on and off at will, whereas the code two commits earlier would just stop rendering as soon as ipairs(stack_colors) does, i.e. no data series were drawn past the first nil hole. This commit only looks large, but is mostly a whitespace change due to nesting from a pair of wrapping ifs. commit 8c0273786de1ade66bdd977699f98b588da9f487 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:32:42 2021 +0700 Unify stacked and non-stacked graph rendering Now stacked graphs support everything the nonstacked do, like step_width ~= 1 and custom step_shape-s. Technical notes: This code no longer calls cr:stroke() for every single value in a stacked graph, but rather paints a whole series and then cr:stroke/fill()-s it at all once with its color, improving performance considerably. Additionally the graph no longer relies on the presence of the stack_colors property to render series. It draws all data that it has, and, if there's no corresponding color in stack_colors, falls back to graph.color. This will be changed in the next commit to the prior behavior of only drawing those series for which there's a color in the stack_colors table, but that would've made this commit's diff much larger due to required nesting ifs over large code chunks. This commit also takes care to properly handle NaNs and negative values. Those are currently (almost) guaranteed to not occur in the values array due to clipping in graph:add_value(), but I intend to eventually drop the clipping, because it does nothing that draw() really relies upon, and allowing unclipped and NaN values is more flexible and less surprising, e.g. when user dynamically toggles graph.scale. Eventually I intend to implement drawing negative values in non-stacked graphs and possibly even in the stacked ones. commit 38c6589c51b2aa057d7c2f40182ecf32b5b2ff5e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 10:04:15 2021 +0700 Convert graph:draw() to use `self` commit f21f1b2550426df1c4ea8abde079b1ccef84b394 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 12:27:26 2021 +0700 Fix scaling for stacked graphs Stacked graphs have cumulative heights, values from all groups that end up in the same graph bar are effectively summed. Previous code didn't take that into account when calculating min/max_value for the purposes of rescaling, but simply examined every value separately. This code adds up all values up front and finds min/max among those, to determine proper scaling that would fit the graph exactly. commit 1ce4c7bf63bdd6c99c195d668347bec49355e921 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 09:49:35 2021 +0700 Draw shaped graph bars only up to the baseline Prior to this commit, every step_shape bar was, for some reason, drawn with height = full widget's height, regardless of the value it represents. So for the most bars their lower end was far outside the box and therefore clipped off. So e.g. one couldn't see the lower round edges of rounded rectangles. This commit fixes that by appropriately varying the height that is passed into step_shape(), so that lower edges of the bars always exactly touch the lower edge of the graph. If the user wants to restore the prior behavior, they can simply ignore the passed-in height in step_shape() and just always draw their shape with height = full widget height. But they couldn't replicate the effect of this commit in their rc.lua without essentially writing their own graph widget. commit e25185b08da23745b5815f85f6e9e43222ed2294 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 08:42:54 2021 +0700 Avoid incremental transform shifting during graph bar rendering Instead of letting addition errors and cognitive load accumulate simply use the same offset calculation that is used for lines and reset the transform back to the top-left corner after every drawn bar. It's conceptually simpler, and will be useful later for stacked graphs and more. It also fixes a discrepancy between plain and step_shape rendering. Prior to this commit, bar coloring occurred with a disturbed transform, which was centered at the last drawn bar, wherever it happened to be. This meant that nontrivial colors, like gradients and images, were arbitrarily offset when step_shape was used. commit f3e98866d8583c35d0b557c09f529299edc9ba2c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:45:29 2021 +0700 Refactor out graph bar height expression into a variable commit f54528d6892f5aaa4595917853c9e2130f743fdb Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:20:25 2021 +0700 Draw graph bars with lines only when step_width == 1 Before this commit graph bars of step_width < 1 were drawn with lines of width 1, but that's a bit unfortunate, because it led to much overpainting, while still ending up with a graphically incorrect result. Let's draw such bars with rectangles and let cairo do things with subpixels, getting more graphically accurate result, while still keeping the common case of step_width == 1 (supposedly) fast-drawn with lines. commit b6515dca67c18b78eb5657378131a62ab43d4cfc Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:54:07 2021 +0700 Fix unsharp edges when drawing graph bars with rectangles The 0.5 offset is only helpful for 1-width strokes, not for fills. commit 7f3db8f07e82f821bcd1dc418d66564f537609d6 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:21:01 2021 +0700 Do coordinate transformations even if the value isn't drawn The graph presently doesn't draw values < 0, but there was a discrepancy between drawing with and without step_shape. The default line/rectangle drawing reserves an empty place where the skipped value should've been, but no such empty place is reserved, when step_shape is used. This commit fixes that. Skipped values always have their empty space now. commit 7b731ebfe96af7a9dba9c57371da6761cf22c00c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:03:30 2021 +0700 Fix graph bar scaling when min_value is not 0 Previous calculation lead to underscaling of values so that even value = max_value wasn't drawn to full height. commit dca59fd8b7fff0e7e646f2dfc6bd92047028e5a8 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:18:40 2021 +0700 Calculate the horizontal graph bar offset only when needed commit 940ddf4b9825e9c3a91d276208c089d8c74e49db Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:17:23 2021 +0700 Call cr:move_to() only when drawing the graph with lines It's redundant otherwise and might even interfere with user's drawing in step_shape(). commit b5d5cd10ca29fb29cd751faaa95137c2fe89ff63 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:04:06 2021 +0700 Fix wrong horizontal offset calculations in widget.graph Indices in the loop are zero-based, so things like i-1 and i>1 aren't necessary and actually wouldn't have been correct even in a 1-based loop. step_shape = gears.shape.rectangle and step_shape = nil, which is supposed to default to rectangle according to docs, were drawn differently due to mismatched offset calculations, which led to problems especially noticeable when step_spacing is not zero, e.g: 1) whole graph shifted left too much, drawn partially off-screen. 2) whole graph shifted right too much, and the first bar placed closer to the rest than others. commit dc369fa198281e000afc1bd17326f3a264adf7d1 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 02:09:21 2021 +0700 Fix the "border_width affects graph bar width" bug Graph bars were drawn with lines of border_width thickness instead of 1, i.e. possibly too fat to be readable. commit 1ba73378cf29ed27a6ecd18716394779ceb543d9 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 00:13:03 2021 +0700 Simplify internal values representation in graph even more New values in graph._private.values were pushed to the end, and old values were removed from the beginning of the table. In draw() however newer values are drawn first, at the left edge of the graph, necessitating annoying idx = #values - idx conversions everywhere. In particular in stacked graph that means that values that are drawn together may have different indices. One has to shift the values array in add_value() either way, so one might as well insert new values at the beginning of the array and simplify the rest of the code. This doesn't seem like much yet, but it will prove to be much more convenient and less error-prone in what I'm going to do later. commit f70523ee68dbd6b67050ace4b82903a9b22440c7 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 13:35:10 2021 +0700 Simplify internal values representation in graph widget graph._private.values was either a table of values or a table of tables of values, depending on whether "stack" property is enabled, ... or a mix of both if user forgot to pass the second "group" parameter to graph:add_value(). This resulted in pretty fragile and confusing behavior, e.g. leaving user to wonder, why they see nothing more after setting stack=true for a graph in a previously working rc.lua, and demanding a very careful use of add_value() from the user, or else some values or even entire stacks would mysteriously vanish and errors in draw() get triggered, sometimes after a period of seemingly normal functioning. Now `values` is always a table of tables and the group parameter in add_value() defaults to 1, thus making the non-stacked graph a trivial variant of the stacked graph.
2021-05-20 17:14:03 +02:00
-- @propemits true false
function graph:set_capacity(capacity)
-- Property override to avoid emitting the "redraw_needed" signal,
-- because nothing visibly changes until the next add_value() call,
-- which emits the signal itself.
-- It might have been prudent to truncate the values array here
-- and emit the signal, but I don't think anyone really needs that.
if self._private.capacity ~= capacity then
self._private.capacity = capacity
self:emit_signal("property::capacity", capacity)
end
return self
end
2016-05-23 07:30:44 +02:00
--- Set the graph height.
--
graph: complete widget overhaul and bug fixes Squashed commit of the following: commit 69821f51fe1e8652715658543e50631ece495090 Author: Alex Belykh <albel727@ngs.ru> Date: Mon May 10 19:17:55 2021 +0700 Refactor property handling in wibox.widget.graph In addition, baseline_value, step_width and step_spacing properties are all set to their default non-nil numeric values during instance creation now. commit 842eb429bf5df4b2ba46b2e6f7646afe89b1a3c3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 9 06:18:45 2021 +0700 Make graph:draw_values() private There's no documentation nor confidence that it's a good API, so I'm hiding it for now. commit 720746780574e4bad14a71fc4174955139c2eb50 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 04:04:13 2021 +0700 Document graph_should_draw_data_group() for posterity commit 7c73f3754ce6ba86b118f1170a6f9e406ab4841d Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:59:21 2021 +0700 Make graph:should_draw_data_group() private There's no confidence whether it's a good API, so I'm hiding it for now. commit b3539e20adb6423118af726ae4f8751ef864b5d2 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:36:44 2021 +0700 Make graph:preprocess_values() private There's no confidence whether it's a good API, so I'm hiding it for now. commit 2df4400e780a6260f8d0ab1c60734fbb11bf3c4f Author: Alex Belykh <albel727@ngs.ru> Date: Thu May 6 10:05:23 2021 +0700 Add spec/wibox/widget/graph_spec.lua commit ce01a9771ab439b8df841f1565aa4aa8a44a5e0d Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 08:16:18 2021 +0700 Improve graph.capacity documentation commit e28fade8f655802f5bf89154169f68d256b8e332 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 07:36:01 2021 +0700 Document graph:compute_drawn_values_num() commit dd68332292465422d08b48e9f0229f143beccbc6 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 05:24:23 2021 +0700 Document graph:pick_data_group_color() method commit ca6dc55f77aca88a6e9cc90bb6fd0045739f41f5 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 04:13:58 2021 +0700 Guard against setting data groups sparsely in graph:add_value() commit cdea5f126f7984e72924f6dffbd312c4fe3b032f Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 02:36:12 2021 +0700 Refactor scaling and baseline choice out of graph:draw() commit 10135deafe8a9390ba66ab657ebb1fb41d549e8c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 21:34:48 2021 +0700 Document graph.nan_color and graph.stack properties commit 0d73bb84ac66b87e16aee472bd1f299549f66a1c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 20:49:24 2021 +0700 Use a better example in docs for the graph.stack property commit c27d160a0826d033d49d6141608c40aa8a0642fa Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 16:05:38 2021 +0700 Document graph.baseline_value property commit 459bc8176c13d4ea32af0dcdc2399d3b929b1c6a Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:53:47 2021 +0700 Document graph.clamp_bars property commit e732ee0b3b007d213e689b98cc00ae7cb141ae08 Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:34 2021 +0700 Improve graph:add_value() and :clear() documentation commit ae73e3aa9f8d60409cbe4cb4da9612f31af4f38e Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:11 2021 +0700 Improve graph min_value/max_value/scale documentation commit a7350996a9def773b91b1631e6c0507e33d8f93b Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:27:36 2021 +0700 Cleanup graph examples to show only relevant things in docs commit 30740c26f4f41c16f4c8a3c339bcb7bd299e14cd Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 14:04:36 2021 +0700 Rename graph.stack_colors property to `group_colors` The colors are used for stacked and non-stacked graphs alike, so the name didn't make sense. Also document and clarify some color stuff. commit 1ea01bfd410971cf0d0c25fd74cd764d5eb7279d Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 21:25:41 2021 +0700 Add an example file for code coverage of some fringe graph cases commit eae68e39553f88a6275a0c65bf040b5d4c262b54 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 20:23:32 2021 +0700 Add step_shape to the graph/nan_color example for code coverage commit 8027147bc2eab34a97b49a428fb8cef27f122bed Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:55:18 2021 +0700 Add example files for graph.nan_color/nan_indication properties commit 89a25a165c52b2c15b6c4d95ecb777a9067e66f9 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:33:07 2021 +0700 Make widget graph:add_value(v) default v to NaN 0 as a default makes no sense and silences the error that is failing to specify a value, to which the user should rather be alerted. The graph widget has sane NaN handling now and will do just that. commit 2835d552440be48d356a7f7b1d64963696dd550b Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:20:48 2021 +0700 Implement graph.nan_color and nan_indication properties Now, whenever there's a NaN among values and graph.nan_indication is set, the corresponding area, where the value bar should have been drawn, is filled with the nan_color from top to bottom. The painting is done after all data is rendered, to make sure that it won't be overpainted and go unnoticed. NaNs among graph's values are inevitably a sign of some error and it is arguably not a sane default behavior to simply not render them at all. The user should take immediate note of any errors by default, instead of wondering at a mysteriously empty graph widget, which is why nan_indication is set to true by default. If the user wants to silence NaNs, they can always simply set it to false. nan_color if unset, defaults to a noticeable yellow-black pattern. As a direct and planned consequence, stacked graphs with negative values in them trigger the NaN handling too. But silencing NaN handling for those graphs is also possible and semi-sane, because then NaNs and negatives are ignored and the graph behaves (almost) like they were zeros. commit 9c1f8f08b400e01e19c534f3810c9c9ab485760c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 26 08:10:19 2021 +0700 Refactor out graph_map_value_to_widget_coordinates() function commit 1905991ddecabdae3e15ffb8ee5b18a1bee1a711 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 28 13:39:29 2021 +0700 Add an example file for graph.baseline_value property commit b31b39d66f4c037a9484b71a3f910703ae01fac0 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 07:03:49 2021 +0700 Add an example file for graph.clamp_bars=false/true comparison commit a1fc850e3047f9ee2c9ddeffacfbabf3ded34171 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 04:06:45 2021 +0700 Add an example file for graphs with negative values and shapes commit 2f012f8ff1bb5094c14e724a61e59a2a3a296bc8 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 01:06:05 2021 +0700 Add an example file for a graph displaying negative values commit 7b14fbd59a4c4c51962fd3110a1a900dd5f4eb98 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 11:18:26 2021 +0700 Add an example file for graphs with step_width < 1 commit dfd9a54544b637e2fbe3d1325f9cc24b3f4d2313 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 10:17:38 2021 +0700 Add an example file for graph.stack=false/true comparison Should also demonstrate that multiple data groups are possible for non-stacked graphs too. commit 5b532106c3fd255f9b66a66cfb88d977b489ab5d Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 09:34:14 2021 +0700 Add an example file for disabling graph data groups with nil colors commit ac6f1083c02fcb428f089a616f9b0ebaa5496b6c Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:57:25 2021 +0700 Add an example file for step_shape/width/spacing on stacked graphs commit b3703db6a5139e73e72c1cfd198ece2cd9be7df2 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:43:56 2021 +0700 Add an example file for scale=false/true on stacked graph commit b000aed4a12cb44fb501e1c8432a71a14b2f8f3f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 20 08:22:19 2021 +0700 Document deprecation of graph's width and height commit cb0ad617bd6c83a499896797318e3b0b588c3566 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 07:53:37 2021 +0700 Deprecate width and height properties of wibox.widget.graph commit 8c64e14ad91a074b784166a7054e53444cccb9d2 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 04:37:59 2021 +0700 Refactor graph:draw() to separate concerns Firstly, summation of group values for stacked graphs is extracted into graph:preprocess_values(). This makes graph:draw() smaller and exposes the fact, that stacked graphs are almost entirely just a form of a more general concept: pre-render data manipulation. It's something a user might want to customize, and now they can do it by monkey-patching the method. Secondly, the logic for deciding, if and with which color a data group should be drawn, is extracted into graph:should_draw_data_group() and get_data_group_color() methods respectively. This makes it customizable too and makes more obvious the fact, that it is a general thing that has nothing to do with stacked graphs in particular. With this there are only 5 lines remaining in graph:draw(), that could be said to be specific to stacked graphs, all pertaining to the `prev_y` variable, which merely preserves the bar coordinates from a previous data group. commit 2a32b6305d893f413303728a0a3da1dcd9a5591e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 22:52:37 2021 +0700 Pointlessly optimize graph:add_value() 1) Purge old values first, then insert the new one, if needed. Since Lua grows arrays in power of two steps, and our guess_capacity() also takes care to return even integers, it would be a shame to overstep the capacity by one by inserting first and thus getting the table size doubled only to never use the most of it. 2) Use a removal procedure that doesn't call the slow length operator at all. Previous code called it twice (once in table.remove) for every removed value. commit 680ea8f5d81e9040afb4662b3229e937e4b89e02 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:23:38 2021 +0700 Set graph data series color before the painting loop commit b970f0f221ae76250f2a94433ff79d653caa6e21 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:05:37 2021 +0700 Drop a large no longer necessary `if` in graph:draw_values() This commit is almost purely a whitespace change. commit 6b4f0541c70ec99d7f6e276bae64939f337120d6 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:00:50 2021 +0700 Extract values rendering code into graph:draw_values() method This is to avoid ungodly if nesting levels, which are needed to bail out of drawing values early and skip to drawing the border. The next commit will get rid of the if. commit e0950e4f99d1ea1c4b8cea2cc67629fa40030c2a Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 14:50:20 2021 +0700 Track graph draw() usage to guess necessary array capacity The last step of divorcing add_values() from width calculations, while ensuring that user doesn't observe a lack of data to draw. The widget now tracks an approximate number of values that it has been asked to draw() in _private.last_drawn_values_num, and truncates its values array based on that (if not overridden by the capacity property). commit 2e3d542282f35e7c0fed454da0f2eada0fcf3b78 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 13:35:12 2021 +0700 Add `capacity` property to graph widget This is the second step of divorcing add_values() from calculations involving widths. How many values are stored and how many values are drawn are distinct concepts. There isn't even a single definitive "how many values are drawn" because the widget could be rendered in several places with distinct widths simultaneously, and in every such place the data must be drawn appropriately, e.g. with auto-scaling, that takes only the visible data points into account. And the draw() method can do that now. The default capacity value of nil uses heuristics based on widget's _private.width to guess some capacity that would tolerate some dynamic widget resizing. If the user finds the heuristic failing them, they can set the capacity property to the desired maximum number of values that will be kept in the _private.values array (in each data group). commit 08d4a80686f7fe363551c35589a460839f60853e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 03:27:18 2021 +0700 Extract values size computation into compute_drawn_values_num() This is the first step of making add_values() not concern itself (directly) with widget dimensions. The user can monkey-patch the method to override how many values are (logically) drawn, because it affects graph auto-scaling and can be useful for rendering of step_shape-s that paint outside of their step_width. I didn't make it a full-blown function-valued property, because it seems a fringe functionality, which I'm too lazy to document. commit 3c5617c98ab1147618ed22e8414a77fc56b45f6e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 02:32:29 2021 +0700 Copy graph widget prototype methods over in bulk commit 169538837ece41a03d02ba8fbc0e4097062b67a3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun Apr 25 05:58:01 2021 +0700 Remove graph.baseline_y property After giving it some thought I've concluded, that it is unlikely to be useful as it is now. I'd better reserve the name for some more sensible functionality, like fixing the baseline_value defined axis at the given widget coordinate, adjusting the scaling accordingly, so min_value/max_value won't generally correspond to widget top and bottom anymore. commit 202ffaa9a0b820c7c471e1121d01b91f8f76cd38 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 12:06:55 2021 +0700 Add baseline_y and baseline_value properties to graph widget Now values in graph have a *baseline*, tunable with baseline_y and baseline_value properties. It is the (for now invisible) horizontal line from which all bars in the graph grow. I call it a "baseline" instead of "axis", because it doesn't actually have to correspond to the 0 value. But it can, and actually does now by default. baseline_y and baseline_value properties determine the vertical placement of the baseline in the widget. baseline_y is the position of the baseline in normalized widget coordinates, e.g. 0 corresponds to widget's top, 1 - to widget's bottom, and 0.5 - to widget's middle. So if e.g. one sets baseline_y = 0, then no matter how the widget is resized and how its values are scaled, bars in it will be drawn starting from the top. baseline_value is the position of the baseline in value coordinates. So if one sets baseline_value = 10, then no matter what happens, bars for values greater than 10 will be drawn as growing up from this line, and bars for values smaller as growing down. Where and if it will be seen in the widget, depends on the scaling. baseline_value = 0 is the familiar zero axis. baseline_y is unset by default and has priority, i.e. if it's set, then baseline_value is ignored. baseline_value defaults to 0. Prior to this commit, bars were always drawn from the bottom edge of the widget, or, in the new terms, the widget had baseline_y = 1. But it was indistinguishable from baseline_value = 0, because the widget clamped all negative values to 0, so the difference between the two could never be observed. baseline_value = 0 is clearly more useful and familiar to people, so now it is in force by default. commit df7e19abaebf0a871b5ca217b4cc3365533267a6 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 11:18:12 2021 +0700 Move value clamping from graph:add_value() into draw() Previously all values were forcibly clamped between 0 (not tunable at all) and max_value (1 by default). Thus graphs with negative values were not an option. NaN values were conflated with zeros, even though it may be useful to maintain the distinction and render them differently. And if user happened to dynamically change the max_value property, they discovered, that their data got truncated. Preserving user data as it was given is more flexible and makes things easier to debug. This commit does exactly that. So what happens now to the newly allowed values during draw()? There are 3 new cases to consider: 1) NaNs 2) Negatives 3) Values outside of min_value..max_value range NaNs are presently simply not drawn. An empty place is reserved where the value should've been, but nothing else is done. This is distinct from how a zero is handled, e.g. if step_shape is set, it is called with the height parameter = 0, which can still end up drawing something, depending on the shape. One could also consider addition of nan_color/nan_shape properties which could be drawn to alert user to a gap in their data. Negative values necessitate introduction of the concept of the zero axis, from which they will be drawn DOWN, in contrast to positive values. Where should this axis be drawn? How should step_shape-s with inherent direction, e.g. arrows, be drawn for negatives? This will be addressed in the next commit. Values outside of min/max_value range can imply shapes that go beyond widget dimensions. An arrow shape that goes beyond the roof is visibly different from an arrow that saturates and merely touches the widget top, and the user may reasonably desire either behavior. Therefore this commit introduces a boolean clamp_bars property, which defaults to true for reasons of robustness and backward compatibility with the behaviour prior to this commit. The property of course affects only the display of the values, the actual user data stays intact. commit c9c24fb1ab741cd6bb8074971d568e0646c51532 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:29:01 2021 +0700 Use rectangles for drawing graph bars of all sizes I have benchmarked various bar drawing strategies and graph sizes and found out that drawing graph bars with rectangles is as fast or even marginally faster than using fat lines. Thus the code that tries to special-case 1px bars as lines seems to be not worth the additional complexity it introduces, and this commit removes it. Incidentally, I've also established, that rectangles are more robust. If one draws graph bars and feeds cairo very large coordinates (~2^62), then corresponding rectangles fail to render, but all the other rectangles in the same series get rendered successfully, whereas a single large coordinate in a move_to/line_to() sequence is enough to cause all lines in the batch to fail to render. commit bc7f4be5e06405b5aa7fb443a5ace2b9b9575b1f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:01:42 2021 +0700 Cache cairo methods used in the inner loop of graph:draw() I have benchmarked the draw() method, both on cairo xcb and on argb32 image surfaces, and have discovered that cairo context method calls are unnaturally slow, to the point, that almost nothing else in the function matters. For example, drawing bars with vertical lines made the whole draw() function almost 2 times slower than doing the same with rectangles, merely because the former requires two method calls (move_to/line_to), and the latter only one. Simply caching hotpath cairo methods in local variables, reduced the difference between the drawing methods to a negligible one and made the whole function 4 times faster on a typical 100-bar graph, (1.4ms to 0.33ms on my laptop) thus bringing closer my secret dream of drawing live audio data with awesome graphs (kidding). I guess, LGI does something not very optimal there, like gobject-introspecting the method every time, or something. There might be some untapped optimization potential, possibly in upstream, like a method cache, from which everything in awesome could benefit, even though I imagine not many things are so call-intensive as the graph widget, to really feel the difference. commit 538df25bdf02b53d0bbe81809eef2e6fdb05ff6f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 07:32:37 2021 +0700 Use 1-based index in the inner graph draw loop It's used there only once for a calculation and then several times to index some tables, and index 0 is bad for lua tables performance-wise. commit 7412546916cd0a0a490130a120255ffb719398b8 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:55:12 2021 +0700 Leave graph.max_value uninitialized at construction This is a user-visible change, but the previous behavior was probably not what user expected anyway, namely with graph.scale set the graph mysteriously couldn't zoom in onto values that are smaller than 1. commit eb81d7929a161e79c13262cfa73ac2e6a820e8ed Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:48:29 2021 +0700 Allow graph.max_value be nil This makes its behave symmetrically to min_value. Now it defaults to 1, when graph.scale is not set, and to the maximum value in the graph data, when graph.scale is set. commit f622a8430cbfde7007b58d7f4c9c76b08c91e997 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 12:01:13 2021 +0700 Calculate max values num in graph:add_value() more precisely At most so many values were kept in the widget values array, as there are pixels of usable widget width (i.e. sans border). This is exactly right for the surely most common case of graphs with 1px-wide bars, but is more than needed for graphs with step_width+step_spacing>1, and not enough for graphs with subpixel bars. Moreover this made graph autoscaling behave weirdly in graphs with thicker bars, because old values survived longer in the array and kept affecting min_value/max_value calculations even after they were shifted off-screen. The maximum number of kept values is now calculated exactly as the number of values that can be displayed at once, taking step_width and step_spacing into account. It might be worth adding a max_values_num property to the graph to let user override this calculation, in case they draw shapes in step_shape() outside of its supposed rectangle, so our assumptions of visibility are wrong, or the widget often gets resized, resulting in the annoyance of visibly truncated series. commit 0aa63249a18641251a1bf77423739164f0eab39f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 02:30:13 2021 +0700 Fix border_color/border_width bugs in graph widget add_value() wrongly assumed that border_width is always set, when border_color is, so if user set only the latter, an error occurred. add_value() assumed that border_width is always < width which led to awesome hanging up in an infinite loop, should that fail to be the case. The graph border_width property was driven by the border_color property, instead of the other way round. If border_color wasn't set, the border wasn't drawn all, even if user sets non-zero border_width, which is a confusing behavior, in contrast to all other widgets, which simply use a plethora of fallback colors to draw the border, when needed. This also meant that, despite being documented, the beautiful.graph_border_color fallback color could not and was not ever used. If the border_color was set though, an unset border_width was immediately assumed to be 1 instead of 0, also contrary to what other widgets do. Theme colors shouldn't influence widget layout, so this commit fixes this behavior to: 1) border_width always defaults to 0, if unset. 2) border_color falls back to beautiful.graph_border_color or white. Maybe one should also add a beautiful.graph_border_width theme variable, like some widgets do. commit dad4a3253b8fef971fb82720d2a407fcc0a879ad Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 03:11:27 2021 +0700 Implement multiple data series drawing for non-stacked graph The widget can already hold and draw multiple data groups, so it makes little sense to limit drawing only to the first group when graph.stack = false, probably leading to user's confusion. Now user can call add_value(v, group) with group > 1 and the data series will be drawn (over groups with lesser id-s) with graph.stack_colors[group] color, if stack_colors is set, or with graph.color, if stack_colors isn't set. Thus nothing visibly changes for all current users of non-stacked graphs with only single data series, but the new functionality is now there for those who need it. One can't fully emulate this functionality, e.g. with multiple graphs widgets in a wibox.layout.stack, because data series in a single widget can enjoy a common auto-scaling. commit 3e645c976184ab5f50d625271c25062db9ab7ed9 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:59:12 2021 +0700 Don't draw a stacked graph data group if there's no color for it If stack_colors[idx] is nil for the group `idx`, don't draw it at all (as opposed, e.g. to the effect of drawing it with a transparent color, or as though it had all values == 0, which both produce different results). This code is still subtly different from prior behavior, because now user can have a non-contiguous stack_colors table and turn the display of any data group on and off at will, whereas the code two commits earlier would just stop rendering as soon as ipairs(stack_colors) does, i.e. no data series were drawn past the first nil hole. This commit only looks large, but is mostly a whitespace change due to nesting from a pair of wrapping ifs. commit 8c0273786de1ade66bdd977699f98b588da9f487 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:32:42 2021 +0700 Unify stacked and non-stacked graph rendering Now stacked graphs support everything the nonstacked do, like step_width ~= 1 and custom step_shape-s. Technical notes: This code no longer calls cr:stroke() for every single value in a stacked graph, but rather paints a whole series and then cr:stroke/fill()-s it at all once with its color, improving performance considerably. Additionally the graph no longer relies on the presence of the stack_colors property to render series. It draws all data that it has, and, if there's no corresponding color in stack_colors, falls back to graph.color. This will be changed in the next commit to the prior behavior of only drawing those series for which there's a color in the stack_colors table, but that would've made this commit's diff much larger due to required nesting ifs over large code chunks. This commit also takes care to properly handle NaNs and negative values. Those are currently (almost) guaranteed to not occur in the values array due to clipping in graph:add_value(), but I intend to eventually drop the clipping, because it does nothing that draw() really relies upon, and allowing unclipped and NaN values is more flexible and less surprising, e.g. when user dynamically toggles graph.scale. Eventually I intend to implement drawing negative values in non-stacked graphs and possibly even in the stacked ones. commit 38c6589c51b2aa057d7c2f40182ecf32b5b2ff5e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 10:04:15 2021 +0700 Convert graph:draw() to use `self` commit f21f1b2550426df1c4ea8abde079b1ccef84b394 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 12:27:26 2021 +0700 Fix scaling for stacked graphs Stacked graphs have cumulative heights, values from all groups that end up in the same graph bar are effectively summed. Previous code didn't take that into account when calculating min/max_value for the purposes of rescaling, but simply examined every value separately. This code adds up all values up front and finds min/max among those, to determine proper scaling that would fit the graph exactly. commit 1ce4c7bf63bdd6c99c195d668347bec49355e921 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 09:49:35 2021 +0700 Draw shaped graph bars only up to the baseline Prior to this commit, every step_shape bar was, for some reason, drawn with height = full widget's height, regardless of the value it represents. So for the most bars their lower end was far outside the box and therefore clipped off. So e.g. one couldn't see the lower round edges of rounded rectangles. This commit fixes that by appropriately varying the height that is passed into step_shape(), so that lower edges of the bars always exactly touch the lower edge of the graph. If the user wants to restore the prior behavior, they can simply ignore the passed-in height in step_shape() and just always draw their shape with height = full widget height. But they couldn't replicate the effect of this commit in their rc.lua without essentially writing their own graph widget. commit e25185b08da23745b5815f85f6e9e43222ed2294 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 08:42:54 2021 +0700 Avoid incremental transform shifting during graph bar rendering Instead of letting addition errors and cognitive load accumulate simply use the same offset calculation that is used for lines and reset the transform back to the top-left corner after every drawn bar. It's conceptually simpler, and will be useful later for stacked graphs and more. It also fixes a discrepancy between plain and step_shape rendering. Prior to this commit, bar coloring occurred with a disturbed transform, which was centered at the last drawn bar, wherever it happened to be. This meant that nontrivial colors, like gradients and images, were arbitrarily offset when step_shape was used. commit f3e98866d8583c35d0b557c09f529299edc9ba2c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:45:29 2021 +0700 Refactor out graph bar height expression into a variable commit f54528d6892f5aaa4595917853c9e2130f743fdb Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:20:25 2021 +0700 Draw graph bars with lines only when step_width == 1 Before this commit graph bars of step_width < 1 were drawn with lines of width 1, but that's a bit unfortunate, because it led to much overpainting, while still ending up with a graphically incorrect result. Let's draw such bars with rectangles and let cairo do things with subpixels, getting more graphically accurate result, while still keeping the common case of step_width == 1 (supposedly) fast-drawn with lines. commit b6515dca67c18b78eb5657378131a62ab43d4cfc Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:54:07 2021 +0700 Fix unsharp edges when drawing graph bars with rectangles The 0.5 offset is only helpful for 1-width strokes, not for fills. commit 7f3db8f07e82f821bcd1dc418d66564f537609d6 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:21:01 2021 +0700 Do coordinate transformations even if the value isn't drawn The graph presently doesn't draw values < 0, but there was a discrepancy between drawing with and without step_shape. The default line/rectangle drawing reserves an empty place where the skipped value should've been, but no such empty place is reserved, when step_shape is used. This commit fixes that. Skipped values always have their empty space now. commit 7b731ebfe96af7a9dba9c57371da6761cf22c00c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:03:30 2021 +0700 Fix graph bar scaling when min_value is not 0 Previous calculation lead to underscaling of values so that even value = max_value wasn't drawn to full height. commit dca59fd8b7fff0e7e646f2dfc6bd92047028e5a8 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:18:40 2021 +0700 Calculate the horizontal graph bar offset only when needed commit 940ddf4b9825e9c3a91d276208c089d8c74e49db Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:17:23 2021 +0700 Call cr:move_to() only when drawing the graph with lines It's redundant otherwise and might even interfere with user's drawing in step_shape(). commit b5d5cd10ca29fb29cd751faaa95137c2fe89ff63 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:04:06 2021 +0700 Fix wrong horizontal offset calculations in widget.graph Indices in the loop are zero-based, so things like i-1 and i>1 aren't necessary and actually wouldn't have been correct even in a 1-based loop. step_shape = gears.shape.rectangle and step_shape = nil, which is supposed to default to rectangle according to docs, were drawn differently due to mismatched offset calculations, which led to problems especially noticeable when step_spacing is not zero, e.g: 1) whole graph shifted left too much, drawn partially off-screen. 2) whole graph shifted right too much, and the first bar placed closer to the rest than others. commit dc369fa198281e000afc1bd17326f3a264adf7d1 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 02:09:21 2021 +0700 Fix the "border_width affects graph bar width" bug Graph bars were drawn with lines of border_width thickness instead of 1, i.e. possibly too fat to be readable. commit 1ba73378cf29ed27a6ecd18716394779ceb543d9 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 00:13:03 2021 +0700 Simplify internal values representation in graph even more New values in graph._private.values were pushed to the end, and old values were removed from the beginning of the table. In draw() however newer values are drawn first, at the left edge of the graph, necessitating annoying idx = #values - idx conversions everywhere. In particular in stacked graph that means that values that are drawn together may have different indices. One has to shift the values array in add_value() either way, so one might as well insert new values at the beginning of the array and simplify the rest of the code. This doesn't seem like much yet, but it will prove to be much more convenient and less error-prone in what I'm going to do later. commit f70523ee68dbd6b67050ace4b82903a9b22440c7 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 13:35:10 2021 +0700 Simplify internal values representation in graph widget graph._private.values was either a table of values or a table of tables of values, depending on whether "stack" property is enabled, ... or a mix of both if user forgot to pass the second "group" parameter to graph:add_value(). This resulted in pretty fragile and confusing behavior, e.g. leaving user to wonder, why they see nothing more after setting stack=true for a graph in a previously working rc.lua, and demanding a very careful use of add_value() from the user, or else some values or even entire stacks would mysteriously vanish and errors in draw() get triggered, sometimes after a period of seemingly normal functioning. Now `values` is always a table of tables and the group parameter in add_value() defaults to 1, thus making the non-stacked graph a trivial variant of the stacked graph.
2021-05-20 17:14:03 +02:00
-- This property is deprecated. Use a `wibox.container.constraint` widget or
-- `forced_height`.
---
-- @deprecatedproperty height
-- @tparam number height The height to set.
graph: complete widget overhaul and bug fixes Squashed commit of the following: commit 69821f51fe1e8652715658543e50631ece495090 Author: Alex Belykh <albel727@ngs.ru> Date: Mon May 10 19:17:55 2021 +0700 Refactor property handling in wibox.widget.graph In addition, baseline_value, step_width and step_spacing properties are all set to their default non-nil numeric values during instance creation now. commit 842eb429bf5df4b2ba46b2e6f7646afe89b1a3c3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 9 06:18:45 2021 +0700 Make graph:draw_values() private There's no documentation nor confidence that it's a good API, so I'm hiding it for now. commit 720746780574e4bad14a71fc4174955139c2eb50 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 04:04:13 2021 +0700 Document graph_should_draw_data_group() for posterity commit 7c73f3754ce6ba86b118f1170a6f9e406ab4841d Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:59:21 2021 +0700 Make graph:should_draw_data_group() private There's no confidence whether it's a good API, so I'm hiding it for now. commit b3539e20adb6423118af726ae4f8751ef864b5d2 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:36:44 2021 +0700 Make graph:preprocess_values() private There's no confidence whether it's a good API, so I'm hiding it for now. commit 2df4400e780a6260f8d0ab1c60734fbb11bf3c4f Author: Alex Belykh <albel727@ngs.ru> Date: Thu May 6 10:05:23 2021 +0700 Add spec/wibox/widget/graph_spec.lua commit ce01a9771ab439b8df841f1565aa4aa8a44a5e0d Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 08:16:18 2021 +0700 Improve graph.capacity documentation commit e28fade8f655802f5bf89154169f68d256b8e332 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 07:36:01 2021 +0700 Document graph:compute_drawn_values_num() commit dd68332292465422d08b48e9f0229f143beccbc6 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 05:24:23 2021 +0700 Document graph:pick_data_group_color() method commit ca6dc55f77aca88a6e9cc90bb6fd0045739f41f5 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 04:13:58 2021 +0700 Guard against setting data groups sparsely in graph:add_value() commit cdea5f126f7984e72924f6dffbd312c4fe3b032f Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 02:36:12 2021 +0700 Refactor scaling and baseline choice out of graph:draw() commit 10135deafe8a9390ba66ab657ebb1fb41d549e8c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 21:34:48 2021 +0700 Document graph.nan_color and graph.stack properties commit 0d73bb84ac66b87e16aee472bd1f299549f66a1c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 20:49:24 2021 +0700 Use a better example in docs for the graph.stack property commit c27d160a0826d033d49d6141608c40aa8a0642fa Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 16:05:38 2021 +0700 Document graph.baseline_value property commit 459bc8176c13d4ea32af0dcdc2399d3b929b1c6a Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:53:47 2021 +0700 Document graph.clamp_bars property commit e732ee0b3b007d213e689b98cc00ae7cb141ae08 Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:34 2021 +0700 Improve graph:add_value() and :clear() documentation commit ae73e3aa9f8d60409cbe4cb4da9612f31af4f38e Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:11 2021 +0700 Improve graph min_value/max_value/scale documentation commit a7350996a9def773b91b1631e6c0507e33d8f93b Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:27:36 2021 +0700 Cleanup graph examples to show only relevant things in docs commit 30740c26f4f41c16f4c8a3c339bcb7bd299e14cd Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 14:04:36 2021 +0700 Rename graph.stack_colors property to `group_colors` The colors are used for stacked and non-stacked graphs alike, so the name didn't make sense. Also document and clarify some color stuff. commit 1ea01bfd410971cf0d0c25fd74cd764d5eb7279d Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 21:25:41 2021 +0700 Add an example file for code coverage of some fringe graph cases commit eae68e39553f88a6275a0c65bf040b5d4c262b54 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 20:23:32 2021 +0700 Add step_shape to the graph/nan_color example for code coverage commit 8027147bc2eab34a97b49a428fb8cef27f122bed Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:55:18 2021 +0700 Add example files for graph.nan_color/nan_indication properties commit 89a25a165c52b2c15b6c4d95ecb777a9067e66f9 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:33:07 2021 +0700 Make widget graph:add_value(v) default v to NaN 0 as a default makes no sense and silences the error that is failing to specify a value, to which the user should rather be alerted. The graph widget has sane NaN handling now and will do just that. commit 2835d552440be48d356a7f7b1d64963696dd550b Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:20:48 2021 +0700 Implement graph.nan_color and nan_indication properties Now, whenever there's a NaN among values and graph.nan_indication is set, the corresponding area, where the value bar should have been drawn, is filled with the nan_color from top to bottom. The painting is done after all data is rendered, to make sure that it won't be overpainted and go unnoticed. NaNs among graph's values are inevitably a sign of some error and it is arguably not a sane default behavior to simply not render them at all. The user should take immediate note of any errors by default, instead of wondering at a mysteriously empty graph widget, which is why nan_indication is set to true by default. If the user wants to silence NaNs, they can always simply set it to false. nan_color if unset, defaults to a noticeable yellow-black pattern. As a direct and planned consequence, stacked graphs with negative values in them trigger the NaN handling too. But silencing NaN handling for those graphs is also possible and semi-sane, because then NaNs and negatives are ignored and the graph behaves (almost) like they were zeros. commit 9c1f8f08b400e01e19c534f3810c9c9ab485760c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 26 08:10:19 2021 +0700 Refactor out graph_map_value_to_widget_coordinates() function commit 1905991ddecabdae3e15ffb8ee5b18a1bee1a711 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 28 13:39:29 2021 +0700 Add an example file for graph.baseline_value property commit b31b39d66f4c037a9484b71a3f910703ae01fac0 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 07:03:49 2021 +0700 Add an example file for graph.clamp_bars=false/true comparison commit a1fc850e3047f9ee2c9ddeffacfbabf3ded34171 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 04:06:45 2021 +0700 Add an example file for graphs with negative values and shapes commit 2f012f8ff1bb5094c14e724a61e59a2a3a296bc8 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 01:06:05 2021 +0700 Add an example file for a graph displaying negative values commit 7b14fbd59a4c4c51962fd3110a1a900dd5f4eb98 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 11:18:26 2021 +0700 Add an example file for graphs with step_width < 1 commit dfd9a54544b637e2fbe3d1325f9cc24b3f4d2313 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 10:17:38 2021 +0700 Add an example file for graph.stack=false/true comparison Should also demonstrate that multiple data groups are possible for non-stacked graphs too. commit 5b532106c3fd255f9b66a66cfb88d977b489ab5d Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 09:34:14 2021 +0700 Add an example file for disabling graph data groups with nil colors commit ac6f1083c02fcb428f089a616f9b0ebaa5496b6c Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:57:25 2021 +0700 Add an example file for step_shape/width/spacing on stacked graphs commit b3703db6a5139e73e72c1cfd198ece2cd9be7df2 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:43:56 2021 +0700 Add an example file for scale=false/true on stacked graph commit b000aed4a12cb44fb501e1c8432a71a14b2f8f3f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 20 08:22:19 2021 +0700 Document deprecation of graph's width and height commit cb0ad617bd6c83a499896797318e3b0b588c3566 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 07:53:37 2021 +0700 Deprecate width and height properties of wibox.widget.graph commit 8c64e14ad91a074b784166a7054e53444cccb9d2 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 04:37:59 2021 +0700 Refactor graph:draw() to separate concerns Firstly, summation of group values for stacked graphs is extracted into graph:preprocess_values(). This makes graph:draw() smaller and exposes the fact, that stacked graphs are almost entirely just a form of a more general concept: pre-render data manipulation. It's something a user might want to customize, and now they can do it by monkey-patching the method. Secondly, the logic for deciding, if and with which color a data group should be drawn, is extracted into graph:should_draw_data_group() and get_data_group_color() methods respectively. This makes it customizable too and makes more obvious the fact, that it is a general thing that has nothing to do with stacked graphs in particular. With this there are only 5 lines remaining in graph:draw(), that could be said to be specific to stacked graphs, all pertaining to the `prev_y` variable, which merely preserves the bar coordinates from a previous data group. commit 2a32b6305d893f413303728a0a3da1dcd9a5591e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 22:52:37 2021 +0700 Pointlessly optimize graph:add_value() 1) Purge old values first, then insert the new one, if needed. Since Lua grows arrays in power of two steps, and our guess_capacity() also takes care to return even integers, it would be a shame to overstep the capacity by one by inserting first and thus getting the table size doubled only to never use the most of it. 2) Use a removal procedure that doesn't call the slow length operator at all. Previous code called it twice (once in table.remove) for every removed value. commit 680ea8f5d81e9040afb4662b3229e937e4b89e02 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:23:38 2021 +0700 Set graph data series color before the painting loop commit b970f0f221ae76250f2a94433ff79d653caa6e21 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:05:37 2021 +0700 Drop a large no longer necessary `if` in graph:draw_values() This commit is almost purely a whitespace change. commit 6b4f0541c70ec99d7f6e276bae64939f337120d6 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:00:50 2021 +0700 Extract values rendering code into graph:draw_values() method This is to avoid ungodly if nesting levels, which are needed to bail out of drawing values early and skip to drawing the border. The next commit will get rid of the if. commit e0950e4f99d1ea1c4b8cea2cc67629fa40030c2a Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 14:50:20 2021 +0700 Track graph draw() usage to guess necessary array capacity The last step of divorcing add_values() from width calculations, while ensuring that user doesn't observe a lack of data to draw. The widget now tracks an approximate number of values that it has been asked to draw() in _private.last_drawn_values_num, and truncates its values array based on that (if not overridden by the capacity property). commit 2e3d542282f35e7c0fed454da0f2eada0fcf3b78 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 13:35:12 2021 +0700 Add `capacity` property to graph widget This is the second step of divorcing add_values() from calculations involving widths. How many values are stored and how many values are drawn are distinct concepts. There isn't even a single definitive "how many values are drawn" because the widget could be rendered in several places with distinct widths simultaneously, and in every such place the data must be drawn appropriately, e.g. with auto-scaling, that takes only the visible data points into account. And the draw() method can do that now. The default capacity value of nil uses heuristics based on widget's _private.width to guess some capacity that would tolerate some dynamic widget resizing. If the user finds the heuristic failing them, they can set the capacity property to the desired maximum number of values that will be kept in the _private.values array (in each data group). commit 08d4a80686f7fe363551c35589a460839f60853e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 03:27:18 2021 +0700 Extract values size computation into compute_drawn_values_num() This is the first step of making add_values() not concern itself (directly) with widget dimensions. The user can monkey-patch the method to override how many values are (logically) drawn, because it affects graph auto-scaling and can be useful for rendering of step_shape-s that paint outside of their step_width. I didn't make it a full-blown function-valued property, because it seems a fringe functionality, which I'm too lazy to document. commit 3c5617c98ab1147618ed22e8414a77fc56b45f6e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 02:32:29 2021 +0700 Copy graph widget prototype methods over in bulk commit 169538837ece41a03d02ba8fbc0e4097062b67a3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun Apr 25 05:58:01 2021 +0700 Remove graph.baseline_y property After giving it some thought I've concluded, that it is unlikely to be useful as it is now. I'd better reserve the name for some more sensible functionality, like fixing the baseline_value defined axis at the given widget coordinate, adjusting the scaling accordingly, so min_value/max_value won't generally correspond to widget top and bottom anymore. commit 202ffaa9a0b820c7c471e1121d01b91f8f76cd38 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 12:06:55 2021 +0700 Add baseline_y and baseline_value properties to graph widget Now values in graph have a *baseline*, tunable with baseline_y and baseline_value properties. It is the (for now invisible) horizontal line from which all bars in the graph grow. I call it a "baseline" instead of "axis", because it doesn't actually have to correspond to the 0 value. But it can, and actually does now by default. baseline_y and baseline_value properties determine the vertical placement of the baseline in the widget. baseline_y is the position of the baseline in normalized widget coordinates, e.g. 0 corresponds to widget's top, 1 - to widget's bottom, and 0.5 - to widget's middle. So if e.g. one sets baseline_y = 0, then no matter how the widget is resized and how its values are scaled, bars in it will be drawn starting from the top. baseline_value is the position of the baseline in value coordinates. So if one sets baseline_value = 10, then no matter what happens, bars for values greater than 10 will be drawn as growing up from this line, and bars for values smaller as growing down. Where and if it will be seen in the widget, depends on the scaling. baseline_value = 0 is the familiar zero axis. baseline_y is unset by default and has priority, i.e. if it's set, then baseline_value is ignored. baseline_value defaults to 0. Prior to this commit, bars were always drawn from the bottom edge of the widget, or, in the new terms, the widget had baseline_y = 1. But it was indistinguishable from baseline_value = 0, because the widget clamped all negative values to 0, so the difference between the two could never be observed. baseline_value = 0 is clearly more useful and familiar to people, so now it is in force by default. commit df7e19abaebf0a871b5ca217b4cc3365533267a6 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 11:18:12 2021 +0700 Move value clamping from graph:add_value() into draw() Previously all values were forcibly clamped between 0 (not tunable at all) and max_value (1 by default). Thus graphs with negative values were not an option. NaN values were conflated with zeros, even though it may be useful to maintain the distinction and render them differently. And if user happened to dynamically change the max_value property, they discovered, that their data got truncated. Preserving user data as it was given is more flexible and makes things easier to debug. This commit does exactly that. So what happens now to the newly allowed values during draw()? There are 3 new cases to consider: 1) NaNs 2) Negatives 3) Values outside of min_value..max_value range NaNs are presently simply not drawn. An empty place is reserved where the value should've been, but nothing else is done. This is distinct from how a zero is handled, e.g. if step_shape is set, it is called with the height parameter = 0, which can still end up drawing something, depending on the shape. One could also consider addition of nan_color/nan_shape properties which could be drawn to alert user to a gap in their data. Negative values necessitate introduction of the concept of the zero axis, from which they will be drawn DOWN, in contrast to positive values. Where should this axis be drawn? How should step_shape-s with inherent direction, e.g. arrows, be drawn for negatives? This will be addressed in the next commit. Values outside of min/max_value range can imply shapes that go beyond widget dimensions. An arrow shape that goes beyond the roof is visibly different from an arrow that saturates and merely touches the widget top, and the user may reasonably desire either behavior. Therefore this commit introduces a boolean clamp_bars property, which defaults to true for reasons of robustness and backward compatibility with the behaviour prior to this commit. The property of course affects only the display of the values, the actual user data stays intact. commit c9c24fb1ab741cd6bb8074971d568e0646c51532 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:29:01 2021 +0700 Use rectangles for drawing graph bars of all sizes I have benchmarked various bar drawing strategies and graph sizes and found out that drawing graph bars with rectangles is as fast or even marginally faster than using fat lines. Thus the code that tries to special-case 1px bars as lines seems to be not worth the additional complexity it introduces, and this commit removes it. Incidentally, I've also established, that rectangles are more robust. If one draws graph bars and feeds cairo very large coordinates (~2^62), then corresponding rectangles fail to render, but all the other rectangles in the same series get rendered successfully, whereas a single large coordinate in a move_to/line_to() sequence is enough to cause all lines in the batch to fail to render. commit bc7f4be5e06405b5aa7fb443a5ace2b9b9575b1f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:01:42 2021 +0700 Cache cairo methods used in the inner loop of graph:draw() I have benchmarked the draw() method, both on cairo xcb and on argb32 image surfaces, and have discovered that cairo context method calls are unnaturally slow, to the point, that almost nothing else in the function matters. For example, drawing bars with vertical lines made the whole draw() function almost 2 times slower than doing the same with rectangles, merely because the former requires two method calls (move_to/line_to), and the latter only one. Simply caching hotpath cairo methods in local variables, reduced the difference between the drawing methods to a negligible one and made the whole function 4 times faster on a typical 100-bar graph, (1.4ms to 0.33ms on my laptop) thus bringing closer my secret dream of drawing live audio data with awesome graphs (kidding). I guess, LGI does something not very optimal there, like gobject-introspecting the method every time, or something. There might be some untapped optimization potential, possibly in upstream, like a method cache, from which everything in awesome could benefit, even though I imagine not many things are so call-intensive as the graph widget, to really feel the difference. commit 538df25bdf02b53d0bbe81809eef2e6fdb05ff6f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 07:32:37 2021 +0700 Use 1-based index in the inner graph draw loop It's used there only once for a calculation and then several times to index some tables, and index 0 is bad for lua tables performance-wise. commit 7412546916cd0a0a490130a120255ffb719398b8 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:55:12 2021 +0700 Leave graph.max_value uninitialized at construction This is a user-visible change, but the previous behavior was probably not what user expected anyway, namely with graph.scale set the graph mysteriously couldn't zoom in onto values that are smaller than 1. commit eb81d7929a161e79c13262cfa73ac2e6a820e8ed Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:48:29 2021 +0700 Allow graph.max_value be nil This makes its behave symmetrically to min_value. Now it defaults to 1, when graph.scale is not set, and to the maximum value in the graph data, when graph.scale is set. commit f622a8430cbfde7007b58d7f4c9c76b08c91e997 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 12:01:13 2021 +0700 Calculate max values num in graph:add_value() more precisely At most so many values were kept in the widget values array, as there are pixels of usable widget width (i.e. sans border). This is exactly right for the surely most common case of graphs with 1px-wide bars, but is more than needed for graphs with step_width+step_spacing>1, and not enough for graphs with subpixel bars. Moreover this made graph autoscaling behave weirdly in graphs with thicker bars, because old values survived longer in the array and kept affecting min_value/max_value calculations even after they were shifted off-screen. The maximum number of kept values is now calculated exactly as the number of values that can be displayed at once, taking step_width and step_spacing into account. It might be worth adding a max_values_num property to the graph to let user override this calculation, in case they draw shapes in step_shape() outside of its supposed rectangle, so our assumptions of visibility are wrong, or the widget often gets resized, resulting in the annoyance of visibly truncated series. commit 0aa63249a18641251a1bf77423739164f0eab39f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 02:30:13 2021 +0700 Fix border_color/border_width bugs in graph widget add_value() wrongly assumed that border_width is always set, when border_color is, so if user set only the latter, an error occurred. add_value() assumed that border_width is always < width which led to awesome hanging up in an infinite loop, should that fail to be the case. The graph border_width property was driven by the border_color property, instead of the other way round. If border_color wasn't set, the border wasn't drawn all, even if user sets non-zero border_width, which is a confusing behavior, in contrast to all other widgets, which simply use a plethora of fallback colors to draw the border, when needed. This also meant that, despite being documented, the beautiful.graph_border_color fallback color could not and was not ever used. If the border_color was set though, an unset border_width was immediately assumed to be 1 instead of 0, also contrary to what other widgets do. Theme colors shouldn't influence widget layout, so this commit fixes this behavior to: 1) border_width always defaults to 0, if unset. 2) border_color falls back to beautiful.graph_border_color or white. Maybe one should also add a beautiful.graph_border_width theme variable, like some widgets do. commit dad4a3253b8fef971fb82720d2a407fcc0a879ad Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 03:11:27 2021 +0700 Implement multiple data series drawing for non-stacked graph The widget can already hold and draw multiple data groups, so it makes little sense to limit drawing only to the first group when graph.stack = false, probably leading to user's confusion. Now user can call add_value(v, group) with group > 1 and the data series will be drawn (over groups with lesser id-s) with graph.stack_colors[group] color, if stack_colors is set, or with graph.color, if stack_colors isn't set. Thus nothing visibly changes for all current users of non-stacked graphs with only single data series, but the new functionality is now there for those who need it. One can't fully emulate this functionality, e.g. with multiple graphs widgets in a wibox.layout.stack, because data series in a single widget can enjoy a common auto-scaling. commit 3e645c976184ab5f50d625271c25062db9ab7ed9 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:59:12 2021 +0700 Don't draw a stacked graph data group if there's no color for it If stack_colors[idx] is nil for the group `idx`, don't draw it at all (as opposed, e.g. to the effect of drawing it with a transparent color, or as though it had all values == 0, which both produce different results). This code is still subtly different from prior behavior, because now user can have a non-contiguous stack_colors table and turn the display of any data group on and off at will, whereas the code two commits earlier would just stop rendering as soon as ipairs(stack_colors) does, i.e. no data series were drawn past the first nil hole. This commit only looks large, but is mostly a whitespace change due to nesting from a pair of wrapping ifs. commit 8c0273786de1ade66bdd977699f98b588da9f487 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:32:42 2021 +0700 Unify stacked and non-stacked graph rendering Now stacked graphs support everything the nonstacked do, like step_width ~= 1 and custom step_shape-s. Technical notes: This code no longer calls cr:stroke() for every single value in a stacked graph, but rather paints a whole series and then cr:stroke/fill()-s it at all once with its color, improving performance considerably. Additionally the graph no longer relies on the presence of the stack_colors property to render series. It draws all data that it has, and, if there's no corresponding color in stack_colors, falls back to graph.color. This will be changed in the next commit to the prior behavior of only drawing those series for which there's a color in the stack_colors table, but that would've made this commit's diff much larger due to required nesting ifs over large code chunks. This commit also takes care to properly handle NaNs and negative values. Those are currently (almost) guaranteed to not occur in the values array due to clipping in graph:add_value(), but I intend to eventually drop the clipping, because it does nothing that draw() really relies upon, and allowing unclipped and NaN values is more flexible and less surprising, e.g. when user dynamically toggles graph.scale. Eventually I intend to implement drawing negative values in non-stacked graphs and possibly even in the stacked ones. commit 38c6589c51b2aa057d7c2f40182ecf32b5b2ff5e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 10:04:15 2021 +0700 Convert graph:draw() to use `self` commit f21f1b2550426df1c4ea8abde079b1ccef84b394 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 12:27:26 2021 +0700 Fix scaling for stacked graphs Stacked graphs have cumulative heights, values from all groups that end up in the same graph bar are effectively summed. Previous code didn't take that into account when calculating min/max_value for the purposes of rescaling, but simply examined every value separately. This code adds up all values up front and finds min/max among those, to determine proper scaling that would fit the graph exactly. commit 1ce4c7bf63bdd6c99c195d668347bec49355e921 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 09:49:35 2021 +0700 Draw shaped graph bars only up to the baseline Prior to this commit, every step_shape bar was, for some reason, drawn with height = full widget's height, regardless of the value it represents. So for the most bars their lower end was far outside the box and therefore clipped off. So e.g. one couldn't see the lower round edges of rounded rectangles. This commit fixes that by appropriately varying the height that is passed into step_shape(), so that lower edges of the bars always exactly touch the lower edge of the graph. If the user wants to restore the prior behavior, they can simply ignore the passed-in height in step_shape() and just always draw their shape with height = full widget height. But they couldn't replicate the effect of this commit in their rc.lua without essentially writing their own graph widget. commit e25185b08da23745b5815f85f6e9e43222ed2294 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 08:42:54 2021 +0700 Avoid incremental transform shifting during graph bar rendering Instead of letting addition errors and cognitive load accumulate simply use the same offset calculation that is used for lines and reset the transform back to the top-left corner after every drawn bar. It's conceptually simpler, and will be useful later for stacked graphs and more. It also fixes a discrepancy between plain and step_shape rendering. Prior to this commit, bar coloring occurred with a disturbed transform, which was centered at the last drawn bar, wherever it happened to be. This meant that nontrivial colors, like gradients and images, were arbitrarily offset when step_shape was used. commit f3e98866d8583c35d0b557c09f529299edc9ba2c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:45:29 2021 +0700 Refactor out graph bar height expression into a variable commit f54528d6892f5aaa4595917853c9e2130f743fdb Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:20:25 2021 +0700 Draw graph bars with lines only when step_width == 1 Before this commit graph bars of step_width < 1 were drawn with lines of width 1, but that's a bit unfortunate, because it led to much overpainting, while still ending up with a graphically incorrect result. Let's draw such bars with rectangles and let cairo do things with subpixels, getting more graphically accurate result, while still keeping the common case of step_width == 1 (supposedly) fast-drawn with lines. commit b6515dca67c18b78eb5657378131a62ab43d4cfc Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:54:07 2021 +0700 Fix unsharp edges when drawing graph bars with rectangles The 0.5 offset is only helpful for 1-width strokes, not for fills. commit 7f3db8f07e82f821bcd1dc418d66564f537609d6 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:21:01 2021 +0700 Do coordinate transformations even if the value isn't drawn The graph presently doesn't draw values < 0, but there was a discrepancy between drawing with and without step_shape. The default line/rectangle drawing reserves an empty place where the skipped value should've been, but no such empty place is reserved, when step_shape is used. This commit fixes that. Skipped values always have their empty space now. commit 7b731ebfe96af7a9dba9c57371da6761cf22c00c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:03:30 2021 +0700 Fix graph bar scaling when min_value is not 0 Previous calculation lead to underscaling of values so that even value = max_value wasn't drawn to full height. commit dca59fd8b7fff0e7e646f2dfc6bd92047028e5a8 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:18:40 2021 +0700 Calculate the horizontal graph bar offset only when needed commit 940ddf4b9825e9c3a91d276208c089d8c74e49db Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:17:23 2021 +0700 Call cr:move_to() only when drawing the graph with lines It's redundant otherwise and might even interfere with user's drawing in step_shape(). commit b5d5cd10ca29fb29cd751faaa95137c2fe89ff63 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:04:06 2021 +0700 Fix wrong horizontal offset calculations in widget.graph Indices in the loop are zero-based, so things like i-1 and i>1 aren't necessary and actually wouldn't have been correct even in a 1-based loop. step_shape = gears.shape.rectangle and step_shape = nil, which is supposed to default to rectangle according to docs, were drawn differently due to mismatched offset calculations, which led to problems especially noticeable when step_spacing is not zero, e.g: 1) whole graph shifted left too much, drawn partially off-screen. 2) whole graph shifted right too much, and the first bar placed closer to the rest than others. commit dc369fa198281e000afc1bd17326f3a264adf7d1 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 02:09:21 2021 +0700 Fix the "border_width affects graph bar width" bug Graph bars were drawn with lines of border_width thickness instead of 1, i.e. possibly too fat to be readable. commit 1ba73378cf29ed27a6ecd18716394779ceb543d9 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 00:13:03 2021 +0700 Simplify internal values representation in graph even more New values in graph._private.values were pushed to the end, and old values were removed from the beginning of the table. In draw() however newer values are drawn first, at the left edge of the graph, necessitating annoying idx = #values - idx conversions everywhere. In particular in stacked graph that means that values that are drawn together may have different indices. One has to shift the values array in add_value() either way, so one might as well insert new values at the beginning of the array and simplify the rest of the code. This doesn't seem like much yet, but it will prove to be much more convenient and less error-prone in what I'm going to do later. commit f70523ee68dbd6b67050ace4b82903a9b22440c7 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 13:35:10 2021 +0700 Simplify internal values representation in graph widget graph._private.values was either a table of values or a table of tables of values, depending on whether "stack" property is enabled, ... or a mix of both if user forgot to pass the second "group" parameter to graph:add_value(). This resulted in pretty fragile and confusing behavior, e.g. leaving user to wonder, why they see nothing more after setting stack=true for a graph in a previously working rc.lua, and demanding a very careful use of add_value() from the user, or else some values or even entire stacks would mysteriously vanish and errors in draw() get triggered, sometimes after a period of seemingly normal functioning. Now `values` is always a table of tables and the group parameter in add_value() defaults to 1, thus making the non-stacked graph a trivial variant of the stacked graph.
2021-05-20 17:14:03 +02:00
-- @renamedin 5.0 forced_height
-- @propemits true false
2016-05-23 07:30:44 +02:00
function graph:set_height(height)
graph: complete widget overhaul and bug fixes Squashed commit of the following: commit 69821f51fe1e8652715658543e50631ece495090 Author: Alex Belykh <albel727@ngs.ru> Date: Mon May 10 19:17:55 2021 +0700 Refactor property handling in wibox.widget.graph In addition, baseline_value, step_width and step_spacing properties are all set to their default non-nil numeric values during instance creation now. commit 842eb429bf5df4b2ba46b2e6f7646afe89b1a3c3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 9 06:18:45 2021 +0700 Make graph:draw_values() private There's no documentation nor confidence that it's a good API, so I'm hiding it for now. commit 720746780574e4bad14a71fc4174955139c2eb50 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 04:04:13 2021 +0700 Document graph_should_draw_data_group() for posterity commit 7c73f3754ce6ba86b118f1170a6f9e406ab4841d Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:59:21 2021 +0700 Make graph:should_draw_data_group() private There's no confidence whether it's a good API, so I'm hiding it for now. commit b3539e20adb6423118af726ae4f8751ef864b5d2 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:36:44 2021 +0700 Make graph:preprocess_values() private There's no confidence whether it's a good API, so I'm hiding it for now. commit 2df4400e780a6260f8d0ab1c60734fbb11bf3c4f Author: Alex Belykh <albel727@ngs.ru> Date: Thu May 6 10:05:23 2021 +0700 Add spec/wibox/widget/graph_spec.lua commit ce01a9771ab439b8df841f1565aa4aa8a44a5e0d Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 08:16:18 2021 +0700 Improve graph.capacity documentation commit e28fade8f655802f5bf89154169f68d256b8e332 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 07:36:01 2021 +0700 Document graph:compute_drawn_values_num() commit dd68332292465422d08b48e9f0229f143beccbc6 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 05:24:23 2021 +0700 Document graph:pick_data_group_color() method commit ca6dc55f77aca88a6e9cc90bb6fd0045739f41f5 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 04:13:58 2021 +0700 Guard against setting data groups sparsely in graph:add_value() commit cdea5f126f7984e72924f6dffbd312c4fe3b032f Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 02:36:12 2021 +0700 Refactor scaling and baseline choice out of graph:draw() commit 10135deafe8a9390ba66ab657ebb1fb41d549e8c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 21:34:48 2021 +0700 Document graph.nan_color and graph.stack properties commit 0d73bb84ac66b87e16aee472bd1f299549f66a1c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 20:49:24 2021 +0700 Use a better example in docs for the graph.stack property commit c27d160a0826d033d49d6141608c40aa8a0642fa Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 16:05:38 2021 +0700 Document graph.baseline_value property commit 459bc8176c13d4ea32af0dcdc2399d3b929b1c6a Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:53:47 2021 +0700 Document graph.clamp_bars property commit e732ee0b3b007d213e689b98cc00ae7cb141ae08 Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:34 2021 +0700 Improve graph:add_value() and :clear() documentation commit ae73e3aa9f8d60409cbe4cb4da9612f31af4f38e Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:11 2021 +0700 Improve graph min_value/max_value/scale documentation commit a7350996a9def773b91b1631e6c0507e33d8f93b Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:27:36 2021 +0700 Cleanup graph examples to show only relevant things in docs commit 30740c26f4f41c16f4c8a3c339bcb7bd299e14cd Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 14:04:36 2021 +0700 Rename graph.stack_colors property to `group_colors` The colors are used for stacked and non-stacked graphs alike, so the name didn't make sense. Also document and clarify some color stuff. commit 1ea01bfd410971cf0d0c25fd74cd764d5eb7279d Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 21:25:41 2021 +0700 Add an example file for code coverage of some fringe graph cases commit eae68e39553f88a6275a0c65bf040b5d4c262b54 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 20:23:32 2021 +0700 Add step_shape to the graph/nan_color example for code coverage commit 8027147bc2eab34a97b49a428fb8cef27f122bed Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:55:18 2021 +0700 Add example files for graph.nan_color/nan_indication properties commit 89a25a165c52b2c15b6c4d95ecb777a9067e66f9 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:33:07 2021 +0700 Make widget graph:add_value(v) default v to NaN 0 as a default makes no sense and silences the error that is failing to specify a value, to which the user should rather be alerted. The graph widget has sane NaN handling now and will do just that. commit 2835d552440be48d356a7f7b1d64963696dd550b Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:20:48 2021 +0700 Implement graph.nan_color and nan_indication properties Now, whenever there's a NaN among values and graph.nan_indication is set, the corresponding area, where the value bar should have been drawn, is filled with the nan_color from top to bottom. The painting is done after all data is rendered, to make sure that it won't be overpainted and go unnoticed. NaNs among graph's values are inevitably a sign of some error and it is arguably not a sane default behavior to simply not render them at all. The user should take immediate note of any errors by default, instead of wondering at a mysteriously empty graph widget, which is why nan_indication is set to true by default. If the user wants to silence NaNs, they can always simply set it to false. nan_color if unset, defaults to a noticeable yellow-black pattern. As a direct and planned consequence, stacked graphs with negative values in them trigger the NaN handling too. But silencing NaN handling for those graphs is also possible and semi-sane, because then NaNs and negatives are ignored and the graph behaves (almost) like they were zeros. commit 9c1f8f08b400e01e19c534f3810c9c9ab485760c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 26 08:10:19 2021 +0700 Refactor out graph_map_value_to_widget_coordinates() function commit 1905991ddecabdae3e15ffb8ee5b18a1bee1a711 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 28 13:39:29 2021 +0700 Add an example file for graph.baseline_value property commit b31b39d66f4c037a9484b71a3f910703ae01fac0 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 07:03:49 2021 +0700 Add an example file for graph.clamp_bars=false/true comparison commit a1fc850e3047f9ee2c9ddeffacfbabf3ded34171 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 04:06:45 2021 +0700 Add an example file for graphs with negative values and shapes commit 2f012f8ff1bb5094c14e724a61e59a2a3a296bc8 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 01:06:05 2021 +0700 Add an example file for a graph displaying negative values commit 7b14fbd59a4c4c51962fd3110a1a900dd5f4eb98 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 11:18:26 2021 +0700 Add an example file for graphs with step_width < 1 commit dfd9a54544b637e2fbe3d1325f9cc24b3f4d2313 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 10:17:38 2021 +0700 Add an example file for graph.stack=false/true comparison Should also demonstrate that multiple data groups are possible for non-stacked graphs too. commit 5b532106c3fd255f9b66a66cfb88d977b489ab5d Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 09:34:14 2021 +0700 Add an example file for disabling graph data groups with nil colors commit ac6f1083c02fcb428f089a616f9b0ebaa5496b6c Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:57:25 2021 +0700 Add an example file for step_shape/width/spacing on stacked graphs commit b3703db6a5139e73e72c1cfd198ece2cd9be7df2 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:43:56 2021 +0700 Add an example file for scale=false/true on stacked graph commit b000aed4a12cb44fb501e1c8432a71a14b2f8f3f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 20 08:22:19 2021 +0700 Document deprecation of graph's width and height commit cb0ad617bd6c83a499896797318e3b0b588c3566 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 07:53:37 2021 +0700 Deprecate width and height properties of wibox.widget.graph commit 8c64e14ad91a074b784166a7054e53444cccb9d2 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 04:37:59 2021 +0700 Refactor graph:draw() to separate concerns Firstly, summation of group values for stacked graphs is extracted into graph:preprocess_values(). This makes graph:draw() smaller and exposes the fact, that stacked graphs are almost entirely just a form of a more general concept: pre-render data manipulation. It's something a user might want to customize, and now they can do it by monkey-patching the method. Secondly, the logic for deciding, if and with which color a data group should be drawn, is extracted into graph:should_draw_data_group() and get_data_group_color() methods respectively. This makes it customizable too and makes more obvious the fact, that it is a general thing that has nothing to do with stacked graphs in particular. With this there are only 5 lines remaining in graph:draw(), that could be said to be specific to stacked graphs, all pertaining to the `prev_y` variable, which merely preserves the bar coordinates from a previous data group. commit 2a32b6305d893f413303728a0a3da1dcd9a5591e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 22:52:37 2021 +0700 Pointlessly optimize graph:add_value() 1) Purge old values first, then insert the new one, if needed. Since Lua grows arrays in power of two steps, and our guess_capacity() also takes care to return even integers, it would be a shame to overstep the capacity by one by inserting first and thus getting the table size doubled only to never use the most of it. 2) Use a removal procedure that doesn't call the slow length operator at all. Previous code called it twice (once in table.remove) for every removed value. commit 680ea8f5d81e9040afb4662b3229e937e4b89e02 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:23:38 2021 +0700 Set graph data series color before the painting loop commit b970f0f221ae76250f2a94433ff79d653caa6e21 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:05:37 2021 +0700 Drop a large no longer necessary `if` in graph:draw_values() This commit is almost purely a whitespace change. commit 6b4f0541c70ec99d7f6e276bae64939f337120d6 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:00:50 2021 +0700 Extract values rendering code into graph:draw_values() method This is to avoid ungodly if nesting levels, which are needed to bail out of drawing values early and skip to drawing the border. The next commit will get rid of the if. commit e0950e4f99d1ea1c4b8cea2cc67629fa40030c2a Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 14:50:20 2021 +0700 Track graph draw() usage to guess necessary array capacity The last step of divorcing add_values() from width calculations, while ensuring that user doesn't observe a lack of data to draw. The widget now tracks an approximate number of values that it has been asked to draw() in _private.last_drawn_values_num, and truncates its values array based on that (if not overridden by the capacity property). commit 2e3d542282f35e7c0fed454da0f2eada0fcf3b78 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 13:35:12 2021 +0700 Add `capacity` property to graph widget This is the second step of divorcing add_values() from calculations involving widths. How many values are stored and how many values are drawn are distinct concepts. There isn't even a single definitive "how many values are drawn" because the widget could be rendered in several places with distinct widths simultaneously, and in every such place the data must be drawn appropriately, e.g. with auto-scaling, that takes only the visible data points into account. And the draw() method can do that now. The default capacity value of nil uses heuristics based on widget's _private.width to guess some capacity that would tolerate some dynamic widget resizing. If the user finds the heuristic failing them, they can set the capacity property to the desired maximum number of values that will be kept in the _private.values array (in each data group). commit 08d4a80686f7fe363551c35589a460839f60853e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 03:27:18 2021 +0700 Extract values size computation into compute_drawn_values_num() This is the first step of making add_values() not concern itself (directly) with widget dimensions. The user can monkey-patch the method to override how many values are (logically) drawn, because it affects graph auto-scaling and can be useful for rendering of step_shape-s that paint outside of their step_width. I didn't make it a full-blown function-valued property, because it seems a fringe functionality, which I'm too lazy to document. commit 3c5617c98ab1147618ed22e8414a77fc56b45f6e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 02:32:29 2021 +0700 Copy graph widget prototype methods over in bulk commit 169538837ece41a03d02ba8fbc0e4097062b67a3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun Apr 25 05:58:01 2021 +0700 Remove graph.baseline_y property After giving it some thought I've concluded, that it is unlikely to be useful as it is now. I'd better reserve the name for some more sensible functionality, like fixing the baseline_value defined axis at the given widget coordinate, adjusting the scaling accordingly, so min_value/max_value won't generally correspond to widget top and bottom anymore. commit 202ffaa9a0b820c7c471e1121d01b91f8f76cd38 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 12:06:55 2021 +0700 Add baseline_y and baseline_value properties to graph widget Now values in graph have a *baseline*, tunable with baseline_y and baseline_value properties. It is the (for now invisible) horizontal line from which all bars in the graph grow. I call it a "baseline" instead of "axis", because it doesn't actually have to correspond to the 0 value. But it can, and actually does now by default. baseline_y and baseline_value properties determine the vertical placement of the baseline in the widget. baseline_y is the position of the baseline in normalized widget coordinates, e.g. 0 corresponds to widget's top, 1 - to widget's bottom, and 0.5 - to widget's middle. So if e.g. one sets baseline_y = 0, then no matter how the widget is resized and how its values are scaled, bars in it will be drawn starting from the top. baseline_value is the position of the baseline in value coordinates. So if one sets baseline_value = 10, then no matter what happens, bars for values greater than 10 will be drawn as growing up from this line, and bars for values smaller as growing down. Where and if it will be seen in the widget, depends on the scaling. baseline_value = 0 is the familiar zero axis. baseline_y is unset by default and has priority, i.e. if it's set, then baseline_value is ignored. baseline_value defaults to 0. Prior to this commit, bars were always drawn from the bottom edge of the widget, or, in the new terms, the widget had baseline_y = 1. But it was indistinguishable from baseline_value = 0, because the widget clamped all negative values to 0, so the difference between the two could never be observed. baseline_value = 0 is clearly more useful and familiar to people, so now it is in force by default. commit df7e19abaebf0a871b5ca217b4cc3365533267a6 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 11:18:12 2021 +0700 Move value clamping from graph:add_value() into draw() Previously all values were forcibly clamped between 0 (not tunable at all) and max_value (1 by default). Thus graphs with negative values were not an option. NaN values were conflated with zeros, even though it may be useful to maintain the distinction and render them differently. And if user happened to dynamically change the max_value property, they discovered, that their data got truncated. Preserving user data as it was given is more flexible and makes things easier to debug. This commit does exactly that. So what happens now to the newly allowed values during draw()? There are 3 new cases to consider: 1) NaNs 2) Negatives 3) Values outside of min_value..max_value range NaNs are presently simply not drawn. An empty place is reserved where the value should've been, but nothing else is done. This is distinct from how a zero is handled, e.g. if step_shape is set, it is called with the height parameter = 0, which can still end up drawing something, depending on the shape. One could also consider addition of nan_color/nan_shape properties which could be drawn to alert user to a gap in their data. Negative values necessitate introduction of the concept of the zero axis, from which they will be drawn DOWN, in contrast to positive values. Where should this axis be drawn? How should step_shape-s with inherent direction, e.g. arrows, be drawn for negatives? This will be addressed in the next commit. Values outside of min/max_value range can imply shapes that go beyond widget dimensions. An arrow shape that goes beyond the roof is visibly different from an arrow that saturates and merely touches the widget top, and the user may reasonably desire either behavior. Therefore this commit introduces a boolean clamp_bars property, which defaults to true for reasons of robustness and backward compatibility with the behaviour prior to this commit. The property of course affects only the display of the values, the actual user data stays intact. commit c9c24fb1ab741cd6bb8074971d568e0646c51532 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:29:01 2021 +0700 Use rectangles for drawing graph bars of all sizes I have benchmarked various bar drawing strategies and graph sizes and found out that drawing graph bars with rectangles is as fast or even marginally faster than using fat lines. Thus the code that tries to special-case 1px bars as lines seems to be not worth the additional complexity it introduces, and this commit removes it. Incidentally, I've also established, that rectangles are more robust. If one draws graph bars and feeds cairo very large coordinates (~2^62), then corresponding rectangles fail to render, but all the other rectangles in the same series get rendered successfully, whereas a single large coordinate in a move_to/line_to() sequence is enough to cause all lines in the batch to fail to render. commit bc7f4be5e06405b5aa7fb443a5ace2b9b9575b1f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:01:42 2021 +0700 Cache cairo methods used in the inner loop of graph:draw() I have benchmarked the draw() method, both on cairo xcb and on argb32 image surfaces, and have discovered that cairo context method calls are unnaturally slow, to the point, that almost nothing else in the function matters. For example, drawing bars with vertical lines made the whole draw() function almost 2 times slower than doing the same with rectangles, merely because the former requires two method calls (move_to/line_to), and the latter only one. Simply caching hotpath cairo methods in local variables, reduced the difference between the drawing methods to a negligible one and made the whole function 4 times faster on a typical 100-bar graph, (1.4ms to 0.33ms on my laptop) thus bringing closer my secret dream of drawing live audio data with awesome graphs (kidding). I guess, LGI does something not very optimal there, like gobject-introspecting the method every time, or something. There might be some untapped optimization potential, possibly in upstream, like a method cache, from which everything in awesome could benefit, even though I imagine not many things are so call-intensive as the graph widget, to really feel the difference. commit 538df25bdf02b53d0bbe81809eef2e6fdb05ff6f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 07:32:37 2021 +0700 Use 1-based index in the inner graph draw loop It's used there only once for a calculation and then several times to index some tables, and index 0 is bad for lua tables performance-wise. commit 7412546916cd0a0a490130a120255ffb719398b8 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:55:12 2021 +0700 Leave graph.max_value uninitialized at construction This is a user-visible change, but the previous behavior was probably not what user expected anyway, namely with graph.scale set the graph mysteriously couldn't zoom in onto values that are smaller than 1. commit eb81d7929a161e79c13262cfa73ac2e6a820e8ed Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:48:29 2021 +0700 Allow graph.max_value be nil This makes its behave symmetrically to min_value. Now it defaults to 1, when graph.scale is not set, and to the maximum value in the graph data, when graph.scale is set. commit f622a8430cbfde7007b58d7f4c9c76b08c91e997 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 12:01:13 2021 +0700 Calculate max values num in graph:add_value() more precisely At most so many values were kept in the widget values array, as there are pixels of usable widget width (i.e. sans border). This is exactly right for the surely most common case of graphs with 1px-wide bars, but is more than needed for graphs with step_width+step_spacing>1, and not enough for graphs with subpixel bars. Moreover this made graph autoscaling behave weirdly in graphs with thicker bars, because old values survived longer in the array and kept affecting min_value/max_value calculations even after they were shifted off-screen. The maximum number of kept values is now calculated exactly as the number of values that can be displayed at once, taking step_width and step_spacing into account. It might be worth adding a max_values_num property to the graph to let user override this calculation, in case they draw shapes in step_shape() outside of its supposed rectangle, so our assumptions of visibility are wrong, or the widget often gets resized, resulting in the annoyance of visibly truncated series. commit 0aa63249a18641251a1bf77423739164f0eab39f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 02:30:13 2021 +0700 Fix border_color/border_width bugs in graph widget add_value() wrongly assumed that border_width is always set, when border_color is, so if user set only the latter, an error occurred. add_value() assumed that border_width is always < width which led to awesome hanging up in an infinite loop, should that fail to be the case. The graph border_width property was driven by the border_color property, instead of the other way round. If border_color wasn't set, the border wasn't drawn all, even if user sets non-zero border_width, which is a confusing behavior, in contrast to all other widgets, which simply use a plethora of fallback colors to draw the border, when needed. This also meant that, despite being documented, the beautiful.graph_border_color fallback color could not and was not ever used. If the border_color was set though, an unset border_width was immediately assumed to be 1 instead of 0, also contrary to what other widgets do. Theme colors shouldn't influence widget layout, so this commit fixes this behavior to: 1) border_width always defaults to 0, if unset. 2) border_color falls back to beautiful.graph_border_color or white. Maybe one should also add a beautiful.graph_border_width theme variable, like some widgets do. commit dad4a3253b8fef971fb82720d2a407fcc0a879ad Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 03:11:27 2021 +0700 Implement multiple data series drawing for non-stacked graph The widget can already hold and draw multiple data groups, so it makes little sense to limit drawing only to the first group when graph.stack = false, probably leading to user's confusion. Now user can call add_value(v, group) with group > 1 and the data series will be drawn (over groups with lesser id-s) with graph.stack_colors[group] color, if stack_colors is set, or with graph.color, if stack_colors isn't set. Thus nothing visibly changes for all current users of non-stacked graphs with only single data series, but the new functionality is now there for those who need it. One can't fully emulate this functionality, e.g. with multiple graphs widgets in a wibox.layout.stack, because data series in a single widget can enjoy a common auto-scaling. commit 3e645c976184ab5f50d625271c25062db9ab7ed9 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:59:12 2021 +0700 Don't draw a stacked graph data group if there's no color for it If stack_colors[idx] is nil for the group `idx`, don't draw it at all (as opposed, e.g. to the effect of drawing it with a transparent color, or as though it had all values == 0, which both produce different results). This code is still subtly different from prior behavior, because now user can have a non-contiguous stack_colors table and turn the display of any data group on and off at will, whereas the code two commits earlier would just stop rendering as soon as ipairs(stack_colors) does, i.e. no data series were drawn past the first nil hole. This commit only looks large, but is mostly a whitespace change due to nesting from a pair of wrapping ifs. commit 8c0273786de1ade66bdd977699f98b588da9f487 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:32:42 2021 +0700 Unify stacked and non-stacked graph rendering Now stacked graphs support everything the nonstacked do, like step_width ~= 1 and custom step_shape-s. Technical notes: This code no longer calls cr:stroke() for every single value in a stacked graph, but rather paints a whole series and then cr:stroke/fill()-s it at all once with its color, improving performance considerably. Additionally the graph no longer relies on the presence of the stack_colors property to render series. It draws all data that it has, and, if there's no corresponding color in stack_colors, falls back to graph.color. This will be changed in the next commit to the prior behavior of only drawing those series for which there's a color in the stack_colors table, but that would've made this commit's diff much larger due to required nesting ifs over large code chunks. This commit also takes care to properly handle NaNs and negative values. Those are currently (almost) guaranteed to not occur in the values array due to clipping in graph:add_value(), but I intend to eventually drop the clipping, because it does nothing that draw() really relies upon, and allowing unclipped and NaN values is more flexible and less surprising, e.g. when user dynamically toggles graph.scale. Eventually I intend to implement drawing negative values in non-stacked graphs and possibly even in the stacked ones. commit 38c6589c51b2aa057d7c2f40182ecf32b5b2ff5e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 10:04:15 2021 +0700 Convert graph:draw() to use `self` commit f21f1b2550426df1c4ea8abde079b1ccef84b394 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 12:27:26 2021 +0700 Fix scaling for stacked graphs Stacked graphs have cumulative heights, values from all groups that end up in the same graph bar are effectively summed. Previous code didn't take that into account when calculating min/max_value for the purposes of rescaling, but simply examined every value separately. This code adds up all values up front and finds min/max among those, to determine proper scaling that would fit the graph exactly. commit 1ce4c7bf63bdd6c99c195d668347bec49355e921 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 09:49:35 2021 +0700 Draw shaped graph bars only up to the baseline Prior to this commit, every step_shape bar was, for some reason, drawn with height = full widget's height, regardless of the value it represents. So for the most bars their lower end was far outside the box and therefore clipped off. So e.g. one couldn't see the lower round edges of rounded rectangles. This commit fixes that by appropriately varying the height that is passed into step_shape(), so that lower edges of the bars always exactly touch the lower edge of the graph. If the user wants to restore the prior behavior, they can simply ignore the passed-in height in step_shape() and just always draw their shape with height = full widget height. But they couldn't replicate the effect of this commit in their rc.lua without essentially writing their own graph widget. commit e25185b08da23745b5815f85f6e9e43222ed2294 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 08:42:54 2021 +0700 Avoid incremental transform shifting during graph bar rendering Instead of letting addition errors and cognitive load accumulate simply use the same offset calculation that is used for lines and reset the transform back to the top-left corner after every drawn bar. It's conceptually simpler, and will be useful later for stacked graphs and more. It also fixes a discrepancy between plain and step_shape rendering. Prior to this commit, bar coloring occurred with a disturbed transform, which was centered at the last drawn bar, wherever it happened to be. This meant that nontrivial colors, like gradients and images, were arbitrarily offset when step_shape was used. commit f3e98866d8583c35d0b557c09f529299edc9ba2c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:45:29 2021 +0700 Refactor out graph bar height expression into a variable commit f54528d6892f5aaa4595917853c9e2130f743fdb Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:20:25 2021 +0700 Draw graph bars with lines only when step_width == 1 Before this commit graph bars of step_width < 1 were drawn with lines of width 1, but that's a bit unfortunate, because it led to much overpainting, while still ending up with a graphically incorrect result. Let's draw such bars with rectangles and let cairo do things with subpixels, getting more graphically accurate result, while still keeping the common case of step_width == 1 (supposedly) fast-drawn with lines. commit b6515dca67c18b78eb5657378131a62ab43d4cfc Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:54:07 2021 +0700 Fix unsharp edges when drawing graph bars with rectangles The 0.5 offset is only helpful for 1-width strokes, not for fills. commit 7f3db8f07e82f821bcd1dc418d66564f537609d6 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:21:01 2021 +0700 Do coordinate transformations even if the value isn't drawn The graph presently doesn't draw values < 0, but there was a discrepancy between drawing with and without step_shape. The default line/rectangle drawing reserves an empty place where the skipped value should've been, but no such empty place is reserved, when step_shape is used. This commit fixes that. Skipped values always have their empty space now. commit 7b731ebfe96af7a9dba9c57371da6761cf22c00c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:03:30 2021 +0700 Fix graph bar scaling when min_value is not 0 Previous calculation lead to underscaling of values so that even value = max_value wasn't drawn to full height. commit dca59fd8b7fff0e7e646f2dfc6bd92047028e5a8 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:18:40 2021 +0700 Calculate the horizontal graph bar offset only when needed commit 940ddf4b9825e9c3a91d276208c089d8c74e49db Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:17:23 2021 +0700 Call cr:move_to() only when drawing the graph with lines It's redundant otherwise and might even interfere with user's drawing in step_shape(). commit b5d5cd10ca29fb29cd751faaa95137c2fe89ff63 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:04:06 2021 +0700 Fix wrong horizontal offset calculations in widget.graph Indices in the loop are zero-based, so things like i-1 and i>1 aren't necessary and actually wouldn't have been correct even in a 1-based loop. step_shape = gears.shape.rectangle and step_shape = nil, which is supposed to default to rectangle according to docs, were drawn differently due to mismatched offset calculations, which led to problems especially noticeable when step_spacing is not zero, e.g: 1) whole graph shifted left too much, drawn partially off-screen. 2) whole graph shifted right too much, and the first bar placed closer to the rest than others. commit dc369fa198281e000afc1bd17326f3a264adf7d1 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 02:09:21 2021 +0700 Fix the "border_width affects graph bar width" bug Graph bars were drawn with lines of border_width thickness instead of 1, i.e. possibly too fat to be readable. commit 1ba73378cf29ed27a6ecd18716394779ceb543d9 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 00:13:03 2021 +0700 Simplify internal values representation in graph even more New values in graph._private.values were pushed to the end, and old values were removed from the beginning of the table. In draw() however newer values are drawn first, at the left edge of the graph, necessitating annoying idx = #values - idx conversions everywhere. In particular in stacked graph that means that values that are drawn together may have different indices. One has to shift the values array in add_value() either way, so one might as well insert new values at the beginning of the array and simplify the rest of the code. This doesn't seem like much yet, but it will prove to be much more convenient and less error-prone in what I'm going to do later. commit f70523ee68dbd6b67050ace4b82903a9b22440c7 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 13:35:10 2021 +0700 Simplify internal values representation in graph widget graph._private.values was either a table of values or a table of tables of values, depending on whether "stack" property is enabled, ... or a mix of both if user forgot to pass the second "group" parameter to graph:add_value(). This resulted in pretty fragile and confusing behavior, e.g. leaving user to wonder, why they see nothing more after setting stack=true for a graph in a previously working rc.lua, and demanding a very careful use of add_value() from the user, or else some values or even entire stacks would mysteriously vanish and errors in draw() get triggered, sometimes after a period of seemingly normal functioning. Now `values` is always a table of tables and the group parameter in add_value() defaults to 1, thus making the non-stacked graph a trivial variant of the stacked graph.
2021-05-20 17:14:03 +02:00
gdebug.deprecate("Use a `wibox.container.constraint` widget or `forced_height`", {deprecated_in=5})
if awesome.api_level <= 5 then
if height >= 5 then
-- this sends "layout_changed" for us
self:set_forced_height(height)
-- signal, because we did it before
self:emit_signal("property::height", height)
end
return self
2016-05-23 07:30:44 +02:00
end
graph: complete widget overhaul and bug fixes Squashed commit of the following: commit 69821f51fe1e8652715658543e50631ece495090 Author: Alex Belykh <albel727@ngs.ru> Date: Mon May 10 19:17:55 2021 +0700 Refactor property handling in wibox.widget.graph In addition, baseline_value, step_width and step_spacing properties are all set to their default non-nil numeric values during instance creation now. commit 842eb429bf5df4b2ba46b2e6f7646afe89b1a3c3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 9 06:18:45 2021 +0700 Make graph:draw_values() private There's no documentation nor confidence that it's a good API, so I'm hiding it for now. commit 720746780574e4bad14a71fc4174955139c2eb50 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 04:04:13 2021 +0700 Document graph_should_draw_data_group() for posterity commit 7c73f3754ce6ba86b118f1170a6f9e406ab4841d Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:59:21 2021 +0700 Make graph:should_draw_data_group() private There's no confidence whether it's a good API, so I'm hiding it for now. commit b3539e20adb6423118af726ae4f8751ef864b5d2 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:36:44 2021 +0700 Make graph:preprocess_values() private There's no confidence whether it's a good API, so I'm hiding it for now. commit 2df4400e780a6260f8d0ab1c60734fbb11bf3c4f Author: Alex Belykh <albel727@ngs.ru> Date: Thu May 6 10:05:23 2021 +0700 Add spec/wibox/widget/graph_spec.lua commit ce01a9771ab439b8df841f1565aa4aa8a44a5e0d Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 08:16:18 2021 +0700 Improve graph.capacity documentation commit e28fade8f655802f5bf89154169f68d256b8e332 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 07:36:01 2021 +0700 Document graph:compute_drawn_values_num() commit dd68332292465422d08b48e9f0229f143beccbc6 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 05:24:23 2021 +0700 Document graph:pick_data_group_color() method commit ca6dc55f77aca88a6e9cc90bb6fd0045739f41f5 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 04:13:58 2021 +0700 Guard against setting data groups sparsely in graph:add_value() commit cdea5f126f7984e72924f6dffbd312c4fe3b032f Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 02:36:12 2021 +0700 Refactor scaling and baseline choice out of graph:draw() commit 10135deafe8a9390ba66ab657ebb1fb41d549e8c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 21:34:48 2021 +0700 Document graph.nan_color and graph.stack properties commit 0d73bb84ac66b87e16aee472bd1f299549f66a1c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 20:49:24 2021 +0700 Use a better example in docs for the graph.stack property commit c27d160a0826d033d49d6141608c40aa8a0642fa Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 16:05:38 2021 +0700 Document graph.baseline_value property commit 459bc8176c13d4ea32af0dcdc2399d3b929b1c6a Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:53:47 2021 +0700 Document graph.clamp_bars property commit e732ee0b3b007d213e689b98cc00ae7cb141ae08 Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:34 2021 +0700 Improve graph:add_value() and :clear() documentation commit ae73e3aa9f8d60409cbe4cb4da9612f31af4f38e Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:11 2021 +0700 Improve graph min_value/max_value/scale documentation commit a7350996a9def773b91b1631e6c0507e33d8f93b Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:27:36 2021 +0700 Cleanup graph examples to show only relevant things in docs commit 30740c26f4f41c16f4c8a3c339bcb7bd299e14cd Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 14:04:36 2021 +0700 Rename graph.stack_colors property to `group_colors` The colors are used for stacked and non-stacked graphs alike, so the name didn't make sense. Also document and clarify some color stuff. commit 1ea01bfd410971cf0d0c25fd74cd764d5eb7279d Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 21:25:41 2021 +0700 Add an example file for code coverage of some fringe graph cases commit eae68e39553f88a6275a0c65bf040b5d4c262b54 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 20:23:32 2021 +0700 Add step_shape to the graph/nan_color example for code coverage commit 8027147bc2eab34a97b49a428fb8cef27f122bed Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:55:18 2021 +0700 Add example files for graph.nan_color/nan_indication properties commit 89a25a165c52b2c15b6c4d95ecb777a9067e66f9 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:33:07 2021 +0700 Make widget graph:add_value(v) default v to NaN 0 as a default makes no sense and silences the error that is failing to specify a value, to which the user should rather be alerted. The graph widget has sane NaN handling now and will do just that. commit 2835d552440be48d356a7f7b1d64963696dd550b Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:20:48 2021 +0700 Implement graph.nan_color and nan_indication properties Now, whenever there's a NaN among values and graph.nan_indication is set, the corresponding area, where the value bar should have been drawn, is filled with the nan_color from top to bottom. The painting is done after all data is rendered, to make sure that it won't be overpainted and go unnoticed. NaNs among graph's values are inevitably a sign of some error and it is arguably not a sane default behavior to simply not render them at all. The user should take immediate note of any errors by default, instead of wondering at a mysteriously empty graph widget, which is why nan_indication is set to true by default. If the user wants to silence NaNs, they can always simply set it to false. nan_color if unset, defaults to a noticeable yellow-black pattern. As a direct and planned consequence, stacked graphs with negative values in them trigger the NaN handling too. But silencing NaN handling for those graphs is also possible and semi-sane, because then NaNs and negatives are ignored and the graph behaves (almost) like they were zeros. commit 9c1f8f08b400e01e19c534f3810c9c9ab485760c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 26 08:10:19 2021 +0700 Refactor out graph_map_value_to_widget_coordinates() function commit 1905991ddecabdae3e15ffb8ee5b18a1bee1a711 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 28 13:39:29 2021 +0700 Add an example file for graph.baseline_value property commit b31b39d66f4c037a9484b71a3f910703ae01fac0 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 07:03:49 2021 +0700 Add an example file for graph.clamp_bars=false/true comparison commit a1fc850e3047f9ee2c9ddeffacfbabf3ded34171 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 04:06:45 2021 +0700 Add an example file for graphs with negative values and shapes commit 2f012f8ff1bb5094c14e724a61e59a2a3a296bc8 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 01:06:05 2021 +0700 Add an example file for a graph displaying negative values commit 7b14fbd59a4c4c51962fd3110a1a900dd5f4eb98 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 11:18:26 2021 +0700 Add an example file for graphs with step_width < 1 commit dfd9a54544b637e2fbe3d1325f9cc24b3f4d2313 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 10:17:38 2021 +0700 Add an example file for graph.stack=false/true comparison Should also demonstrate that multiple data groups are possible for non-stacked graphs too. commit 5b532106c3fd255f9b66a66cfb88d977b489ab5d Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 09:34:14 2021 +0700 Add an example file for disabling graph data groups with nil colors commit ac6f1083c02fcb428f089a616f9b0ebaa5496b6c Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:57:25 2021 +0700 Add an example file for step_shape/width/spacing on stacked graphs commit b3703db6a5139e73e72c1cfd198ece2cd9be7df2 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:43:56 2021 +0700 Add an example file for scale=false/true on stacked graph commit b000aed4a12cb44fb501e1c8432a71a14b2f8f3f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 20 08:22:19 2021 +0700 Document deprecation of graph's width and height commit cb0ad617bd6c83a499896797318e3b0b588c3566 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 07:53:37 2021 +0700 Deprecate width and height properties of wibox.widget.graph commit 8c64e14ad91a074b784166a7054e53444cccb9d2 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 04:37:59 2021 +0700 Refactor graph:draw() to separate concerns Firstly, summation of group values for stacked graphs is extracted into graph:preprocess_values(). This makes graph:draw() smaller and exposes the fact, that stacked graphs are almost entirely just a form of a more general concept: pre-render data manipulation. It's something a user might want to customize, and now they can do it by monkey-patching the method. Secondly, the logic for deciding, if and with which color a data group should be drawn, is extracted into graph:should_draw_data_group() and get_data_group_color() methods respectively. This makes it customizable too and makes more obvious the fact, that it is a general thing that has nothing to do with stacked graphs in particular. With this there are only 5 lines remaining in graph:draw(), that could be said to be specific to stacked graphs, all pertaining to the `prev_y` variable, which merely preserves the bar coordinates from a previous data group. commit 2a32b6305d893f413303728a0a3da1dcd9a5591e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 22:52:37 2021 +0700 Pointlessly optimize graph:add_value() 1) Purge old values first, then insert the new one, if needed. Since Lua grows arrays in power of two steps, and our guess_capacity() also takes care to return even integers, it would be a shame to overstep the capacity by one by inserting first and thus getting the table size doubled only to never use the most of it. 2) Use a removal procedure that doesn't call the slow length operator at all. Previous code called it twice (once in table.remove) for every removed value. commit 680ea8f5d81e9040afb4662b3229e937e4b89e02 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:23:38 2021 +0700 Set graph data series color before the painting loop commit b970f0f221ae76250f2a94433ff79d653caa6e21 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:05:37 2021 +0700 Drop a large no longer necessary `if` in graph:draw_values() This commit is almost purely a whitespace change. commit 6b4f0541c70ec99d7f6e276bae64939f337120d6 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:00:50 2021 +0700 Extract values rendering code into graph:draw_values() method This is to avoid ungodly if nesting levels, which are needed to bail out of drawing values early and skip to drawing the border. The next commit will get rid of the if. commit e0950e4f99d1ea1c4b8cea2cc67629fa40030c2a Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 14:50:20 2021 +0700 Track graph draw() usage to guess necessary array capacity The last step of divorcing add_values() from width calculations, while ensuring that user doesn't observe a lack of data to draw. The widget now tracks an approximate number of values that it has been asked to draw() in _private.last_drawn_values_num, and truncates its values array based on that (if not overridden by the capacity property). commit 2e3d542282f35e7c0fed454da0f2eada0fcf3b78 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 13:35:12 2021 +0700 Add `capacity` property to graph widget This is the second step of divorcing add_values() from calculations involving widths. How many values are stored and how many values are drawn are distinct concepts. There isn't even a single definitive "how many values are drawn" because the widget could be rendered in several places with distinct widths simultaneously, and in every such place the data must be drawn appropriately, e.g. with auto-scaling, that takes only the visible data points into account. And the draw() method can do that now. The default capacity value of nil uses heuristics based on widget's _private.width to guess some capacity that would tolerate some dynamic widget resizing. If the user finds the heuristic failing them, they can set the capacity property to the desired maximum number of values that will be kept in the _private.values array (in each data group). commit 08d4a80686f7fe363551c35589a460839f60853e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 03:27:18 2021 +0700 Extract values size computation into compute_drawn_values_num() This is the first step of making add_values() not concern itself (directly) with widget dimensions. The user can monkey-patch the method to override how many values are (logically) drawn, because it affects graph auto-scaling and can be useful for rendering of step_shape-s that paint outside of their step_width. I didn't make it a full-blown function-valued property, because it seems a fringe functionality, which I'm too lazy to document. commit 3c5617c98ab1147618ed22e8414a77fc56b45f6e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 02:32:29 2021 +0700 Copy graph widget prototype methods over in bulk commit 169538837ece41a03d02ba8fbc0e4097062b67a3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun Apr 25 05:58:01 2021 +0700 Remove graph.baseline_y property After giving it some thought I've concluded, that it is unlikely to be useful as it is now. I'd better reserve the name for some more sensible functionality, like fixing the baseline_value defined axis at the given widget coordinate, adjusting the scaling accordingly, so min_value/max_value won't generally correspond to widget top and bottom anymore. commit 202ffaa9a0b820c7c471e1121d01b91f8f76cd38 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 12:06:55 2021 +0700 Add baseline_y and baseline_value properties to graph widget Now values in graph have a *baseline*, tunable with baseline_y and baseline_value properties. It is the (for now invisible) horizontal line from which all bars in the graph grow. I call it a "baseline" instead of "axis", because it doesn't actually have to correspond to the 0 value. But it can, and actually does now by default. baseline_y and baseline_value properties determine the vertical placement of the baseline in the widget. baseline_y is the position of the baseline in normalized widget coordinates, e.g. 0 corresponds to widget's top, 1 - to widget's bottom, and 0.5 - to widget's middle. So if e.g. one sets baseline_y = 0, then no matter how the widget is resized and how its values are scaled, bars in it will be drawn starting from the top. baseline_value is the position of the baseline in value coordinates. So if one sets baseline_value = 10, then no matter what happens, bars for values greater than 10 will be drawn as growing up from this line, and bars for values smaller as growing down. Where and if it will be seen in the widget, depends on the scaling. baseline_value = 0 is the familiar zero axis. baseline_y is unset by default and has priority, i.e. if it's set, then baseline_value is ignored. baseline_value defaults to 0. Prior to this commit, bars were always drawn from the bottom edge of the widget, or, in the new terms, the widget had baseline_y = 1. But it was indistinguishable from baseline_value = 0, because the widget clamped all negative values to 0, so the difference between the two could never be observed. baseline_value = 0 is clearly more useful and familiar to people, so now it is in force by default. commit df7e19abaebf0a871b5ca217b4cc3365533267a6 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 11:18:12 2021 +0700 Move value clamping from graph:add_value() into draw() Previously all values were forcibly clamped between 0 (not tunable at all) and max_value (1 by default). Thus graphs with negative values were not an option. NaN values were conflated with zeros, even though it may be useful to maintain the distinction and render them differently. And if user happened to dynamically change the max_value property, they discovered, that their data got truncated. Preserving user data as it was given is more flexible and makes things easier to debug. This commit does exactly that. So what happens now to the newly allowed values during draw()? There are 3 new cases to consider: 1) NaNs 2) Negatives 3) Values outside of min_value..max_value range NaNs are presently simply not drawn. An empty place is reserved where the value should've been, but nothing else is done. This is distinct from how a zero is handled, e.g. if step_shape is set, it is called with the height parameter = 0, which can still end up drawing something, depending on the shape. One could also consider addition of nan_color/nan_shape properties which could be drawn to alert user to a gap in their data. Negative values necessitate introduction of the concept of the zero axis, from which they will be drawn DOWN, in contrast to positive values. Where should this axis be drawn? How should step_shape-s with inherent direction, e.g. arrows, be drawn for negatives? This will be addressed in the next commit. Values outside of min/max_value range can imply shapes that go beyond widget dimensions. An arrow shape that goes beyond the roof is visibly different from an arrow that saturates and merely touches the widget top, and the user may reasonably desire either behavior. Therefore this commit introduces a boolean clamp_bars property, which defaults to true for reasons of robustness and backward compatibility with the behaviour prior to this commit. The property of course affects only the display of the values, the actual user data stays intact. commit c9c24fb1ab741cd6bb8074971d568e0646c51532 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:29:01 2021 +0700 Use rectangles for drawing graph bars of all sizes I have benchmarked various bar drawing strategies and graph sizes and found out that drawing graph bars with rectangles is as fast or even marginally faster than using fat lines. Thus the code that tries to special-case 1px bars as lines seems to be not worth the additional complexity it introduces, and this commit removes it. Incidentally, I've also established, that rectangles are more robust. If one draws graph bars and feeds cairo very large coordinates (~2^62), then corresponding rectangles fail to render, but all the other rectangles in the same series get rendered successfully, whereas a single large coordinate in a move_to/line_to() sequence is enough to cause all lines in the batch to fail to render. commit bc7f4be5e06405b5aa7fb443a5ace2b9b9575b1f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:01:42 2021 +0700 Cache cairo methods used in the inner loop of graph:draw() I have benchmarked the draw() method, both on cairo xcb and on argb32 image surfaces, and have discovered that cairo context method calls are unnaturally slow, to the point, that almost nothing else in the function matters. For example, drawing bars with vertical lines made the whole draw() function almost 2 times slower than doing the same with rectangles, merely because the former requires two method calls (move_to/line_to), and the latter only one. Simply caching hotpath cairo methods in local variables, reduced the difference between the drawing methods to a negligible one and made the whole function 4 times faster on a typical 100-bar graph, (1.4ms to 0.33ms on my laptop) thus bringing closer my secret dream of drawing live audio data with awesome graphs (kidding). I guess, LGI does something not very optimal there, like gobject-introspecting the method every time, or something. There might be some untapped optimization potential, possibly in upstream, like a method cache, from which everything in awesome could benefit, even though I imagine not many things are so call-intensive as the graph widget, to really feel the difference. commit 538df25bdf02b53d0bbe81809eef2e6fdb05ff6f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 07:32:37 2021 +0700 Use 1-based index in the inner graph draw loop It's used there only once for a calculation and then several times to index some tables, and index 0 is bad for lua tables performance-wise. commit 7412546916cd0a0a490130a120255ffb719398b8 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:55:12 2021 +0700 Leave graph.max_value uninitialized at construction This is a user-visible change, but the previous behavior was probably not what user expected anyway, namely with graph.scale set the graph mysteriously couldn't zoom in onto values that are smaller than 1. commit eb81d7929a161e79c13262cfa73ac2e6a820e8ed Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:48:29 2021 +0700 Allow graph.max_value be nil This makes its behave symmetrically to min_value. Now it defaults to 1, when graph.scale is not set, and to the maximum value in the graph data, when graph.scale is set. commit f622a8430cbfde7007b58d7f4c9c76b08c91e997 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 12:01:13 2021 +0700 Calculate max values num in graph:add_value() more precisely At most so many values were kept in the widget values array, as there are pixels of usable widget width (i.e. sans border). This is exactly right for the surely most common case of graphs with 1px-wide bars, but is more than needed for graphs with step_width+step_spacing>1, and not enough for graphs with subpixel bars. Moreover this made graph autoscaling behave weirdly in graphs with thicker bars, because old values survived longer in the array and kept affecting min_value/max_value calculations even after they were shifted off-screen. The maximum number of kept values is now calculated exactly as the number of values that can be displayed at once, taking step_width and step_spacing into account. It might be worth adding a max_values_num property to the graph to let user override this calculation, in case they draw shapes in step_shape() outside of its supposed rectangle, so our assumptions of visibility are wrong, or the widget often gets resized, resulting in the annoyance of visibly truncated series. commit 0aa63249a18641251a1bf77423739164f0eab39f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 02:30:13 2021 +0700 Fix border_color/border_width bugs in graph widget add_value() wrongly assumed that border_width is always set, when border_color is, so if user set only the latter, an error occurred. add_value() assumed that border_width is always < width which led to awesome hanging up in an infinite loop, should that fail to be the case. The graph border_width property was driven by the border_color property, instead of the other way round. If border_color wasn't set, the border wasn't drawn all, even if user sets non-zero border_width, which is a confusing behavior, in contrast to all other widgets, which simply use a plethora of fallback colors to draw the border, when needed. This also meant that, despite being documented, the beautiful.graph_border_color fallback color could not and was not ever used. If the border_color was set though, an unset border_width was immediately assumed to be 1 instead of 0, also contrary to what other widgets do. Theme colors shouldn't influence widget layout, so this commit fixes this behavior to: 1) border_width always defaults to 0, if unset. 2) border_color falls back to beautiful.graph_border_color or white. Maybe one should also add a beautiful.graph_border_width theme variable, like some widgets do. commit dad4a3253b8fef971fb82720d2a407fcc0a879ad Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 03:11:27 2021 +0700 Implement multiple data series drawing for non-stacked graph The widget can already hold and draw multiple data groups, so it makes little sense to limit drawing only to the first group when graph.stack = false, probably leading to user's confusion. Now user can call add_value(v, group) with group > 1 and the data series will be drawn (over groups with lesser id-s) with graph.stack_colors[group] color, if stack_colors is set, or with graph.color, if stack_colors isn't set. Thus nothing visibly changes for all current users of non-stacked graphs with only single data series, but the new functionality is now there for those who need it. One can't fully emulate this functionality, e.g. with multiple graphs widgets in a wibox.layout.stack, because data series in a single widget can enjoy a common auto-scaling. commit 3e645c976184ab5f50d625271c25062db9ab7ed9 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:59:12 2021 +0700 Don't draw a stacked graph data group if there's no color for it If stack_colors[idx] is nil for the group `idx`, don't draw it at all (as opposed, e.g. to the effect of drawing it with a transparent color, or as though it had all values == 0, which both produce different results). This code is still subtly different from prior behavior, because now user can have a non-contiguous stack_colors table and turn the display of any data group on and off at will, whereas the code two commits earlier would just stop rendering as soon as ipairs(stack_colors) does, i.e. no data series were drawn past the first nil hole. This commit only looks large, but is mostly a whitespace change due to nesting from a pair of wrapping ifs. commit 8c0273786de1ade66bdd977699f98b588da9f487 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:32:42 2021 +0700 Unify stacked and non-stacked graph rendering Now stacked graphs support everything the nonstacked do, like step_width ~= 1 and custom step_shape-s. Technical notes: This code no longer calls cr:stroke() for every single value in a stacked graph, but rather paints a whole series and then cr:stroke/fill()-s it at all once with its color, improving performance considerably. Additionally the graph no longer relies on the presence of the stack_colors property to render series. It draws all data that it has, and, if there's no corresponding color in stack_colors, falls back to graph.color. This will be changed in the next commit to the prior behavior of only drawing those series for which there's a color in the stack_colors table, but that would've made this commit's diff much larger due to required nesting ifs over large code chunks. This commit also takes care to properly handle NaNs and negative values. Those are currently (almost) guaranteed to not occur in the values array due to clipping in graph:add_value(), but I intend to eventually drop the clipping, because it does nothing that draw() really relies upon, and allowing unclipped and NaN values is more flexible and less surprising, e.g. when user dynamically toggles graph.scale. Eventually I intend to implement drawing negative values in non-stacked graphs and possibly even in the stacked ones. commit 38c6589c51b2aa057d7c2f40182ecf32b5b2ff5e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 10:04:15 2021 +0700 Convert graph:draw() to use `self` commit f21f1b2550426df1c4ea8abde079b1ccef84b394 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 12:27:26 2021 +0700 Fix scaling for stacked graphs Stacked graphs have cumulative heights, values from all groups that end up in the same graph bar are effectively summed. Previous code didn't take that into account when calculating min/max_value for the purposes of rescaling, but simply examined every value separately. This code adds up all values up front and finds min/max among those, to determine proper scaling that would fit the graph exactly. commit 1ce4c7bf63bdd6c99c195d668347bec49355e921 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 09:49:35 2021 +0700 Draw shaped graph bars only up to the baseline Prior to this commit, every step_shape bar was, for some reason, drawn with height = full widget's height, regardless of the value it represents. So for the most bars their lower end was far outside the box and therefore clipped off. So e.g. one couldn't see the lower round edges of rounded rectangles. This commit fixes that by appropriately varying the height that is passed into step_shape(), so that lower edges of the bars always exactly touch the lower edge of the graph. If the user wants to restore the prior behavior, they can simply ignore the passed-in height in step_shape() and just always draw their shape with height = full widget height. But they couldn't replicate the effect of this commit in their rc.lua without essentially writing their own graph widget. commit e25185b08da23745b5815f85f6e9e43222ed2294 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 08:42:54 2021 +0700 Avoid incremental transform shifting during graph bar rendering Instead of letting addition errors and cognitive load accumulate simply use the same offset calculation that is used for lines and reset the transform back to the top-left corner after every drawn bar. It's conceptually simpler, and will be useful later for stacked graphs and more. It also fixes a discrepancy between plain and step_shape rendering. Prior to this commit, bar coloring occurred with a disturbed transform, which was centered at the last drawn bar, wherever it happened to be. This meant that nontrivial colors, like gradients and images, were arbitrarily offset when step_shape was used. commit f3e98866d8583c35d0b557c09f529299edc9ba2c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:45:29 2021 +0700 Refactor out graph bar height expression into a variable commit f54528d6892f5aaa4595917853c9e2130f743fdb Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:20:25 2021 +0700 Draw graph bars with lines only when step_width == 1 Before this commit graph bars of step_width < 1 were drawn with lines of width 1, but that's a bit unfortunate, because it led to much overpainting, while still ending up with a graphically incorrect result. Let's draw such bars with rectangles and let cairo do things with subpixels, getting more graphically accurate result, while still keeping the common case of step_width == 1 (supposedly) fast-drawn with lines. commit b6515dca67c18b78eb5657378131a62ab43d4cfc Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:54:07 2021 +0700 Fix unsharp edges when drawing graph bars with rectangles The 0.5 offset is only helpful for 1-width strokes, not for fills. commit 7f3db8f07e82f821bcd1dc418d66564f537609d6 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:21:01 2021 +0700 Do coordinate transformations even if the value isn't drawn The graph presently doesn't draw values < 0, but there was a discrepancy between drawing with and without step_shape. The default line/rectangle drawing reserves an empty place where the skipped value should've been, but no such empty place is reserved, when step_shape is used. This commit fixes that. Skipped values always have their empty space now. commit 7b731ebfe96af7a9dba9c57371da6761cf22c00c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:03:30 2021 +0700 Fix graph bar scaling when min_value is not 0 Previous calculation lead to underscaling of values so that even value = max_value wasn't drawn to full height. commit dca59fd8b7fff0e7e646f2dfc6bd92047028e5a8 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:18:40 2021 +0700 Calculate the horizontal graph bar offset only when needed commit 940ddf4b9825e9c3a91d276208c089d8c74e49db Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:17:23 2021 +0700 Call cr:move_to() only when drawing the graph with lines It's redundant otherwise and might even interfere with user's drawing in step_shape(). commit b5d5cd10ca29fb29cd751faaa95137c2fe89ff63 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:04:06 2021 +0700 Fix wrong horizontal offset calculations in widget.graph Indices in the loop are zero-based, so things like i-1 and i>1 aren't necessary and actually wouldn't have been correct even in a 1-based loop. step_shape = gears.shape.rectangle and step_shape = nil, which is supposed to default to rectangle according to docs, were drawn differently due to mismatched offset calculations, which led to problems especially noticeable when step_spacing is not zero, e.g: 1) whole graph shifted left too much, drawn partially off-screen. 2) whole graph shifted right too much, and the first bar placed closer to the rest than others. commit dc369fa198281e000afc1bd17326f3a264adf7d1 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 02:09:21 2021 +0700 Fix the "border_width affects graph bar width" bug Graph bars were drawn with lines of border_width thickness instead of 1, i.e. possibly too fat to be readable. commit 1ba73378cf29ed27a6ecd18716394779ceb543d9 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 00:13:03 2021 +0700 Simplify internal values representation in graph even more New values in graph._private.values were pushed to the end, and old values were removed from the beginning of the table. In draw() however newer values are drawn first, at the left edge of the graph, necessitating annoying idx = #values - idx conversions everywhere. In particular in stacked graph that means that values that are drawn together may have different indices. One has to shift the values array in add_value() either way, so one might as well insert new values at the beginning of the array and simplify the rest of the code. This doesn't seem like much yet, but it will prove to be much more convenient and less error-prone in what I'm going to do later. commit f70523ee68dbd6b67050ace4b82903a9b22440c7 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 13:35:10 2021 +0700 Simplify internal values representation in graph widget graph._private.values was either a table of values or a table of tables of values, depending on whether "stack" property is enabled, ... or a mix of both if user forgot to pass the second "group" parameter to graph:add_value(). This resulted in pretty fragile and confusing behavior, e.g. leaving user to wonder, why they see nothing more after setting stack=true for a graph in a previously working rc.lua, and demanding a very careful use of add_value() from the user, or else some values or even entire stacks would mysteriously vanish and errors in draw() get triggered, sometimes after a period of seemingly normal functioning. Now `values` is always a table of tables and the group parameter in add_value() defaults to 1, thus making the non-stacked graph a trivial variant of the stacked graph.
2021-05-20 17:14:03 +02:00
end
function graph:get_height()
gdebug.deprecate("Use `forced_height`", {deprecated_in=5})
return awesome.api_level <= 5 and self._private.forced_height or nil
2016-05-23 07:30:44 +02:00
end
--- Set the graph width.
--
graph: complete widget overhaul and bug fixes Squashed commit of the following: commit 69821f51fe1e8652715658543e50631ece495090 Author: Alex Belykh <albel727@ngs.ru> Date: Mon May 10 19:17:55 2021 +0700 Refactor property handling in wibox.widget.graph In addition, baseline_value, step_width and step_spacing properties are all set to their default non-nil numeric values during instance creation now. commit 842eb429bf5df4b2ba46b2e6f7646afe89b1a3c3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 9 06:18:45 2021 +0700 Make graph:draw_values() private There's no documentation nor confidence that it's a good API, so I'm hiding it for now. commit 720746780574e4bad14a71fc4174955139c2eb50 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 04:04:13 2021 +0700 Document graph_should_draw_data_group() for posterity commit 7c73f3754ce6ba86b118f1170a6f9e406ab4841d Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:59:21 2021 +0700 Make graph:should_draw_data_group() private There's no confidence whether it's a good API, so I'm hiding it for now. commit b3539e20adb6423118af726ae4f8751ef864b5d2 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:36:44 2021 +0700 Make graph:preprocess_values() private There's no confidence whether it's a good API, so I'm hiding it for now. commit 2df4400e780a6260f8d0ab1c60734fbb11bf3c4f Author: Alex Belykh <albel727@ngs.ru> Date: Thu May 6 10:05:23 2021 +0700 Add spec/wibox/widget/graph_spec.lua commit ce01a9771ab439b8df841f1565aa4aa8a44a5e0d Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 08:16:18 2021 +0700 Improve graph.capacity documentation commit e28fade8f655802f5bf89154169f68d256b8e332 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 07:36:01 2021 +0700 Document graph:compute_drawn_values_num() commit dd68332292465422d08b48e9f0229f143beccbc6 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 05:24:23 2021 +0700 Document graph:pick_data_group_color() method commit ca6dc55f77aca88a6e9cc90bb6fd0045739f41f5 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 04:13:58 2021 +0700 Guard against setting data groups sparsely in graph:add_value() commit cdea5f126f7984e72924f6dffbd312c4fe3b032f Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 02:36:12 2021 +0700 Refactor scaling and baseline choice out of graph:draw() commit 10135deafe8a9390ba66ab657ebb1fb41d549e8c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 21:34:48 2021 +0700 Document graph.nan_color and graph.stack properties commit 0d73bb84ac66b87e16aee472bd1f299549f66a1c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 20:49:24 2021 +0700 Use a better example in docs for the graph.stack property commit c27d160a0826d033d49d6141608c40aa8a0642fa Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 16:05:38 2021 +0700 Document graph.baseline_value property commit 459bc8176c13d4ea32af0dcdc2399d3b929b1c6a Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:53:47 2021 +0700 Document graph.clamp_bars property commit e732ee0b3b007d213e689b98cc00ae7cb141ae08 Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:34 2021 +0700 Improve graph:add_value() and :clear() documentation commit ae73e3aa9f8d60409cbe4cb4da9612f31af4f38e Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:11 2021 +0700 Improve graph min_value/max_value/scale documentation commit a7350996a9def773b91b1631e6c0507e33d8f93b Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:27:36 2021 +0700 Cleanup graph examples to show only relevant things in docs commit 30740c26f4f41c16f4c8a3c339bcb7bd299e14cd Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 14:04:36 2021 +0700 Rename graph.stack_colors property to `group_colors` The colors are used for stacked and non-stacked graphs alike, so the name didn't make sense. Also document and clarify some color stuff. commit 1ea01bfd410971cf0d0c25fd74cd764d5eb7279d Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 21:25:41 2021 +0700 Add an example file for code coverage of some fringe graph cases commit eae68e39553f88a6275a0c65bf040b5d4c262b54 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 20:23:32 2021 +0700 Add step_shape to the graph/nan_color example for code coverage commit 8027147bc2eab34a97b49a428fb8cef27f122bed Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:55:18 2021 +0700 Add example files for graph.nan_color/nan_indication properties commit 89a25a165c52b2c15b6c4d95ecb777a9067e66f9 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:33:07 2021 +0700 Make widget graph:add_value(v) default v to NaN 0 as a default makes no sense and silences the error that is failing to specify a value, to which the user should rather be alerted. The graph widget has sane NaN handling now and will do just that. commit 2835d552440be48d356a7f7b1d64963696dd550b Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:20:48 2021 +0700 Implement graph.nan_color and nan_indication properties Now, whenever there's a NaN among values and graph.nan_indication is set, the corresponding area, where the value bar should have been drawn, is filled with the nan_color from top to bottom. The painting is done after all data is rendered, to make sure that it won't be overpainted and go unnoticed. NaNs among graph's values are inevitably a sign of some error and it is arguably not a sane default behavior to simply not render them at all. The user should take immediate note of any errors by default, instead of wondering at a mysteriously empty graph widget, which is why nan_indication is set to true by default. If the user wants to silence NaNs, they can always simply set it to false. nan_color if unset, defaults to a noticeable yellow-black pattern. As a direct and planned consequence, stacked graphs with negative values in them trigger the NaN handling too. But silencing NaN handling for those graphs is also possible and semi-sane, because then NaNs and negatives are ignored and the graph behaves (almost) like they were zeros. commit 9c1f8f08b400e01e19c534f3810c9c9ab485760c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 26 08:10:19 2021 +0700 Refactor out graph_map_value_to_widget_coordinates() function commit 1905991ddecabdae3e15ffb8ee5b18a1bee1a711 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 28 13:39:29 2021 +0700 Add an example file for graph.baseline_value property commit b31b39d66f4c037a9484b71a3f910703ae01fac0 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 07:03:49 2021 +0700 Add an example file for graph.clamp_bars=false/true comparison commit a1fc850e3047f9ee2c9ddeffacfbabf3ded34171 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 04:06:45 2021 +0700 Add an example file for graphs with negative values and shapes commit 2f012f8ff1bb5094c14e724a61e59a2a3a296bc8 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 01:06:05 2021 +0700 Add an example file for a graph displaying negative values commit 7b14fbd59a4c4c51962fd3110a1a900dd5f4eb98 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 11:18:26 2021 +0700 Add an example file for graphs with step_width < 1 commit dfd9a54544b637e2fbe3d1325f9cc24b3f4d2313 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 10:17:38 2021 +0700 Add an example file for graph.stack=false/true comparison Should also demonstrate that multiple data groups are possible for non-stacked graphs too. commit 5b532106c3fd255f9b66a66cfb88d977b489ab5d Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 09:34:14 2021 +0700 Add an example file for disabling graph data groups with nil colors commit ac6f1083c02fcb428f089a616f9b0ebaa5496b6c Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:57:25 2021 +0700 Add an example file for step_shape/width/spacing on stacked graphs commit b3703db6a5139e73e72c1cfd198ece2cd9be7df2 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:43:56 2021 +0700 Add an example file for scale=false/true on stacked graph commit b000aed4a12cb44fb501e1c8432a71a14b2f8f3f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 20 08:22:19 2021 +0700 Document deprecation of graph's width and height commit cb0ad617bd6c83a499896797318e3b0b588c3566 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 07:53:37 2021 +0700 Deprecate width and height properties of wibox.widget.graph commit 8c64e14ad91a074b784166a7054e53444cccb9d2 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 04:37:59 2021 +0700 Refactor graph:draw() to separate concerns Firstly, summation of group values for stacked graphs is extracted into graph:preprocess_values(). This makes graph:draw() smaller and exposes the fact, that stacked graphs are almost entirely just a form of a more general concept: pre-render data manipulation. It's something a user might want to customize, and now they can do it by monkey-patching the method. Secondly, the logic for deciding, if and with which color a data group should be drawn, is extracted into graph:should_draw_data_group() and get_data_group_color() methods respectively. This makes it customizable too and makes more obvious the fact, that it is a general thing that has nothing to do with stacked graphs in particular. With this there are only 5 lines remaining in graph:draw(), that could be said to be specific to stacked graphs, all pertaining to the `prev_y` variable, which merely preserves the bar coordinates from a previous data group. commit 2a32b6305d893f413303728a0a3da1dcd9a5591e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 22:52:37 2021 +0700 Pointlessly optimize graph:add_value() 1) Purge old values first, then insert the new one, if needed. Since Lua grows arrays in power of two steps, and our guess_capacity() also takes care to return even integers, it would be a shame to overstep the capacity by one by inserting first and thus getting the table size doubled only to never use the most of it. 2) Use a removal procedure that doesn't call the slow length operator at all. Previous code called it twice (once in table.remove) for every removed value. commit 680ea8f5d81e9040afb4662b3229e937e4b89e02 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:23:38 2021 +0700 Set graph data series color before the painting loop commit b970f0f221ae76250f2a94433ff79d653caa6e21 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:05:37 2021 +0700 Drop a large no longer necessary `if` in graph:draw_values() This commit is almost purely a whitespace change. commit 6b4f0541c70ec99d7f6e276bae64939f337120d6 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:00:50 2021 +0700 Extract values rendering code into graph:draw_values() method This is to avoid ungodly if nesting levels, which are needed to bail out of drawing values early and skip to drawing the border. The next commit will get rid of the if. commit e0950e4f99d1ea1c4b8cea2cc67629fa40030c2a Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 14:50:20 2021 +0700 Track graph draw() usage to guess necessary array capacity The last step of divorcing add_values() from width calculations, while ensuring that user doesn't observe a lack of data to draw. The widget now tracks an approximate number of values that it has been asked to draw() in _private.last_drawn_values_num, and truncates its values array based on that (if not overridden by the capacity property). commit 2e3d542282f35e7c0fed454da0f2eada0fcf3b78 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 13:35:12 2021 +0700 Add `capacity` property to graph widget This is the second step of divorcing add_values() from calculations involving widths. How many values are stored and how many values are drawn are distinct concepts. There isn't even a single definitive "how many values are drawn" because the widget could be rendered in several places with distinct widths simultaneously, and in every such place the data must be drawn appropriately, e.g. with auto-scaling, that takes only the visible data points into account. And the draw() method can do that now. The default capacity value of nil uses heuristics based on widget's _private.width to guess some capacity that would tolerate some dynamic widget resizing. If the user finds the heuristic failing them, they can set the capacity property to the desired maximum number of values that will be kept in the _private.values array (in each data group). commit 08d4a80686f7fe363551c35589a460839f60853e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 03:27:18 2021 +0700 Extract values size computation into compute_drawn_values_num() This is the first step of making add_values() not concern itself (directly) with widget dimensions. The user can monkey-patch the method to override how many values are (logically) drawn, because it affects graph auto-scaling and can be useful for rendering of step_shape-s that paint outside of their step_width. I didn't make it a full-blown function-valued property, because it seems a fringe functionality, which I'm too lazy to document. commit 3c5617c98ab1147618ed22e8414a77fc56b45f6e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 02:32:29 2021 +0700 Copy graph widget prototype methods over in bulk commit 169538837ece41a03d02ba8fbc0e4097062b67a3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun Apr 25 05:58:01 2021 +0700 Remove graph.baseline_y property After giving it some thought I've concluded, that it is unlikely to be useful as it is now. I'd better reserve the name for some more sensible functionality, like fixing the baseline_value defined axis at the given widget coordinate, adjusting the scaling accordingly, so min_value/max_value won't generally correspond to widget top and bottom anymore. commit 202ffaa9a0b820c7c471e1121d01b91f8f76cd38 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 12:06:55 2021 +0700 Add baseline_y and baseline_value properties to graph widget Now values in graph have a *baseline*, tunable with baseline_y and baseline_value properties. It is the (for now invisible) horizontal line from which all bars in the graph grow. I call it a "baseline" instead of "axis", because it doesn't actually have to correspond to the 0 value. But it can, and actually does now by default. baseline_y and baseline_value properties determine the vertical placement of the baseline in the widget. baseline_y is the position of the baseline in normalized widget coordinates, e.g. 0 corresponds to widget's top, 1 - to widget's bottom, and 0.5 - to widget's middle. So if e.g. one sets baseline_y = 0, then no matter how the widget is resized and how its values are scaled, bars in it will be drawn starting from the top. baseline_value is the position of the baseline in value coordinates. So if one sets baseline_value = 10, then no matter what happens, bars for values greater than 10 will be drawn as growing up from this line, and bars for values smaller as growing down. Where and if it will be seen in the widget, depends on the scaling. baseline_value = 0 is the familiar zero axis. baseline_y is unset by default and has priority, i.e. if it's set, then baseline_value is ignored. baseline_value defaults to 0. Prior to this commit, bars were always drawn from the bottom edge of the widget, or, in the new terms, the widget had baseline_y = 1. But it was indistinguishable from baseline_value = 0, because the widget clamped all negative values to 0, so the difference between the two could never be observed. baseline_value = 0 is clearly more useful and familiar to people, so now it is in force by default. commit df7e19abaebf0a871b5ca217b4cc3365533267a6 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 11:18:12 2021 +0700 Move value clamping from graph:add_value() into draw() Previously all values were forcibly clamped between 0 (not tunable at all) and max_value (1 by default). Thus graphs with negative values were not an option. NaN values were conflated with zeros, even though it may be useful to maintain the distinction and render them differently. And if user happened to dynamically change the max_value property, they discovered, that their data got truncated. Preserving user data as it was given is more flexible and makes things easier to debug. This commit does exactly that. So what happens now to the newly allowed values during draw()? There are 3 new cases to consider: 1) NaNs 2) Negatives 3) Values outside of min_value..max_value range NaNs are presently simply not drawn. An empty place is reserved where the value should've been, but nothing else is done. This is distinct from how a zero is handled, e.g. if step_shape is set, it is called with the height parameter = 0, which can still end up drawing something, depending on the shape. One could also consider addition of nan_color/nan_shape properties which could be drawn to alert user to a gap in their data. Negative values necessitate introduction of the concept of the zero axis, from which they will be drawn DOWN, in contrast to positive values. Where should this axis be drawn? How should step_shape-s with inherent direction, e.g. arrows, be drawn for negatives? This will be addressed in the next commit. Values outside of min/max_value range can imply shapes that go beyond widget dimensions. An arrow shape that goes beyond the roof is visibly different from an arrow that saturates and merely touches the widget top, and the user may reasonably desire either behavior. Therefore this commit introduces a boolean clamp_bars property, which defaults to true for reasons of robustness and backward compatibility with the behaviour prior to this commit. The property of course affects only the display of the values, the actual user data stays intact. commit c9c24fb1ab741cd6bb8074971d568e0646c51532 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:29:01 2021 +0700 Use rectangles for drawing graph bars of all sizes I have benchmarked various bar drawing strategies and graph sizes and found out that drawing graph bars with rectangles is as fast or even marginally faster than using fat lines. Thus the code that tries to special-case 1px bars as lines seems to be not worth the additional complexity it introduces, and this commit removes it. Incidentally, I've also established, that rectangles are more robust. If one draws graph bars and feeds cairo very large coordinates (~2^62), then corresponding rectangles fail to render, but all the other rectangles in the same series get rendered successfully, whereas a single large coordinate in a move_to/line_to() sequence is enough to cause all lines in the batch to fail to render. commit bc7f4be5e06405b5aa7fb443a5ace2b9b9575b1f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:01:42 2021 +0700 Cache cairo methods used in the inner loop of graph:draw() I have benchmarked the draw() method, both on cairo xcb and on argb32 image surfaces, and have discovered that cairo context method calls are unnaturally slow, to the point, that almost nothing else in the function matters. For example, drawing bars with vertical lines made the whole draw() function almost 2 times slower than doing the same with rectangles, merely because the former requires two method calls (move_to/line_to), and the latter only one. Simply caching hotpath cairo methods in local variables, reduced the difference between the drawing methods to a negligible one and made the whole function 4 times faster on a typical 100-bar graph, (1.4ms to 0.33ms on my laptop) thus bringing closer my secret dream of drawing live audio data with awesome graphs (kidding). I guess, LGI does something not very optimal there, like gobject-introspecting the method every time, or something. There might be some untapped optimization potential, possibly in upstream, like a method cache, from which everything in awesome could benefit, even though I imagine not many things are so call-intensive as the graph widget, to really feel the difference. commit 538df25bdf02b53d0bbe81809eef2e6fdb05ff6f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 07:32:37 2021 +0700 Use 1-based index in the inner graph draw loop It's used there only once for a calculation and then several times to index some tables, and index 0 is bad for lua tables performance-wise. commit 7412546916cd0a0a490130a120255ffb719398b8 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:55:12 2021 +0700 Leave graph.max_value uninitialized at construction This is a user-visible change, but the previous behavior was probably not what user expected anyway, namely with graph.scale set the graph mysteriously couldn't zoom in onto values that are smaller than 1. commit eb81d7929a161e79c13262cfa73ac2e6a820e8ed Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:48:29 2021 +0700 Allow graph.max_value be nil This makes its behave symmetrically to min_value. Now it defaults to 1, when graph.scale is not set, and to the maximum value in the graph data, when graph.scale is set. commit f622a8430cbfde7007b58d7f4c9c76b08c91e997 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 12:01:13 2021 +0700 Calculate max values num in graph:add_value() more precisely At most so many values were kept in the widget values array, as there are pixels of usable widget width (i.e. sans border). This is exactly right for the surely most common case of graphs with 1px-wide bars, but is more than needed for graphs with step_width+step_spacing>1, and not enough for graphs with subpixel bars. Moreover this made graph autoscaling behave weirdly in graphs with thicker bars, because old values survived longer in the array and kept affecting min_value/max_value calculations even after they were shifted off-screen. The maximum number of kept values is now calculated exactly as the number of values that can be displayed at once, taking step_width and step_spacing into account. It might be worth adding a max_values_num property to the graph to let user override this calculation, in case they draw shapes in step_shape() outside of its supposed rectangle, so our assumptions of visibility are wrong, or the widget often gets resized, resulting in the annoyance of visibly truncated series. commit 0aa63249a18641251a1bf77423739164f0eab39f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 02:30:13 2021 +0700 Fix border_color/border_width bugs in graph widget add_value() wrongly assumed that border_width is always set, when border_color is, so if user set only the latter, an error occurred. add_value() assumed that border_width is always < width which led to awesome hanging up in an infinite loop, should that fail to be the case. The graph border_width property was driven by the border_color property, instead of the other way round. If border_color wasn't set, the border wasn't drawn all, even if user sets non-zero border_width, which is a confusing behavior, in contrast to all other widgets, which simply use a plethora of fallback colors to draw the border, when needed. This also meant that, despite being documented, the beautiful.graph_border_color fallback color could not and was not ever used. If the border_color was set though, an unset border_width was immediately assumed to be 1 instead of 0, also contrary to what other widgets do. Theme colors shouldn't influence widget layout, so this commit fixes this behavior to: 1) border_width always defaults to 0, if unset. 2) border_color falls back to beautiful.graph_border_color or white. Maybe one should also add a beautiful.graph_border_width theme variable, like some widgets do. commit dad4a3253b8fef971fb82720d2a407fcc0a879ad Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 03:11:27 2021 +0700 Implement multiple data series drawing for non-stacked graph The widget can already hold and draw multiple data groups, so it makes little sense to limit drawing only to the first group when graph.stack = false, probably leading to user's confusion. Now user can call add_value(v, group) with group > 1 and the data series will be drawn (over groups with lesser id-s) with graph.stack_colors[group] color, if stack_colors is set, or with graph.color, if stack_colors isn't set. Thus nothing visibly changes for all current users of non-stacked graphs with only single data series, but the new functionality is now there for those who need it. One can't fully emulate this functionality, e.g. with multiple graphs widgets in a wibox.layout.stack, because data series in a single widget can enjoy a common auto-scaling. commit 3e645c976184ab5f50d625271c25062db9ab7ed9 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:59:12 2021 +0700 Don't draw a stacked graph data group if there's no color for it If stack_colors[idx] is nil for the group `idx`, don't draw it at all (as opposed, e.g. to the effect of drawing it with a transparent color, or as though it had all values == 0, which both produce different results). This code is still subtly different from prior behavior, because now user can have a non-contiguous stack_colors table and turn the display of any data group on and off at will, whereas the code two commits earlier would just stop rendering as soon as ipairs(stack_colors) does, i.e. no data series were drawn past the first nil hole. This commit only looks large, but is mostly a whitespace change due to nesting from a pair of wrapping ifs. commit 8c0273786de1ade66bdd977699f98b588da9f487 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:32:42 2021 +0700 Unify stacked and non-stacked graph rendering Now stacked graphs support everything the nonstacked do, like step_width ~= 1 and custom step_shape-s. Technical notes: This code no longer calls cr:stroke() for every single value in a stacked graph, but rather paints a whole series and then cr:stroke/fill()-s it at all once with its color, improving performance considerably. Additionally the graph no longer relies on the presence of the stack_colors property to render series. It draws all data that it has, and, if there's no corresponding color in stack_colors, falls back to graph.color. This will be changed in the next commit to the prior behavior of only drawing those series for which there's a color in the stack_colors table, but that would've made this commit's diff much larger due to required nesting ifs over large code chunks. This commit also takes care to properly handle NaNs and negative values. Those are currently (almost) guaranteed to not occur in the values array due to clipping in graph:add_value(), but I intend to eventually drop the clipping, because it does nothing that draw() really relies upon, and allowing unclipped and NaN values is more flexible and less surprising, e.g. when user dynamically toggles graph.scale. Eventually I intend to implement drawing negative values in non-stacked graphs and possibly even in the stacked ones. commit 38c6589c51b2aa057d7c2f40182ecf32b5b2ff5e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 10:04:15 2021 +0700 Convert graph:draw() to use `self` commit f21f1b2550426df1c4ea8abde079b1ccef84b394 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 12:27:26 2021 +0700 Fix scaling for stacked graphs Stacked graphs have cumulative heights, values from all groups that end up in the same graph bar are effectively summed. Previous code didn't take that into account when calculating min/max_value for the purposes of rescaling, but simply examined every value separately. This code adds up all values up front and finds min/max among those, to determine proper scaling that would fit the graph exactly. commit 1ce4c7bf63bdd6c99c195d668347bec49355e921 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 09:49:35 2021 +0700 Draw shaped graph bars only up to the baseline Prior to this commit, every step_shape bar was, for some reason, drawn with height = full widget's height, regardless of the value it represents. So for the most bars their lower end was far outside the box and therefore clipped off. So e.g. one couldn't see the lower round edges of rounded rectangles. This commit fixes that by appropriately varying the height that is passed into step_shape(), so that lower edges of the bars always exactly touch the lower edge of the graph. If the user wants to restore the prior behavior, they can simply ignore the passed-in height in step_shape() and just always draw their shape with height = full widget height. But they couldn't replicate the effect of this commit in their rc.lua without essentially writing their own graph widget. commit e25185b08da23745b5815f85f6e9e43222ed2294 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 08:42:54 2021 +0700 Avoid incremental transform shifting during graph bar rendering Instead of letting addition errors and cognitive load accumulate simply use the same offset calculation that is used for lines and reset the transform back to the top-left corner after every drawn bar. It's conceptually simpler, and will be useful later for stacked graphs and more. It also fixes a discrepancy between plain and step_shape rendering. Prior to this commit, bar coloring occurred with a disturbed transform, which was centered at the last drawn bar, wherever it happened to be. This meant that nontrivial colors, like gradients and images, were arbitrarily offset when step_shape was used. commit f3e98866d8583c35d0b557c09f529299edc9ba2c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:45:29 2021 +0700 Refactor out graph bar height expression into a variable commit f54528d6892f5aaa4595917853c9e2130f743fdb Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:20:25 2021 +0700 Draw graph bars with lines only when step_width == 1 Before this commit graph bars of step_width < 1 were drawn with lines of width 1, but that's a bit unfortunate, because it led to much overpainting, while still ending up with a graphically incorrect result. Let's draw such bars with rectangles and let cairo do things with subpixels, getting more graphically accurate result, while still keeping the common case of step_width == 1 (supposedly) fast-drawn with lines. commit b6515dca67c18b78eb5657378131a62ab43d4cfc Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:54:07 2021 +0700 Fix unsharp edges when drawing graph bars with rectangles The 0.5 offset is only helpful for 1-width strokes, not for fills. commit 7f3db8f07e82f821bcd1dc418d66564f537609d6 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:21:01 2021 +0700 Do coordinate transformations even if the value isn't drawn The graph presently doesn't draw values < 0, but there was a discrepancy between drawing with and without step_shape. The default line/rectangle drawing reserves an empty place where the skipped value should've been, but no such empty place is reserved, when step_shape is used. This commit fixes that. Skipped values always have their empty space now. commit 7b731ebfe96af7a9dba9c57371da6761cf22c00c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:03:30 2021 +0700 Fix graph bar scaling when min_value is not 0 Previous calculation lead to underscaling of values so that even value = max_value wasn't drawn to full height. commit dca59fd8b7fff0e7e646f2dfc6bd92047028e5a8 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:18:40 2021 +0700 Calculate the horizontal graph bar offset only when needed commit 940ddf4b9825e9c3a91d276208c089d8c74e49db Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:17:23 2021 +0700 Call cr:move_to() only when drawing the graph with lines It's redundant otherwise and might even interfere with user's drawing in step_shape(). commit b5d5cd10ca29fb29cd751faaa95137c2fe89ff63 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:04:06 2021 +0700 Fix wrong horizontal offset calculations in widget.graph Indices in the loop are zero-based, so things like i-1 and i>1 aren't necessary and actually wouldn't have been correct even in a 1-based loop. step_shape = gears.shape.rectangle and step_shape = nil, which is supposed to default to rectangle according to docs, were drawn differently due to mismatched offset calculations, which led to problems especially noticeable when step_spacing is not zero, e.g: 1) whole graph shifted left too much, drawn partially off-screen. 2) whole graph shifted right too much, and the first bar placed closer to the rest than others. commit dc369fa198281e000afc1bd17326f3a264adf7d1 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 02:09:21 2021 +0700 Fix the "border_width affects graph bar width" bug Graph bars were drawn with lines of border_width thickness instead of 1, i.e. possibly too fat to be readable. commit 1ba73378cf29ed27a6ecd18716394779ceb543d9 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 00:13:03 2021 +0700 Simplify internal values representation in graph even more New values in graph._private.values were pushed to the end, and old values were removed from the beginning of the table. In draw() however newer values are drawn first, at the left edge of the graph, necessitating annoying idx = #values - idx conversions everywhere. In particular in stacked graph that means that values that are drawn together may have different indices. One has to shift the values array in add_value() either way, so one might as well insert new values at the beginning of the array and simplify the rest of the code. This doesn't seem like much yet, but it will prove to be much more convenient and less error-prone in what I'm going to do later. commit f70523ee68dbd6b67050ace4b82903a9b22440c7 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 13:35:10 2021 +0700 Simplify internal values representation in graph widget graph._private.values was either a table of values or a table of tables of values, depending on whether "stack" property is enabled, ... or a mix of both if user forgot to pass the second "group" parameter to graph:add_value(). This resulted in pretty fragile and confusing behavior, e.g. leaving user to wonder, why they see nothing more after setting stack=true for a graph in a previously working rc.lua, and demanding a very careful use of add_value() from the user, or else some values or even entire stacks would mysteriously vanish and errors in draw() get triggered, sometimes after a period of seemingly normal functioning. Now `values` is always a table of tables and the group parameter in add_value() defaults to 1, thus making the non-stacked graph a trivial variant of the stacked graph.
2021-05-20 17:14:03 +02:00
-- This property is deprecated. Use a `wibox.container.constraint` widget or
-- `forced_width`.
---
-- @deprecatedproperty width
-- @tparam number width The width to set.
-- @renamedin 5.0 forced_width
-- @propemits true false
2016-05-23 07:30:44 +02:00
function graph:set_width(width)
graph: complete widget overhaul and bug fixes Squashed commit of the following: commit 69821f51fe1e8652715658543e50631ece495090 Author: Alex Belykh <albel727@ngs.ru> Date: Mon May 10 19:17:55 2021 +0700 Refactor property handling in wibox.widget.graph In addition, baseline_value, step_width and step_spacing properties are all set to their default non-nil numeric values during instance creation now. commit 842eb429bf5df4b2ba46b2e6f7646afe89b1a3c3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 9 06:18:45 2021 +0700 Make graph:draw_values() private There's no documentation nor confidence that it's a good API, so I'm hiding it for now. commit 720746780574e4bad14a71fc4174955139c2eb50 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 04:04:13 2021 +0700 Document graph_should_draw_data_group() for posterity commit 7c73f3754ce6ba86b118f1170a6f9e406ab4841d Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:59:21 2021 +0700 Make graph:should_draw_data_group() private There's no confidence whether it's a good API, so I'm hiding it for now. commit b3539e20adb6423118af726ae4f8751ef864b5d2 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:36:44 2021 +0700 Make graph:preprocess_values() private There's no confidence whether it's a good API, so I'm hiding it for now. commit 2df4400e780a6260f8d0ab1c60734fbb11bf3c4f Author: Alex Belykh <albel727@ngs.ru> Date: Thu May 6 10:05:23 2021 +0700 Add spec/wibox/widget/graph_spec.lua commit ce01a9771ab439b8df841f1565aa4aa8a44a5e0d Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 08:16:18 2021 +0700 Improve graph.capacity documentation commit e28fade8f655802f5bf89154169f68d256b8e332 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 07:36:01 2021 +0700 Document graph:compute_drawn_values_num() commit dd68332292465422d08b48e9f0229f143beccbc6 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 05:24:23 2021 +0700 Document graph:pick_data_group_color() method commit ca6dc55f77aca88a6e9cc90bb6fd0045739f41f5 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 04:13:58 2021 +0700 Guard against setting data groups sparsely in graph:add_value() commit cdea5f126f7984e72924f6dffbd312c4fe3b032f Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 02:36:12 2021 +0700 Refactor scaling and baseline choice out of graph:draw() commit 10135deafe8a9390ba66ab657ebb1fb41d549e8c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 21:34:48 2021 +0700 Document graph.nan_color and graph.stack properties commit 0d73bb84ac66b87e16aee472bd1f299549f66a1c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 20:49:24 2021 +0700 Use a better example in docs for the graph.stack property commit c27d160a0826d033d49d6141608c40aa8a0642fa Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 16:05:38 2021 +0700 Document graph.baseline_value property commit 459bc8176c13d4ea32af0dcdc2399d3b929b1c6a Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:53:47 2021 +0700 Document graph.clamp_bars property commit e732ee0b3b007d213e689b98cc00ae7cb141ae08 Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:34 2021 +0700 Improve graph:add_value() and :clear() documentation commit ae73e3aa9f8d60409cbe4cb4da9612f31af4f38e Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:11 2021 +0700 Improve graph min_value/max_value/scale documentation commit a7350996a9def773b91b1631e6c0507e33d8f93b Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:27:36 2021 +0700 Cleanup graph examples to show only relevant things in docs commit 30740c26f4f41c16f4c8a3c339bcb7bd299e14cd Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 14:04:36 2021 +0700 Rename graph.stack_colors property to `group_colors` The colors are used for stacked and non-stacked graphs alike, so the name didn't make sense. Also document and clarify some color stuff. commit 1ea01bfd410971cf0d0c25fd74cd764d5eb7279d Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 21:25:41 2021 +0700 Add an example file for code coverage of some fringe graph cases commit eae68e39553f88a6275a0c65bf040b5d4c262b54 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 20:23:32 2021 +0700 Add step_shape to the graph/nan_color example for code coverage commit 8027147bc2eab34a97b49a428fb8cef27f122bed Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:55:18 2021 +0700 Add example files for graph.nan_color/nan_indication properties commit 89a25a165c52b2c15b6c4d95ecb777a9067e66f9 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:33:07 2021 +0700 Make widget graph:add_value(v) default v to NaN 0 as a default makes no sense and silences the error that is failing to specify a value, to which the user should rather be alerted. The graph widget has sane NaN handling now and will do just that. commit 2835d552440be48d356a7f7b1d64963696dd550b Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:20:48 2021 +0700 Implement graph.nan_color and nan_indication properties Now, whenever there's a NaN among values and graph.nan_indication is set, the corresponding area, where the value bar should have been drawn, is filled with the nan_color from top to bottom. The painting is done after all data is rendered, to make sure that it won't be overpainted and go unnoticed. NaNs among graph's values are inevitably a sign of some error and it is arguably not a sane default behavior to simply not render them at all. The user should take immediate note of any errors by default, instead of wondering at a mysteriously empty graph widget, which is why nan_indication is set to true by default. If the user wants to silence NaNs, they can always simply set it to false. nan_color if unset, defaults to a noticeable yellow-black pattern. As a direct and planned consequence, stacked graphs with negative values in them trigger the NaN handling too. But silencing NaN handling for those graphs is also possible and semi-sane, because then NaNs and negatives are ignored and the graph behaves (almost) like they were zeros. commit 9c1f8f08b400e01e19c534f3810c9c9ab485760c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 26 08:10:19 2021 +0700 Refactor out graph_map_value_to_widget_coordinates() function commit 1905991ddecabdae3e15ffb8ee5b18a1bee1a711 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 28 13:39:29 2021 +0700 Add an example file for graph.baseline_value property commit b31b39d66f4c037a9484b71a3f910703ae01fac0 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 07:03:49 2021 +0700 Add an example file for graph.clamp_bars=false/true comparison commit a1fc850e3047f9ee2c9ddeffacfbabf3ded34171 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 04:06:45 2021 +0700 Add an example file for graphs with negative values and shapes commit 2f012f8ff1bb5094c14e724a61e59a2a3a296bc8 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 01:06:05 2021 +0700 Add an example file for a graph displaying negative values commit 7b14fbd59a4c4c51962fd3110a1a900dd5f4eb98 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 11:18:26 2021 +0700 Add an example file for graphs with step_width < 1 commit dfd9a54544b637e2fbe3d1325f9cc24b3f4d2313 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 10:17:38 2021 +0700 Add an example file for graph.stack=false/true comparison Should also demonstrate that multiple data groups are possible for non-stacked graphs too. commit 5b532106c3fd255f9b66a66cfb88d977b489ab5d Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 09:34:14 2021 +0700 Add an example file for disabling graph data groups with nil colors commit ac6f1083c02fcb428f089a616f9b0ebaa5496b6c Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:57:25 2021 +0700 Add an example file for step_shape/width/spacing on stacked graphs commit b3703db6a5139e73e72c1cfd198ece2cd9be7df2 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:43:56 2021 +0700 Add an example file for scale=false/true on stacked graph commit b000aed4a12cb44fb501e1c8432a71a14b2f8f3f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 20 08:22:19 2021 +0700 Document deprecation of graph's width and height commit cb0ad617bd6c83a499896797318e3b0b588c3566 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 07:53:37 2021 +0700 Deprecate width and height properties of wibox.widget.graph commit 8c64e14ad91a074b784166a7054e53444cccb9d2 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 04:37:59 2021 +0700 Refactor graph:draw() to separate concerns Firstly, summation of group values for stacked graphs is extracted into graph:preprocess_values(). This makes graph:draw() smaller and exposes the fact, that stacked graphs are almost entirely just a form of a more general concept: pre-render data manipulation. It's something a user might want to customize, and now they can do it by monkey-patching the method. Secondly, the logic for deciding, if and with which color a data group should be drawn, is extracted into graph:should_draw_data_group() and get_data_group_color() methods respectively. This makes it customizable too and makes more obvious the fact, that it is a general thing that has nothing to do with stacked graphs in particular. With this there are only 5 lines remaining in graph:draw(), that could be said to be specific to stacked graphs, all pertaining to the `prev_y` variable, which merely preserves the bar coordinates from a previous data group. commit 2a32b6305d893f413303728a0a3da1dcd9a5591e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 22:52:37 2021 +0700 Pointlessly optimize graph:add_value() 1) Purge old values first, then insert the new one, if needed. Since Lua grows arrays in power of two steps, and our guess_capacity() also takes care to return even integers, it would be a shame to overstep the capacity by one by inserting first and thus getting the table size doubled only to never use the most of it. 2) Use a removal procedure that doesn't call the slow length operator at all. Previous code called it twice (once in table.remove) for every removed value. commit 680ea8f5d81e9040afb4662b3229e937e4b89e02 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:23:38 2021 +0700 Set graph data series color before the painting loop commit b970f0f221ae76250f2a94433ff79d653caa6e21 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:05:37 2021 +0700 Drop a large no longer necessary `if` in graph:draw_values() This commit is almost purely a whitespace change. commit 6b4f0541c70ec99d7f6e276bae64939f337120d6 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:00:50 2021 +0700 Extract values rendering code into graph:draw_values() method This is to avoid ungodly if nesting levels, which are needed to bail out of drawing values early and skip to drawing the border. The next commit will get rid of the if. commit e0950e4f99d1ea1c4b8cea2cc67629fa40030c2a Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 14:50:20 2021 +0700 Track graph draw() usage to guess necessary array capacity The last step of divorcing add_values() from width calculations, while ensuring that user doesn't observe a lack of data to draw. The widget now tracks an approximate number of values that it has been asked to draw() in _private.last_drawn_values_num, and truncates its values array based on that (if not overridden by the capacity property). commit 2e3d542282f35e7c0fed454da0f2eada0fcf3b78 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 13:35:12 2021 +0700 Add `capacity` property to graph widget This is the second step of divorcing add_values() from calculations involving widths. How many values are stored and how many values are drawn are distinct concepts. There isn't even a single definitive "how many values are drawn" because the widget could be rendered in several places with distinct widths simultaneously, and in every such place the data must be drawn appropriately, e.g. with auto-scaling, that takes only the visible data points into account. And the draw() method can do that now. The default capacity value of nil uses heuristics based on widget's _private.width to guess some capacity that would tolerate some dynamic widget resizing. If the user finds the heuristic failing them, they can set the capacity property to the desired maximum number of values that will be kept in the _private.values array (in each data group). commit 08d4a80686f7fe363551c35589a460839f60853e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 03:27:18 2021 +0700 Extract values size computation into compute_drawn_values_num() This is the first step of making add_values() not concern itself (directly) with widget dimensions. The user can monkey-patch the method to override how many values are (logically) drawn, because it affects graph auto-scaling and can be useful for rendering of step_shape-s that paint outside of their step_width. I didn't make it a full-blown function-valued property, because it seems a fringe functionality, which I'm too lazy to document. commit 3c5617c98ab1147618ed22e8414a77fc56b45f6e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 02:32:29 2021 +0700 Copy graph widget prototype methods over in bulk commit 169538837ece41a03d02ba8fbc0e4097062b67a3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun Apr 25 05:58:01 2021 +0700 Remove graph.baseline_y property After giving it some thought I've concluded, that it is unlikely to be useful as it is now. I'd better reserve the name for some more sensible functionality, like fixing the baseline_value defined axis at the given widget coordinate, adjusting the scaling accordingly, so min_value/max_value won't generally correspond to widget top and bottom anymore. commit 202ffaa9a0b820c7c471e1121d01b91f8f76cd38 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 12:06:55 2021 +0700 Add baseline_y and baseline_value properties to graph widget Now values in graph have a *baseline*, tunable with baseline_y and baseline_value properties. It is the (for now invisible) horizontal line from which all bars in the graph grow. I call it a "baseline" instead of "axis", because it doesn't actually have to correspond to the 0 value. But it can, and actually does now by default. baseline_y and baseline_value properties determine the vertical placement of the baseline in the widget. baseline_y is the position of the baseline in normalized widget coordinates, e.g. 0 corresponds to widget's top, 1 - to widget's bottom, and 0.5 - to widget's middle. So if e.g. one sets baseline_y = 0, then no matter how the widget is resized and how its values are scaled, bars in it will be drawn starting from the top. baseline_value is the position of the baseline in value coordinates. So if one sets baseline_value = 10, then no matter what happens, bars for values greater than 10 will be drawn as growing up from this line, and bars for values smaller as growing down. Where and if it will be seen in the widget, depends on the scaling. baseline_value = 0 is the familiar zero axis. baseline_y is unset by default and has priority, i.e. if it's set, then baseline_value is ignored. baseline_value defaults to 0. Prior to this commit, bars were always drawn from the bottom edge of the widget, or, in the new terms, the widget had baseline_y = 1. But it was indistinguishable from baseline_value = 0, because the widget clamped all negative values to 0, so the difference between the two could never be observed. baseline_value = 0 is clearly more useful and familiar to people, so now it is in force by default. commit df7e19abaebf0a871b5ca217b4cc3365533267a6 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 11:18:12 2021 +0700 Move value clamping from graph:add_value() into draw() Previously all values were forcibly clamped between 0 (not tunable at all) and max_value (1 by default). Thus graphs with negative values were not an option. NaN values were conflated with zeros, even though it may be useful to maintain the distinction and render them differently. And if user happened to dynamically change the max_value property, they discovered, that their data got truncated. Preserving user data as it was given is more flexible and makes things easier to debug. This commit does exactly that. So what happens now to the newly allowed values during draw()? There are 3 new cases to consider: 1) NaNs 2) Negatives 3) Values outside of min_value..max_value range NaNs are presently simply not drawn. An empty place is reserved where the value should've been, but nothing else is done. This is distinct from how a zero is handled, e.g. if step_shape is set, it is called with the height parameter = 0, which can still end up drawing something, depending on the shape. One could also consider addition of nan_color/nan_shape properties which could be drawn to alert user to a gap in their data. Negative values necessitate introduction of the concept of the zero axis, from which they will be drawn DOWN, in contrast to positive values. Where should this axis be drawn? How should step_shape-s with inherent direction, e.g. arrows, be drawn for negatives? This will be addressed in the next commit. Values outside of min/max_value range can imply shapes that go beyond widget dimensions. An arrow shape that goes beyond the roof is visibly different from an arrow that saturates and merely touches the widget top, and the user may reasonably desire either behavior. Therefore this commit introduces a boolean clamp_bars property, which defaults to true for reasons of robustness and backward compatibility with the behaviour prior to this commit. The property of course affects only the display of the values, the actual user data stays intact. commit c9c24fb1ab741cd6bb8074971d568e0646c51532 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:29:01 2021 +0700 Use rectangles for drawing graph bars of all sizes I have benchmarked various bar drawing strategies and graph sizes and found out that drawing graph bars with rectangles is as fast or even marginally faster than using fat lines. Thus the code that tries to special-case 1px bars as lines seems to be not worth the additional complexity it introduces, and this commit removes it. Incidentally, I've also established, that rectangles are more robust. If one draws graph bars and feeds cairo very large coordinates (~2^62), then corresponding rectangles fail to render, but all the other rectangles in the same series get rendered successfully, whereas a single large coordinate in a move_to/line_to() sequence is enough to cause all lines in the batch to fail to render. commit bc7f4be5e06405b5aa7fb443a5ace2b9b9575b1f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:01:42 2021 +0700 Cache cairo methods used in the inner loop of graph:draw() I have benchmarked the draw() method, both on cairo xcb and on argb32 image surfaces, and have discovered that cairo context method calls are unnaturally slow, to the point, that almost nothing else in the function matters. For example, drawing bars with vertical lines made the whole draw() function almost 2 times slower than doing the same with rectangles, merely because the former requires two method calls (move_to/line_to), and the latter only one. Simply caching hotpath cairo methods in local variables, reduced the difference between the drawing methods to a negligible one and made the whole function 4 times faster on a typical 100-bar graph, (1.4ms to 0.33ms on my laptop) thus bringing closer my secret dream of drawing live audio data with awesome graphs (kidding). I guess, LGI does something not very optimal there, like gobject-introspecting the method every time, or something. There might be some untapped optimization potential, possibly in upstream, like a method cache, from which everything in awesome could benefit, even though I imagine not many things are so call-intensive as the graph widget, to really feel the difference. commit 538df25bdf02b53d0bbe81809eef2e6fdb05ff6f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 07:32:37 2021 +0700 Use 1-based index in the inner graph draw loop It's used there only once for a calculation and then several times to index some tables, and index 0 is bad for lua tables performance-wise. commit 7412546916cd0a0a490130a120255ffb719398b8 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:55:12 2021 +0700 Leave graph.max_value uninitialized at construction This is a user-visible change, but the previous behavior was probably not what user expected anyway, namely with graph.scale set the graph mysteriously couldn't zoom in onto values that are smaller than 1. commit eb81d7929a161e79c13262cfa73ac2e6a820e8ed Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:48:29 2021 +0700 Allow graph.max_value be nil This makes its behave symmetrically to min_value. Now it defaults to 1, when graph.scale is not set, and to the maximum value in the graph data, when graph.scale is set. commit f622a8430cbfde7007b58d7f4c9c76b08c91e997 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 12:01:13 2021 +0700 Calculate max values num in graph:add_value() more precisely At most so many values were kept in the widget values array, as there are pixels of usable widget width (i.e. sans border). This is exactly right for the surely most common case of graphs with 1px-wide bars, but is more than needed for graphs with step_width+step_spacing>1, and not enough for graphs with subpixel bars. Moreover this made graph autoscaling behave weirdly in graphs with thicker bars, because old values survived longer in the array and kept affecting min_value/max_value calculations even after they were shifted off-screen. The maximum number of kept values is now calculated exactly as the number of values that can be displayed at once, taking step_width and step_spacing into account. It might be worth adding a max_values_num property to the graph to let user override this calculation, in case they draw shapes in step_shape() outside of its supposed rectangle, so our assumptions of visibility are wrong, or the widget often gets resized, resulting in the annoyance of visibly truncated series. commit 0aa63249a18641251a1bf77423739164f0eab39f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 02:30:13 2021 +0700 Fix border_color/border_width bugs in graph widget add_value() wrongly assumed that border_width is always set, when border_color is, so if user set only the latter, an error occurred. add_value() assumed that border_width is always < width which led to awesome hanging up in an infinite loop, should that fail to be the case. The graph border_width property was driven by the border_color property, instead of the other way round. If border_color wasn't set, the border wasn't drawn all, even if user sets non-zero border_width, which is a confusing behavior, in contrast to all other widgets, which simply use a plethora of fallback colors to draw the border, when needed. This also meant that, despite being documented, the beautiful.graph_border_color fallback color could not and was not ever used. If the border_color was set though, an unset border_width was immediately assumed to be 1 instead of 0, also contrary to what other widgets do. Theme colors shouldn't influence widget layout, so this commit fixes this behavior to: 1) border_width always defaults to 0, if unset. 2) border_color falls back to beautiful.graph_border_color or white. Maybe one should also add a beautiful.graph_border_width theme variable, like some widgets do. commit dad4a3253b8fef971fb82720d2a407fcc0a879ad Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 03:11:27 2021 +0700 Implement multiple data series drawing for non-stacked graph The widget can already hold and draw multiple data groups, so it makes little sense to limit drawing only to the first group when graph.stack = false, probably leading to user's confusion. Now user can call add_value(v, group) with group > 1 and the data series will be drawn (over groups with lesser id-s) with graph.stack_colors[group] color, if stack_colors is set, or with graph.color, if stack_colors isn't set. Thus nothing visibly changes for all current users of non-stacked graphs with only single data series, but the new functionality is now there for those who need it. One can't fully emulate this functionality, e.g. with multiple graphs widgets in a wibox.layout.stack, because data series in a single widget can enjoy a common auto-scaling. commit 3e645c976184ab5f50d625271c25062db9ab7ed9 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:59:12 2021 +0700 Don't draw a stacked graph data group if there's no color for it If stack_colors[idx] is nil for the group `idx`, don't draw it at all (as opposed, e.g. to the effect of drawing it with a transparent color, or as though it had all values == 0, which both produce different results). This code is still subtly different from prior behavior, because now user can have a non-contiguous stack_colors table and turn the display of any data group on and off at will, whereas the code two commits earlier would just stop rendering as soon as ipairs(stack_colors) does, i.e. no data series were drawn past the first nil hole. This commit only looks large, but is mostly a whitespace change due to nesting from a pair of wrapping ifs. commit 8c0273786de1ade66bdd977699f98b588da9f487 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:32:42 2021 +0700 Unify stacked and non-stacked graph rendering Now stacked graphs support everything the nonstacked do, like step_width ~= 1 and custom step_shape-s. Technical notes: This code no longer calls cr:stroke() for every single value in a stacked graph, but rather paints a whole series and then cr:stroke/fill()-s it at all once with its color, improving performance considerably. Additionally the graph no longer relies on the presence of the stack_colors property to render series. It draws all data that it has, and, if there's no corresponding color in stack_colors, falls back to graph.color. This will be changed in the next commit to the prior behavior of only drawing those series for which there's a color in the stack_colors table, but that would've made this commit's diff much larger due to required nesting ifs over large code chunks. This commit also takes care to properly handle NaNs and negative values. Those are currently (almost) guaranteed to not occur in the values array due to clipping in graph:add_value(), but I intend to eventually drop the clipping, because it does nothing that draw() really relies upon, and allowing unclipped and NaN values is more flexible and less surprising, e.g. when user dynamically toggles graph.scale. Eventually I intend to implement drawing negative values in non-stacked graphs and possibly even in the stacked ones. commit 38c6589c51b2aa057d7c2f40182ecf32b5b2ff5e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 10:04:15 2021 +0700 Convert graph:draw() to use `self` commit f21f1b2550426df1c4ea8abde079b1ccef84b394 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 12:27:26 2021 +0700 Fix scaling for stacked graphs Stacked graphs have cumulative heights, values from all groups that end up in the same graph bar are effectively summed. Previous code didn't take that into account when calculating min/max_value for the purposes of rescaling, but simply examined every value separately. This code adds up all values up front and finds min/max among those, to determine proper scaling that would fit the graph exactly. commit 1ce4c7bf63bdd6c99c195d668347bec49355e921 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 09:49:35 2021 +0700 Draw shaped graph bars only up to the baseline Prior to this commit, every step_shape bar was, for some reason, drawn with height = full widget's height, regardless of the value it represents. So for the most bars their lower end was far outside the box and therefore clipped off. So e.g. one couldn't see the lower round edges of rounded rectangles. This commit fixes that by appropriately varying the height that is passed into step_shape(), so that lower edges of the bars always exactly touch the lower edge of the graph. If the user wants to restore the prior behavior, they can simply ignore the passed-in height in step_shape() and just always draw their shape with height = full widget height. But they couldn't replicate the effect of this commit in their rc.lua without essentially writing their own graph widget. commit e25185b08da23745b5815f85f6e9e43222ed2294 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 08:42:54 2021 +0700 Avoid incremental transform shifting during graph bar rendering Instead of letting addition errors and cognitive load accumulate simply use the same offset calculation that is used for lines and reset the transform back to the top-left corner after every drawn bar. It's conceptually simpler, and will be useful later for stacked graphs and more. It also fixes a discrepancy between plain and step_shape rendering. Prior to this commit, bar coloring occurred with a disturbed transform, which was centered at the last drawn bar, wherever it happened to be. This meant that nontrivial colors, like gradients and images, were arbitrarily offset when step_shape was used. commit f3e98866d8583c35d0b557c09f529299edc9ba2c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:45:29 2021 +0700 Refactor out graph bar height expression into a variable commit f54528d6892f5aaa4595917853c9e2130f743fdb Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:20:25 2021 +0700 Draw graph bars with lines only when step_width == 1 Before this commit graph bars of step_width < 1 were drawn with lines of width 1, but that's a bit unfortunate, because it led to much overpainting, while still ending up with a graphically incorrect result. Let's draw such bars with rectangles and let cairo do things with subpixels, getting more graphically accurate result, while still keeping the common case of step_width == 1 (supposedly) fast-drawn with lines. commit b6515dca67c18b78eb5657378131a62ab43d4cfc Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:54:07 2021 +0700 Fix unsharp edges when drawing graph bars with rectangles The 0.5 offset is only helpful for 1-width strokes, not for fills. commit 7f3db8f07e82f821bcd1dc418d66564f537609d6 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:21:01 2021 +0700 Do coordinate transformations even if the value isn't drawn The graph presently doesn't draw values < 0, but there was a discrepancy between drawing with and without step_shape. The default line/rectangle drawing reserves an empty place where the skipped value should've been, but no such empty place is reserved, when step_shape is used. This commit fixes that. Skipped values always have their empty space now. commit 7b731ebfe96af7a9dba9c57371da6761cf22c00c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:03:30 2021 +0700 Fix graph bar scaling when min_value is not 0 Previous calculation lead to underscaling of values so that even value = max_value wasn't drawn to full height. commit dca59fd8b7fff0e7e646f2dfc6bd92047028e5a8 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:18:40 2021 +0700 Calculate the horizontal graph bar offset only when needed commit 940ddf4b9825e9c3a91d276208c089d8c74e49db Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:17:23 2021 +0700 Call cr:move_to() only when drawing the graph with lines It's redundant otherwise and might even interfere with user's drawing in step_shape(). commit b5d5cd10ca29fb29cd751faaa95137c2fe89ff63 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:04:06 2021 +0700 Fix wrong horizontal offset calculations in widget.graph Indices in the loop are zero-based, so things like i-1 and i>1 aren't necessary and actually wouldn't have been correct even in a 1-based loop. step_shape = gears.shape.rectangle and step_shape = nil, which is supposed to default to rectangle according to docs, were drawn differently due to mismatched offset calculations, which led to problems especially noticeable when step_spacing is not zero, e.g: 1) whole graph shifted left too much, drawn partially off-screen. 2) whole graph shifted right too much, and the first bar placed closer to the rest than others. commit dc369fa198281e000afc1bd17326f3a264adf7d1 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 02:09:21 2021 +0700 Fix the "border_width affects graph bar width" bug Graph bars were drawn with lines of border_width thickness instead of 1, i.e. possibly too fat to be readable. commit 1ba73378cf29ed27a6ecd18716394779ceb543d9 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 00:13:03 2021 +0700 Simplify internal values representation in graph even more New values in graph._private.values were pushed to the end, and old values were removed from the beginning of the table. In draw() however newer values are drawn first, at the left edge of the graph, necessitating annoying idx = #values - idx conversions everywhere. In particular in stacked graph that means that values that are drawn together may have different indices. One has to shift the values array in add_value() either way, so one might as well insert new values at the beginning of the array and simplify the rest of the code. This doesn't seem like much yet, but it will prove to be much more convenient and less error-prone in what I'm going to do later. commit f70523ee68dbd6b67050ace4b82903a9b22440c7 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 13:35:10 2021 +0700 Simplify internal values representation in graph widget graph._private.values was either a table of values or a table of tables of values, depending on whether "stack" property is enabled, ... or a mix of both if user forgot to pass the second "group" parameter to graph:add_value(). This resulted in pretty fragile and confusing behavior, e.g. leaving user to wonder, why they see nothing more after setting stack=true for a graph in a previously working rc.lua, and demanding a very careful use of add_value() from the user, or else some values or even entire stacks would mysteriously vanish and errors in draw() get triggered, sometimes after a period of seemingly normal functioning. Now `values` is always a table of tables and the group parameter in add_value() defaults to 1, thus making the non-stacked graph a trivial variant of the stacked graph.
2021-05-20 17:14:03 +02:00
gdebug.deprecate("Use a `wibox.container.constraint` widget or `forced_width`", {deprecated_in=5})
if awesome.api_level <= 5 then
if width >= 5 then
-- this sends "layout_changed" for us
self:set_forced_width(width)
-- signal, because we did it before
self:emit_signal("property::width", width)
end
return self
2016-05-23 07:30:44 +02:00
end
end
graph: complete widget overhaul and bug fixes Squashed commit of the following: commit 69821f51fe1e8652715658543e50631ece495090 Author: Alex Belykh <albel727@ngs.ru> Date: Mon May 10 19:17:55 2021 +0700 Refactor property handling in wibox.widget.graph In addition, baseline_value, step_width and step_spacing properties are all set to their default non-nil numeric values during instance creation now. commit 842eb429bf5df4b2ba46b2e6f7646afe89b1a3c3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 9 06:18:45 2021 +0700 Make graph:draw_values() private There's no documentation nor confidence that it's a good API, so I'm hiding it for now. commit 720746780574e4bad14a71fc4174955139c2eb50 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 04:04:13 2021 +0700 Document graph_should_draw_data_group() for posterity commit 7c73f3754ce6ba86b118f1170a6f9e406ab4841d Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:59:21 2021 +0700 Make graph:should_draw_data_group() private There's no confidence whether it's a good API, so I'm hiding it for now. commit b3539e20adb6423118af726ae4f8751ef864b5d2 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:36:44 2021 +0700 Make graph:preprocess_values() private There's no confidence whether it's a good API, so I'm hiding it for now. commit 2df4400e780a6260f8d0ab1c60734fbb11bf3c4f Author: Alex Belykh <albel727@ngs.ru> Date: Thu May 6 10:05:23 2021 +0700 Add spec/wibox/widget/graph_spec.lua commit ce01a9771ab439b8df841f1565aa4aa8a44a5e0d Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 08:16:18 2021 +0700 Improve graph.capacity documentation commit e28fade8f655802f5bf89154169f68d256b8e332 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 07:36:01 2021 +0700 Document graph:compute_drawn_values_num() commit dd68332292465422d08b48e9f0229f143beccbc6 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 05:24:23 2021 +0700 Document graph:pick_data_group_color() method commit ca6dc55f77aca88a6e9cc90bb6fd0045739f41f5 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 04:13:58 2021 +0700 Guard against setting data groups sparsely in graph:add_value() commit cdea5f126f7984e72924f6dffbd312c4fe3b032f Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 02:36:12 2021 +0700 Refactor scaling and baseline choice out of graph:draw() commit 10135deafe8a9390ba66ab657ebb1fb41d549e8c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 21:34:48 2021 +0700 Document graph.nan_color and graph.stack properties commit 0d73bb84ac66b87e16aee472bd1f299549f66a1c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 20:49:24 2021 +0700 Use a better example in docs for the graph.stack property commit c27d160a0826d033d49d6141608c40aa8a0642fa Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 16:05:38 2021 +0700 Document graph.baseline_value property commit 459bc8176c13d4ea32af0dcdc2399d3b929b1c6a Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:53:47 2021 +0700 Document graph.clamp_bars property commit e732ee0b3b007d213e689b98cc00ae7cb141ae08 Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:34 2021 +0700 Improve graph:add_value() and :clear() documentation commit ae73e3aa9f8d60409cbe4cb4da9612f31af4f38e Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:11 2021 +0700 Improve graph min_value/max_value/scale documentation commit a7350996a9def773b91b1631e6c0507e33d8f93b Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:27:36 2021 +0700 Cleanup graph examples to show only relevant things in docs commit 30740c26f4f41c16f4c8a3c339bcb7bd299e14cd Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 14:04:36 2021 +0700 Rename graph.stack_colors property to `group_colors` The colors are used for stacked and non-stacked graphs alike, so the name didn't make sense. Also document and clarify some color stuff. commit 1ea01bfd410971cf0d0c25fd74cd764d5eb7279d Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 21:25:41 2021 +0700 Add an example file for code coverage of some fringe graph cases commit eae68e39553f88a6275a0c65bf040b5d4c262b54 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 20:23:32 2021 +0700 Add step_shape to the graph/nan_color example for code coverage commit 8027147bc2eab34a97b49a428fb8cef27f122bed Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:55:18 2021 +0700 Add example files for graph.nan_color/nan_indication properties commit 89a25a165c52b2c15b6c4d95ecb777a9067e66f9 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:33:07 2021 +0700 Make widget graph:add_value(v) default v to NaN 0 as a default makes no sense and silences the error that is failing to specify a value, to which the user should rather be alerted. The graph widget has sane NaN handling now and will do just that. commit 2835d552440be48d356a7f7b1d64963696dd550b Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:20:48 2021 +0700 Implement graph.nan_color and nan_indication properties Now, whenever there's a NaN among values and graph.nan_indication is set, the corresponding area, where the value bar should have been drawn, is filled with the nan_color from top to bottom. The painting is done after all data is rendered, to make sure that it won't be overpainted and go unnoticed. NaNs among graph's values are inevitably a sign of some error and it is arguably not a sane default behavior to simply not render them at all. The user should take immediate note of any errors by default, instead of wondering at a mysteriously empty graph widget, which is why nan_indication is set to true by default. If the user wants to silence NaNs, they can always simply set it to false. nan_color if unset, defaults to a noticeable yellow-black pattern. As a direct and planned consequence, stacked graphs with negative values in them trigger the NaN handling too. But silencing NaN handling for those graphs is also possible and semi-sane, because then NaNs and negatives are ignored and the graph behaves (almost) like they were zeros. commit 9c1f8f08b400e01e19c534f3810c9c9ab485760c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 26 08:10:19 2021 +0700 Refactor out graph_map_value_to_widget_coordinates() function commit 1905991ddecabdae3e15ffb8ee5b18a1bee1a711 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 28 13:39:29 2021 +0700 Add an example file for graph.baseline_value property commit b31b39d66f4c037a9484b71a3f910703ae01fac0 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 07:03:49 2021 +0700 Add an example file for graph.clamp_bars=false/true comparison commit a1fc850e3047f9ee2c9ddeffacfbabf3ded34171 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 04:06:45 2021 +0700 Add an example file for graphs with negative values and shapes commit 2f012f8ff1bb5094c14e724a61e59a2a3a296bc8 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 01:06:05 2021 +0700 Add an example file for a graph displaying negative values commit 7b14fbd59a4c4c51962fd3110a1a900dd5f4eb98 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 11:18:26 2021 +0700 Add an example file for graphs with step_width < 1 commit dfd9a54544b637e2fbe3d1325f9cc24b3f4d2313 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 10:17:38 2021 +0700 Add an example file for graph.stack=false/true comparison Should also demonstrate that multiple data groups are possible for non-stacked graphs too. commit 5b532106c3fd255f9b66a66cfb88d977b489ab5d Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 09:34:14 2021 +0700 Add an example file for disabling graph data groups with nil colors commit ac6f1083c02fcb428f089a616f9b0ebaa5496b6c Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:57:25 2021 +0700 Add an example file for step_shape/width/spacing on stacked graphs commit b3703db6a5139e73e72c1cfd198ece2cd9be7df2 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:43:56 2021 +0700 Add an example file for scale=false/true on stacked graph commit b000aed4a12cb44fb501e1c8432a71a14b2f8f3f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 20 08:22:19 2021 +0700 Document deprecation of graph's width and height commit cb0ad617bd6c83a499896797318e3b0b588c3566 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 07:53:37 2021 +0700 Deprecate width and height properties of wibox.widget.graph commit 8c64e14ad91a074b784166a7054e53444cccb9d2 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 04:37:59 2021 +0700 Refactor graph:draw() to separate concerns Firstly, summation of group values for stacked graphs is extracted into graph:preprocess_values(). This makes graph:draw() smaller and exposes the fact, that stacked graphs are almost entirely just a form of a more general concept: pre-render data manipulation. It's something a user might want to customize, and now they can do it by monkey-patching the method. Secondly, the logic for deciding, if and with which color a data group should be drawn, is extracted into graph:should_draw_data_group() and get_data_group_color() methods respectively. This makes it customizable too and makes more obvious the fact, that it is a general thing that has nothing to do with stacked graphs in particular. With this there are only 5 lines remaining in graph:draw(), that could be said to be specific to stacked graphs, all pertaining to the `prev_y` variable, which merely preserves the bar coordinates from a previous data group. commit 2a32b6305d893f413303728a0a3da1dcd9a5591e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 22:52:37 2021 +0700 Pointlessly optimize graph:add_value() 1) Purge old values first, then insert the new one, if needed. Since Lua grows arrays in power of two steps, and our guess_capacity() also takes care to return even integers, it would be a shame to overstep the capacity by one by inserting first and thus getting the table size doubled only to never use the most of it. 2) Use a removal procedure that doesn't call the slow length operator at all. Previous code called it twice (once in table.remove) for every removed value. commit 680ea8f5d81e9040afb4662b3229e937e4b89e02 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:23:38 2021 +0700 Set graph data series color before the painting loop commit b970f0f221ae76250f2a94433ff79d653caa6e21 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:05:37 2021 +0700 Drop a large no longer necessary `if` in graph:draw_values() This commit is almost purely a whitespace change. commit 6b4f0541c70ec99d7f6e276bae64939f337120d6 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:00:50 2021 +0700 Extract values rendering code into graph:draw_values() method This is to avoid ungodly if nesting levels, which are needed to bail out of drawing values early and skip to drawing the border. The next commit will get rid of the if. commit e0950e4f99d1ea1c4b8cea2cc67629fa40030c2a Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 14:50:20 2021 +0700 Track graph draw() usage to guess necessary array capacity The last step of divorcing add_values() from width calculations, while ensuring that user doesn't observe a lack of data to draw. The widget now tracks an approximate number of values that it has been asked to draw() in _private.last_drawn_values_num, and truncates its values array based on that (if not overridden by the capacity property). commit 2e3d542282f35e7c0fed454da0f2eada0fcf3b78 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 13:35:12 2021 +0700 Add `capacity` property to graph widget This is the second step of divorcing add_values() from calculations involving widths. How many values are stored and how many values are drawn are distinct concepts. There isn't even a single definitive "how many values are drawn" because the widget could be rendered in several places with distinct widths simultaneously, and in every such place the data must be drawn appropriately, e.g. with auto-scaling, that takes only the visible data points into account. And the draw() method can do that now. The default capacity value of nil uses heuristics based on widget's _private.width to guess some capacity that would tolerate some dynamic widget resizing. If the user finds the heuristic failing them, they can set the capacity property to the desired maximum number of values that will be kept in the _private.values array (in each data group). commit 08d4a80686f7fe363551c35589a460839f60853e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 03:27:18 2021 +0700 Extract values size computation into compute_drawn_values_num() This is the first step of making add_values() not concern itself (directly) with widget dimensions. The user can monkey-patch the method to override how many values are (logically) drawn, because it affects graph auto-scaling and can be useful for rendering of step_shape-s that paint outside of their step_width. I didn't make it a full-blown function-valued property, because it seems a fringe functionality, which I'm too lazy to document. commit 3c5617c98ab1147618ed22e8414a77fc56b45f6e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 02:32:29 2021 +0700 Copy graph widget prototype methods over in bulk commit 169538837ece41a03d02ba8fbc0e4097062b67a3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun Apr 25 05:58:01 2021 +0700 Remove graph.baseline_y property After giving it some thought I've concluded, that it is unlikely to be useful as it is now. I'd better reserve the name for some more sensible functionality, like fixing the baseline_value defined axis at the given widget coordinate, adjusting the scaling accordingly, so min_value/max_value won't generally correspond to widget top and bottom anymore. commit 202ffaa9a0b820c7c471e1121d01b91f8f76cd38 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 12:06:55 2021 +0700 Add baseline_y and baseline_value properties to graph widget Now values in graph have a *baseline*, tunable with baseline_y and baseline_value properties. It is the (for now invisible) horizontal line from which all bars in the graph grow. I call it a "baseline" instead of "axis", because it doesn't actually have to correspond to the 0 value. But it can, and actually does now by default. baseline_y and baseline_value properties determine the vertical placement of the baseline in the widget. baseline_y is the position of the baseline in normalized widget coordinates, e.g. 0 corresponds to widget's top, 1 - to widget's bottom, and 0.5 - to widget's middle. So if e.g. one sets baseline_y = 0, then no matter how the widget is resized and how its values are scaled, bars in it will be drawn starting from the top. baseline_value is the position of the baseline in value coordinates. So if one sets baseline_value = 10, then no matter what happens, bars for values greater than 10 will be drawn as growing up from this line, and bars for values smaller as growing down. Where and if it will be seen in the widget, depends on the scaling. baseline_value = 0 is the familiar zero axis. baseline_y is unset by default and has priority, i.e. if it's set, then baseline_value is ignored. baseline_value defaults to 0. Prior to this commit, bars were always drawn from the bottom edge of the widget, or, in the new terms, the widget had baseline_y = 1. But it was indistinguishable from baseline_value = 0, because the widget clamped all negative values to 0, so the difference between the two could never be observed. baseline_value = 0 is clearly more useful and familiar to people, so now it is in force by default. commit df7e19abaebf0a871b5ca217b4cc3365533267a6 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 11:18:12 2021 +0700 Move value clamping from graph:add_value() into draw() Previously all values were forcibly clamped between 0 (not tunable at all) and max_value (1 by default). Thus graphs with negative values were not an option. NaN values were conflated with zeros, even though it may be useful to maintain the distinction and render them differently. And if user happened to dynamically change the max_value property, they discovered, that their data got truncated. Preserving user data as it was given is more flexible and makes things easier to debug. This commit does exactly that. So what happens now to the newly allowed values during draw()? There are 3 new cases to consider: 1) NaNs 2) Negatives 3) Values outside of min_value..max_value range NaNs are presently simply not drawn. An empty place is reserved where the value should've been, but nothing else is done. This is distinct from how a zero is handled, e.g. if step_shape is set, it is called with the height parameter = 0, which can still end up drawing something, depending on the shape. One could also consider addition of nan_color/nan_shape properties which could be drawn to alert user to a gap in their data. Negative values necessitate introduction of the concept of the zero axis, from which they will be drawn DOWN, in contrast to positive values. Where should this axis be drawn? How should step_shape-s with inherent direction, e.g. arrows, be drawn for negatives? This will be addressed in the next commit. Values outside of min/max_value range can imply shapes that go beyond widget dimensions. An arrow shape that goes beyond the roof is visibly different from an arrow that saturates and merely touches the widget top, and the user may reasonably desire either behavior. Therefore this commit introduces a boolean clamp_bars property, which defaults to true for reasons of robustness and backward compatibility with the behaviour prior to this commit. The property of course affects only the display of the values, the actual user data stays intact. commit c9c24fb1ab741cd6bb8074971d568e0646c51532 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:29:01 2021 +0700 Use rectangles for drawing graph bars of all sizes I have benchmarked various bar drawing strategies and graph sizes and found out that drawing graph bars with rectangles is as fast or even marginally faster than using fat lines. Thus the code that tries to special-case 1px bars as lines seems to be not worth the additional complexity it introduces, and this commit removes it. Incidentally, I've also established, that rectangles are more robust. If one draws graph bars and feeds cairo very large coordinates (~2^62), then corresponding rectangles fail to render, but all the other rectangles in the same series get rendered successfully, whereas a single large coordinate in a move_to/line_to() sequence is enough to cause all lines in the batch to fail to render. commit bc7f4be5e06405b5aa7fb443a5ace2b9b9575b1f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:01:42 2021 +0700 Cache cairo methods used in the inner loop of graph:draw() I have benchmarked the draw() method, both on cairo xcb and on argb32 image surfaces, and have discovered that cairo context method calls are unnaturally slow, to the point, that almost nothing else in the function matters. For example, drawing bars with vertical lines made the whole draw() function almost 2 times slower than doing the same with rectangles, merely because the former requires two method calls (move_to/line_to), and the latter only one. Simply caching hotpath cairo methods in local variables, reduced the difference between the drawing methods to a negligible one and made the whole function 4 times faster on a typical 100-bar graph, (1.4ms to 0.33ms on my laptop) thus bringing closer my secret dream of drawing live audio data with awesome graphs (kidding). I guess, LGI does something not very optimal there, like gobject-introspecting the method every time, or something. There might be some untapped optimization potential, possibly in upstream, like a method cache, from which everything in awesome could benefit, even though I imagine not many things are so call-intensive as the graph widget, to really feel the difference. commit 538df25bdf02b53d0bbe81809eef2e6fdb05ff6f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 07:32:37 2021 +0700 Use 1-based index in the inner graph draw loop It's used there only once for a calculation and then several times to index some tables, and index 0 is bad for lua tables performance-wise. commit 7412546916cd0a0a490130a120255ffb719398b8 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:55:12 2021 +0700 Leave graph.max_value uninitialized at construction This is a user-visible change, but the previous behavior was probably not what user expected anyway, namely with graph.scale set the graph mysteriously couldn't zoom in onto values that are smaller than 1. commit eb81d7929a161e79c13262cfa73ac2e6a820e8ed Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:48:29 2021 +0700 Allow graph.max_value be nil This makes its behave symmetrically to min_value. Now it defaults to 1, when graph.scale is not set, and to the maximum value in the graph data, when graph.scale is set. commit f622a8430cbfde7007b58d7f4c9c76b08c91e997 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 12:01:13 2021 +0700 Calculate max values num in graph:add_value() more precisely At most so many values were kept in the widget values array, as there are pixels of usable widget width (i.e. sans border). This is exactly right for the surely most common case of graphs with 1px-wide bars, but is more than needed for graphs with step_width+step_spacing>1, and not enough for graphs with subpixel bars. Moreover this made graph autoscaling behave weirdly in graphs with thicker bars, because old values survived longer in the array and kept affecting min_value/max_value calculations even after they were shifted off-screen. The maximum number of kept values is now calculated exactly as the number of values that can be displayed at once, taking step_width and step_spacing into account. It might be worth adding a max_values_num property to the graph to let user override this calculation, in case they draw shapes in step_shape() outside of its supposed rectangle, so our assumptions of visibility are wrong, or the widget often gets resized, resulting in the annoyance of visibly truncated series. commit 0aa63249a18641251a1bf77423739164f0eab39f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 02:30:13 2021 +0700 Fix border_color/border_width bugs in graph widget add_value() wrongly assumed that border_width is always set, when border_color is, so if user set only the latter, an error occurred. add_value() assumed that border_width is always < width which led to awesome hanging up in an infinite loop, should that fail to be the case. The graph border_width property was driven by the border_color property, instead of the other way round. If border_color wasn't set, the border wasn't drawn all, even if user sets non-zero border_width, which is a confusing behavior, in contrast to all other widgets, which simply use a plethora of fallback colors to draw the border, when needed. This also meant that, despite being documented, the beautiful.graph_border_color fallback color could not and was not ever used. If the border_color was set though, an unset border_width was immediately assumed to be 1 instead of 0, also contrary to what other widgets do. Theme colors shouldn't influence widget layout, so this commit fixes this behavior to: 1) border_width always defaults to 0, if unset. 2) border_color falls back to beautiful.graph_border_color or white. Maybe one should also add a beautiful.graph_border_width theme variable, like some widgets do. commit dad4a3253b8fef971fb82720d2a407fcc0a879ad Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 03:11:27 2021 +0700 Implement multiple data series drawing for non-stacked graph The widget can already hold and draw multiple data groups, so it makes little sense to limit drawing only to the first group when graph.stack = false, probably leading to user's confusion. Now user can call add_value(v, group) with group > 1 and the data series will be drawn (over groups with lesser id-s) with graph.stack_colors[group] color, if stack_colors is set, or with graph.color, if stack_colors isn't set. Thus nothing visibly changes for all current users of non-stacked graphs with only single data series, but the new functionality is now there for those who need it. One can't fully emulate this functionality, e.g. with multiple graphs widgets in a wibox.layout.stack, because data series in a single widget can enjoy a common auto-scaling. commit 3e645c976184ab5f50d625271c25062db9ab7ed9 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:59:12 2021 +0700 Don't draw a stacked graph data group if there's no color for it If stack_colors[idx] is nil for the group `idx`, don't draw it at all (as opposed, e.g. to the effect of drawing it with a transparent color, or as though it had all values == 0, which both produce different results). This code is still subtly different from prior behavior, because now user can have a non-contiguous stack_colors table and turn the display of any data group on and off at will, whereas the code two commits earlier would just stop rendering as soon as ipairs(stack_colors) does, i.e. no data series were drawn past the first nil hole. This commit only looks large, but is mostly a whitespace change due to nesting from a pair of wrapping ifs. commit 8c0273786de1ade66bdd977699f98b588da9f487 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:32:42 2021 +0700 Unify stacked and non-stacked graph rendering Now stacked graphs support everything the nonstacked do, like step_width ~= 1 and custom step_shape-s. Technical notes: This code no longer calls cr:stroke() for every single value in a stacked graph, but rather paints a whole series and then cr:stroke/fill()-s it at all once with its color, improving performance considerably. Additionally the graph no longer relies on the presence of the stack_colors property to render series. It draws all data that it has, and, if there's no corresponding color in stack_colors, falls back to graph.color. This will be changed in the next commit to the prior behavior of only drawing those series for which there's a color in the stack_colors table, but that would've made this commit's diff much larger due to required nesting ifs over large code chunks. This commit also takes care to properly handle NaNs and negative values. Those are currently (almost) guaranteed to not occur in the values array due to clipping in graph:add_value(), but I intend to eventually drop the clipping, because it does nothing that draw() really relies upon, and allowing unclipped and NaN values is more flexible and less surprising, e.g. when user dynamically toggles graph.scale. Eventually I intend to implement drawing negative values in non-stacked graphs and possibly even in the stacked ones. commit 38c6589c51b2aa057d7c2f40182ecf32b5b2ff5e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 10:04:15 2021 +0700 Convert graph:draw() to use `self` commit f21f1b2550426df1c4ea8abde079b1ccef84b394 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 12:27:26 2021 +0700 Fix scaling for stacked graphs Stacked graphs have cumulative heights, values from all groups that end up in the same graph bar are effectively summed. Previous code didn't take that into account when calculating min/max_value for the purposes of rescaling, but simply examined every value separately. This code adds up all values up front and finds min/max among those, to determine proper scaling that would fit the graph exactly. commit 1ce4c7bf63bdd6c99c195d668347bec49355e921 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 09:49:35 2021 +0700 Draw shaped graph bars only up to the baseline Prior to this commit, every step_shape bar was, for some reason, drawn with height = full widget's height, regardless of the value it represents. So for the most bars their lower end was far outside the box and therefore clipped off. So e.g. one couldn't see the lower round edges of rounded rectangles. This commit fixes that by appropriately varying the height that is passed into step_shape(), so that lower edges of the bars always exactly touch the lower edge of the graph. If the user wants to restore the prior behavior, they can simply ignore the passed-in height in step_shape() and just always draw their shape with height = full widget height. But they couldn't replicate the effect of this commit in their rc.lua without essentially writing their own graph widget. commit e25185b08da23745b5815f85f6e9e43222ed2294 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 08:42:54 2021 +0700 Avoid incremental transform shifting during graph bar rendering Instead of letting addition errors and cognitive load accumulate simply use the same offset calculation that is used for lines and reset the transform back to the top-left corner after every drawn bar. It's conceptually simpler, and will be useful later for stacked graphs and more. It also fixes a discrepancy between plain and step_shape rendering. Prior to this commit, bar coloring occurred with a disturbed transform, which was centered at the last drawn bar, wherever it happened to be. This meant that nontrivial colors, like gradients and images, were arbitrarily offset when step_shape was used. commit f3e98866d8583c35d0b557c09f529299edc9ba2c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:45:29 2021 +0700 Refactor out graph bar height expression into a variable commit f54528d6892f5aaa4595917853c9e2130f743fdb Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:20:25 2021 +0700 Draw graph bars with lines only when step_width == 1 Before this commit graph bars of step_width < 1 were drawn with lines of width 1, but that's a bit unfortunate, because it led to much overpainting, while still ending up with a graphically incorrect result. Let's draw such bars with rectangles and let cairo do things with subpixels, getting more graphically accurate result, while still keeping the common case of step_width == 1 (supposedly) fast-drawn with lines. commit b6515dca67c18b78eb5657378131a62ab43d4cfc Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:54:07 2021 +0700 Fix unsharp edges when drawing graph bars with rectangles The 0.5 offset is only helpful for 1-width strokes, not for fills. commit 7f3db8f07e82f821bcd1dc418d66564f537609d6 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:21:01 2021 +0700 Do coordinate transformations even if the value isn't drawn The graph presently doesn't draw values < 0, but there was a discrepancy between drawing with and without step_shape. The default line/rectangle drawing reserves an empty place where the skipped value should've been, but no such empty place is reserved, when step_shape is used. This commit fixes that. Skipped values always have their empty space now. commit 7b731ebfe96af7a9dba9c57371da6761cf22c00c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:03:30 2021 +0700 Fix graph bar scaling when min_value is not 0 Previous calculation lead to underscaling of values so that even value = max_value wasn't drawn to full height. commit dca59fd8b7fff0e7e646f2dfc6bd92047028e5a8 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:18:40 2021 +0700 Calculate the horizontal graph bar offset only when needed commit 940ddf4b9825e9c3a91d276208c089d8c74e49db Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:17:23 2021 +0700 Call cr:move_to() only when drawing the graph with lines It's redundant otherwise and might even interfere with user's drawing in step_shape(). commit b5d5cd10ca29fb29cd751faaa95137c2fe89ff63 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:04:06 2021 +0700 Fix wrong horizontal offset calculations in widget.graph Indices in the loop are zero-based, so things like i-1 and i>1 aren't necessary and actually wouldn't have been correct even in a 1-based loop. step_shape = gears.shape.rectangle and step_shape = nil, which is supposed to default to rectangle according to docs, were drawn differently due to mismatched offset calculations, which led to problems especially noticeable when step_spacing is not zero, e.g: 1) whole graph shifted left too much, drawn partially off-screen. 2) whole graph shifted right too much, and the first bar placed closer to the rest than others. commit dc369fa198281e000afc1bd17326f3a264adf7d1 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 02:09:21 2021 +0700 Fix the "border_width affects graph bar width" bug Graph bars were drawn with lines of border_width thickness instead of 1, i.e. possibly too fat to be readable. commit 1ba73378cf29ed27a6ecd18716394779ceb543d9 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 00:13:03 2021 +0700 Simplify internal values representation in graph even more New values in graph._private.values were pushed to the end, and old values were removed from the beginning of the table. In draw() however newer values are drawn first, at the left edge of the graph, necessitating annoying idx = #values - idx conversions everywhere. In particular in stacked graph that means that values that are drawn together may have different indices. One has to shift the values array in add_value() either way, so one might as well insert new values at the beginning of the array and simplify the rest of the code. This doesn't seem like much yet, but it will prove to be much more convenient and less error-prone in what I'm going to do later. commit f70523ee68dbd6b67050ace4b82903a9b22440c7 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 13:35:10 2021 +0700 Simplify internal values representation in graph widget graph._private.values was either a table of values or a table of tables of values, depending on whether "stack" property is enabled, ... or a mix of both if user forgot to pass the second "group" parameter to graph:add_value(). This resulted in pretty fragile and confusing behavior, e.g. leaving user to wonder, why they see nothing more after setting stack=true for a graph in a previously working rc.lua, and demanding a very careful use of add_value() from the user, or else some values or even entire stacks would mysteriously vanish and errors in draw() get triggered, sometimes after a period of seemingly normal functioning. Now `values` is always a table of tables and the group parameter in add_value() defaults to 1, thus making the non-stacked graph a trivial variant of the stacked graph.
2021-05-20 17:14:03 +02:00
function graph:get_width()
gdebug.deprecate("Use `forced_width`", {deprecated_in=5})
return awesome.api_level <= 5 and self._private.forced_width or nil
end
--- Set the colors for data groups.
--
-- This property is deprecated. Use `group_colors` instead.
---
-- @deprecatedproperty stack_colors
-- @renamedin 5.0 group_colors
-- @tparam table colors A table with colors for data groups.
-- @see group_colors
function graph:set_stack_colors(colors)
gdebug.deprecate("Use `group_colors`", {deprecated_in=5})
if awesome.api_level <= 5 then
if self._private.group_colors ~= colors then
-- this sends "redraw_needed" for us
self:set_group_colors(colors)
-- signal, because we did it before
self:emit_signal("property::stack_colors", colors)
2016-05-23 07:30:44 +02:00
end
graph: complete widget overhaul and bug fixes Squashed commit of the following: commit 69821f51fe1e8652715658543e50631ece495090 Author: Alex Belykh <albel727@ngs.ru> Date: Mon May 10 19:17:55 2021 +0700 Refactor property handling in wibox.widget.graph In addition, baseline_value, step_width and step_spacing properties are all set to their default non-nil numeric values during instance creation now. commit 842eb429bf5df4b2ba46b2e6f7646afe89b1a3c3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 9 06:18:45 2021 +0700 Make graph:draw_values() private There's no documentation nor confidence that it's a good API, so I'm hiding it for now. commit 720746780574e4bad14a71fc4174955139c2eb50 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 04:04:13 2021 +0700 Document graph_should_draw_data_group() for posterity commit 7c73f3754ce6ba86b118f1170a6f9e406ab4841d Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:59:21 2021 +0700 Make graph:should_draw_data_group() private There's no confidence whether it's a good API, so I'm hiding it for now. commit b3539e20adb6423118af726ae4f8751ef864b5d2 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:36:44 2021 +0700 Make graph:preprocess_values() private There's no confidence whether it's a good API, so I'm hiding it for now. commit 2df4400e780a6260f8d0ab1c60734fbb11bf3c4f Author: Alex Belykh <albel727@ngs.ru> Date: Thu May 6 10:05:23 2021 +0700 Add spec/wibox/widget/graph_spec.lua commit ce01a9771ab439b8df841f1565aa4aa8a44a5e0d Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 08:16:18 2021 +0700 Improve graph.capacity documentation commit e28fade8f655802f5bf89154169f68d256b8e332 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 07:36:01 2021 +0700 Document graph:compute_drawn_values_num() commit dd68332292465422d08b48e9f0229f143beccbc6 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 05:24:23 2021 +0700 Document graph:pick_data_group_color() method commit ca6dc55f77aca88a6e9cc90bb6fd0045739f41f5 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 04:13:58 2021 +0700 Guard against setting data groups sparsely in graph:add_value() commit cdea5f126f7984e72924f6dffbd312c4fe3b032f Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 02:36:12 2021 +0700 Refactor scaling and baseline choice out of graph:draw() commit 10135deafe8a9390ba66ab657ebb1fb41d549e8c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 21:34:48 2021 +0700 Document graph.nan_color and graph.stack properties commit 0d73bb84ac66b87e16aee472bd1f299549f66a1c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 20:49:24 2021 +0700 Use a better example in docs for the graph.stack property commit c27d160a0826d033d49d6141608c40aa8a0642fa Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 16:05:38 2021 +0700 Document graph.baseline_value property commit 459bc8176c13d4ea32af0dcdc2399d3b929b1c6a Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:53:47 2021 +0700 Document graph.clamp_bars property commit e732ee0b3b007d213e689b98cc00ae7cb141ae08 Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:34 2021 +0700 Improve graph:add_value() and :clear() documentation commit ae73e3aa9f8d60409cbe4cb4da9612f31af4f38e Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:11 2021 +0700 Improve graph min_value/max_value/scale documentation commit a7350996a9def773b91b1631e6c0507e33d8f93b Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:27:36 2021 +0700 Cleanup graph examples to show only relevant things in docs commit 30740c26f4f41c16f4c8a3c339bcb7bd299e14cd Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 14:04:36 2021 +0700 Rename graph.stack_colors property to `group_colors` The colors are used for stacked and non-stacked graphs alike, so the name didn't make sense. Also document and clarify some color stuff. commit 1ea01bfd410971cf0d0c25fd74cd764d5eb7279d Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 21:25:41 2021 +0700 Add an example file for code coverage of some fringe graph cases commit eae68e39553f88a6275a0c65bf040b5d4c262b54 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 20:23:32 2021 +0700 Add step_shape to the graph/nan_color example for code coverage commit 8027147bc2eab34a97b49a428fb8cef27f122bed Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:55:18 2021 +0700 Add example files for graph.nan_color/nan_indication properties commit 89a25a165c52b2c15b6c4d95ecb777a9067e66f9 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:33:07 2021 +0700 Make widget graph:add_value(v) default v to NaN 0 as a default makes no sense and silences the error that is failing to specify a value, to which the user should rather be alerted. The graph widget has sane NaN handling now and will do just that. commit 2835d552440be48d356a7f7b1d64963696dd550b Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:20:48 2021 +0700 Implement graph.nan_color and nan_indication properties Now, whenever there's a NaN among values and graph.nan_indication is set, the corresponding area, where the value bar should have been drawn, is filled with the nan_color from top to bottom. The painting is done after all data is rendered, to make sure that it won't be overpainted and go unnoticed. NaNs among graph's values are inevitably a sign of some error and it is arguably not a sane default behavior to simply not render them at all. The user should take immediate note of any errors by default, instead of wondering at a mysteriously empty graph widget, which is why nan_indication is set to true by default. If the user wants to silence NaNs, they can always simply set it to false. nan_color if unset, defaults to a noticeable yellow-black pattern. As a direct and planned consequence, stacked graphs with negative values in them trigger the NaN handling too. But silencing NaN handling for those graphs is also possible and semi-sane, because then NaNs and negatives are ignored and the graph behaves (almost) like they were zeros. commit 9c1f8f08b400e01e19c534f3810c9c9ab485760c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 26 08:10:19 2021 +0700 Refactor out graph_map_value_to_widget_coordinates() function commit 1905991ddecabdae3e15ffb8ee5b18a1bee1a711 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 28 13:39:29 2021 +0700 Add an example file for graph.baseline_value property commit b31b39d66f4c037a9484b71a3f910703ae01fac0 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 07:03:49 2021 +0700 Add an example file for graph.clamp_bars=false/true comparison commit a1fc850e3047f9ee2c9ddeffacfbabf3ded34171 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 04:06:45 2021 +0700 Add an example file for graphs with negative values and shapes commit 2f012f8ff1bb5094c14e724a61e59a2a3a296bc8 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 01:06:05 2021 +0700 Add an example file for a graph displaying negative values commit 7b14fbd59a4c4c51962fd3110a1a900dd5f4eb98 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 11:18:26 2021 +0700 Add an example file for graphs with step_width < 1 commit dfd9a54544b637e2fbe3d1325f9cc24b3f4d2313 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 10:17:38 2021 +0700 Add an example file for graph.stack=false/true comparison Should also demonstrate that multiple data groups are possible for non-stacked graphs too. commit 5b532106c3fd255f9b66a66cfb88d977b489ab5d Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 09:34:14 2021 +0700 Add an example file for disabling graph data groups with nil colors commit ac6f1083c02fcb428f089a616f9b0ebaa5496b6c Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:57:25 2021 +0700 Add an example file for step_shape/width/spacing on stacked graphs commit b3703db6a5139e73e72c1cfd198ece2cd9be7df2 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:43:56 2021 +0700 Add an example file for scale=false/true on stacked graph commit b000aed4a12cb44fb501e1c8432a71a14b2f8f3f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 20 08:22:19 2021 +0700 Document deprecation of graph's width and height commit cb0ad617bd6c83a499896797318e3b0b588c3566 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 07:53:37 2021 +0700 Deprecate width and height properties of wibox.widget.graph commit 8c64e14ad91a074b784166a7054e53444cccb9d2 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 04:37:59 2021 +0700 Refactor graph:draw() to separate concerns Firstly, summation of group values for stacked graphs is extracted into graph:preprocess_values(). This makes graph:draw() smaller and exposes the fact, that stacked graphs are almost entirely just a form of a more general concept: pre-render data manipulation. It's something a user might want to customize, and now they can do it by monkey-patching the method. Secondly, the logic for deciding, if and with which color a data group should be drawn, is extracted into graph:should_draw_data_group() and get_data_group_color() methods respectively. This makes it customizable too and makes more obvious the fact, that it is a general thing that has nothing to do with stacked graphs in particular. With this there are only 5 lines remaining in graph:draw(), that could be said to be specific to stacked graphs, all pertaining to the `prev_y` variable, which merely preserves the bar coordinates from a previous data group. commit 2a32b6305d893f413303728a0a3da1dcd9a5591e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 22:52:37 2021 +0700 Pointlessly optimize graph:add_value() 1) Purge old values first, then insert the new one, if needed. Since Lua grows arrays in power of two steps, and our guess_capacity() also takes care to return even integers, it would be a shame to overstep the capacity by one by inserting first and thus getting the table size doubled only to never use the most of it. 2) Use a removal procedure that doesn't call the slow length operator at all. Previous code called it twice (once in table.remove) for every removed value. commit 680ea8f5d81e9040afb4662b3229e937e4b89e02 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:23:38 2021 +0700 Set graph data series color before the painting loop commit b970f0f221ae76250f2a94433ff79d653caa6e21 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:05:37 2021 +0700 Drop a large no longer necessary `if` in graph:draw_values() This commit is almost purely a whitespace change. commit 6b4f0541c70ec99d7f6e276bae64939f337120d6 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:00:50 2021 +0700 Extract values rendering code into graph:draw_values() method This is to avoid ungodly if nesting levels, which are needed to bail out of drawing values early and skip to drawing the border. The next commit will get rid of the if. commit e0950e4f99d1ea1c4b8cea2cc67629fa40030c2a Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 14:50:20 2021 +0700 Track graph draw() usage to guess necessary array capacity The last step of divorcing add_values() from width calculations, while ensuring that user doesn't observe a lack of data to draw. The widget now tracks an approximate number of values that it has been asked to draw() in _private.last_drawn_values_num, and truncates its values array based on that (if not overridden by the capacity property). commit 2e3d542282f35e7c0fed454da0f2eada0fcf3b78 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 13:35:12 2021 +0700 Add `capacity` property to graph widget This is the second step of divorcing add_values() from calculations involving widths. How many values are stored and how many values are drawn are distinct concepts. There isn't even a single definitive "how many values are drawn" because the widget could be rendered in several places with distinct widths simultaneously, and in every such place the data must be drawn appropriately, e.g. with auto-scaling, that takes only the visible data points into account. And the draw() method can do that now. The default capacity value of nil uses heuristics based on widget's _private.width to guess some capacity that would tolerate some dynamic widget resizing. If the user finds the heuristic failing them, they can set the capacity property to the desired maximum number of values that will be kept in the _private.values array (in each data group). commit 08d4a80686f7fe363551c35589a460839f60853e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 03:27:18 2021 +0700 Extract values size computation into compute_drawn_values_num() This is the first step of making add_values() not concern itself (directly) with widget dimensions. The user can monkey-patch the method to override how many values are (logically) drawn, because it affects graph auto-scaling and can be useful for rendering of step_shape-s that paint outside of their step_width. I didn't make it a full-blown function-valued property, because it seems a fringe functionality, which I'm too lazy to document. commit 3c5617c98ab1147618ed22e8414a77fc56b45f6e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 02:32:29 2021 +0700 Copy graph widget prototype methods over in bulk commit 169538837ece41a03d02ba8fbc0e4097062b67a3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun Apr 25 05:58:01 2021 +0700 Remove graph.baseline_y property After giving it some thought I've concluded, that it is unlikely to be useful as it is now. I'd better reserve the name for some more sensible functionality, like fixing the baseline_value defined axis at the given widget coordinate, adjusting the scaling accordingly, so min_value/max_value won't generally correspond to widget top and bottom anymore. commit 202ffaa9a0b820c7c471e1121d01b91f8f76cd38 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 12:06:55 2021 +0700 Add baseline_y and baseline_value properties to graph widget Now values in graph have a *baseline*, tunable with baseline_y and baseline_value properties. It is the (for now invisible) horizontal line from which all bars in the graph grow. I call it a "baseline" instead of "axis", because it doesn't actually have to correspond to the 0 value. But it can, and actually does now by default. baseline_y and baseline_value properties determine the vertical placement of the baseline in the widget. baseline_y is the position of the baseline in normalized widget coordinates, e.g. 0 corresponds to widget's top, 1 - to widget's bottom, and 0.5 - to widget's middle. So if e.g. one sets baseline_y = 0, then no matter how the widget is resized and how its values are scaled, bars in it will be drawn starting from the top. baseline_value is the position of the baseline in value coordinates. So if one sets baseline_value = 10, then no matter what happens, bars for values greater than 10 will be drawn as growing up from this line, and bars for values smaller as growing down. Where and if it will be seen in the widget, depends on the scaling. baseline_value = 0 is the familiar zero axis. baseline_y is unset by default and has priority, i.e. if it's set, then baseline_value is ignored. baseline_value defaults to 0. Prior to this commit, bars were always drawn from the bottom edge of the widget, or, in the new terms, the widget had baseline_y = 1. But it was indistinguishable from baseline_value = 0, because the widget clamped all negative values to 0, so the difference between the two could never be observed. baseline_value = 0 is clearly more useful and familiar to people, so now it is in force by default. commit df7e19abaebf0a871b5ca217b4cc3365533267a6 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 11:18:12 2021 +0700 Move value clamping from graph:add_value() into draw() Previously all values were forcibly clamped between 0 (not tunable at all) and max_value (1 by default). Thus graphs with negative values were not an option. NaN values were conflated with zeros, even though it may be useful to maintain the distinction and render them differently. And if user happened to dynamically change the max_value property, they discovered, that their data got truncated. Preserving user data as it was given is more flexible and makes things easier to debug. This commit does exactly that. So what happens now to the newly allowed values during draw()? There are 3 new cases to consider: 1) NaNs 2) Negatives 3) Values outside of min_value..max_value range NaNs are presently simply not drawn. An empty place is reserved where the value should've been, but nothing else is done. This is distinct from how a zero is handled, e.g. if step_shape is set, it is called with the height parameter = 0, which can still end up drawing something, depending on the shape. One could also consider addition of nan_color/nan_shape properties which could be drawn to alert user to a gap in their data. Negative values necessitate introduction of the concept of the zero axis, from which they will be drawn DOWN, in contrast to positive values. Where should this axis be drawn? How should step_shape-s with inherent direction, e.g. arrows, be drawn for negatives? This will be addressed in the next commit. Values outside of min/max_value range can imply shapes that go beyond widget dimensions. An arrow shape that goes beyond the roof is visibly different from an arrow that saturates and merely touches the widget top, and the user may reasonably desire either behavior. Therefore this commit introduces a boolean clamp_bars property, which defaults to true for reasons of robustness and backward compatibility with the behaviour prior to this commit. The property of course affects only the display of the values, the actual user data stays intact. commit c9c24fb1ab741cd6bb8074971d568e0646c51532 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:29:01 2021 +0700 Use rectangles for drawing graph bars of all sizes I have benchmarked various bar drawing strategies and graph sizes and found out that drawing graph bars with rectangles is as fast or even marginally faster than using fat lines. Thus the code that tries to special-case 1px bars as lines seems to be not worth the additional complexity it introduces, and this commit removes it. Incidentally, I've also established, that rectangles are more robust. If one draws graph bars and feeds cairo very large coordinates (~2^62), then corresponding rectangles fail to render, but all the other rectangles in the same series get rendered successfully, whereas a single large coordinate in a move_to/line_to() sequence is enough to cause all lines in the batch to fail to render. commit bc7f4be5e06405b5aa7fb443a5ace2b9b9575b1f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:01:42 2021 +0700 Cache cairo methods used in the inner loop of graph:draw() I have benchmarked the draw() method, both on cairo xcb and on argb32 image surfaces, and have discovered that cairo context method calls are unnaturally slow, to the point, that almost nothing else in the function matters. For example, drawing bars with vertical lines made the whole draw() function almost 2 times slower than doing the same with rectangles, merely because the former requires two method calls (move_to/line_to), and the latter only one. Simply caching hotpath cairo methods in local variables, reduced the difference between the drawing methods to a negligible one and made the whole function 4 times faster on a typical 100-bar graph, (1.4ms to 0.33ms on my laptop) thus bringing closer my secret dream of drawing live audio data with awesome graphs (kidding). I guess, LGI does something not very optimal there, like gobject-introspecting the method every time, or something. There might be some untapped optimization potential, possibly in upstream, like a method cache, from which everything in awesome could benefit, even though I imagine not many things are so call-intensive as the graph widget, to really feel the difference. commit 538df25bdf02b53d0bbe81809eef2e6fdb05ff6f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 07:32:37 2021 +0700 Use 1-based index in the inner graph draw loop It's used there only once for a calculation and then several times to index some tables, and index 0 is bad for lua tables performance-wise. commit 7412546916cd0a0a490130a120255ffb719398b8 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:55:12 2021 +0700 Leave graph.max_value uninitialized at construction This is a user-visible change, but the previous behavior was probably not what user expected anyway, namely with graph.scale set the graph mysteriously couldn't zoom in onto values that are smaller than 1. commit eb81d7929a161e79c13262cfa73ac2e6a820e8ed Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:48:29 2021 +0700 Allow graph.max_value be nil This makes its behave symmetrically to min_value. Now it defaults to 1, when graph.scale is not set, and to the maximum value in the graph data, when graph.scale is set. commit f622a8430cbfde7007b58d7f4c9c76b08c91e997 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 12:01:13 2021 +0700 Calculate max values num in graph:add_value() more precisely At most so many values were kept in the widget values array, as there are pixels of usable widget width (i.e. sans border). This is exactly right for the surely most common case of graphs with 1px-wide bars, but is more than needed for graphs with step_width+step_spacing>1, and not enough for graphs with subpixel bars. Moreover this made graph autoscaling behave weirdly in graphs with thicker bars, because old values survived longer in the array and kept affecting min_value/max_value calculations even after they were shifted off-screen. The maximum number of kept values is now calculated exactly as the number of values that can be displayed at once, taking step_width and step_spacing into account. It might be worth adding a max_values_num property to the graph to let user override this calculation, in case they draw shapes in step_shape() outside of its supposed rectangle, so our assumptions of visibility are wrong, or the widget often gets resized, resulting in the annoyance of visibly truncated series. commit 0aa63249a18641251a1bf77423739164f0eab39f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 02:30:13 2021 +0700 Fix border_color/border_width bugs in graph widget add_value() wrongly assumed that border_width is always set, when border_color is, so if user set only the latter, an error occurred. add_value() assumed that border_width is always < width which led to awesome hanging up in an infinite loop, should that fail to be the case. The graph border_width property was driven by the border_color property, instead of the other way round. If border_color wasn't set, the border wasn't drawn all, even if user sets non-zero border_width, which is a confusing behavior, in contrast to all other widgets, which simply use a plethora of fallback colors to draw the border, when needed. This also meant that, despite being documented, the beautiful.graph_border_color fallback color could not and was not ever used. If the border_color was set though, an unset border_width was immediately assumed to be 1 instead of 0, also contrary to what other widgets do. Theme colors shouldn't influence widget layout, so this commit fixes this behavior to: 1) border_width always defaults to 0, if unset. 2) border_color falls back to beautiful.graph_border_color or white. Maybe one should also add a beautiful.graph_border_width theme variable, like some widgets do. commit dad4a3253b8fef971fb82720d2a407fcc0a879ad Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 03:11:27 2021 +0700 Implement multiple data series drawing for non-stacked graph The widget can already hold and draw multiple data groups, so it makes little sense to limit drawing only to the first group when graph.stack = false, probably leading to user's confusion. Now user can call add_value(v, group) with group > 1 and the data series will be drawn (over groups with lesser id-s) with graph.stack_colors[group] color, if stack_colors is set, or with graph.color, if stack_colors isn't set. Thus nothing visibly changes for all current users of non-stacked graphs with only single data series, but the new functionality is now there for those who need it. One can't fully emulate this functionality, e.g. with multiple graphs widgets in a wibox.layout.stack, because data series in a single widget can enjoy a common auto-scaling. commit 3e645c976184ab5f50d625271c25062db9ab7ed9 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:59:12 2021 +0700 Don't draw a stacked graph data group if there's no color for it If stack_colors[idx] is nil for the group `idx`, don't draw it at all (as opposed, e.g. to the effect of drawing it with a transparent color, or as though it had all values == 0, which both produce different results). This code is still subtly different from prior behavior, because now user can have a non-contiguous stack_colors table and turn the display of any data group on and off at will, whereas the code two commits earlier would just stop rendering as soon as ipairs(stack_colors) does, i.e. no data series were drawn past the first nil hole. This commit only looks large, but is mostly a whitespace change due to nesting from a pair of wrapping ifs. commit 8c0273786de1ade66bdd977699f98b588da9f487 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:32:42 2021 +0700 Unify stacked and non-stacked graph rendering Now stacked graphs support everything the nonstacked do, like step_width ~= 1 and custom step_shape-s. Technical notes: This code no longer calls cr:stroke() for every single value in a stacked graph, but rather paints a whole series and then cr:stroke/fill()-s it at all once with its color, improving performance considerably. Additionally the graph no longer relies on the presence of the stack_colors property to render series. It draws all data that it has, and, if there's no corresponding color in stack_colors, falls back to graph.color. This will be changed in the next commit to the prior behavior of only drawing those series for which there's a color in the stack_colors table, but that would've made this commit's diff much larger due to required nesting ifs over large code chunks. This commit also takes care to properly handle NaNs and negative values. Those are currently (almost) guaranteed to not occur in the values array due to clipping in graph:add_value(), but I intend to eventually drop the clipping, because it does nothing that draw() really relies upon, and allowing unclipped and NaN values is more flexible and less surprising, e.g. when user dynamically toggles graph.scale. Eventually I intend to implement drawing negative values in non-stacked graphs and possibly even in the stacked ones. commit 38c6589c51b2aa057d7c2f40182ecf32b5b2ff5e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 10:04:15 2021 +0700 Convert graph:draw() to use `self` commit f21f1b2550426df1c4ea8abde079b1ccef84b394 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 12:27:26 2021 +0700 Fix scaling for stacked graphs Stacked graphs have cumulative heights, values from all groups that end up in the same graph bar are effectively summed. Previous code didn't take that into account when calculating min/max_value for the purposes of rescaling, but simply examined every value separately. This code adds up all values up front and finds min/max among those, to determine proper scaling that would fit the graph exactly. commit 1ce4c7bf63bdd6c99c195d668347bec49355e921 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 09:49:35 2021 +0700 Draw shaped graph bars only up to the baseline Prior to this commit, every step_shape bar was, for some reason, drawn with height = full widget's height, regardless of the value it represents. So for the most bars their lower end was far outside the box and therefore clipped off. So e.g. one couldn't see the lower round edges of rounded rectangles. This commit fixes that by appropriately varying the height that is passed into step_shape(), so that lower edges of the bars always exactly touch the lower edge of the graph. If the user wants to restore the prior behavior, they can simply ignore the passed-in height in step_shape() and just always draw their shape with height = full widget height. But they couldn't replicate the effect of this commit in their rc.lua without essentially writing their own graph widget. commit e25185b08da23745b5815f85f6e9e43222ed2294 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 08:42:54 2021 +0700 Avoid incremental transform shifting during graph bar rendering Instead of letting addition errors and cognitive load accumulate simply use the same offset calculation that is used for lines and reset the transform back to the top-left corner after every drawn bar. It's conceptually simpler, and will be useful later for stacked graphs and more. It also fixes a discrepancy between plain and step_shape rendering. Prior to this commit, bar coloring occurred with a disturbed transform, which was centered at the last drawn bar, wherever it happened to be. This meant that nontrivial colors, like gradients and images, were arbitrarily offset when step_shape was used. commit f3e98866d8583c35d0b557c09f529299edc9ba2c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:45:29 2021 +0700 Refactor out graph bar height expression into a variable commit f54528d6892f5aaa4595917853c9e2130f743fdb Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:20:25 2021 +0700 Draw graph bars with lines only when step_width == 1 Before this commit graph bars of step_width < 1 were drawn with lines of width 1, but that's a bit unfortunate, because it led to much overpainting, while still ending up with a graphically incorrect result. Let's draw such bars with rectangles and let cairo do things with subpixels, getting more graphically accurate result, while still keeping the common case of step_width == 1 (supposedly) fast-drawn with lines. commit b6515dca67c18b78eb5657378131a62ab43d4cfc Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:54:07 2021 +0700 Fix unsharp edges when drawing graph bars with rectangles The 0.5 offset is only helpful for 1-width strokes, not for fills. commit 7f3db8f07e82f821bcd1dc418d66564f537609d6 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:21:01 2021 +0700 Do coordinate transformations even if the value isn't drawn The graph presently doesn't draw values < 0, but there was a discrepancy between drawing with and without step_shape. The default line/rectangle drawing reserves an empty place where the skipped value should've been, but no such empty place is reserved, when step_shape is used. This commit fixes that. Skipped values always have their empty space now. commit 7b731ebfe96af7a9dba9c57371da6761cf22c00c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:03:30 2021 +0700 Fix graph bar scaling when min_value is not 0 Previous calculation lead to underscaling of values so that even value = max_value wasn't drawn to full height. commit dca59fd8b7fff0e7e646f2dfc6bd92047028e5a8 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:18:40 2021 +0700 Calculate the horizontal graph bar offset only when needed commit 940ddf4b9825e9c3a91d276208c089d8c74e49db Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:17:23 2021 +0700 Call cr:move_to() only when drawing the graph with lines It's redundant otherwise and might even interfere with user's drawing in step_shape(). commit b5d5cd10ca29fb29cd751faaa95137c2fe89ff63 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:04:06 2021 +0700 Fix wrong horizontal offset calculations in widget.graph Indices in the loop are zero-based, so things like i-1 and i>1 aren't necessary and actually wouldn't have been correct even in a 1-based loop. step_shape = gears.shape.rectangle and step_shape = nil, which is supposed to default to rectangle according to docs, were drawn differently due to mismatched offset calculations, which led to problems especially noticeable when step_spacing is not zero, e.g: 1) whole graph shifted left too much, drawn partially off-screen. 2) whole graph shifted right too much, and the first bar placed closer to the rest than others. commit dc369fa198281e000afc1bd17326f3a264adf7d1 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 02:09:21 2021 +0700 Fix the "border_width affects graph bar width" bug Graph bars were drawn with lines of border_width thickness instead of 1, i.e. possibly too fat to be readable. commit 1ba73378cf29ed27a6ecd18716394779ceb543d9 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 00:13:03 2021 +0700 Simplify internal values representation in graph even more New values in graph._private.values were pushed to the end, and old values were removed from the beginning of the table. In draw() however newer values are drawn first, at the left edge of the graph, necessitating annoying idx = #values - idx conversions everywhere. In particular in stacked graph that means that values that are drawn together may have different indices. One has to shift the values array in add_value() either way, so one might as well insert new values at the beginning of the array and simplify the rest of the code. This doesn't seem like much yet, but it will prove to be much more convenient and less error-prone in what I'm going to do later. commit f70523ee68dbd6b67050ace4b82903a9b22440c7 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 13:35:10 2021 +0700 Simplify internal values representation in graph widget graph._private.values was either a table of values or a table of tables of values, depending on whether "stack" property is enabled, ... or a mix of both if user forgot to pass the second "group" parameter to graph:add_value(). This resulted in pretty fragile and confusing behavior, e.g. leaving user to wonder, why they see nothing more after setting stack=true for a graph in a previously working rc.lua, and demanding a very careful use of add_value() from the user, or else some values or even entire stacks would mysteriously vanish and errors in draw() get triggered, sometimes after a period of seemingly normal functioning. Now `values` is always a table of tables and the group parameter in add_value() defaults to 1, thus making the non-stacked graph a trivial variant of the stacked graph.
2021-05-20 17:14:03 +02:00
return self
2016-05-23 07:30:44 +02:00
end
end
graph: complete widget overhaul and bug fixes Squashed commit of the following: commit 69821f51fe1e8652715658543e50631ece495090 Author: Alex Belykh <albel727@ngs.ru> Date: Mon May 10 19:17:55 2021 +0700 Refactor property handling in wibox.widget.graph In addition, baseline_value, step_width and step_spacing properties are all set to their default non-nil numeric values during instance creation now. commit 842eb429bf5df4b2ba46b2e6f7646afe89b1a3c3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 9 06:18:45 2021 +0700 Make graph:draw_values() private There's no documentation nor confidence that it's a good API, so I'm hiding it for now. commit 720746780574e4bad14a71fc4174955139c2eb50 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 04:04:13 2021 +0700 Document graph_should_draw_data_group() for posterity commit 7c73f3754ce6ba86b118f1170a6f9e406ab4841d Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:59:21 2021 +0700 Make graph:should_draw_data_group() private There's no confidence whether it's a good API, so I'm hiding it for now. commit b3539e20adb6423118af726ae4f8751ef864b5d2 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:36:44 2021 +0700 Make graph:preprocess_values() private There's no confidence whether it's a good API, so I'm hiding it for now. commit 2df4400e780a6260f8d0ab1c60734fbb11bf3c4f Author: Alex Belykh <albel727@ngs.ru> Date: Thu May 6 10:05:23 2021 +0700 Add spec/wibox/widget/graph_spec.lua commit ce01a9771ab439b8df841f1565aa4aa8a44a5e0d Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 08:16:18 2021 +0700 Improve graph.capacity documentation commit e28fade8f655802f5bf89154169f68d256b8e332 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 07:36:01 2021 +0700 Document graph:compute_drawn_values_num() commit dd68332292465422d08b48e9f0229f143beccbc6 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 05:24:23 2021 +0700 Document graph:pick_data_group_color() method commit ca6dc55f77aca88a6e9cc90bb6fd0045739f41f5 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 04:13:58 2021 +0700 Guard against setting data groups sparsely in graph:add_value() commit cdea5f126f7984e72924f6dffbd312c4fe3b032f Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 02:36:12 2021 +0700 Refactor scaling and baseline choice out of graph:draw() commit 10135deafe8a9390ba66ab657ebb1fb41d549e8c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 21:34:48 2021 +0700 Document graph.nan_color and graph.stack properties commit 0d73bb84ac66b87e16aee472bd1f299549f66a1c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 20:49:24 2021 +0700 Use a better example in docs for the graph.stack property commit c27d160a0826d033d49d6141608c40aa8a0642fa Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 16:05:38 2021 +0700 Document graph.baseline_value property commit 459bc8176c13d4ea32af0dcdc2399d3b929b1c6a Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:53:47 2021 +0700 Document graph.clamp_bars property commit e732ee0b3b007d213e689b98cc00ae7cb141ae08 Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:34 2021 +0700 Improve graph:add_value() and :clear() documentation commit ae73e3aa9f8d60409cbe4cb4da9612f31af4f38e Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:11 2021 +0700 Improve graph min_value/max_value/scale documentation commit a7350996a9def773b91b1631e6c0507e33d8f93b Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:27:36 2021 +0700 Cleanup graph examples to show only relevant things in docs commit 30740c26f4f41c16f4c8a3c339bcb7bd299e14cd Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 14:04:36 2021 +0700 Rename graph.stack_colors property to `group_colors` The colors are used for stacked and non-stacked graphs alike, so the name didn't make sense. Also document and clarify some color stuff. commit 1ea01bfd410971cf0d0c25fd74cd764d5eb7279d Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 21:25:41 2021 +0700 Add an example file for code coverage of some fringe graph cases commit eae68e39553f88a6275a0c65bf040b5d4c262b54 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 20:23:32 2021 +0700 Add step_shape to the graph/nan_color example for code coverage commit 8027147bc2eab34a97b49a428fb8cef27f122bed Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:55:18 2021 +0700 Add example files for graph.nan_color/nan_indication properties commit 89a25a165c52b2c15b6c4d95ecb777a9067e66f9 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:33:07 2021 +0700 Make widget graph:add_value(v) default v to NaN 0 as a default makes no sense and silences the error that is failing to specify a value, to which the user should rather be alerted. The graph widget has sane NaN handling now and will do just that. commit 2835d552440be48d356a7f7b1d64963696dd550b Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:20:48 2021 +0700 Implement graph.nan_color and nan_indication properties Now, whenever there's a NaN among values and graph.nan_indication is set, the corresponding area, where the value bar should have been drawn, is filled with the nan_color from top to bottom. The painting is done after all data is rendered, to make sure that it won't be overpainted and go unnoticed. NaNs among graph's values are inevitably a sign of some error and it is arguably not a sane default behavior to simply not render them at all. The user should take immediate note of any errors by default, instead of wondering at a mysteriously empty graph widget, which is why nan_indication is set to true by default. If the user wants to silence NaNs, they can always simply set it to false. nan_color if unset, defaults to a noticeable yellow-black pattern. As a direct and planned consequence, stacked graphs with negative values in them trigger the NaN handling too. But silencing NaN handling for those graphs is also possible and semi-sane, because then NaNs and negatives are ignored and the graph behaves (almost) like they were zeros. commit 9c1f8f08b400e01e19c534f3810c9c9ab485760c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 26 08:10:19 2021 +0700 Refactor out graph_map_value_to_widget_coordinates() function commit 1905991ddecabdae3e15ffb8ee5b18a1bee1a711 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 28 13:39:29 2021 +0700 Add an example file for graph.baseline_value property commit b31b39d66f4c037a9484b71a3f910703ae01fac0 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 07:03:49 2021 +0700 Add an example file for graph.clamp_bars=false/true comparison commit a1fc850e3047f9ee2c9ddeffacfbabf3ded34171 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 04:06:45 2021 +0700 Add an example file for graphs with negative values and shapes commit 2f012f8ff1bb5094c14e724a61e59a2a3a296bc8 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 01:06:05 2021 +0700 Add an example file for a graph displaying negative values commit 7b14fbd59a4c4c51962fd3110a1a900dd5f4eb98 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 11:18:26 2021 +0700 Add an example file for graphs with step_width < 1 commit dfd9a54544b637e2fbe3d1325f9cc24b3f4d2313 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 10:17:38 2021 +0700 Add an example file for graph.stack=false/true comparison Should also demonstrate that multiple data groups are possible for non-stacked graphs too. commit 5b532106c3fd255f9b66a66cfb88d977b489ab5d Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 09:34:14 2021 +0700 Add an example file for disabling graph data groups with nil colors commit ac6f1083c02fcb428f089a616f9b0ebaa5496b6c Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:57:25 2021 +0700 Add an example file for step_shape/width/spacing on stacked graphs commit b3703db6a5139e73e72c1cfd198ece2cd9be7df2 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:43:56 2021 +0700 Add an example file for scale=false/true on stacked graph commit b000aed4a12cb44fb501e1c8432a71a14b2f8f3f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 20 08:22:19 2021 +0700 Document deprecation of graph's width and height commit cb0ad617bd6c83a499896797318e3b0b588c3566 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 07:53:37 2021 +0700 Deprecate width and height properties of wibox.widget.graph commit 8c64e14ad91a074b784166a7054e53444cccb9d2 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 04:37:59 2021 +0700 Refactor graph:draw() to separate concerns Firstly, summation of group values for stacked graphs is extracted into graph:preprocess_values(). This makes graph:draw() smaller and exposes the fact, that stacked graphs are almost entirely just a form of a more general concept: pre-render data manipulation. It's something a user might want to customize, and now they can do it by monkey-patching the method. Secondly, the logic for deciding, if and with which color a data group should be drawn, is extracted into graph:should_draw_data_group() and get_data_group_color() methods respectively. This makes it customizable too and makes more obvious the fact, that it is a general thing that has nothing to do with stacked graphs in particular. With this there are only 5 lines remaining in graph:draw(), that could be said to be specific to stacked graphs, all pertaining to the `prev_y` variable, which merely preserves the bar coordinates from a previous data group. commit 2a32b6305d893f413303728a0a3da1dcd9a5591e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 22:52:37 2021 +0700 Pointlessly optimize graph:add_value() 1) Purge old values first, then insert the new one, if needed. Since Lua grows arrays in power of two steps, and our guess_capacity() also takes care to return even integers, it would be a shame to overstep the capacity by one by inserting first and thus getting the table size doubled only to never use the most of it. 2) Use a removal procedure that doesn't call the slow length operator at all. Previous code called it twice (once in table.remove) for every removed value. commit 680ea8f5d81e9040afb4662b3229e937e4b89e02 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:23:38 2021 +0700 Set graph data series color before the painting loop commit b970f0f221ae76250f2a94433ff79d653caa6e21 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:05:37 2021 +0700 Drop a large no longer necessary `if` in graph:draw_values() This commit is almost purely a whitespace change. commit 6b4f0541c70ec99d7f6e276bae64939f337120d6 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:00:50 2021 +0700 Extract values rendering code into graph:draw_values() method This is to avoid ungodly if nesting levels, which are needed to bail out of drawing values early and skip to drawing the border. The next commit will get rid of the if. commit e0950e4f99d1ea1c4b8cea2cc67629fa40030c2a Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 14:50:20 2021 +0700 Track graph draw() usage to guess necessary array capacity The last step of divorcing add_values() from width calculations, while ensuring that user doesn't observe a lack of data to draw. The widget now tracks an approximate number of values that it has been asked to draw() in _private.last_drawn_values_num, and truncates its values array based on that (if not overridden by the capacity property). commit 2e3d542282f35e7c0fed454da0f2eada0fcf3b78 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 13:35:12 2021 +0700 Add `capacity` property to graph widget This is the second step of divorcing add_values() from calculations involving widths. How many values are stored and how many values are drawn are distinct concepts. There isn't even a single definitive "how many values are drawn" because the widget could be rendered in several places with distinct widths simultaneously, and in every such place the data must be drawn appropriately, e.g. with auto-scaling, that takes only the visible data points into account. And the draw() method can do that now. The default capacity value of nil uses heuristics based on widget's _private.width to guess some capacity that would tolerate some dynamic widget resizing. If the user finds the heuristic failing them, they can set the capacity property to the desired maximum number of values that will be kept in the _private.values array (in each data group). commit 08d4a80686f7fe363551c35589a460839f60853e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 03:27:18 2021 +0700 Extract values size computation into compute_drawn_values_num() This is the first step of making add_values() not concern itself (directly) with widget dimensions. The user can monkey-patch the method to override how many values are (logically) drawn, because it affects graph auto-scaling and can be useful for rendering of step_shape-s that paint outside of their step_width. I didn't make it a full-blown function-valued property, because it seems a fringe functionality, which I'm too lazy to document. commit 3c5617c98ab1147618ed22e8414a77fc56b45f6e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 02:32:29 2021 +0700 Copy graph widget prototype methods over in bulk commit 169538837ece41a03d02ba8fbc0e4097062b67a3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun Apr 25 05:58:01 2021 +0700 Remove graph.baseline_y property After giving it some thought I've concluded, that it is unlikely to be useful as it is now. I'd better reserve the name for some more sensible functionality, like fixing the baseline_value defined axis at the given widget coordinate, adjusting the scaling accordingly, so min_value/max_value won't generally correspond to widget top and bottom anymore. commit 202ffaa9a0b820c7c471e1121d01b91f8f76cd38 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 12:06:55 2021 +0700 Add baseline_y and baseline_value properties to graph widget Now values in graph have a *baseline*, tunable with baseline_y and baseline_value properties. It is the (for now invisible) horizontal line from which all bars in the graph grow. I call it a "baseline" instead of "axis", because it doesn't actually have to correspond to the 0 value. But it can, and actually does now by default. baseline_y and baseline_value properties determine the vertical placement of the baseline in the widget. baseline_y is the position of the baseline in normalized widget coordinates, e.g. 0 corresponds to widget's top, 1 - to widget's bottom, and 0.5 - to widget's middle. So if e.g. one sets baseline_y = 0, then no matter how the widget is resized and how its values are scaled, bars in it will be drawn starting from the top. baseline_value is the position of the baseline in value coordinates. So if one sets baseline_value = 10, then no matter what happens, bars for values greater than 10 will be drawn as growing up from this line, and bars for values smaller as growing down. Where and if it will be seen in the widget, depends on the scaling. baseline_value = 0 is the familiar zero axis. baseline_y is unset by default and has priority, i.e. if it's set, then baseline_value is ignored. baseline_value defaults to 0. Prior to this commit, bars were always drawn from the bottom edge of the widget, or, in the new terms, the widget had baseline_y = 1. But it was indistinguishable from baseline_value = 0, because the widget clamped all negative values to 0, so the difference between the two could never be observed. baseline_value = 0 is clearly more useful and familiar to people, so now it is in force by default. commit df7e19abaebf0a871b5ca217b4cc3365533267a6 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 11:18:12 2021 +0700 Move value clamping from graph:add_value() into draw() Previously all values were forcibly clamped between 0 (not tunable at all) and max_value (1 by default). Thus graphs with negative values were not an option. NaN values were conflated with zeros, even though it may be useful to maintain the distinction and render them differently. And if user happened to dynamically change the max_value property, they discovered, that their data got truncated. Preserving user data as it was given is more flexible and makes things easier to debug. This commit does exactly that. So what happens now to the newly allowed values during draw()? There are 3 new cases to consider: 1) NaNs 2) Negatives 3) Values outside of min_value..max_value range NaNs are presently simply not drawn. An empty place is reserved where the value should've been, but nothing else is done. This is distinct from how a zero is handled, e.g. if step_shape is set, it is called with the height parameter = 0, which can still end up drawing something, depending on the shape. One could also consider addition of nan_color/nan_shape properties which could be drawn to alert user to a gap in their data. Negative values necessitate introduction of the concept of the zero axis, from which they will be drawn DOWN, in contrast to positive values. Where should this axis be drawn? How should step_shape-s with inherent direction, e.g. arrows, be drawn for negatives? This will be addressed in the next commit. Values outside of min/max_value range can imply shapes that go beyond widget dimensions. An arrow shape that goes beyond the roof is visibly different from an arrow that saturates and merely touches the widget top, and the user may reasonably desire either behavior. Therefore this commit introduces a boolean clamp_bars property, which defaults to true for reasons of robustness and backward compatibility with the behaviour prior to this commit. The property of course affects only the display of the values, the actual user data stays intact. commit c9c24fb1ab741cd6bb8074971d568e0646c51532 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:29:01 2021 +0700 Use rectangles for drawing graph bars of all sizes I have benchmarked various bar drawing strategies and graph sizes and found out that drawing graph bars with rectangles is as fast or even marginally faster than using fat lines. Thus the code that tries to special-case 1px bars as lines seems to be not worth the additional complexity it introduces, and this commit removes it. Incidentally, I've also established, that rectangles are more robust. If one draws graph bars and feeds cairo very large coordinates (~2^62), then corresponding rectangles fail to render, but all the other rectangles in the same series get rendered successfully, whereas a single large coordinate in a move_to/line_to() sequence is enough to cause all lines in the batch to fail to render. commit bc7f4be5e06405b5aa7fb443a5ace2b9b9575b1f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:01:42 2021 +0700 Cache cairo methods used in the inner loop of graph:draw() I have benchmarked the draw() method, both on cairo xcb and on argb32 image surfaces, and have discovered that cairo context method calls are unnaturally slow, to the point, that almost nothing else in the function matters. For example, drawing bars with vertical lines made the whole draw() function almost 2 times slower than doing the same with rectangles, merely because the former requires two method calls (move_to/line_to), and the latter only one. Simply caching hotpath cairo methods in local variables, reduced the difference between the drawing methods to a negligible one and made the whole function 4 times faster on a typical 100-bar graph, (1.4ms to 0.33ms on my laptop) thus bringing closer my secret dream of drawing live audio data with awesome graphs (kidding). I guess, LGI does something not very optimal there, like gobject-introspecting the method every time, or something. There might be some untapped optimization potential, possibly in upstream, like a method cache, from which everything in awesome could benefit, even though I imagine not many things are so call-intensive as the graph widget, to really feel the difference. commit 538df25bdf02b53d0bbe81809eef2e6fdb05ff6f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 07:32:37 2021 +0700 Use 1-based index in the inner graph draw loop It's used there only once for a calculation and then several times to index some tables, and index 0 is bad for lua tables performance-wise. commit 7412546916cd0a0a490130a120255ffb719398b8 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:55:12 2021 +0700 Leave graph.max_value uninitialized at construction This is a user-visible change, but the previous behavior was probably not what user expected anyway, namely with graph.scale set the graph mysteriously couldn't zoom in onto values that are smaller than 1. commit eb81d7929a161e79c13262cfa73ac2e6a820e8ed Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:48:29 2021 +0700 Allow graph.max_value be nil This makes its behave symmetrically to min_value. Now it defaults to 1, when graph.scale is not set, and to the maximum value in the graph data, when graph.scale is set. commit f622a8430cbfde7007b58d7f4c9c76b08c91e997 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 12:01:13 2021 +0700 Calculate max values num in graph:add_value() more precisely At most so many values were kept in the widget values array, as there are pixels of usable widget width (i.e. sans border). This is exactly right for the surely most common case of graphs with 1px-wide bars, but is more than needed for graphs with step_width+step_spacing>1, and not enough for graphs with subpixel bars. Moreover this made graph autoscaling behave weirdly in graphs with thicker bars, because old values survived longer in the array and kept affecting min_value/max_value calculations even after they were shifted off-screen. The maximum number of kept values is now calculated exactly as the number of values that can be displayed at once, taking step_width and step_spacing into account. It might be worth adding a max_values_num property to the graph to let user override this calculation, in case they draw shapes in step_shape() outside of its supposed rectangle, so our assumptions of visibility are wrong, or the widget often gets resized, resulting in the annoyance of visibly truncated series. commit 0aa63249a18641251a1bf77423739164f0eab39f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 02:30:13 2021 +0700 Fix border_color/border_width bugs in graph widget add_value() wrongly assumed that border_width is always set, when border_color is, so if user set only the latter, an error occurred. add_value() assumed that border_width is always < width which led to awesome hanging up in an infinite loop, should that fail to be the case. The graph border_width property was driven by the border_color property, instead of the other way round. If border_color wasn't set, the border wasn't drawn all, even if user sets non-zero border_width, which is a confusing behavior, in contrast to all other widgets, which simply use a plethora of fallback colors to draw the border, when needed. This also meant that, despite being documented, the beautiful.graph_border_color fallback color could not and was not ever used. If the border_color was set though, an unset border_width was immediately assumed to be 1 instead of 0, also contrary to what other widgets do. Theme colors shouldn't influence widget layout, so this commit fixes this behavior to: 1) border_width always defaults to 0, if unset. 2) border_color falls back to beautiful.graph_border_color or white. Maybe one should also add a beautiful.graph_border_width theme variable, like some widgets do. commit dad4a3253b8fef971fb82720d2a407fcc0a879ad Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 03:11:27 2021 +0700 Implement multiple data series drawing for non-stacked graph The widget can already hold and draw multiple data groups, so it makes little sense to limit drawing only to the first group when graph.stack = false, probably leading to user's confusion. Now user can call add_value(v, group) with group > 1 and the data series will be drawn (over groups with lesser id-s) with graph.stack_colors[group] color, if stack_colors is set, or with graph.color, if stack_colors isn't set. Thus nothing visibly changes for all current users of non-stacked graphs with only single data series, but the new functionality is now there for those who need it. One can't fully emulate this functionality, e.g. with multiple graphs widgets in a wibox.layout.stack, because data series in a single widget can enjoy a common auto-scaling. commit 3e645c976184ab5f50d625271c25062db9ab7ed9 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:59:12 2021 +0700 Don't draw a stacked graph data group if there's no color for it If stack_colors[idx] is nil for the group `idx`, don't draw it at all (as opposed, e.g. to the effect of drawing it with a transparent color, or as though it had all values == 0, which both produce different results). This code is still subtly different from prior behavior, because now user can have a non-contiguous stack_colors table and turn the display of any data group on and off at will, whereas the code two commits earlier would just stop rendering as soon as ipairs(stack_colors) does, i.e. no data series were drawn past the first nil hole. This commit only looks large, but is mostly a whitespace change due to nesting from a pair of wrapping ifs. commit 8c0273786de1ade66bdd977699f98b588da9f487 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:32:42 2021 +0700 Unify stacked and non-stacked graph rendering Now stacked graphs support everything the nonstacked do, like step_width ~= 1 and custom step_shape-s. Technical notes: This code no longer calls cr:stroke() for every single value in a stacked graph, but rather paints a whole series and then cr:stroke/fill()-s it at all once with its color, improving performance considerably. Additionally the graph no longer relies on the presence of the stack_colors property to render series. It draws all data that it has, and, if there's no corresponding color in stack_colors, falls back to graph.color. This will be changed in the next commit to the prior behavior of only drawing those series for which there's a color in the stack_colors table, but that would've made this commit's diff much larger due to required nesting ifs over large code chunks. This commit also takes care to properly handle NaNs and negative values. Those are currently (almost) guaranteed to not occur in the values array due to clipping in graph:add_value(), but I intend to eventually drop the clipping, because it does nothing that draw() really relies upon, and allowing unclipped and NaN values is more flexible and less surprising, e.g. when user dynamically toggles graph.scale. Eventually I intend to implement drawing negative values in non-stacked graphs and possibly even in the stacked ones. commit 38c6589c51b2aa057d7c2f40182ecf32b5b2ff5e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 10:04:15 2021 +0700 Convert graph:draw() to use `self` commit f21f1b2550426df1c4ea8abde079b1ccef84b394 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 12:27:26 2021 +0700 Fix scaling for stacked graphs Stacked graphs have cumulative heights, values from all groups that end up in the same graph bar are effectively summed. Previous code didn't take that into account when calculating min/max_value for the purposes of rescaling, but simply examined every value separately. This code adds up all values up front and finds min/max among those, to determine proper scaling that would fit the graph exactly. commit 1ce4c7bf63bdd6c99c195d668347bec49355e921 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 09:49:35 2021 +0700 Draw shaped graph bars only up to the baseline Prior to this commit, every step_shape bar was, for some reason, drawn with height = full widget's height, regardless of the value it represents. So for the most bars their lower end was far outside the box and therefore clipped off. So e.g. one couldn't see the lower round edges of rounded rectangles. This commit fixes that by appropriately varying the height that is passed into step_shape(), so that lower edges of the bars always exactly touch the lower edge of the graph. If the user wants to restore the prior behavior, they can simply ignore the passed-in height in step_shape() and just always draw their shape with height = full widget height. But they couldn't replicate the effect of this commit in their rc.lua without essentially writing their own graph widget. commit e25185b08da23745b5815f85f6e9e43222ed2294 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 08:42:54 2021 +0700 Avoid incremental transform shifting during graph bar rendering Instead of letting addition errors and cognitive load accumulate simply use the same offset calculation that is used for lines and reset the transform back to the top-left corner after every drawn bar. It's conceptually simpler, and will be useful later for stacked graphs and more. It also fixes a discrepancy between plain and step_shape rendering. Prior to this commit, bar coloring occurred with a disturbed transform, which was centered at the last drawn bar, wherever it happened to be. This meant that nontrivial colors, like gradients and images, were arbitrarily offset when step_shape was used. commit f3e98866d8583c35d0b557c09f529299edc9ba2c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:45:29 2021 +0700 Refactor out graph bar height expression into a variable commit f54528d6892f5aaa4595917853c9e2130f743fdb Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:20:25 2021 +0700 Draw graph bars with lines only when step_width == 1 Before this commit graph bars of step_width < 1 were drawn with lines of width 1, but that's a bit unfortunate, because it led to much overpainting, while still ending up with a graphically incorrect result. Let's draw such bars with rectangles and let cairo do things with subpixels, getting more graphically accurate result, while still keeping the common case of step_width == 1 (supposedly) fast-drawn with lines. commit b6515dca67c18b78eb5657378131a62ab43d4cfc Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:54:07 2021 +0700 Fix unsharp edges when drawing graph bars with rectangles The 0.5 offset is only helpful for 1-width strokes, not for fills. commit 7f3db8f07e82f821bcd1dc418d66564f537609d6 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:21:01 2021 +0700 Do coordinate transformations even if the value isn't drawn The graph presently doesn't draw values < 0, but there was a discrepancy between drawing with and without step_shape. The default line/rectangle drawing reserves an empty place where the skipped value should've been, but no such empty place is reserved, when step_shape is used. This commit fixes that. Skipped values always have their empty space now. commit 7b731ebfe96af7a9dba9c57371da6761cf22c00c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:03:30 2021 +0700 Fix graph bar scaling when min_value is not 0 Previous calculation lead to underscaling of values so that even value = max_value wasn't drawn to full height. commit dca59fd8b7fff0e7e646f2dfc6bd92047028e5a8 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:18:40 2021 +0700 Calculate the horizontal graph bar offset only when needed commit 940ddf4b9825e9c3a91d276208c089d8c74e49db Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:17:23 2021 +0700 Call cr:move_to() only when drawing the graph with lines It's redundant otherwise and might even interfere with user's drawing in step_shape(). commit b5d5cd10ca29fb29cd751faaa95137c2fe89ff63 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:04:06 2021 +0700 Fix wrong horizontal offset calculations in widget.graph Indices in the loop are zero-based, so things like i-1 and i>1 aren't necessary and actually wouldn't have been correct even in a 1-based loop. step_shape = gears.shape.rectangle and step_shape = nil, which is supposed to default to rectangle according to docs, were drawn differently due to mismatched offset calculations, which led to problems especially noticeable when step_spacing is not zero, e.g: 1) whole graph shifted left too much, drawn partially off-screen. 2) whole graph shifted right too much, and the first bar placed closer to the rest than others. commit dc369fa198281e000afc1bd17326f3a264adf7d1 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 02:09:21 2021 +0700 Fix the "border_width affects graph bar width" bug Graph bars were drawn with lines of border_width thickness instead of 1, i.e. possibly too fat to be readable. commit 1ba73378cf29ed27a6ecd18716394779ceb543d9 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 00:13:03 2021 +0700 Simplify internal values representation in graph even more New values in graph._private.values were pushed to the end, and old values were removed from the beginning of the table. In draw() however newer values are drawn first, at the left edge of the graph, necessitating annoying idx = #values - idx conversions everywhere. In particular in stacked graph that means that values that are drawn together may have different indices. One has to shift the values array in add_value() either way, so one might as well insert new values at the beginning of the array and simplify the rest of the code. This doesn't seem like much yet, but it will prove to be much more convenient and less error-prone in what I'm going to do later. commit f70523ee68dbd6b67050ace4b82903a9b22440c7 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 13:35:10 2021 +0700 Simplify internal values representation in graph widget graph._private.values was either a table of values or a table of tables of values, depending on whether "stack" property is enabled, ... or a mix of both if user forgot to pass the second "group" parameter to graph:add_value(). This resulted in pretty fragile and confusing behavior, e.g. leaving user to wonder, why they see nothing more after setting stack=true for a graph in a previously working rc.lua, and demanding a very careful use of add_value() from the user, or else some values or even entire stacks would mysteriously vanish and errors in draw() get triggered, sometimes after a period of seemingly normal functioning. Now `values` is always a table of tables and the group parameter in add_value() defaults to 1, thus making the non-stacked graph a trivial variant of the stacked graph.
2021-05-20 17:14:03 +02:00
function graph:get_stack_colors()
gdebug.deprecate("Use `group_colors`", {deprecated_in=5})
return awesome.api_level <= 5 and self._private.group_colors or nil
end
2016-05-23 07:30:44 +02:00
--- Create a graph widget.
--
graph: complete widget overhaul and bug fixes Squashed commit of the following: commit 69821f51fe1e8652715658543e50631ece495090 Author: Alex Belykh <albel727@ngs.ru> Date: Mon May 10 19:17:55 2021 +0700 Refactor property handling in wibox.widget.graph In addition, baseline_value, step_width and step_spacing properties are all set to their default non-nil numeric values during instance creation now. commit 842eb429bf5df4b2ba46b2e6f7646afe89b1a3c3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 9 06:18:45 2021 +0700 Make graph:draw_values() private There's no documentation nor confidence that it's a good API, so I'm hiding it for now. commit 720746780574e4bad14a71fc4174955139c2eb50 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 04:04:13 2021 +0700 Document graph_should_draw_data_group() for posterity commit 7c73f3754ce6ba86b118f1170a6f9e406ab4841d Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:59:21 2021 +0700 Make graph:should_draw_data_group() private There's no confidence whether it's a good API, so I'm hiding it for now. commit b3539e20adb6423118af726ae4f8751ef864b5d2 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:36:44 2021 +0700 Make graph:preprocess_values() private There's no confidence whether it's a good API, so I'm hiding it for now. commit 2df4400e780a6260f8d0ab1c60734fbb11bf3c4f Author: Alex Belykh <albel727@ngs.ru> Date: Thu May 6 10:05:23 2021 +0700 Add spec/wibox/widget/graph_spec.lua commit ce01a9771ab439b8df841f1565aa4aa8a44a5e0d Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 08:16:18 2021 +0700 Improve graph.capacity documentation commit e28fade8f655802f5bf89154169f68d256b8e332 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 07:36:01 2021 +0700 Document graph:compute_drawn_values_num() commit dd68332292465422d08b48e9f0229f143beccbc6 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 05:24:23 2021 +0700 Document graph:pick_data_group_color() method commit ca6dc55f77aca88a6e9cc90bb6fd0045739f41f5 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 04:13:58 2021 +0700 Guard against setting data groups sparsely in graph:add_value() commit cdea5f126f7984e72924f6dffbd312c4fe3b032f Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 02:36:12 2021 +0700 Refactor scaling and baseline choice out of graph:draw() commit 10135deafe8a9390ba66ab657ebb1fb41d549e8c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 21:34:48 2021 +0700 Document graph.nan_color and graph.stack properties commit 0d73bb84ac66b87e16aee472bd1f299549f66a1c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 20:49:24 2021 +0700 Use a better example in docs for the graph.stack property commit c27d160a0826d033d49d6141608c40aa8a0642fa Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 16:05:38 2021 +0700 Document graph.baseline_value property commit 459bc8176c13d4ea32af0dcdc2399d3b929b1c6a Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:53:47 2021 +0700 Document graph.clamp_bars property commit e732ee0b3b007d213e689b98cc00ae7cb141ae08 Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:34 2021 +0700 Improve graph:add_value() and :clear() documentation commit ae73e3aa9f8d60409cbe4cb4da9612f31af4f38e Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:11 2021 +0700 Improve graph min_value/max_value/scale documentation commit a7350996a9def773b91b1631e6c0507e33d8f93b Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:27:36 2021 +0700 Cleanup graph examples to show only relevant things in docs commit 30740c26f4f41c16f4c8a3c339bcb7bd299e14cd Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 14:04:36 2021 +0700 Rename graph.stack_colors property to `group_colors` The colors are used for stacked and non-stacked graphs alike, so the name didn't make sense. Also document and clarify some color stuff. commit 1ea01bfd410971cf0d0c25fd74cd764d5eb7279d Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 21:25:41 2021 +0700 Add an example file for code coverage of some fringe graph cases commit eae68e39553f88a6275a0c65bf040b5d4c262b54 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 20:23:32 2021 +0700 Add step_shape to the graph/nan_color example for code coverage commit 8027147bc2eab34a97b49a428fb8cef27f122bed Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:55:18 2021 +0700 Add example files for graph.nan_color/nan_indication properties commit 89a25a165c52b2c15b6c4d95ecb777a9067e66f9 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:33:07 2021 +0700 Make widget graph:add_value(v) default v to NaN 0 as a default makes no sense and silences the error that is failing to specify a value, to which the user should rather be alerted. The graph widget has sane NaN handling now and will do just that. commit 2835d552440be48d356a7f7b1d64963696dd550b Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:20:48 2021 +0700 Implement graph.nan_color and nan_indication properties Now, whenever there's a NaN among values and graph.nan_indication is set, the corresponding area, where the value bar should have been drawn, is filled with the nan_color from top to bottom. The painting is done after all data is rendered, to make sure that it won't be overpainted and go unnoticed. NaNs among graph's values are inevitably a sign of some error and it is arguably not a sane default behavior to simply not render them at all. The user should take immediate note of any errors by default, instead of wondering at a mysteriously empty graph widget, which is why nan_indication is set to true by default. If the user wants to silence NaNs, they can always simply set it to false. nan_color if unset, defaults to a noticeable yellow-black pattern. As a direct and planned consequence, stacked graphs with negative values in them trigger the NaN handling too. But silencing NaN handling for those graphs is also possible and semi-sane, because then NaNs and negatives are ignored and the graph behaves (almost) like they were zeros. commit 9c1f8f08b400e01e19c534f3810c9c9ab485760c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 26 08:10:19 2021 +0700 Refactor out graph_map_value_to_widget_coordinates() function commit 1905991ddecabdae3e15ffb8ee5b18a1bee1a711 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 28 13:39:29 2021 +0700 Add an example file for graph.baseline_value property commit b31b39d66f4c037a9484b71a3f910703ae01fac0 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 07:03:49 2021 +0700 Add an example file for graph.clamp_bars=false/true comparison commit a1fc850e3047f9ee2c9ddeffacfbabf3ded34171 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 04:06:45 2021 +0700 Add an example file for graphs with negative values and shapes commit 2f012f8ff1bb5094c14e724a61e59a2a3a296bc8 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 01:06:05 2021 +0700 Add an example file for a graph displaying negative values commit 7b14fbd59a4c4c51962fd3110a1a900dd5f4eb98 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 11:18:26 2021 +0700 Add an example file for graphs with step_width < 1 commit dfd9a54544b637e2fbe3d1325f9cc24b3f4d2313 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 10:17:38 2021 +0700 Add an example file for graph.stack=false/true comparison Should also demonstrate that multiple data groups are possible for non-stacked graphs too. commit 5b532106c3fd255f9b66a66cfb88d977b489ab5d Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 09:34:14 2021 +0700 Add an example file for disabling graph data groups with nil colors commit ac6f1083c02fcb428f089a616f9b0ebaa5496b6c Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:57:25 2021 +0700 Add an example file for step_shape/width/spacing on stacked graphs commit b3703db6a5139e73e72c1cfd198ece2cd9be7df2 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:43:56 2021 +0700 Add an example file for scale=false/true on stacked graph commit b000aed4a12cb44fb501e1c8432a71a14b2f8f3f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 20 08:22:19 2021 +0700 Document deprecation of graph's width and height commit cb0ad617bd6c83a499896797318e3b0b588c3566 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 07:53:37 2021 +0700 Deprecate width and height properties of wibox.widget.graph commit 8c64e14ad91a074b784166a7054e53444cccb9d2 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 04:37:59 2021 +0700 Refactor graph:draw() to separate concerns Firstly, summation of group values for stacked graphs is extracted into graph:preprocess_values(). This makes graph:draw() smaller and exposes the fact, that stacked graphs are almost entirely just a form of a more general concept: pre-render data manipulation. It's something a user might want to customize, and now they can do it by monkey-patching the method. Secondly, the logic for deciding, if and with which color a data group should be drawn, is extracted into graph:should_draw_data_group() and get_data_group_color() methods respectively. This makes it customizable too and makes more obvious the fact, that it is a general thing that has nothing to do with stacked graphs in particular. With this there are only 5 lines remaining in graph:draw(), that could be said to be specific to stacked graphs, all pertaining to the `prev_y` variable, which merely preserves the bar coordinates from a previous data group. commit 2a32b6305d893f413303728a0a3da1dcd9a5591e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 22:52:37 2021 +0700 Pointlessly optimize graph:add_value() 1) Purge old values first, then insert the new one, if needed. Since Lua grows arrays in power of two steps, and our guess_capacity() also takes care to return even integers, it would be a shame to overstep the capacity by one by inserting first and thus getting the table size doubled only to never use the most of it. 2) Use a removal procedure that doesn't call the slow length operator at all. Previous code called it twice (once in table.remove) for every removed value. commit 680ea8f5d81e9040afb4662b3229e937e4b89e02 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:23:38 2021 +0700 Set graph data series color before the painting loop commit b970f0f221ae76250f2a94433ff79d653caa6e21 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:05:37 2021 +0700 Drop a large no longer necessary `if` in graph:draw_values() This commit is almost purely a whitespace change. commit 6b4f0541c70ec99d7f6e276bae64939f337120d6 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:00:50 2021 +0700 Extract values rendering code into graph:draw_values() method This is to avoid ungodly if nesting levels, which are needed to bail out of drawing values early and skip to drawing the border. The next commit will get rid of the if. commit e0950e4f99d1ea1c4b8cea2cc67629fa40030c2a Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 14:50:20 2021 +0700 Track graph draw() usage to guess necessary array capacity The last step of divorcing add_values() from width calculations, while ensuring that user doesn't observe a lack of data to draw. The widget now tracks an approximate number of values that it has been asked to draw() in _private.last_drawn_values_num, and truncates its values array based on that (if not overridden by the capacity property). commit 2e3d542282f35e7c0fed454da0f2eada0fcf3b78 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 13:35:12 2021 +0700 Add `capacity` property to graph widget This is the second step of divorcing add_values() from calculations involving widths. How many values are stored and how many values are drawn are distinct concepts. There isn't even a single definitive "how many values are drawn" because the widget could be rendered in several places with distinct widths simultaneously, and in every such place the data must be drawn appropriately, e.g. with auto-scaling, that takes only the visible data points into account. And the draw() method can do that now. The default capacity value of nil uses heuristics based on widget's _private.width to guess some capacity that would tolerate some dynamic widget resizing. If the user finds the heuristic failing them, they can set the capacity property to the desired maximum number of values that will be kept in the _private.values array (in each data group). commit 08d4a80686f7fe363551c35589a460839f60853e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 03:27:18 2021 +0700 Extract values size computation into compute_drawn_values_num() This is the first step of making add_values() not concern itself (directly) with widget dimensions. The user can monkey-patch the method to override how many values are (logically) drawn, because it affects graph auto-scaling and can be useful for rendering of step_shape-s that paint outside of their step_width. I didn't make it a full-blown function-valued property, because it seems a fringe functionality, which I'm too lazy to document. commit 3c5617c98ab1147618ed22e8414a77fc56b45f6e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 02:32:29 2021 +0700 Copy graph widget prototype methods over in bulk commit 169538837ece41a03d02ba8fbc0e4097062b67a3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun Apr 25 05:58:01 2021 +0700 Remove graph.baseline_y property After giving it some thought I've concluded, that it is unlikely to be useful as it is now. I'd better reserve the name for some more sensible functionality, like fixing the baseline_value defined axis at the given widget coordinate, adjusting the scaling accordingly, so min_value/max_value won't generally correspond to widget top and bottom anymore. commit 202ffaa9a0b820c7c471e1121d01b91f8f76cd38 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 12:06:55 2021 +0700 Add baseline_y and baseline_value properties to graph widget Now values in graph have a *baseline*, tunable with baseline_y and baseline_value properties. It is the (for now invisible) horizontal line from which all bars in the graph grow. I call it a "baseline" instead of "axis", because it doesn't actually have to correspond to the 0 value. But it can, and actually does now by default. baseline_y and baseline_value properties determine the vertical placement of the baseline in the widget. baseline_y is the position of the baseline in normalized widget coordinates, e.g. 0 corresponds to widget's top, 1 - to widget's bottom, and 0.5 - to widget's middle. So if e.g. one sets baseline_y = 0, then no matter how the widget is resized and how its values are scaled, bars in it will be drawn starting from the top. baseline_value is the position of the baseline in value coordinates. So if one sets baseline_value = 10, then no matter what happens, bars for values greater than 10 will be drawn as growing up from this line, and bars for values smaller as growing down. Where and if it will be seen in the widget, depends on the scaling. baseline_value = 0 is the familiar zero axis. baseline_y is unset by default and has priority, i.e. if it's set, then baseline_value is ignored. baseline_value defaults to 0. Prior to this commit, bars were always drawn from the bottom edge of the widget, or, in the new terms, the widget had baseline_y = 1. But it was indistinguishable from baseline_value = 0, because the widget clamped all negative values to 0, so the difference between the two could never be observed. baseline_value = 0 is clearly more useful and familiar to people, so now it is in force by default. commit df7e19abaebf0a871b5ca217b4cc3365533267a6 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 11:18:12 2021 +0700 Move value clamping from graph:add_value() into draw() Previously all values were forcibly clamped between 0 (not tunable at all) and max_value (1 by default). Thus graphs with negative values were not an option. NaN values were conflated with zeros, even though it may be useful to maintain the distinction and render them differently. And if user happened to dynamically change the max_value property, they discovered, that their data got truncated. Preserving user data as it was given is more flexible and makes things easier to debug. This commit does exactly that. So what happens now to the newly allowed values during draw()? There are 3 new cases to consider: 1) NaNs 2) Negatives 3) Values outside of min_value..max_value range NaNs are presently simply not drawn. An empty place is reserved where the value should've been, but nothing else is done. This is distinct from how a zero is handled, e.g. if step_shape is set, it is called with the height parameter = 0, which can still end up drawing something, depending on the shape. One could also consider addition of nan_color/nan_shape properties which could be drawn to alert user to a gap in their data. Negative values necessitate introduction of the concept of the zero axis, from which they will be drawn DOWN, in contrast to positive values. Where should this axis be drawn? How should step_shape-s with inherent direction, e.g. arrows, be drawn for negatives? This will be addressed in the next commit. Values outside of min/max_value range can imply shapes that go beyond widget dimensions. An arrow shape that goes beyond the roof is visibly different from an arrow that saturates and merely touches the widget top, and the user may reasonably desire either behavior. Therefore this commit introduces a boolean clamp_bars property, which defaults to true for reasons of robustness and backward compatibility with the behaviour prior to this commit. The property of course affects only the display of the values, the actual user data stays intact. commit c9c24fb1ab741cd6bb8074971d568e0646c51532 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:29:01 2021 +0700 Use rectangles for drawing graph bars of all sizes I have benchmarked various bar drawing strategies and graph sizes and found out that drawing graph bars with rectangles is as fast or even marginally faster than using fat lines. Thus the code that tries to special-case 1px bars as lines seems to be not worth the additional complexity it introduces, and this commit removes it. Incidentally, I've also established, that rectangles are more robust. If one draws graph bars and feeds cairo very large coordinates (~2^62), then corresponding rectangles fail to render, but all the other rectangles in the same series get rendered successfully, whereas a single large coordinate in a move_to/line_to() sequence is enough to cause all lines in the batch to fail to render. commit bc7f4be5e06405b5aa7fb443a5ace2b9b9575b1f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:01:42 2021 +0700 Cache cairo methods used in the inner loop of graph:draw() I have benchmarked the draw() method, both on cairo xcb and on argb32 image surfaces, and have discovered that cairo context method calls are unnaturally slow, to the point, that almost nothing else in the function matters. For example, drawing bars with vertical lines made the whole draw() function almost 2 times slower than doing the same with rectangles, merely because the former requires two method calls (move_to/line_to), and the latter only one. Simply caching hotpath cairo methods in local variables, reduced the difference between the drawing methods to a negligible one and made the whole function 4 times faster on a typical 100-bar graph, (1.4ms to 0.33ms on my laptop) thus bringing closer my secret dream of drawing live audio data with awesome graphs (kidding). I guess, LGI does something not very optimal there, like gobject-introspecting the method every time, or something. There might be some untapped optimization potential, possibly in upstream, like a method cache, from which everything in awesome could benefit, even though I imagine not many things are so call-intensive as the graph widget, to really feel the difference. commit 538df25bdf02b53d0bbe81809eef2e6fdb05ff6f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 07:32:37 2021 +0700 Use 1-based index in the inner graph draw loop It's used there only once for a calculation and then several times to index some tables, and index 0 is bad for lua tables performance-wise. commit 7412546916cd0a0a490130a120255ffb719398b8 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:55:12 2021 +0700 Leave graph.max_value uninitialized at construction This is a user-visible change, but the previous behavior was probably not what user expected anyway, namely with graph.scale set the graph mysteriously couldn't zoom in onto values that are smaller than 1. commit eb81d7929a161e79c13262cfa73ac2e6a820e8ed Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:48:29 2021 +0700 Allow graph.max_value be nil This makes its behave symmetrically to min_value. Now it defaults to 1, when graph.scale is not set, and to the maximum value in the graph data, when graph.scale is set. commit f622a8430cbfde7007b58d7f4c9c76b08c91e997 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 12:01:13 2021 +0700 Calculate max values num in graph:add_value() more precisely At most so many values were kept in the widget values array, as there are pixels of usable widget width (i.e. sans border). This is exactly right for the surely most common case of graphs with 1px-wide bars, but is more than needed for graphs with step_width+step_spacing>1, and not enough for graphs with subpixel bars. Moreover this made graph autoscaling behave weirdly in graphs with thicker bars, because old values survived longer in the array and kept affecting min_value/max_value calculations even after they were shifted off-screen. The maximum number of kept values is now calculated exactly as the number of values that can be displayed at once, taking step_width and step_spacing into account. It might be worth adding a max_values_num property to the graph to let user override this calculation, in case they draw shapes in step_shape() outside of its supposed rectangle, so our assumptions of visibility are wrong, or the widget often gets resized, resulting in the annoyance of visibly truncated series. commit 0aa63249a18641251a1bf77423739164f0eab39f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 02:30:13 2021 +0700 Fix border_color/border_width bugs in graph widget add_value() wrongly assumed that border_width is always set, when border_color is, so if user set only the latter, an error occurred. add_value() assumed that border_width is always < width which led to awesome hanging up in an infinite loop, should that fail to be the case. The graph border_width property was driven by the border_color property, instead of the other way round. If border_color wasn't set, the border wasn't drawn all, even if user sets non-zero border_width, which is a confusing behavior, in contrast to all other widgets, which simply use a plethora of fallback colors to draw the border, when needed. This also meant that, despite being documented, the beautiful.graph_border_color fallback color could not and was not ever used. If the border_color was set though, an unset border_width was immediately assumed to be 1 instead of 0, also contrary to what other widgets do. Theme colors shouldn't influence widget layout, so this commit fixes this behavior to: 1) border_width always defaults to 0, if unset. 2) border_color falls back to beautiful.graph_border_color or white. Maybe one should also add a beautiful.graph_border_width theme variable, like some widgets do. commit dad4a3253b8fef971fb82720d2a407fcc0a879ad Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 03:11:27 2021 +0700 Implement multiple data series drawing for non-stacked graph The widget can already hold and draw multiple data groups, so it makes little sense to limit drawing only to the first group when graph.stack = false, probably leading to user's confusion. Now user can call add_value(v, group) with group > 1 and the data series will be drawn (over groups with lesser id-s) with graph.stack_colors[group] color, if stack_colors is set, or with graph.color, if stack_colors isn't set. Thus nothing visibly changes for all current users of non-stacked graphs with only single data series, but the new functionality is now there for those who need it. One can't fully emulate this functionality, e.g. with multiple graphs widgets in a wibox.layout.stack, because data series in a single widget can enjoy a common auto-scaling. commit 3e645c976184ab5f50d625271c25062db9ab7ed9 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:59:12 2021 +0700 Don't draw a stacked graph data group if there's no color for it If stack_colors[idx] is nil for the group `idx`, don't draw it at all (as opposed, e.g. to the effect of drawing it with a transparent color, or as though it had all values == 0, which both produce different results). This code is still subtly different from prior behavior, because now user can have a non-contiguous stack_colors table and turn the display of any data group on and off at will, whereas the code two commits earlier would just stop rendering as soon as ipairs(stack_colors) does, i.e. no data series were drawn past the first nil hole. This commit only looks large, but is mostly a whitespace change due to nesting from a pair of wrapping ifs. commit 8c0273786de1ade66bdd977699f98b588da9f487 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:32:42 2021 +0700 Unify stacked and non-stacked graph rendering Now stacked graphs support everything the nonstacked do, like step_width ~= 1 and custom step_shape-s. Technical notes: This code no longer calls cr:stroke() for every single value in a stacked graph, but rather paints a whole series and then cr:stroke/fill()-s it at all once with its color, improving performance considerably. Additionally the graph no longer relies on the presence of the stack_colors property to render series. It draws all data that it has, and, if there's no corresponding color in stack_colors, falls back to graph.color. This will be changed in the next commit to the prior behavior of only drawing those series for which there's a color in the stack_colors table, but that would've made this commit's diff much larger due to required nesting ifs over large code chunks. This commit also takes care to properly handle NaNs and negative values. Those are currently (almost) guaranteed to not occur in the values array due to clipping in graph:add_value(), but I intend to eventually drop the clipping, because it does nothing that draw() really relies upon, and allowing unclipped and NaN values is more flexible and less surprising, e.g. when user dynamically toggles graph.scale. Eventually I intend to implement drawing negative values in non-stacked graphs and possibly even in the stacked ones. commit 38c6589c51b2aa057d7c2f40182ecf32b5b2ff5e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 10:04:15 2021 +0700 Convert graph:draw() to use `self` commit f21f1b2550426df1c4ea8abde079b1ccef84b394 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 12:27:26 2021 +0700 Fix scaling for stacked graphs Stacked graphs have cumulative heights, values from all groups that end up in the same graph bar are effectively summed. Previous code didn't take that into account when calculating min/max_value for the purposes of rescaling, but simply examined every value separately. This code adds up all values up front and finds min/max among those, to determine proper scaling that would fit the graph exactly. commit 1ce4c7bf63bdd6c99c195d668347bec49355e921 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 09:49:35 2021 +0700 Draw shaped graph bars only up to the baseline Prior to this commit, every step_shape bar was, for some reason, drawn with height = full widget's height, regardless of the value it represents. So for the most bars their lower end was far outside the box and therefore clipped off. So e.g. one couldn't see the lower round edges of rounded rectangles. This commit fixes that by appropriately varying the height that is passed into step_shape(), so that lower edges of the bars always exactly touch the lower edge of the graph. If the user wants to restore the prior behavior, they can simply ignore the passed-in height in step_shape() and just always draw their shape with height = full widget height. But they couldn't replicate the effect of this commit in their rc.lua without essentially writing their own graph widget. commit e25185b08da23745b5815f85f6e9e43222ed2294 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 08:42:54 2021 +0700 Avoid incremental transform shifting during graph bar rendering Instead of letting addition errors and cognitive load accumulate simply use the same offset calculation that is used for lines and reset the transform back to the top-left corner after every drawn bar. It's conceptually simpler, and will be useful later for stacked graphs and more. It also fixes a discrepancy between plain and step_shape rendering. Prior to this commit, bar coloring occurred with a disturbed transform, which was centered at the last drawn bar, wherever it happened to be. This meant that nontrivial colors, like gradients and images, were arbitrarily offset when step_shape was used. commit f3e98866d8583c35d0b557c09f529299edc9ba2c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:45:29 2021 +0700 Refactor out graph bar height expression into a variable commit f54528d6892f5aaa4595917853c9e2130f743fdb Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:20:25 2021 +0700 Draw graph bars with lines only when step_width == 1 Before this commit graph bars of step_width < 1 were drawn with lines of width 1, but that's a bit unfortunate, because it led to much overpainting, while still ending up with a graphically incorrect result. Let's draw such bars with rectangles and let cairo do things with subpixels, getting more graphically accurate result, while still keeping the common case of step_width == 1 (supposedly) fast-drawn with lines. commit b6515dca67c18b78eb5657378131a62ab43d4cfc Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:54:07 2021 +0700 Fix unsharp edges when drawing graph bars with rectangles The 0.5 offset is only helpful for 1-width strokes, not for fills. commit 7f3db8f07e82f821bcd1dc418d66564f537609d6 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:21:01 2021 +0700 Do coordinate transformations even if the value isn't drawn The graph presently doesn't draw values < 0, but there was a discrepancy between drawing with and without step_shape. The default line/rectangle drawing reserves an empty place where the skipped value should've been, but no such empty place is reserved, when step_shape is used. This commit fixes that. Skipped values always have their empty space now. commit 7b731ebfe96af7a9dba9c57371da6761cf22c00c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:03:30 2021 +0700 Fix graph bar scaling when min_value is not 0 Previous calculation lead to underscaling of values so that even value = max_value wasn't drawn to full height. commit dca59fd8b7fff0e7e646f2dfc6bd92047028e5a8 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:18:40 2021 +0700 Calculate the horizontal graph bar offset only when needed commit 940ddf4b9825e9c3a91d276208c089d8c74e49db Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:17:23 2021 +0700 Call cr:move_to() only when drawing the graph with lines It's redundant otherwise and might even interfere with user's drawing in step_shape(). commit b5d5cd10ca29fb29cd751faaa95137c2fe89ff63 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:04:06 2021 +0700 Fix wrong horizontal offset calculations in widget.graph Indices in the loop are zero-based, so things like i-1 and i>1 aren't necessary and actually wouldn't have been correct even in a 1-based loop. step_shape = gears.shape.rectangle and step_shape = nil, which is supposed to default to rectangle according to docs, were drawn differently due to mismatched offset calculations, which led to problems especially noticeable when step_spacing is not zero, e.g: 1) whole graph shifted left too much, drawn partially off-screen. 2) whole graph shifted right too much, and the first bar placed closer to the rest than others. commit dc369fa198281e000afc1bd17326f3a264adf7d1 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 02:09:21 2021 +0700 Fix the "border_width affects graph bar width" bug Graph bars were drawn with lines of border_width thickness instead of 1, i.e. possibly too fat to be readable. commit 1ba73378cf29ed27a6ecd18716394779ceb543d9 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 00:13:03 2021 +0700 Simplify internal values representation in graph even more New values in graph._private.values were pushed to the end, and old values were removed from the beginning of the table. In draw() however newer values are drawn first, at the left edge of the graph, necessitating annoying idx = #values - idx conversions everywhere. In particular in stacked graph that means that values that are drawn together may have different indices. One has to shift the values array in add_value() either way, so one might as well insert new values at the beginning of the array and simplify the rest of the code. This doesn't seem like much yet, but it will prove to be much more convenient and less error-prone in what I'm going to do later. commit f70523ee68dbd6b67050ace4b82903a9b22440c7 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 13:35:10 2021 +0700 Simplify internal values representation in graph widget graph._private.values was either a table of values or a table of tables of values, depending on whether "stack" property is enabled, ... or a mix of both if user forgot to pass the second "group" parameter to graph:add_value(). This resulted in pretty fragile and confusing behavior, e.g. leaving user to wonder, why they see nothing more after setting stack=true for a graph in a previously working rc.lua, and demanding a very careful use of add_value() from the user, or else some values or even entire stacks would mysteriously vanish and errors in draw() get triggered, sometimes after a period of seemingly normal functioning. Now `values` is always a table of tables and the group parameter in add_value() defaults to 1, thus making the non-stacked graph a trivial variant of the stacked graph.
2021-05-20 17:14:03 +02:00
-- @tparam table args Standard widget() arguments.
-- @treturn wibox.widget.graph A new graph widget.
-- @constructorfct wibox.widget.graph
2016-05-23 07:30:44 +02:00
function graph.new(args)
args = args or {}
local _graph = base.make_widget(nil, nil, {enable_properties = true})
2016-05-23 07:30:44 +02:00
graph: complete widget overhaul and bug fixes Squashed commit of the following: commit 69821f51fe1e8652715658543e50631ece495090 Author: Alex Belykh <albel727@ngs.ru> Date: Mon May 10 19:17:55 2021 +0700 Refactor property handling in wibox.widget.graph In addition, baseline_value, step_width and step_spacing properties are all set to their default non-nil numeric values during instance creation now. commit 842eb429bf5df4b2ba46b2e6f7646afe89b1a3c3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 9 06:18:45 2021 +0700 Make graph:draw_values() private There's no documentation nor confidence that it's a good API, so I'm hiding it for now. commit 720746780574e4bad14a71fc4174955139c2eb50 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 04:04:13 2021 +0700 Document graph_should_draw_data_group() for posterity commit 7c73f3754ce6ba86b118f1170a6f9e406ab4841d Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:59:21 2021 +0700 Make graph:should_draw_data_group() private There's no confidence whether it's a good API, so I'm hiding it for now. commit b3539e20adb6423118af726ae4f8751ef864b5d2 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:36:44 2021 +0700 Make graph:preprocess_values() private There's no confidence whether it's a good API, so I'm hiding it for now. commit 2df4400e780a6260f8d0ab1c60734fbb11bf3c4f Author: Alex Belykh <albel727@ngs.ru> Date: Thu May 6 10:05:23 2021 +0700 Add spec/wibox/widget/graph_spec.lua commit ce01a9771ab439b8df841f1565aa4aa8a44a5e0d Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 08:16:18 2021 +0700 Improve graph.capacity documentation commit e28fade8f655802f5bf89154169f68d256b8e332 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 07:36:01 2021 +0700 Document graph:compute_drawn_values_num() commit dd68332292465422d08b48e9f0229f143beccbc6 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 05:24:23 2021 +0700 Document graph:pick_data_group_color() method commit ca6dc55f77aca88a6e9cc90bb6fd0045739f41f5 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 04:13:58 2021 +0700 Guard against setting data groups sparsely in graph:add_value() commit cdea5f126f7984e72924f6dffbd312c4fe3b032f Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 02:36:12 2021 +0700 Refactor scaling and baseline choice out of graph:draw() commit 10135deafe8a9390ba66ab657ebb1fb41d549e8c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 21:34:48 2021 +0700 Document graph.nan_color and graph.stack properties commit 0d73bb84ac66b87e16aee472bd1f299549f66a1c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 20:49:24 2021 +0700 Use a better example in docs for the graph.stack property commit c27d160a0826d033d49d6141608c40aa8a0642fa Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 16:05:38 2021 +0700 Document graph.baseline_value property commit 459bc8176c13d4ea32af0dcdc2399d3b929b1c6a Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:53:47 2021 +0700 Document graph.clamp_bars property commit e732ee0b3b007d213e689b98cc00ae7cb141ae08 Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:34 2021 +0700 Improve graph:add_value() and :clear() documentation commit ae73e3aa9f8d60409cbe4cb4da9612f31af4f38e Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:11 2021 +0700 Improve graph min_value/max_value/scale documentation commit a7350996a9def773b91b1631e6c0507e33d8f93b Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:27:36 2021 +0700 Cleanup graph examples to show only relevant things in docs commit 30740c26f4f41c16f4c8a3c339bcb7bd299e14cd Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 14:04:36 2021 +0700 Rename graph.stack_colors property to `group_colors` The colors are used for stacked and non-stacked graphs alike, so the name didn't make sense. Also document and clarify some color stuff. commit 1ea01bfd410971cf0d0c25fd74cd764d5eb7279d Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 21:25:41 2021 +0700 Add an example file for code coverage of some fringe graph cases commit eae68e39553f88a6275a0c65bf040b5d4c262b54 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 20:23:32 2021 +0700 Add step_shape to the graph/nan_color example for code coverage commit 8027147bc2eab34a97b49a428fb8cef27f122bed Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:55:18 2021 +0700 Add example files for graph.nan_color/nan_indication properties commit 89a25a165c52b2c15b6c4d95ecb777a9067e66f9 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:33:07 2021 +0700 Make widget graph:add_value(v) default v to NaN 0 as a default makes no sense and silences the error that is failing to specify a value, to which the user should rather be alerted. The graph widget has sane NaN handling now and will do just that. commit 2835d552440be48d356a7f7b1d64963696dd550b Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:20:48 2021 +0700 Implement graph.nan_color and nan_indication properties Now, whenever there's a NaN among values and graph.nan_indication is set, the corresponding area, where the value bar should have been drawn, is filled with the nan_color from top to bottom. The painting is done after all data is rendered, to make sure that it won't be overpainted and go unnoticed. NaNs among graph's values are inevitably a sign of some error and it is arguably not a sane default behavior to simply not render them at all. The user should take immediate note of any errors by default, instead of wondering at a mysteriously empty graph widget, which is why nan_indication is set to true by default. If the user wants to silence NaNs, they can always simply set it to false. nan_color if unset, defaults to a noticeable yellow-black pattern. As a direct and planned consequence, stacked graphs with negative values in them trigger the NaN handling too. But silencing NaN handling for those graphs is also possible and semi-sane, because then NaNs and negatives are ignored and the graph behaves (almost) like they were zeros. commit 9c1f8f08b400e01e19c534f3810c9c9ab485760c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 26 08:10:19 2021 +0700 Refactor out graph_map_value_to_widget_coordinates() function commit 1905991ddecabdae3e15ffb8ee5b18a1bee1a711 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 28 13:39:29 2021 +0700 Add an example file for graph.baseline_value property commit b31b39d66f4c037a9484b71a3f910703ae01fac0 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 07:03:49 2021 +0700 Add an example file for graph.clamp_bars=false/true comparison commit a1fc850e3047f9ee2c9ddeffacfbabf3ded34171 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 04:06:45 2021 +0700 Add an example file for graphs with negative values and shapes commit 2f012f8ff1bb5094c14e724a61e59a2a3a296bc8 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 01:06:05 2021 +0700 Add an example file for a graph displaying negative values commit 7b14fbd59a4c4c51962fd3110a1a900dd5f4eb98 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 11:18:26 2021 +0700 Add an example file for graphs with step_width < 1 commit dfd9a54544b637e2fbe3d1325f9cc24b3f4d2313 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 10:17:38 2021 +0700 Add an example file for graph.stack=false/true comparison Should also demonstrate that multiple data groups are possible for non-stacked graphs too. commit 5b532106c3fd255f9b66a66cfb88d977b489ab5d Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 09:34:14 2021 +0700 Add an example file for disabling graph data groups with nil colors commit ac6f1083c02fcb428f089a616f9b0ebaa5496b6c Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:57:25 2021 +0700 Add an example file for step_shape/width/spacing on stacked graphs commit b3703db6a5139e73e72c1cfd198ece2cd9be7df2 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:43:56 2021 +0700 Add an example file for scale=false/true on stacked graph commit b000aed4a12cb44fb501e1c8432a71a14b2f8f3f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 20 08:22:19 2021 +0700 Document deprecation of graph's width and height commit cb0ad617bd6c83a499896797318e3b0b588c3566 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 07:53:37 2021 +0700 Deprecate width and height properties of wibox.widget.graph commit 8c64e14ad91a074b784166a7054e53444cccb9d2 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 04:37:59 2021 +0700 Refactor graph:draw() to separate concerns Firstly, summation of group values for stacked graphs is extracted into graph:preprocess_values(). This makes graph:draw() smaller and exposes the fact, that stacked graphs are almost entirely just a form of a more general concept: pre-render data manipulation. It's something a user might want to customize, and now they can do it by monkey-patching the method. Secondly, the logic for deciding, if and with which color a data group should be drawn, is extracted into graph:should_draw_data_group() and get_data_group_color() methods respectively. This makes it customizable too and makes more obvious the fact, that it is a general thing that has nothing to do with stacked graphs in particular. With this there are only 5 lines remaining in graph:draw(), that could be said to be specific to stacked graphs, all pertaining to the `prev_y` variable, which merely preserves the bar coordinates from a previous data group. commit 2a32b6305d893f413303728a0a3da1dcd9a5591e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 22:52:37 2021 +0700 Pointlessly optimize graph:add_value() 1) Purge old values first, then insert the new one, if needed. Since Lua grows arrays in power of two steps, and our guess_capacity() also takes care to return even integers, it would be a shame to overstep the capacity by one by inserting first and thus getting the table size doubled only to never use the most of it. 2) Use a removal procedure that doesn't call the slow length operator at all. Previous code called it twice (once in table.remove) for every removed value. commit 680ea8f5d81e9040afb4662b3229e937e4b89e02 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:23:38 2021 +0700 Set graph data series color before the painting loop commit b970f0f221ae76250f2a94433ff79d653caa6e21 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:05:37 2021 +0700 Drop a large no longer necessary `if` in graph:draw_values() This commit is almost purely a whitespace change. commit 6b4f0541c70ec99d7f6e276bae64939f337120d6 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:00:50 2021 +0700 Extract values rendering code into graph:draw_values() method This is to avoid ungodly if nesting levels, which are needed to bail out of drawing values early and skip to drawing the border. The next commit will get rid of the if. commit e0950e4f99d1ea1c4b8cea2cc67629fa40030c2a Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 14:50:20 2021 +0700 Track graph draw() usage to guess necessary array capacity The last step of divorcing add_values() from width calculations, while ensuring that user doesn't observe a lack of data to draw. The widget now tracks an approximate number of values that it has been asked to draw() in _private.last_drawn_values_num, and truncates its values array based on that (if not overridden by the capacity property). commit 2e3d542282f35e7c0fed454da0f2eada0fcf3b78 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 13:35:12 2021 +0700 Add `capacity` property to graph widget This is the second step of divorcing add_values() from calculations involving widths. How many values are stored and how many values are drawn are distinct concepts. There isn't even a single definitive "how many values are drawn" because the widget could be rendered in several places with distinct widths simultaneously, and in every such place the data must be drawn appropriately, e.g. with auto-scaling, that takes only the visible data points into account. And the draw() method can do that now. The default capacity value of nil uses heuristics based on widget's _private.width to guess some capacity that would tolerate some dynamic widget resizing. If the user finds the heuristic failing them, they can set the capacity property to the desired maximum number of values that will be kept in the _private.values array (in each data group). commit 08d4a80686f7fe363551c35589a460839f60853e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 03:27:18 2021 +0700 Extract values size computation into compute_drawn_values_num() This is the first step of making add_values() not concern itself (directly) with widget dimensions. The user can monkey-patch the method to override how many values are (logically) drawn, because it affects graph auto-scaling and can be useful for rendering of step_shape-s that paint outside of their step_width. I didn't make it a full-blown function-valued property, because it seems a fringe functionality, which I'm too lazy to document. commit 3c5617c98ab1147618ed22e8414a77fc56b45f6e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 02:32:29 2021 +0700 Copy graph widget prototype methods over in bulk commit 169538837ece41a03d02ba8fbc0e4097062b67a3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun Apr 25 05:58:01 2021 +0700 Remove graph.baseline_y property After giving it some thought I've concluded, that it is unlikely to be useful as it is now. I'd better reserve the name for some more sensible functionality, like fixing the baseline_value defined axis at the given widget coordinate, adjusting the scaling accordingly, so min_value/max_value won't generally correspond to widget top and bottom anymore. commit 202ffaa9a0b820c7c471e1121d01b91f8f76cd38 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 12:06:55 2021 +0700 Add baseline_y and baseline_value properties to graph widget Now values in graph have a *baseline*, tunable with baseline_y and baseline_value properties. It is the (for now invisible) horizontal line from which all bars in the graph grow. I call it a "baseline" instead of "axis", because it doesn't actually have to correspond to the 0 value. But it can, and actually does now by default. baseline_y and baseline_value properties determine the vertical placement of the baseline in the widget. baseline_y is the position of the baseline in normalized widget coordinates, e.g. 0 corresponds to widget's top, 1 - to widget's bottom, and 0.5 - to widget's middle. So if e.g. one sets baseline_y = 0, then no matter how the widget is resized and how its values are scaled, bars in it will be drawn starting from the top. baseline_value is the position of the baseline in value coordinates. So if one sets baseline_value = 10, then no matter what happens, bars for values greater than 10 will be drawn as growing up from this line, and bars for values smaller as growing down. Where and if it will be seen in the widget, depends on the scaling. baseline_value = 0 is the familiar zero axis. baseline_y is unset by default and has priority, i.e. if it's set, then baseline_value is ignored. baseline_value defaults to 0. Prior to this commit, bars were always drawn from the bottom edge of the widget, or, in the new terms, the widget had baseline_y = 1. But it was indistinguishable from baseline_value = 0, because the widget clamped all negative values to 0, so the difference between the two could never be observed. baseline_value = 0 is clearly more useful and familiar to people, so now it is in force by default. commit df7e19abaebf0a871b5ca217b4cc3365533267a6 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 11:18:12 2021 +0700 Move value clamping from graph:add_value() into draw() Previously all values were forcibly clamped between 0 (not tunable at all) and max_value (1 by default). Thus graphs with negative values were not an option. NaN values were conflated with zeros, even though it may be useful to maintain the distinction and render them differently. And if user happened to dynamically change the max_value property, they discovered, that their data got truncated. Preserving user data as it was given is more flexible and makes things easier to debug. This commit does exactly that. So what happens now to the newly allowed values during draw()? There are 3 new cases to consider: 1) NaNs 2) Negatives 3) Values outside of min_value..max_value range NaNs are presently simply not drawn. An empty place is reserved where the value should've been, but nothing else is done. This is distinct from how a zero is handled, e.g. if step_shape is set, it is called with the height parameter = 0, which can still end up drawing something, depending on the shape. One could also consider addition of nan_color/nan_shape properties which could be drawn to alert user to a gap in their data. Negative values necessitate introduction of the concept of the zero axis, from which they will be drawn DOWN, in contrast to positive values. Where should this axis be drawn? How should step_shape-s with inherent direction, e.g. arrows, be drawn for negatives? This will be addressed in the next commit. Values outside of min/max_value range can imply shapes that go beyond widget dimensions. An arrow shape that goes beyond the roof is visibly different from an arrow that saturates and merely touches the widget top, and the user may reasonably desire either behavior. Therefore this commit introduces a boolean clamp_bars property, which defaults to true for reasons of robustness and backward compatibility with the behaviour prior to this commit. The property of course affects only the display of the values, the actual user data stays intact. commit c9c24fb1ab741cd6bb8074971d568e0646c51532 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:29:01 2021 +0700 Use rectangles for drawing graph bars of all sizes I have benchmarked various bar drawing strategies and graph sizes and found out that drawing graph bars with rectangles is as fast or even marginally faster than using fat lines. Thus the code that tries to special-case 1px bars as lines seems to be not worth the additional complexity it introduces, and this commit removes it. Incidentally, I've also established, that rectangles are more robust. If one draws graph bars and feeds cairo very large coordinates (~2^62), then corresponding rectangles fail to render, but all the other rectangles in the same series get rendered successfully, whereas a single large coordinate in a move_to/line_to() sequence is enough to cause all lines in the batch to fail to render. commit bc7f4be5e06405b5aa7fb443a5ace2b9b9575b1f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:01:42 2021 +0700 Cache cairo methods used in the inner loop of graph:draw() I have benchmarked the draw() method, both on cairo xcb and on argb32 image surfaces, and have discovered that cairo context method calls are unnaturally slow, to the point, that almost nothing else in the function matters. For example, drawing bars with vertical lines made the whole draw() function almost 2 times slower than doing the same with rectangles, merely because the former requires two method calls (move_to/line_to), and the latter only one. Simply caching hotpath cairo methods in local variables, reduced the difference between the drawing methods to a negligible one and made the whole function 4 times faster on a typical 100-bar graph, (1.4ms to 0.33ms on my laptop) thus bringing closer my secret dream of drawing live audio data with awesome graphs (kidding). I guess, LGI does something not very optimal there, like gobject-introspecting the method every time, or something. There might be some untapped optimization potential, possibly in upstream, like a method cache, from which everything in awesome could benefit, even though I imagine not many things are so call-intensive as the graph widget, to really feel the difference. commit 538df25bdf02b53d0bbe81809eef2e6fdb05ff6f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 07:32:37 2021 +0700 Use 1-based index in the inner graph draw loop It's used there only once for a calculation and then several times to index some tables, and index 0 is bad for lua tables performance-wise. commit 7412546916cd0a0a490130a120255ffb719398b8 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:55:12 2021 +0700 Leave graph.max_value uninitialized at construction This is a user-visible change, but the previous behavior was probably not what user expected anyway, namely with graph.scale set the graph mysteriously couldn't zoom in onto values that are smaller than 1. commit eb81d7929a161e79c13262cfa73ac2e6a820e8ed Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:48:29 2021 +0700 Allow graph.max_value be nil This makes its behave symmetrically to min_value. Now it defaults to 1, when graph.scale is not set, and to the maximum value in the graph data, when graph.scale is set. commit f622a8430cbfde7007b58d7f4c9c76b08c91e997 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 12:01:13 2021 +0700 Calculate max values num in graph:add_value() more precisely At most so many values were kept in the widget values array, as there are pixels of usable widget width (i.e. sans border). This is exactly right for the surely most common case of graphs with 1px-wide bars, but is more than needed for graphs with step_width+step_spacing>1, and not enough for graphs with subpixel bars. Moreover this made graph autoscaling behave weirdly in graphs with thicker bars, because old values survived longer in the array and kept affecting min_value/max_value calculations even after they were shifted off-screen. The maximum number of kept values is now calculated exactly as the number of values that can be displayed at once, taking step_width and step_spacing into account. It might be worth adding a max_values_num property to the graph to let user override this calculation, in case they draw shapes in step_shape() outside of its supposed rectangle, so our assumptions of visibility are wrong, or the widget often gets resized, resulting in the annoyance of visibly truncated series. commit 0aa63249a18641251a1bf77423739164f0eab39f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 02:30:13 2021 +0700 Fix border_color/border_width bugs in graph widget add_value() wrongly assumed that border_width is always set, when border_color is, so if user set only the latter, an error occurred. add_value() assumed that border_width is always < width which led to awesome hanging up in an infinite loop, should that fail to be the case. The graph border_width property was driven by the border_color property, instead of the other way round. If border_color wasn't set, the border wasn't drawn all, even if user sets non-zero border_width, which is a confusing behavior, in contrast to all other widgets, which simply use a plethora of fallback colors to draw the border, when needed. This also meant that, despite being documented, the beautiful.graph_border_color fallback color could not and was not ever used. If the border_color was set though, an unset border_width was immediately assumed to be 1 instead of 0, also contrary to what other widgets do. Theme colors shouldn't influence widget layout, so this commit fixes this behavior to: 1) border_width always defaults to 0, if unset. 2) border_color falls back to beautiful.graph_border_color or white. Maybe one should also add a beautiful.graph_border_width theme variable, like some widgets do. commit dad4a3253b8fef971fb82720d2a407fcc0a879ad Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 03:11:27 2021 +0700 Implement multiple data series drawing for non-stacked graph The widget can already hold and draw multiple data groups, so it makes little sense to limit drawing only to the first group when graph.stack = false, probably leading to user's confusion. Now user can call add_value(v, group) with group > 1 and the data series will be drawn (over groups with lesser id-s) with graph.stack_colors[group] color, if stack_colors is set, or with graph.color, if stack_colors isn't set. Thus nothing visibly changes for all current users of non-stacked graphs with only single data series, but the new functionality is now there for those who need it. One can't fully emulate this functionality, e.g. with multiple graphs widgets in a wibox.layout.stack, because data series in a single widget can enjoy a common auto-scaling. commit 3e645c976184ab5f50d625271c25062db9ab7ed9 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:59:12 2021 +0700 Don't draw a stacked graph data group if there's no color for it If stack_colors[idx] is nil for the group `idx`, don't draw it at all (as opposed, e.g. to the effect of drawing it with a transparent color, or as though it had all values == 0, which both produce different results). This code is still subtly different from prior behavior, because now user can have a non-contiguous stack_colors table and turn the display of any data group on and off at will, whereas the code two commits earlier would just stop rendering as soon as ipairs(stack_colors) does, i.e. no data series were drawn past the first nil hole. This commit only looks large, but is mostly a whitespace change due to nesting from a pair of wrapping ifs. commit 8c0273786de1ade66bdd977699f98b588da9f487 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:32:42 2021 +0700 Unify stacked and non-stacked graph rendering Now stacked graphs support everything the nonstacked do, like step_width ~= 1 and custom step_shape-s. Technical notes: This code no longer calls cr:stroke() for every single value in a stacked graph, but rather paints a whole series and then cr:stroke/fill()-s it at all once with its color, improving performance considerably. Additionally the graph no longer relies on the presence of the stack_colors property to render series. It draws all data that it has, and, if there's no corresponding color in stack_colors, falls back to graph.color. This will be changed in the next commit to the prior behavior of only drawing those series for which there's a color in the stack_colors table, but that would've made this commit's diff much larger due to required nesting ifs over large code chunks. This commit also takes care to properly handle NaNs and negative values. Those are currently (almost) guaranteed to not occur in the values array due to clipping in graph:add_value(), but I intend to eventually drop the clipping, because it does nothing that draw() really relies upon, and allowing unclipped and NaN values is more flexible and less surprising, e.g. when user dynamically toggles graph.scale. Eventually I intend to implement drawing negative values in non-stacked graphs and possibly even in the stacked ones. commit 38c6589c51b2aa057d7c2f40182ecf32b5b2ff5e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 10:04:15 2021 +0700 Convert graph:draw() to use `self` commit f21f1b2550426df1c4ea8abde079b1ccef84b394 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 12:27:26 2021 +0700 Fix scaling for stacked graphs Stacked graphs have cumulative heights, values from all groups that end up in the same graph bar are effectively summed. Previous code didn't take that into account when calculating min/max_value for the purposes of rescaling, but simply examined every value separately. This code adds up all values up front and finds min/max among those, to determine proper scaling that would fit the graph exactly. commit 1ce4c7bf63bdd6c99c195d668347bec49355e921 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 09:49:35 2021 +0700 Draw shaped graph bars only up to the baseline Prior to this commit, every step_shape bar was, for some reason, drawn with height = full widget's height, regardless of the value it represents. So for the most bars their lower end was far outside the box and therefore clipped off. So e.g. one couldn't see the lower round edges of rounded rectangles. This commit fixes that by appropriately varying the height that is passed into step_shape(), so that lower edges of the bars always exactly touch the lower edge of the graph. If the user wants to restore the prior behavior, they can simply ignore the passed-in height in step_shape() and just always draw their shape with height = full widget height. But they couldn't replicate the effect of this commit in their rc.lua without essentially writing their own graph widget. commit e25185b08da23745b5815f85f6e9e43222ed2294 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 08:42:54 2021 +0700 Avoid incremental transform shifting during graph bar rendering Instead of letting addition errors and cognitive load accumulate simply use the same offset calculation that is used for lines and reset the transform back to the top-left corner after every drawn bar. It's conceptually simpler, and will be useful later for stacked graphs and more. It also fixes a discrepancy between plain and step_shape rendering. Prior to this commit, bar coloring occurred with a disturbed transform, which was centered at the last drawn bar, wherever it happened to be. This meant that nontrivial colors, like gradients and images, were arbitrarily offset when step_shape was used. commit f3e98866d8583c35d0b557c09f529299edc9ba2c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:45:29 2021 +0700 Refactor out graph bar height expression into a variable commit f54528d6892f5aaa4595917853c9e2130f743fdb Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:20:25 2021 +0700 Draw graph bars with lines only when step_width == 1 Before this commit graph bars of step_width < 1 were drawn with lines of width 1, but that's a bit unfortunate, because it led to much overpainting, while still ending up with a graphically incorrect result. Let's draw such bars with rectangles and let cairo do things with subpixels, getting more graphically accurate result, while still keeping the common case of step_width == 1 (supposedly) fast-drawn with lines. commit b6515dca67c18b78eb5657378131a62ab43d4cfc Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:54:07 2021 +0700 Fix unsharp edges when drawing graph bars with rectangles The 0.5 offset is only helpful for 1-width strokes, not for fills. commit 7f3db8f07e82f821bcd1dc418d66564f537609d6 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:21:01 2021 +0700 Do coordinate transformations even if the value isn't drawn The graph presently doesn't draw values < 0, but there was a discrepancy between drawing with and without step_shape. The default line/rectangle drawing reserves an empty place where the skipped value should've been, but no such empty place is reserved, when step_shape is used. This commit fixes that. Skipped values always have their empty space now. commit 7b731ebfe96af7a9dba9c57371da6761cf22c00c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:03:30 2021 +0700 Fix graph bar scaling when min_value is not 0 Previous calculation lead to underscaling of values so that even value = max_value wasn't drawn to full height. commit dca59fd8b7fff0e7e646f2dfc6bd92047028e5a8 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:18:40 2021 +0700 Calculate the horizontal graph bar offset only when needed commit 940ddf4b9825e9c3a91d276208c089d8c74e49db Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:17:23 2021 +0700 Call cr:move_to() only when drawing the graph with lines It's redundant otherwise and might even interfere with user's drawing in step_shape(). commit b5d5cd10ca29fb29cd751faaa95137c2fe89ff63 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:04:06 2021 +0700 Fix wrong horizontal offset calculations in widget.graph Indices in the loop are zero-based, so things like i-1 and i>1 aren't necessary and actually wouldn't have been correct even in a 1-based loop. step_shape = gears.shape.rectangle and step_shape = nil, which is supposed to default to rectangle according to docs, were drawn differently due to mismatched offset calculations, which led to problems especially noticeable when step_spacing is not zero, e.g: 1) whole graph shifted left too much, drawn partially off-screen. 2) whole graph shifted right too much, and the first bar placed closer to the rest than others. commit dc369fa198281e000afc1bd17326f3a264adf7d1 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 02:09:21 2021 +0700 Fix the "border_width affects graph bar width" bug Graph bars were drawn with lines of border_width thickness instead of 1, i.e. possibly too fat to be readable. commit 1ba73378cf29ed27a6ecd18716394779ceb543d9 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 00:13:03 2021 +0700 Simplify internal values representation in graph even more New values in graph._private.values were pushed to the end, and old values were removed from the beginning of the table. In draw() however newer values are drawn first, at the left edge of the graph, necessitating annoying idx = #values - idx conversions everywhere. In particular in stacked graph that means that values that are drawn together may have different indices. One has to shift the values array in add_value() either way, so one might as well insert new values at the beginning of the array and simplify the rest of the code. This doesn't seem like much yet, but it will prove to be much more convenient and less error-prone in what I'm going to do later. commit f70523ee68dbd6b67050ace4b82903a9b22440c7 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 13:35:10 2021 +0700 Simplify internal values representation in graph widget graph._private.values was either a table of values or a table of tables of values, depending on whether "stack" property is enabled, ... or a mix of both if user forgot to pass the second "group" parameter to graph:add_value(). This resulted in pretty fragile and confusing behavior, e.g. leaving user to wonder, why they see nothing more after setting stack=true for a graph in a previously working rc.lua, and demanding a very careful use of add_value() from the user, or else some values or even entire stacks would mysteriously vanish and errors in draw() get triggered, sometimes after a period of seemingly normal functioning. Now `values` is always a table of tables and the group parameter in add_value() defaults to 1, thus making the non-stacked graph a trivial variant of the stacked graph.
2021-05-20 17:14:03 +02:00
if args.width or args.height then
gdebug.deprecate(
"`args.width` and `args.height` are deprecated. "..
"Use a `wibox.container.constraint` widget "..
"or `forced_width`/`forced_height`",
{deprecated_in=5, raw=true}
)
end
if awesome.api_level <= 5 then
local width = args.width or 100
local height = args.height or 20
2016-05-23 07:30:44 +02:00
graph: complete widget overhaul and bug fixes Squashed commit of the following: commit 69821f51fe1e8652715658543e50631ece495090 Author: Alex Belykh <albel727@ngs.ru> Date: Mon May 10 19:17:55 2021 +0700 Refactor property handling in wibox.widget.graph In addition, baseline_value, step_width and step_spacing properties are all set to their default non-nil numeric values during instance creation now. commit 842eb429bf5df4b2ba46b2e6f7646afe89b1a3c3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 9 06:18:45 2021 +0700 Make graph:draw_values() private There's no documentation nor confidence that it's a good API, so I'm hiding it for now. commit 720746780574e4bad14a71fc4174955139c2eb50 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 04:04:13 2021 +0700 Document graph_should_draw_data_group() for posterity commit 7c73f3754ce6ba86b118f1170a6f9e406ab4841d Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:59:21 2021 +0700 Make graph:should_draw_data_group() private There's no confidence whether it's a good API, so I'm hiding it for now. commit b3539e20adb6423118af726ae4f8751ef864b5d2 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:36:44 2021 +0700 Make graph:preprocess_values() private There's no confidence whether it's a good API, so I'm hiding it for now. commit 2df4400e780a6260f8d0ab1c60734fbb11bf3c4f Author: Alex Belykh <albel727@ngs.ru> Date: Thu May 6 10:05:23 2021 +0700 Add spec/wibox/widget/graph_spec.lua commit ce01a9771ab439b8df841f1565aa4aa8a44a5e0d Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 08:16:18 2021 +0700 Improve graph.capacity documentation commit e28fade8f655802f5bf89154169f68d256b8e332 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 07:36:01 2021 +0700 Document graph:compute_drawn_values_num() commit dd68332292465422d08b48e9f0229f143beccbc6 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 05:24:23 2021 +0700 Document graph:pick_data_group_color() method commit ca6dc55f77aca88a6e9cc90bb6fd0045739f41f5 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 04:13:58 2021 +0700 Guard against setting data groups sparsely in graph:add_value() commit cdea5f126f7984e72924f6dffbd312c4fe3b032f Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 02:36:12 2021 +0700 Refactor scaling and baseline choice out of graph:draw() commit 10135deafe8a9390ba66ab657ebb1fb41d549e8c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 21:34:48 2021 +0700 Document graph.nan_color and graph.stack properties commit 0d73bb84ac66b87e16aee472bd1f299549f66a1c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 20:49:24 2021 +0700 Use a better example in docs for the graph.stack property commit c27d160a0826d033d49d6141608c40aa8a0642fa Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 16:05:38 2021 +0700 Document graph.baseline_value property commit 459bc8176c13d4ea32af0dcdc2399d3b929b1c6a Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:53:47 2021 +0700 Document graph.clamp_bars property commit e732ee0b3b007d213e689b98cc00ae7cb141ae08 Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:34 2021 +0700 Improve graph:add_value() and :clear() documentation commit ae73e3aa9f8d60409cbe4cb4da9612f31af4f38e Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:11 2021 +0700 Improve graph min_value/max_value/scale documentation commit a7350996a9def773b91b1631e6c0507e33d8f93b Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:27:36 2021 +0700 Cleanup graph examples to show only relevant things in docs commit 30740c26f4f41c16f4c8a3c339bcb7bd299e14cd Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 14:04:36 2021 +0700 Rename graph.stack_colors property to `group_colors` The colors are used for stacked and non-stacked graphs alike, so the name didn't make sense. Also document and clarify some color stuff. commit 1ea01bfd410971cf0d0c25fd74cd764d5eb7279d Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 21:25:41 2021 +0700 Add an example file for code coverage of some fringe graph cases commit eae68e39553f88a6275a0c65bf040b5d4c262b54 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 20:23:32 2021 +0700 Add step_shape to the graph/nan_color example for code coverage commit 8027147bc2eab34a97b49a428fb8cef27f122bed Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:55:18 2021 +0700 Add example files for graph.nan_color/nan_indication properties commit 89a25a165c52b2c15b6c4d95ecb777a9067e66f9 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:33:07 2021 +0700 Make widget graph:add_value(v) default v to NaN 0 as a default makes no sense and silences the error that is failing to specify a value, to which the user should rather be alerted. The graph widget has sane NaN handling now and will do just that. commit 2835d552440be48d356a7f7b1d64963696dd550b Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:20:48 2021 +0700 Implement graph.nan_color and nan_indication properties Now, whenever there's a NaN among values and graph.nan_indication is set, the corresponding area, where the value bar should have been drawn, is filled with the nan_color from top to bottom. The painting is done after all data is rendered, to make sure that it won't be overpainted and go unnoticed. NaNs among graph's values are inevitably a sign of some error and it is arguably not a sane default behavior to simply not render them at all. The user should take immediate note of any errors by default, instead of wondering at a mysteriously empty graph widget, which is why nan_indication is set to true by default. If the user wants to silence NaNs, they can always simply set it to false. nan_color if unset, defaults to a noticeable yellow-black pattern. As a direct and planned consequence, stacked graphs with negative values in them trigger the NaN handling too. But silencing NaN handling for those graphs is also possible and semi-sane, because then NaNs and negatives are ignored and the graph behaves (almost) like they were zeros. commit 9c1f8f08b400e01e19c534f3810c9c9ab485760c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 26 08:10:19 2021 +0700 Refactor out graph_map_value_to_widget_coordinates() function commit 1905991ddecabdae3e15ffb8ee5b18a1bee1a711 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 28 13:39:29 2021 +0700 Add an example file for graph.baseline_value property commit b31b39d66f4c037a9484b71a3f910703ae01fac0 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 07:03:49 2021 +0700 Add an example file for graph.clamp_bars=false/true comparison commit a1fc850e3047f9ee2c9ddeffacfbabf3ded34171 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 04:06:45 2021 +0700 Add an example file for graphs with negative values and shapes commit 2f012f8ff1bb5094c14e724a61e59a2a3a296bc8 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 01:06:05 2021 +0700 Add an example file for a graph displaying negative values commit 7b14fbd59a4c4c51962fd3110a1a900dd5f4eb98 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 11:18:26 2021 +0700 Add an example file for graphs with step_width < 1 commit dfd9a54544b637e2fbe3d1325f9cc24b3f4d2313 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 10:17:38 2021 +0700 Add an example file for graph.stack=false/true comparison Should also demonstrate that multiple data groups are possible for non-stacked graphs too. commit 5b532106c3fd255f9b66a66cfb88d977b489ab5d Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 09:34:14 2021 +0700 Add an example file for disabling graph data groups with nil colors commit ac6f1083c02fcb428f089a616f9b0ebaa5496b6c Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:57:25 2021 +0700 Add an example file for step_shape/width/spacing on stacked graphs commit b3703db6a5139e73e72c1cfd198ece2cd9be7df2 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:43:56 2021 +0700 Add an example file for scale=false/true on stacked graph commit b000aed4a12cb44fb501e1c8432a71a14b2f8f3f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 20 08:22:19 2021 +0700 Document deprecation of graph's width and height commit cb0ad617bd6c83a499896797318e3b0b588c3566 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 07:53:37 2021 +0700 Deprecate width and height properties of wibox.widget.graph commit 8c64e14ad91a074b784166a7054e53444cccb9d2 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 04:37:59 2021 +0700 Refactor graph:draw() to separate concerns Firstly, summation of group values for stacked graphs is extracted into graph:preprocess_values(). This makes graph:draw() smaller and exposes the fact, that stacked graphs are almost entirely just a form of a more general concept: pre-render data manipulation. It's something a user might want to customize, and now they can do it by monkey-patching the method. Secondly, the logic for deciding, if and with which color a data group should be drawn, is extracted into graph:should_draw_data_group() and get_data_group_color() methods respectively. This makes it customizable too and makes more obvious the fact, that it is a general thing that has nothing to do with stacked graphs in particular. With this there are only 5 lines remaining in graph:draw(), that could be said to be specific to stacked graphs, all pertaining to the `prev_y` variable, which merely preserves the bar coordinates from a previous data group. commit 2a32b6305d893f413303728a0a3da1dcd9a5591e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 22:52:37 2021 +0700 Pointlessly optimize graph:add_value() 1) Purge old values first, then insert the new one, if needed. Since Lua grows arrays in power of two steps, and our guess_capacity() also takes care to return even integers, it would be a shame to overstep the capacity by one by inserting first and thus getting the table size doubled only to never use the most of it. 2) Use a removal procedure that doesn't call the slow length operator at all. Previous code called it twice (once in table.remove) for every removed value. commit 680ea8f5d81e9040afb4662b3229e937e4b89e02 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:23:38 2021 +0700 Set graph data series color before the painting loop commit b970f0f221ae76250f2a94433ff79d653caa6e21 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:05:37 2021 +0700 Drop a large no longer necessary `if` in graph:draw_values() This commit is almost purely a whitespace change. commit 6b4f0541c70ec99d7f6e276bae64939f337120d6 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:00:50 2021 +0700 Extract values rendering code into graph:draw_values() method This is to avoid ungodly if nesting levels, which are needed to bail out of drawing values early and skip to drawing the border. The next commit will get rid of the if. commit e0950e4f99d1ea1c4b8cea2cc67629fa40030c2a Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 14:50:20 2021 +0700 Track graph draw() usage to guess necessary array capacity The last step of divorcing add_values() from width calculations, while ensuring that user doesn't observe a lack of data to draw. The widget now tracks an approximate number of values that it has been asked to draw() in _private.last_drawn_values_num, and truncates its values array based on that (if not overridden by the capacity property). commit 2e3d542282f35e7c0fed454da0f2eada0fcf3b78 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 13:35:12 2021 +0700 Add `capacity` property to graph widget This is the second step of divorcing add_values() from calculations involving widths. How many values are stored and how many values are drawn are distinct concepts. There isn't even a single definitive "how many values are drawn" because the widget could be rendered in several places with distinct widths simultaneously, and in every such place the data must be drawn appropriately, e.g. with auto-scaling, that takes only the visible data points into account. And the draw() method can do that now. The default capacity value of nil uses heuristics based on widget's _private.width to guess some capacity that would tolerate some dynamic widget resizing. If the user finds the heuristic failing them, they can set the capacity property to the desired maximum number of values that will be kept in the _private.values array (in each data group). commit 08d4a80686f7fe363551c35589a460839f60853e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 03:27:18 2021 +0700 Extract values size computation into compute_drawn_values_num() This is the first step of making add_values() not concern itself (directly) with widget dimensions. The user can monkey-patch the method to override how many values are (logically) drawn, because it affects graph auto-scaling and can be useful for rendering of step_shape-s that paint outside of their step_width. I didn't make it a full-blown function-valued property, because it seems a fringe functionality, which I'm too lazy to document. commit 3c5617c98ab1147618ed22e8414a77fc56b45f6e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 02:32:29 2021 +0700 Copy graph widget prototype methods over in bulk commit 169538837ece41a03d02ba8fbc0e4097062b67a3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun Apr 25 05:58:01 2021 +0700 Remove graph.baseline_y property After giving it some thought I've concluded, that it is unlikely to be useful as it is now. I'd better reserve the name for some more sensible functionality, like fixing the baseline_value defined axis at the given widget coordinate, adjusting the scaling accordingly, so min_value/max_value won't generally correspond to widget top and bottom anymore. commit 202ffaa9a0b820c7c471e1121d01b91f8f76cd38 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 12:06:55 2021 +0700 Add baseline_y and baseline_value properties to graph widget Now values in graph have a *baseline*, tunable with baseline_y and baseline_value properties. It is the (for now invisible) horizontal line from which all bars in the graph grow. I call it a "baseline" instead of "axis", because it doesn't actually have to correspond to the 0 value. But it can, and actually does now by default. baseline_y and baseline_value properties determine the vertical placement of the baseline in the widget. baseline_y is the position of the baseline in normalized widget coordinates, e.g. 0 corresponds to widget's top, 1 - to widget's bottom, and 0.5 - to widget's middle. So if e.g. one sets baseline_y = 0, then no matter how the widget is resized and how its values are scaled, bars in it will be drawn starting from the top. baseline_value is the position of the baseline in value coordinates. So if one sets baseline_value = 10, then no matter what happens, bars for values greater than 10 will be drawn as growing up from this line, and bars for values smaller as growing down. Where and if it will be seen in the widget, depends on the scaling. baseline_value = 0 is the familiar zero axis. baseline_y is unset by default and has priority, i.e. if it's set, then baseline_value is ignored. baseline_value defaults to 0. Prior to this commit, bars were always drawn from the bottom edge of the widget, or, in the new terms, the widget had baseline_y = 1. But it was indistinguishable from baseline_value = 0, because the widget clamped all negative values to 0, so the difference between the two could never be observed. baseline_value = 0 is clearly more useful and familiar to people, so now it is in force by default. commit df7e19abaebf0a871b5ca217b4cc3365533267a6 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 11:18:12 2021 +0700 Move value clamping from graph:add_value() into draw() Previously all values were forcibly clamped between 0 (not tunable at all) and max_value (1 by default). Thus graphs with negative values were not an option. NaN values were conflated with zeros, even though it may be useful to maintain the distinction and render them differently. And if user happened to dynamically change the max_value property, they discovered, that their data got truncated. Preserving user data as it was given is more flexible and makes things easier to debug. This commit does exactly that. So what happens now to the newly allowed values during draw()? There are 3 new cases to consider: 1) NaNs 2) Negatives 3) Values outside of min_value..max_value range NaNs are presently simply not drawn. An empty place is reserved where the value should've been, but nothing else is done. This is distinct from how a zero is handled, e.g. if step_shape is set, it is called with the height parameter = 0, which can still end up drawing something, depending on the shape. One could also consider addition of nan_color/nan_shape properties which could be drawn to alert user to a gap in their data. Negative values necessitate introduction of the concept of the zero axis, from which they will be drawn DOWN, in contrast to positive values. Where should this axis be drawn? How should step_shape-s with inherent direction, e.g. arrows, be drawn for negatives? This will be addressed in the next commit. Values outside of min/max_value range can imply shapes that go beyond widget dimensions. An arrow shape that goes beyond the roof is visibly different from an arrow that saturates and merely touches the widget top, and the user may reasonably desire either behavior. Therefore this commit introduces a boolean clamp_bars property, which defaults to true for reasons of robustness and backward compatibility with the behaviour prior to this commit. The property of course affects only the display of the values, the actual user data stays intact. commit c9c24fb1ab741cd6bb8074971d568e0646c51532 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:29:01 2021 +0700 Use rectangles for drawing graph bars of all sizes I have benchmarked various bar drawing strategies and graph sizes and found out that drawing graph bars with rectangles is as fast or even marginally faster than using fat lines. Thus the code that tries to special-case 1px bars as lines seems to be not worth the additional complexity it introduces, and this commit removes it. Incidentally, I've also established, that rectangles are more robust. If one draws graph bars and feeds cairo very large coordinates (~2^62), then corresponding rectangles fail to render, but all the other rectangles in the same series get rendered successfully, whereas a single large coordinate in a move_to/line_to() sequence is enough to cause all lines in the batch to fail to render. commit bc7f4be5e06405b5aa7fb443a5ace2b9b9575b1f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:01:42 2021 +0700 Cache cairo methods used in the inner loop of graph:draw() I have benchmarked the draw() method, both on cairo xcb and on argb32 image surfaces, and have discovered that cairo context method calls are unnaturally slow, to the point, that almost nothing else in the function matters. For example, drawing bars with vertical lines made the whole draw() function almost 2 times slower than doing the same with rectangles, merely because the former requires two method calls (move_to/line_to), and the latter only one. Simply caching hotpath cairo methods in local variables, reduced the difference between the drawing methods to a negligible one and made the whole function 4 times faster on a typical 100-bar graph, (1.4ms to 0.33ms on my laptop) thus bringing closer my secret dream of drawing live audio data with awesome graphs (kidding). I guess, LGI does something not very optimal there, like gobject-introspecting the method every time, or something. There might be some untapped optimization potential, possibly in upstream, like a method cache, from which everything in awesome could benefit, even though I imagine not many things are so call-intensive as the graph widget, to really feel the difference. commit 538df25bdf02b53d0bbe81809eef2e6fdb05ff6f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 07:32:37 2021 +0700 Use 1-based index in the inner graph draw loop It's used there only once for a calculation and then several times to index some tables, and index 0 is bad for lua tables performance-wise. commit 7412546916cd0a0a490130a120255ffb719398b8 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:55:12 2021 +0700 Leave graph.max_value uninitialized at construction This is a user-visible change, but the previous behavior was probably not what user expected anyway, namely with graph.scale set the graph mysteriously couldn't zoom in onto values that are smaller than 1. commit eb81d7929a161e79c13262cfa73ac2e6a820e8ed Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:48:29 2021 +0700 Allow graph.max_value be nil This makes its behave symmetrically to min_value. Now it defaults to 1, when graph.scale is not set, and to the maximum value in the graph data, when graph.scale is set. commit f622a8430cbfde7007b58d7f4c9c76b08c91e997 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 12:01:13 2021 +0700 Calculate max values num in graph:add_value() more precisely At most so many values were kept in the widget values array, as there are pixels of usable widget width (i.e. sans border). This is exactly right for the surely most common case of graphs with 1px-wide bars, but is more than needed for graphs with step_width+step_spacing>1, and not enough for graphs with subpixel bars. Moreover this made graph autoscaling behave weirdly in graphs with thicker bars, because old values survived longer in the array and kept affecting min_value/max_value calculations even after they were shifted off-screen. The maximum number of kept values is now calculated exactly as the number of values that can be displayed at once, taking step_width and step_spacing into account. It might be worth adding a max_values_num property to the graph to let user override this calculation, in case they draw shapes in step_shape() outside of its supposed rectangle, so our assumptions of visibility are wrong, or the widget often gets resized, resulting in the annoyance of visibly truncated series. commit 0aa63249a18641251a1bf77423739164f0eab39f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 02:30:13 2021 +0700 Fix border_color/border_width bugs in graph widget add_value() wrongly assumed that border_width is always set, when border_color is, so if user set only the latter, an error occurred. add_value() assumed that border_width is always < width which led to awesome hanging up in an infinite loop, should that fail to be the case. The graph border_width property was driven by the border_color property, instead of the other way round. If border_color wasn't set, the border wasn't drawn all, even if user sets non-zero border_width, which is a confusing behavior, in contrast to all other widgets, which simply use a plethora of fallback colors to draw the border, when needed. This also meant that, despite being documented, the beautiful.graph_border_color fallback color could not and was not ever used. If the border_color was set though, an unset border_width was immediately assumed to be 1 instead of 0, also contrary to what other widgets do. Theme colors shouldn't influence widget layout, so this commit fixes this behavior to: 1) border_width always defaults to 0, if unset. 2) border_color falls back to beautiful.graph_border_color or white. Maybe one should also add a beautiful.graph_border_width theme variable, like some widgets do. commit dad4a3253b8fef971fb82720d2a407fcc0a879ad Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 03:11:27 2021 +0700 Implement multiple data series drawing for non-stacked graph The widget can already hold and draw multiple data groups, so it makes little sense to limit drawing only to the first group when graph.stack = false, probably leading to user's confusion. Now user can call add_value(v, group) with group > 1 and the data series will be drawn (over groups with lesser id-s) with graph.stack_colors[group] color, if stack_colors is set, or with graph.color, if stack_colors isn't set. Thus nothing visibly changes for all current users of non-stacked graphs with only single data series, but the new functionality is now there for those who need it. One can't fully emulate this functionality, e.g. with multiple graphs widgets in a wibox.layout.stack, because data series in a single widget can enjoy a common auto-scaling. commit 3e645c976184ab5f50d625271c25062db9ab7ed9 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:59:12 2021 +0700 Don't draw a stacked graph data group if there's no color for it If stack_colors[idx] is nil for the group `idx`, don't draw it at all (as opposed, e.g. to the effect of drawing it with a transparent color, or as though it had all values == 0, which both produce different results). This code is still subtly different from prior behavior, because now user can have a non-contiguous stack_colors table and turn the display of any data group on and off at will, whereas the code two commits earlier would just stop rendering as soon as ipairs(stack_colors) does, i.e. no data series were drawn past the first nil hole. This commit only looks large, but is mostly a whitespace change due to nesting from a pair of wrapping ifs. commit 8c0273786de1ade66bdd977699f98b588da9f487 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:32:42 2021 +0700 Unify stacked and non-stacked graph rendering Now stacked graphs support everything the nonstacked do, like step_width ~= 1 and custom step_shape-s. Technical notes: This code no longer calls cr:stroke() for every single value in a stacked graph, but rather paints a whole series and then cr:stroke/fill()-s it at all once with its color, improving performance considerably. Additionally the graph no longer relies on the presence of the stack_colors property to render series. It draws all data that it has, and, if there's no corresponding color in stack_colors, falls back to graph.color. This will be changed in the next commit to the prior behavior of only drawing those series for which there's a color in the stack_colors table, but that would've made this commit's diff much larger due to required nesting ifs over large code chunks. This commit also takes care to properly handle NaNs and negative values. Those are currently (almost) guaranteed to not occur in the values array due to clipping in graph:add_value(), but I intend to eventually drop the clipping, because it does nothing that draw() really relies upon, and allowing unclipped and NaN values is more flexible and less surprising, e.g. when user dynamically toggles graph.scale. Eventually I intend to implement drawing negative values in non-stacked graphs and possibly even in the stacked ones. commit 38c6589c51b2aa057d7c2f40182ecf32b5b2ff5e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 10:04:15 2021 +0700 Convert graph:draw() to use `self` commit f21f1b2550426df1c4ea8abde079b1ccef84b394 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 12:27:26 2021 +0700 Fix scaling for stacked graphs Stacked graphs have cumulative heights, values from all groups that end up in the same graph bar are effectively summed. Previous code didn't take that into account when calculating min/max_value for the purposes of rescaling, but simply examined every value separately. This code adds up all values up front and finds min/max among those, to determine proper scaling that would fit the graph exactly. commit 1ce4c7bf63bdd6c99c195d668347bec49355e921 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 09:49:35 2021 +0700 Draw shaped graph bars only up to the baseline Prior to this commit, every step_shape bar was, for some reason, drawn with height = full widget's height, regardless of the value it represents. So for the most bars their lower end was far outside the box and therefore clipped off. So e.g. one couldn't see the lower round edges of rounded rectangles. This commit fixes that by appropriately varying the height that is passed into step_shape(), so that lower edges of the bars always exactly touch the lower edge of the graph. If the user wants to restore the prior behavior, they can simply ignore the passed-in height in step_shape() and just always draw their shape with height = full widget height. But they couldn't replicate the effect of this commit in their rc.lua without essentially writing their own graph widget. commit e25185b08da23745b5815f85f6e9e43222ed2294 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 08:42:54 2021 +0700 Avoid incremental transform shifting during graph bar rendering Instead of letting addition errors and cognitive load accumulate simply use the same offset calculation that is used for lines and reset the transform back to the top-left corner after every drawn bar. It's conceptually simpler, and will be useful later for stacked graphs and more. It also fixes a discrepancy between plain and step_shape rendering. Prior to this commit, bar coloring occurred with a disturbed transform, which was centered at the last drawn bar, wherever it happened to be. This meant that nontrivial colors, like gradients and images, were arbitrarily offset when step_shape was used. commit f3e98866d8583c35d0b557c09f529299edc9ba2c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:45:29 2021 +0700 Refactor out graph bar height expression into a variable commit f54528d6892f5aaa4595917853c9e2130f743fdb Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:20:25 2021 +0700 Draw graph bars with lines only when step_width == 1 Before this commit graph bars of step_width < 1 were drawn with lines of width 1, but that's a bit unfortunate, because it led to much overpainting, while still ending up with a graphically incorrect result. Let's draw such bars with rectangles and let cairo do things with subpixels, getting more graphically accurate result, while still keeping the common case of step_width == 1 (supposedly) fast-drawn with lines. commit b6515dca67c18b78eb5657378131a62ab43d4cfc Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:54:07 2021 +0700 Fix unsharp edges when drawing graph bars with rectangles The 0.5 offset is only helpful for 1-width strokes, not for fills. commit 7f3db8f07e82f821bcd1dc418d66564f537609d6 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:21:01 2021 +0700 Do coordinate transformations even if the value isn't drawn The graph presently doesn't draw values < 0, but there was a discrepancy between drawing with and without step_shape. The default line/rectangle drawing reserves an empty place where the skipped value should've been, but no such empty place is reserved, when step_shape is used. This commit fixes that. Skipped values always have their empty space now. commit 7b731ebfe96af7a9dba9c57371da6761cf22c00c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:03:30 2021 +0700 Fix graph bar scaling when min_value is not 0 Previous calculation lead to underscaling of values so that even value = max_value wasn't drawn to full height. commit dca59fd8b7fff0e7e646f2dfc6bd92047028e5a8 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:18:40 2021 +0700 Calculate the horizontal graph bar offset only when needed commit 940ddf4b9825e9c3a91d276208c089d8c74e49db Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:17:23 2021 +0700 Call cr:move_to() only when drawing the graph with lines It's redundant otherwise and might even interfere with user's drawing in step_shape(). commit b5d5cd10ca29fb29cd751faaa95137c2fe89ff63 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:04:06 2021 +0700 Fix wrong horizontal offset calculations in widget.graph Indices in the loop are zero-based, so things like i-1 and i>1 aren't necessary and actually wouldn't have been correct even in a 1-based loop. step_shape = gears.shape.rectangle and step_shape = nil, which is supposed to default to rectangle according to docs, were drawn differently due to mismatched offset calculations, which led to problems especially noticeable when step_spacing is not zero, e.g: 1) whole graph shifted left too much, drawn partially off-screen. 2) whole graph shifted right too much, and the first bar placed closer to the rest than others. commit dc369fa198281e000afc1bd17326f3a264adf7d1 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 02:09:21 2021 +0700 Fix the "border_width affects graph bar width" bug Graph bars were drawn with lines of border_width thickness instead of 1, i.e. possibly too fat to be readable. commit 1ba73378cf29ed27a6ecd18716394779ceb543d9 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 00:13:03 2021 +0700 Simplify internal values representation in graph even more New values in graph._private.values were pushed to the end, and old values were removed from the beginning of the table. In draw() however newer values are drawn first, at the left edge of the graph, necessitating annoying idx = #values - idx conversions everywhere. In particular in stacked graph that means that values that are drawn together may have different indices. One has to shift the values array in add_value() either way, so one might as well insert new values at the beginning of the array and simplify the rest of the code. This doesn't seem like much yet, but it will prove to be much more convenient and less error-prone in what I'm going to do later. commit f70523ee68dbd6b67050ace4b82903a9b22440c7 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 13:35:10 2021 +0700 Simplify internal values representation in graph widget graph._private.values was either a table of values or a table of tables of values, depending on whether "stack" property is enabled, ... or a mix of both if user forgot to pass the second "group" parameter to graph:add_value(). This resulted in pretty fragile and confusing behavior, e.g. leaving user to wonder, why they see nothing more after setting stack=true for a graph in a previously working rc.lua, and demanding a very careful use of add_value() from the user, or else some values or even entire stacks would mysteriously vanish and errors in draw() get triggered, sometimes after a period of seemingly normal functioning. Now `values` is always a table of tables and the group parameter in add_value() defaults to 1, thus making the non-stacked graph a trivial variant of the stacked graph.
2021-05-20 17:14:03 +02:00
if width < 5 or height < 5 then return end
2016-05-23 07:30:44 +02:00
graph: complete widget overhaul and bug fixes Squashed commit of the following: commit 69821f51fe1e8652715658543e50631ece495090 Author: Alex Belykh <albel727@ngs.ru> Date: Mon May 10 19:17:55 2021 +0700 Refactor property handling in wibox.widget.graph In addition, baseline_value, step_width and step_spacing properties are all set to their default non-nil numeric values during instance creation now. commit 842eb429bf5df4b2ba46b2e6f7646afe89b1a3c3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 9 06:18:45 2021 +0700 Make graph:draw_values() private There's no documentation nor confidence that it's a good API, so I'm hiding it for now. commit 720746780574e4bad14a71fc4174955139c2eb50 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 04:04:13 2021 +0700 Document graph_should_draw_data_group() for posterity commit 7c73f3754ce6ba86b118f1170a6f9e406ab4841d Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:59:21 2021 +0700 Make graph:should_draw_data_group() private There's no confidence whether it's a good API, so I'm hiding it for now. commit b3539e20adb6423118af726ae4f8751ef864b5d2 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:36:44 2021 +0700 Make graph:preprocess_values() private There's no confidence whether it's a good API, so I'm hiding it for now. commit 2df4400e780a6260f8d0ab1c60734fbb11bf3c4f Author: Alex Belykh <albel727@ngs.ru> Date: Thu May 6 10:05:23 2021 +0700 Add spec/wibox/widget/graph_spec.lua commit ce01a9771ab439b8df841f1565aa4aa8a44a5e0d Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 08:16:18 2021 +0700 Improve graph.capacity documentation commit e28fade8f655802f5bf89154169f68d256b8e332 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 07:36:01 2021 +0700 Document graph:compute_drawn_values_num() commit dd68332292465422d08b48e9f0229f143beccbc6 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 05:24:23 2021 +0700 Document graph:pick_data_group_color() method commit ca6dc55f77aca88a6e9cc90bb6fd0045739f41f5 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 04:13:58 2021 +0700 Guard against setting data groups sparsely in graph:add_value() commit cdea5f126f7984e72924f6dffbd312c4fe3b032f Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 02:36:12 2021 +0700 Refactor scaling and baseline choice out of graph:draw() commit 10135deafe8a9390ba66ab657ebb1fb41d549e8c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 21:34:48 2021 +0700 Document graph.nan_color and graph.stack properties commit 0d73bb84ac66b87e16aee472bd1f299549f66a1c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 20:49:24 2021 +0700 Use a better example in docs for the graph.stack property commit c27d160a0826d033d49d6141608c40aa8a0642fa Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 16:05:38 2021 +0700 Document graph.baseline_value property commit 459bc8176c13d4ea32af0dcdc2399d3b929b1c6a Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:53:47 2021 +0700 Document graph.clamp_bars property commit e732ee0b3b007d213e689b98cc00ae7cb141ae08 Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:34 2021 +0700 Improve graph:add_value() and :clear() documentation commit ae73e3aa9f8d60409cbe4cb4da9612f31af4f38e Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:11 2021 +0700 Improve graph min_value/max_value/scale documentation commit a7350996a9def773b91b1631e6c0507e33d8f93b Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:27:36 2021 +0700 Cleanup graph examples to show only relevant things in docs commit 30740c26f4f41c16f4c8a3c339bcb7bd299e14cd Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 14:04:36 2021 +0700 Rename graph.stack_colors property to `group_colors` The colors are used for stacked and non-stacked graphs alike, so the name didn't make sense. Also document and clarify some color stuff. commit 1ea01bfd410971cf0d0c25fd74cd764d5eb7279d Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 21:25:41 2021 +0700 Add an example file for code coverage of some fringe graph cases commit eae68e39553f88a6275a0c65bf040b5d4c262b54 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 20:23:32 2021 +0700 Add step_shape to the graph/nan_color example for code coverage commit 8027147bc2eab34a97b49a428fb8cef27f122bed Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:55:18 2021 +0700 Add example files for graph.nan_color/nan_indication properties commit 89a25a165c52b2c15b6c4d95ecb777a9067e66f9 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:33:07 2021 +0700 Make widget graph:add_value(v) default v to NaN 0 as a default makes no sense and silences the error that is failing to specify a value, to which the user should rather be alerted. The graph widget has sane NaN handling now and will do just that. commit 2835d552440be48d356a7f7b1d64963696dd550b Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:20:48 2021 +0700 Implement graph.nan_color and nan_indication properties Now, whenever there's a NaN among values and graph.nan_indication is set, the corresponding area, where the value bar should have been drawn, is filled with the nan_color from top to bottom. The painting is done after all data is rendered, to make sure that it won't be overpainted and go unnoticed. NaNs among graph's values are inevitably a sign of some error and it is arguably not a sane default behavior to simply not render them at all. The user should take immediate note of any errors by default, instead of wondering at a mysteriously empty graph widget, which is why nan_indication is set to true by default. If the user wants to silence NaNs, they can always simply set it to false. nan_color if unset, defaults to a noticeable yellow-black pattern. As a direct and planned consequence, stacked graphs with negative values in them trigger the NaN handling too. But silencing NaN handling for those graphs is also possible and semi-sane, because then NaNs and negatives are ignored and the graph behaves (almost) like they were zeros. commit 9c1f8f08b400e01e19c534f3810c9c9ab485760c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 26 08:10:19 2021 +0700 Refactor out graph_map_value_to_widget_coordinates() function commit 1905991ddecabdae3e15ffb8ee5b18a1bee1a711 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 28 13:39:29 2021 +0700 Add an example file for graph.baseline_value property commit b31b39d66f4c037a9484b71a3f910703ae01fac0 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 07:03:49 2021 +0700 Add an example file for graph.clamp_bars=false/true comparison commit a1fc850e3047f9ee2c9ddeffacfbabf3ded34171 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 04:06:45 2021 +0700 Add an example file for graphs with negative values and shapes commit 2f012f8ff1bb5094c14e724a61e59a2a3a296bc8 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 01:06:05 2021 +0700 Add an example file for a graph displaying negative values commit 7b14fbd59a4c4c51962fd3110a1a900dd5f4eb98 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 11:18:26 2021 +0700 Add an example file for graphs with step_width < 1 commit dfd9a54544b637e2fbe3d1325f9cc24b3f4d2313 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 10:17:38 2021 +0700 Add an example file for graph.stack=false/true comparison Should also demonstrate that multiple data groups are possible for non-stacked graphs too. commit 5b532106c3fd255f9b66a66cfb88d977b489ab5d Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 09:34:14 2021 +0700 Add an example file for disabling graph data groups with nil colors commit ac6f1083c02fcb428f089a616f9b0ebaa5496b6c Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:57:25 2021 +0700 Add an example file for step_shape/width/spacing on stacked graphs commit b3703db6a5139e73e72c1cfd198ece2cd9be7df2 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:43:56 2021 +0700 Add an example file for scale=false/true on stacked graph commit b000aed4a12cb44fb501e1c8432a71a14b2f8f3f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 20 08:22:19 2021 +0700 Document deprecation of graph's width and height commit cb0ad617bd6c83a499896797318e3b0b588c3566 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 07:53:37 2021 +0700 Deprecate width and height properties of wibox.widget.graph commit 8c64e14ad91a074b784166a7054e53444cccb9d2 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 04:37:59 2021 +0700 Refactor graph:draw() to separate concerns Firstly, summation of group values for stacked graphs is extracted into graph:preprocess_values(). This makes graph:draw() smaller and exposes the fact, that stacked graphs are almost entirely just a form of a more general concept: pre-render data manipulation. It's something a user might want to customize, and now they can do it by monkey-patching the method. Secondly, the logic for deciding, if and with which color a data group should be drawn, is extracted into graph:should_draw_data_group() and get_data_group_color() methods respectively. This makes it customizable too and makes more obvious the fact, that it is a general thing that has nothing to do with stacked graphs in particular. With this there are only 5 lines remaining in graph:draw(), that could be said to be specific to stacked graphs, all pertaining to the `prev_y` variable, which merely preserves the bar coordinates from a previous data group. commit 2a32b6305d893f413303728a0a3da1dcd9a5591e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 22:52:37 2021 +0700 Pointlessly optimize graph:add_value() 1) Purge old values first, then insert the new one, if needed. Since Lua grows arrays in power of two steps, and our guess_capacity() also takes care to return even integers, it would be a shame to overstep the capacity by one by inserting first and thus getting the table size doubled only to never use the most of it. 2) Use a removal procedure that doesn't call the slow length operator at all. Previous code called it twice (once in table.remove) for every removed value. commit 680ea8f5d81e9040afb4662b3229e937e4b89e02 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:23:38 2021 +0700 Set graph data series color before the painting loop commit b970f0f221ae76250f2a94433ff79d653caa6e21 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:05:37 2021 +0700 Drop a large no longer necessary `if` in graph:draw_values() This commit is almost purely a whitespace change. commit 6b4f0541c70ec99d7f6e276bae64939f337120d6 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:00:50 2021 +0700 Extract values rendering code into graph:draw_values() method This is to avoid ungodly if nesting levels, which are needed to bail out of drawing values early and skip to drawing the border. The next commit will get rid of the if. commit e0950e4f99d1ea1c4b8cea2cc67629fa40030c2a Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 14:50:20 2021 +0700 Track graph draw() usage to guess necessary array capacity The last step of divorcing add_values() from width calculations, while ensuring that user doesn't observe a lack of data to draw. The widget now tracks an approximate number of values that it has been asked to draw() in _private.last_drawn_values_num, and truncates its values array based on that (if not overridden by the capacity property). commit 2e3d542282f35e7c0fed454da0f2eada0fcf3b78 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 13:35:12 2021 +0700 Add `capacity` property to graph widget This is the second step of divorcing add_values() from calculations involving widths. How many values are stored and how many values are drawn are distinct concepts. There isn't even a single definitive "how many values are drawn" because the widget could be rendered in several places with distinct widths simultaneously, and in every such place the data must be drawn appropriately, e.g. with auto-scaling, that takes only the visible data points into account. And the draw() method can do that now. The default capacity value of nil uses heuristics based on widget's _private.width to guess some capacity that would tolerate some dynamic widget resizing. If the user finds the heuristic failing them, they can set the capacity property to the desired maximum number of values that will be kept in the _private.values array (in each data group). commit 08d4a80686f7fe363551c35589a460839f60853e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 03:27:18 2021 +0700 Extract values size computation into compute_drawn_values_num() This is the first step of making add_values() not concern itself (directly) with widget dimensions. The user can monkey-patch the method to override how many values are (logically) drawn, because it affects graph auto-scaling and can be useful for rendering of step_shape-s that paint outside of their step_width. I didn't make it a full-blown function-valued property, because it seems a fringe functionality, which I'm too lazy to document. commit 3c5617c98ab1147618ed22e8414a77fc56b45f6e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 02:32:29 2021 +0700 Copy graph widget prototype methods over in bulk commit 169538837ece41a03d02ba8fbc0e4097062b67a3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun Apr 25 05:58:01 2021 +0700 Remove graph.baseline_y property After giving it some thought I've concluded, that it is unlikely to be useful as it is now. I'd better reserve the name for some more sensible functionality, like fixing the baseline_value defined axis at the given widget coordinate, adjusting the scaling accordingly, so min_value/max_value won't generally correspond to widget top and bottom anymore. commit 202ffaa9a0b820c7c471e1121d01b91f8f76cd38 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 12:06:55 2021 +0700 Add baseline_y and baseline_value properties to graph widget Now values in graph have a *baseline*, tunable with baseline_y and baseline_value properties. It is the (for now invisible) horizontal line from which all bars in the graph grow. I call it a "baseline" instead of "axis", because it doesn't actually have to correspond to the 0 value. But it can, and actually does now by default. baseline_y and baseline_value properties determine the vertical placement of the baseline in the widget. baseline_y is the position of the baseline in normalized widget coordinates, e.g. 0 corresponds to widget's top, 1 - to widget's bottom, and 0.5 - to widget's middle. So if e.g. one sets baseline_y = 0, then no matter how the widget is resized and how its values are scaled, bars in it will be drawn starting from the top. baseline_value is the position of the baseline in value coordinates. So if one sets baseline_value = 10, then no matter what happens, bars for values greater than 10 will be drawn as growing up from this line, and bars for values smaller as growing down. Where and if it will be seen in the widget, depends on the scaling. baseline_value = 0 is the familiar zero axis. baseline_y is unset by default and has priority, i.e. if it's set, then baseline_value is ignored. baseline_value defaults to 0. Prior to this commit, bars were always drawn from the bottom edge of the widget, or, in the new terms, the widget had baseline_y = 1. But it was indistinguishable from baseline_value = 0, because the widget clamped all negative values to 0, so the difference between the two could never be observed. baseline_value = 0 is clearly more useful and familiar to people, so now it is in force by default. commit df7e19abaebf0a871b5ca217b4cc3365533267a6 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 11:18:12 2021 +0700 Move value clamping from graph:add_value() into draw() Previously all values were forcibly clamped between 0 (not tunable at all) and max_value (1 by default). Thus graphs with negative values were not an option. NaN values were conflated with zeros, even though it may be useful to maintain the distinction and render them differently. And if user happened to dynamically change the max_value property, they discovered, that their data got truncated. Preserving user data as it was given is more flexible and makes things easier to debug. This commit does exactly that. So what happens now to the newly allowed values during draw()? There are 3 new cases to consider: 1) NaNs 2) Negatives 3) Values outside of min_value..max_value range NaNs are presently simply not drawn. An empty place is reserved where the value should've been, but nothing else is done. This is distinct from how a zero is handled, e.g. if step_shape is set, it is called with the height parameter = 0, which can still end up drawing something, depending on the shape. One could also consider addition of nan_color/nan_shape properties which could be drawn to alert user to a gap in their data. Negative values necessitate introduction of the concept of the zero axis, from which they will be drawn DOWN, in contrast to positive values. Where should this axis be drawn? How should step_shape-s with inherent direction, e.g. arrows, be drawn for negatives? This will be addressed in the next commit. Values outside of min/max_value range can imply shapes that go beyond widget dimensions. An arrow shape that goes beyond the roof is visibly different from an arrow that saturates and merely touches the widget top, and the user may reasonably desire either behavior. Therefore this commit introduces a boolean clamp_bars property, which defaults to true for reasons of robustness and backward compatibility with the behaviour prior to this commit. The property of course affects only the display of the values, the actual user data stays intact. commit c9c24fb1ab741cd6bb8074971d568e0646c51532 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:29:01 2021 +0700 Use rectangles for drawing graph bars of all sizes I have benchmarked various bar drawing strategies and graph sizes and found out that drawing graph bars with rectangles is as fast or even marginally faster than using fat lines. Thus the code that tries to special-case 1px bars as lines seems to be not worth the additional complexity it introduces, and this commit removes it. Incidentally, I've also established, that rectangles are more robust. If one draws graph bars and feeds cairo very large coordinates (~2^62), then corresponding rectangles fail to render, but all the other rectangles in the same series get rendered successfully, whereas a single large coordinate in a move_to/line_to() sequence is enough to cause all lines in the batch to fail to render. commit bc7f4be5e06405b5aa7fb443a5ace2b9b9575b1f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:01:42 2021 +0700 Cache cairo methods used in the inner loop of graph:draw() I have benchmarked the draw() method, both on cairo xcb and on argb32 image surfaces, and have discovered that cairo context method calls are unnaturally slow, to the point, that almost nothing else in the function matters. For example, drawing bars with vertical lines made the whole draw() function almost 2 times slower than doing the same with rectangles, merely because the former requires two method calls (move_to/line_to), and the latter only one. Simply caching hotpath cairo methods in local variables, reduced the difference between the drawing methods to a negligible one and made the whole function 4 times faster on a typical 100-bar graph, (1.4ms to 0.33ms on my laptop) thus bringing closer my secret dream of drawing live audio data with awesome graphs (kidding). I guess, LGI does something not very optimal there, like gobject-introspecting the method every time, or something. There might be some untapped optimization potential, possibly in upstream, like a method cache, from which everything in awesome could benefit, even though I imagine not many things are so call-intensive as the graph widget, to really feel the difference. commit 538df25bdf02b53d0bbe81809eef2e6fdb05ff6f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 07:32:37 2021 +0700 Use 1-based index in the inner graph draw loop It's used there only once for a calculation and then several times to index some tables, and index 0 is bad for lua tables performance-wise. commit 7412546916cd0a0a490130a120255ffb719398b8 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:55:12 2021 +0700 Leave graph.max_value uninitialized at construction This is a user-visible change, but the previous behavior was probably not what user expected anyway, namely with graph.scale set the graph mysteriously couldn't zoom in onto values that are smaller than 1. commit eb81d7929a161e79c13262cfa73ac2e6a820e8ed Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:48:29 2021 +0700 Allow graph.max_value be nil This makes its behave symmetrically to min_value. Now it defaults to 1, when graph.scale is not set, and to the maximum value in the graph data, when graph.scale is set. commit f622a8430cbfde7007b58d7f4c9c76b08c91e997 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 12:01:13 2021 +0700 Calculate max values num in graph:add_value() more precisely At most so many values were kept in the widget values array, as there are pixels of usable widget width (i.e. sans border). This is exactly right for the surely most common case of graphs with 1px-wide bars, but is more than needed for graphs with step_width+step_spacing>1, and not enough for graphs with subpixel bars. Moreover this made graph autoscaling behave weirdly in graphs with thicker bars, because old values survived longer in the array and kept affecting min_value/max_value calculations even after they were shifted off-screen. The maximum number of kept values is now calculated exactly as the number of values that can be displayed at once, taking step_width and step_spacing into account. It might be worth adding a max_values_num property to the graph to let user override this calculation, in case they draw shapes in step_shape() outside of its supposed rectangle, so our assumptions of visibility are wrong, or the widget often gets resized, resulting in the annoyance of visibly truncated series. commit 0aa63249a18641251a1bf77423739164f0eab39f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 02:30:13 2021 +0700 Fix border_color/border_width bugs in graph widget add_value() wrongly assumed that border_width is always set, when border_color is, so if user set only the latter, an error occurred. add_value() assumed that border_width is always < width which led to awesome hanging up in an infinite loop, should that fail to be the case. The graph border_width property was driven by the border_color property, instead of the other way round. If border_color wasn't set, the border wasn't drawn all, even if user sets non-zero border_width, which is a confusing behavior, in contrast to all other widgets, which simply use a plethora of fallback colors to draw the border, when needed. This also meant that, despite being documented, the beautiful.graph_border_color fallback color could not and was not ever used. If the border_color was set though, an unset border_width was immediately assumed to be 1 instead of 0, also contrary to what other widgets do. Theme colors shouldn't influence widget layout, so this commit fixes this behavior to: 1) border_width always defaults to 0, if unset. 2) border_color falls back to beautiful.graph_border_color or white. Maybe one should also add a beautiful.graph_border_width theme variable, like some widgets do. commit dad4a3253b8fef971fb82720d2a407fcc0a879ad Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 03:11:27 2021 +0700 Implement multiple data series drawing for non-stacked graph The widget can already hold and draw multiple data groups, so it makes little sense to limit drawing only to the first group when graph.stack = false, probably leading to user's confusion. Now user can call add_value(v, group) with group > 1 and the data series will be drawn (over groups with lesser id-s) with graph.stack_colors[group] color, if stack_colors is set, or with graph.color, if stack_colors isn't set. Thus nothing visibly changes for all current users of non-stacked graphs with only single data series, but the new functionality is now there for those who need it. One can't fully emulate this functionality, e.g. with multiple graphs widgets in a wibox.layout.stack, because data series in a single widget can enjoy a common auto-scaling. commit 3e645c976184ab5f50d625271c25062db9ab7ed9 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:59:12 2021 +0700 Don't draw a stacked graph data group if there's no color for it If stack_colors[idx] is nil for the group `idx`, don't draw it at all (as opposed, e.g. to the effect of drawing it with a transparent color, or as though it had all values == 0, which both produce different results). This code is still subtly different from prior behavior, because now user can have a non-contiguous stack_colors table and turn the display of any data group on and off at will, whereas the code two commits earlier would just stop rendering as soon as ipairs(stack_colors) does, i.e. no data series were drawn past the first nil hole. This commit only looks large, but is mostly a whitespace change due to nesting from a pair of wrapping ifs. commit 8c0273786de1ade66bdd977699f98b588da9f487 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:32:42 2021 +0700 Unify stacked and non-stacked graph rendering Now stacked graphs support everything the nonstacked do, like step_width ~= 1 and custom step_shape-s. Technical notes: This code no longer calls cr:stroke() for every single value in a stacked graph, but rather paints a whole series and then cr:stroke/fill()-s it at all once with its color, improving performance considerably. Additionally the graph no longer relies on the presence of the stack_colors property to render series. It draws all data that it has, and, if there's no corresponding color in stack_colors, falls back to graph.color. This will be changed in the next commit to the prior behavior of only drawing those series for which there's a color in the stack_colors table, but that would've made this commit's diff much larger due to required nesting ifs over large code chunks. This commit also takes care to properly handle NaNs and negative values. Those are currently (almost) guaranteed to not occur in the values array due to clipping in graph:add_value(), but I intend to eventually drop the clipping, because it does nothing that draw() really relies upon, and allowing unclipped and NaN values is more flexible and less surprising, e.g. when user dynamically toggles graph.scale. Eventually I intend to implement drawing negative values in non-stacked graphs and possibly even in the stacked ones. commit 38c6589c51b2aa057d7c2f40182ecf32b5b2ff5e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 10:04:15 2021 +0700 Convert graph:draw() to use `self` commit f21f1b2550426df1c4ea8abde079b1ccef84b394 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 12:27:26 2021 +0700 Fix scaling for stacked graphs Stacked graphs have cumulative heights, values from all groups that end up in the same graph bar are effectively summed. Previous code didn't take that into account when calculating min/max_value for the purposes of rescaling, but simply examined every value separately. This code adds up all values up front and finds min/max among those, to determine proper scaling that would fit the graph exactly. commit 1ce4c7bf63bdd6c99c195d668347bec49355e921 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 09:49:35 2021 +0700 Draw shaped graph bars only up to the baseline Prior to this commit, every step_shape bar was, for some reason, drawn with height = full widget's height, regardless of the value it represents. So for the most bars their lower end was far outside the box and therefore clipped off. So e.g. one couldn't see the lower round edges of rounded rectangles. This commit fixes that by appropriately varying the height that is passed into step_shape(), so that lower edges of the bars always exactly touch the lower edge of the graph. If the user wants to restore the prior behavior, they can simply ignore the passed-in height in step_shape() and just always draw their shape with height = full widget height. But they couldn't replicate the effect of this commit in their rc.lua without essentially writing their own graph widget. commit e25185b08da23745b5815f85f6e9e43222ed2294 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 08:42:54 2021 +0700 Avoid incremental transform shifting during graph bar rendering Instead of letting addition errors and cognitive load accumulate simply use the same offset calculation that is used for lines and reset the transform back to the top-left corner after every drawn bar. It's conceptually simpler, and will be useful later for stacked graphs and more. It also fixes a discrepancy between plain and step_shape rendering. Prior to this commit, bar coloring occurred with a disturbed transform, which was centered at the last drawn bar, wherever it happened to be. This meant that nontrivial colors, like gradients and images, were arbitrarily offset when step_shape was used. commit f3e98866d8583c35d0b557c09f529299edc9ba2c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:45:29 2021 +0700 Refactor out graph bar height expression into a variable commit f54528d6892f5aaa4595917853c9e2130f743fdb Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:20:25 2021 +0700 Draw graph bars with lines only when step_width == 1 Before this commit graph bars of step_width < 1 were drawn with lines of width 1, but that's a bit unfortunate, because it led to much overpainting, while still ending up with a graphically incorrect result. Let's draw such bars with rectangles and let cairo do things with subpixels, getting more graphically accurate result, while still keeping the common case of step_width == 1 (supposedly) fast-drawn with lines. commit b6515dca67c18b78eb5657378131a62ab43d4cfc Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:54:07 2021 +0700 Fix unsharp edges when drawing graph bars with rectangles The 0.5 offset is only helpful for 1-width strokes, not for fills. commit 7f3db8f07e82f821bcd1dc418d66564f537609d6 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:21:01 2021 +0700 Do coordinate transformations even if the value isn't drawn The graph presently doesn't draw values < 0, but there was a discrepancy between drawing with and without step_shape. The default line/rectangle drawing reserves an empty place where the skipped value should've been, but no such empty place is reserved, when step_shape is used. This commit fixes that. Skipped values always have their empty space now. commit 7b731ebfe96af7a9dba9c57371da6761cf22c00c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:03:30 2021 +0700 Fix graph bar scaling when min_value is not 0 Previous calculation lead to underscaling of values so that even value = max_value wasn't drawn to full height. commit dca59fd8b7fff0e7e646f2dfc6bd92047028e5a8 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:18:40 2021 +0700 Calculate the horizontal graph bar offset only when needed commit 940ddf4b9825e9c3a91d276208c089d8c74e49db Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:17:23 2021 +0700 Call cr:move_to() only when drawing the graph with lines It's redundant otherwise and might even interfere with user's drawing in step_shape(). commit b5d5cd10ca29fb29cd751faaa95137c2fe89ff63 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:04:06 2021 +0700 Fix wrong horizontal offset calculations in widget.graph Indices in the loop are zero-based, so things like i-1 and i>1 aren't necessary and actually wouldn't have been correct even in a 1-based loop. step_shape = gears.shape.rectangle and step_shape = nil, which is supposed to default to rectangle according to docs, were drawn differently due to mismatched offset calculations, which led to problems especially noticeable when step_spacing is not zero, e.g: 1) whole graph shifted left too much, drawn partially off-screen. 2) whole graph shifted right too much, and the first bar placed closer to the rest than others. commit dc369fa198281e000afc1bd17326f3a264adf7d1 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 02:09:21 2021 +0700 Fix the "border_width affects graph bar width" bug Graph bars were drawn with lines of border_width thickness instead of 1, i.e. possibly too fat to be readable. commit 1ba73378cf29ed27a6ecd18716394779ceb543d9 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 00:13:03 2021 +0700 Simplify internal values representation in graph even more New values in graph._private.values were pushed to the end, and old values were removed from the beginning of the table. In draw() however newer values are drawn first, at the left edge of the graph, necessitating annoying idx = #values - idx conversions everywhere. In particular in stacked graph that means that values that are drawn together may have different indices. One has to shift the values array in add_value() either way, so one might as well insert new values at the beginning of the array and simplify the rest of the code. This doesn't seem like much yet, but it will prove to be much more convenient and less error-prone in what I'm going to do later. commit f70523ee68dbd6b67050ace4b82903a9b22440c7 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 13:35:10 2021 +0700 Simplify internal values representation in graph widget graph._private.values was either a table of values or a table of tables of values, depending on whether "stack" property is enabled, ... or a mix of both if user forgot to pass the second "group" parameter to graph:add_value(). This resulted in pretty fragile and confusing behavior, e.g. leaving user to wonder, why they see nothing more after setting stack=true for a graph in a previously working rc.lua, and demanding a very careful use of add_value() from the user, or else some values or even entire stacks would mysteriously vanish and errors in draw() get triggered, sometimes after a period of seemingly normal functioning. Now `values` is always a table of tables and the group parameter in add_value() defaults to 1, thus making the non-stacked graph a trivial variant of the stacked graph.
2021-05-20 17:14:03 +02:00
_graph._private.forced_width = width
_graph._private.forced_height = height
2016-05-23 07:30:44 +02:00
end
graph: complete widget overhaul and bug fixes Squashed commit of the following: commit 69821f51fe1e8652715658543e50631ece495090 Author: Alex Belykh <albel727@ngs.ru> Date: Mon May 10 19:17:55 2021 +0700 Refactor property handling in wibox.widget.graph In addition, baseline_value, step_width and step_spacing properties are all set to their default non-nil numeric values during instance creation now. commit 842eb429bf5df4b2ba46b2e6f7646afe89b1a3c3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 9 06:18:45 2021 +0700 Make graph:draw_values() private There's no documentation nor confidence that it's a good API, so I'm hiding it for now. commit 720746780574e4bad14a71fc4174955139c2eb50 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 04:04:13 2021 +0700 Document graph_should_draw_data_group() for posterity commit 7c73f3754ce6ba86b118f1170a6f9e406ab4841d Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:59:21 2021 +0700 Make graph:should_draw_data_group() private There's no confidence whether it's a good API, so I'm hiding it for now. commit b3539e20adb6423118af726ae4f8751ef864b5d2 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:36:44 2021 +0700 Make graph:preprocess_values() private There's no confidence whether it's a good API, so I'm hiding it for now. commit 2df4400e780a6260f8d0ab1c60734fbb11bf3c4f Author: Alex Belykh <albel727@ngs.ru> Date: Thu May 6 10:05:23 2021 +0700 Add spec/wibox/widget/graph_spec.lua commit ce01a9771ab439b8df841f1565aa4aa8a44a5e0d Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 08:16:18 2021 +0700 Improve graph.capacity documentation commit e28fade8f655802f5bf89154169f68d256b8e332 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 07:36:01 2021 +0700 Document graph:compute_drawn_values_num() commit dd68332292465422d08b48e9f0229f143beccbc6 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 05:24:23 2021 +0700 Document graph:pick_data_group_color() method commit ca6dc55f77aca88a6e9cc90bb6fd0045739f41f5 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 04:13:58 2021 +0700 Guard against setting data groups sparsely in graph:add_value() commit cdea5f126f7984e72924f6dffbd312c4fe3b032f Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 02:36:12 2021 +0700 Refactor scaling and baseline choice out of graph:draw() commit 10135deafe8a9390ba66ab657ebb1fb41d549e8c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 21:34:48 2021 +0700 Document graph.nan_color and graph.stack properties commit 0d73bb84ac66b87e16aee472bd1f299549f66a1c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 20:49:24 2021 +0700 Use a better example in docs for the graph.stack property commit c27d160a0826d033d49d6141608c40aa8a0642fa Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 16:05:38 2021 +0700 Document graph.baseline_value property commit 459bc8176c13d4ea32af0dcdc2399d3b929b1c6a Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:53:47 2021 +0700 Document graph.clamp_bars property commit e732ee0b3b007d213e689b98cc00ae7cb141ae08 Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:34 2021 +0700 Improve graph:add_value() and :clear() documentation commit ae73e3aa9f8d60409cbe4cb4da9612f31af4f38e Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:11 2021 +0700 Improve graph min_value/max_value/scale documentation commit a7350996a9def773b91b1631e6c0507e33d8f93b Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:27:36 2021 +0700 Cleanup graph examples to show only relevant things in docs commit 30740c26f4f41c16f4c8a3c339bcb7bd299e14cd Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 14:04:36 2021 +0700 Rename graph.stack_colors property to `group_colors` The colors are used for stacked and non-stacked graphs alike, so the name didn't make sense. Also document and clarify some color stuff. commit 1ea01bfd410971cf0d0c25fd74cd764d5eb7279d Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 21:25:41 2021 +0700 Add an example file for code coverage of some fringe graph cases commit eae68e39553f88a6275a0c65bf040b5d4c262b54 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 20:23:32 2021 +0700 Add step_shape to the graph/nan_color example for code coverage commit 8027147bc2eab34a97b49a428fb8cef27f122bed Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:55:18 2021 +0700 Add example files for graph.nan_color/nan_indication properties commit 89a25a165c52b2c15b6c4d95ecb777a9067e66f9 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:33:07 2021 +0700 Make widget graph:add_value(v) default v to NaN 0 as a default makes no sense and silences the error that is failing to specify a value, to which the user should rather be alerted. The graph widget has sane NaN handling now and will do just that. commit 2835d552440be48d356a7f7b1d64963696dd550b Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:20:48 2021 +0700 Implement graph.nan_color and nan_indication properties Now, whenever there's a NaN among values and graph.nan_indication is set, the corresponding area, where the value bar should have been drawn, is filled with the nan_color from top to bottom. The painting is done after all data is rendered, to make sure that it won't be overpainted and go unnoticed. NaNs among graph's values are inevitably a sign of some error and it is arguably not a sane default behavior to simply not render them at all. The user should take immediate note of any errors by default, instead of wondering at a mysteriously empty graph widget, which is why nan_indication is set to true by default. If the user wants to silence NaNs, they can always simply set it to false. nan_color if unset, defaults to a noticeable yellow-black pattern. As a direct and planned consequence, stacked graphs with negative values in them trigger the NaN handling too. But silencing NaN handling for those graphs is also possible and semi-sane, because then NaNs and negatives are ignored and the graph behaves (almost) like they were zeros. commit 9c1f8f08b400e01e19c534f3810c9c9ab485760c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 26 08:10:19 2021 +0700 Refactor out graph_map_value_to_widget_coordinates() function commit 1905991ddecabdae3e15ffb8ee5b18a1bee1a711 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 28 13:39:29 2021 +0700 Add an example file for graph.baseline_value property commit b31b39d66f4c037a9484b71a3f910703ae01fac0 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 07:03:49 2021 +0700 Add an example file for graph.clamp_bars=false/true comparison commit a1fc850e3047f9ee2c9ddeffacfbabf3ded34171 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 04:06:45 2021 +0700 Add an example file for graphs with negative values and shapes commit 2f012f8ff1bb5094c14e724a61e59a2a3a296bc8 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 01:06:05 2021 +0700 Add an example file for a graph displaying negative values commit 7b14fbd59a4c4c51962fd3110a1a900dd5f4eb98 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 11:18:26 2021 +0700 Add an example file for graphs with step_width < 1 commit dfd9a54544b637e2fbe3d1325f9cc24b3f4d2313 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 10:17:38 2021 +0700 Add an example file for graph.stack=false/true comparison Should also demonstrate that multiple data groups are possible for non-stacked graphs too. commit 5b532106c3fd255f9b66a66cfb88d977b489ab5d Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 09:34:14 2021 +0700 Add an example file for disabling graph data groups with nil colors commit ac6f1083c02fcb428f089a616f9b0ebaa5496b6c Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:57:25 2021 +0700 Add an example file for step_shape/width/spacing on stacked graphs commit b3703db6a5139e73e72c1cfd198ece2cd9be7df2 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:43:56 2021 +0700 Add an example file for scale=false/true on stacked graph commit b000aed4a12cb44fb501e1c8432a71a14b2f8f3f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 20 08:22:19 2021 +0700 Document deprecation of graph's width and height commit cb0ad617bd6c83a499896797318e3b0b588c3566 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 07:53:37 2021 +0700 Deprecate width and height properties of wibox.widget.graph commit 8c64e14ad91a074b784166a7054e53444cccb9d2 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 04:37:59 2021 +0700 Refactor graph:draw() to separate concerns Firstly, summation of group values for stacked graphs is extracted into graph:preprocess_values(). This makes graph:draw() smaller and exposes the fact, that stacked graphs are almost entirely just a form of a more general concept: pre-render data manipulation. It's something a user might want to customize, and now they can do it by monkey-patching the method. Secondly, the logic for deciding, if and with which color a data group should be drawn, is extracted into graph:should_draw_data_group() and get_data_group_color() methods respectively. This makes it customizable too and makes more obvious the fact, that it is a general thing that has nothing to do with stacked graphs in particular. With this there are only 5 lines remaining in graph:draw(), that could be said to be specific to stacked graphs, all pertaining to the `prev_y` variable, which merely preserves the bar coordinates from a previous data group. commit 2a32b6305d893f413303728a0a3da1dcd9a5591e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 22:52:37 2021 +0700 Pointlessly optimize graph:add_value() 1) Purge old values first, then insert the new one, if needed. Since Lua grows arrays in power of two steps, and our guess_capacity() also takes care to return even integers, it would be a shame to overstep the capacity by one by inserting first and thus getting the table size doubled only to never use the most of it. 2) Use a removal procedure that doesn't call the slow length operator at all. Previous code called it twice (once in table.remove) for every removed value. commit 680ea8f5d81e9040afb4662b3229e937e4b89e02 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:23:38 2021 +0700 Set graph data series color before the painting loop commit b970f0f221ae76250f2a94433ff79d653caa6e21 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:05:37 2021 +0700 Drop a large no longer necessary `if` in graph:draw_values() This commit is almost purely a whitespace change. commit 6b4f0541c70ec99d7f6e276bae64939f337120d6 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:00:50 2021 +0700 Extract values rendering code into graph:draw_values() method This is to avoid ungodly if nesting levels, which are needed to bail out of drawing values early and skip to drawing the border. The next commit will get rid of the if. commit e0950e4f99d1ea1c4b8cea2cc67629fa40030c2a Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 14:50:20 2021 +0700 Track graph draw() usage to guess necessary array capacity The last step of divorcing add_values() from width calculations, while ensuring that user doesn't observe a lack of data to draw. The widget now tracks an approximate number of values that it has been asked to draw() in _private.last_drawn_values_num, and truncates its values array based on that (if not overridden by the capacity property). commit 2e3d542282f35e7c0fed454da0f2eada0fcf3b78 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 13:35:12 2021 +0700 Add `capacity` property to graph widget This is the second step of divorcing add_values() from calculations involving widths. How many values are stored and how many values are drawn are distinct concepts. There isn't even a single definitive "how many values are drawn" because the widget could be rendered in several places with distinct widths simultaneously, and in every such place the data must be drawn appropriately, e.g. with auto-scaling, that takes only the visible data points into account. And the draw() method can do that now. The default capacity value of nil uses heuristics based on widget's _private.width to guess some capacity that would tolerate some dynamic widget resizing. If the user finds the heuristic failing them, they can set the capacity property to the desired maximum number of values that will be kept in the _private.values array (in each data group). commit 08d4a80686f7fe363551c35589a460839f60853e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 03:27:18 2021 +0700 Extract values size computation into compute_drawn_values_num() This is the first step of making add_values() not concern itself (directly) with widget dimensions. The user can monkey-patch the method to override how many values are (logically) drawn, because it affects graph auto-scaling and can be useful for rendering of step_shape-s that paint outside of their step_width. I didn't make it a full-blown function-valued property, because it seems a fringe functionality, which I'm too lazy to document. commit 3c5617c98ab1147618ed22e8414a77fc56b45f6e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 02:32:29 2021 +0700 Copy graph widget prototype methods over in bulk commit 169538837ece41a03d02ba8fbc0e4097062b67a3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun Apr 25 05:58:01 2021 +0700 Remove graph.baseline_y property After giving it some thought I've concluded, that it is unlikely to be useful as it is now. I'd better reserve the name for some more sensible functionality, like fixing the baseline_value defined axis at the given widget coordinate, adjusting the scaling accordingly, so min_value/max_value won't generally correspond to widget top and bottom anymore. commit 202ffaa9a0b820c7c471e1121d01b91f8f76cd38 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 12:06:55 2021 +0700 Add baseline_y and baseline_value properties to graph widget Now values in graph have a *baseline*, tunable with baseline_y and baseline_value properties. It is the (for now invisible) horizontal line from which all bars in the graph grow. I call it a "baseline" instead of "axis", because it doesn't actually have to correspond to the 0 value. But it can, and actually does now by default. baseline_y and baseline_value properties determine the vertical placement of the baseline in the widget. baseline_y is the position of the baseline in normalized widget coordinates, e.g. 0 corresponds to widget's top, 1 - to widget's bottom, and 0.5 - to widget's middle. So if e.g. one sets baseline_y = 0, then no matter how the widget is resized and how its values are scaled, bars in it will be drawn starting from the top. baseline_value is the position of the baseline in value coordinates. So if one sets baseline_value = 10, then no matter what happens, bars for values greater than 10 will be drawn as growing up from this line, and bars for values smaller as growing down. Where and if it will be seen in the widget, depends on the scaling. baseline_value = 0 is the familiar zero axis. baseline_y is unset by default and has priority, i.e. if it's set, then baseline_value is ignored. baseline_value defaults to 0. Prior to this commit, bars were always drawn from the bottom edge of the widget, or, in the new terms, the widget had baseline_y = 1. But it was indistinguishable from baseline_value = 0, because the widget clamped all negative values to 0, so the difference between the two could never be observed. baseline_value = 0 is clearly more useful and familiar to people, so now it is in force by default. commit df7e19abaebf0a871b5ca217b4cc3365533267a6 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 11:18:12 2021 +0700 Move value clamping from graph:add_value() into draw() Previously all values were forcibly clamped between 0 (not tunable at all) and max_value (1 by default). Thus graphs with negative values were not an option. NaN values were conflated with zeros, even though it may be useful to maintain the distinction and render them differently. And if user happened to dynamically change the max_value property, they discovered, that their data got truncated. Preserving user data as it was given is more flexible and makes things easier to debug. This commit does exactly that. So what happens now to the newly allowed values during draw()? There are 3 new cases to consider: 1) NaNs 2) Negatives 3) Values outside of min_value..max_value range NaNs are presently simply not drawn. An empty place is reserved where the value should've been, but nothing else is done. This is distinct from how a zero is handled, e.g. if step_shape is set, it is called with the height parameter = 0, which can still end up drawing something, depending on the shape. One could also consider addition of nan_color/nan_shape properties which could be drawn to alert user to a gap in their data. Negative values necessitate introduction of the concept of the zero axis, from which they will be drawn DOWN, in contrast to positive values. Where should this axis be drawn? How should step_shape-s with inherent direction, e.g. arrows, be drawn for negatives? This will be addressed in the next commit. Values outside of min/max_value range can imply shapes that go beyond widget dimensions. An arrow shape that goes beyond the roof is visibly different from an arrow that saturates and merely touches the widget top, and the user may reasonably desire either behavior. Therefore this commit introduces a boolean clamp_bars property, which defaults to true for reasons of robustness and backward compatibility with the behaviour prior to this commit. The property of course affects only the display of the values, the actual user data stays intact. commit c9c24fb1ab741cd6bb8074971d568e0646c51532 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:29:01 2021 +0700 Use rectangles for drawing graph bars of all sizes I have benchmarked various bar drawing strategies and graph sizes and found out that drawing graph bars with rectangles is as fast or even marginally faster than using fat lines. Thus the code that tries to special-case 1px bars as lines seems to be not worth the additional complexity it introduces, and this commit removes it. Incidentally, I've also established, that rectangles are more robust. If one draws graph bars and feeds cairo very large coordinates (~2^62), then corresponding rectangles fail to render, but all the other rectangles in the same series get rendered successfully, whereas a single large coordinate in a move_to/line_to() sequence is enough to cause all lines in the batch to fail to render. commit bc7f4be5e06405b5aa7fb443a5ace2b9b9575b1f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:01:42 2021 +0700 Cache cairo methods used in the inner loop of graph:draw() I have benchmarked the draw() method, both on cairo xcb and on argb32 image surfaces, and have discovered that cairo context method calls are unnaturally slow, to the point, that almost nothing else in the function matters. For example, drawing bars with vertical lines made the whole draw() function almost 2 times slower than doing the same with rectangles, merely because the former requires two method calls (move_to/line_to), and the latter only one. Simply caching hotpath cairo methods in local variables, reduced the difference between the drawing methods to a negligible one and made the whole function 4 times faster on a typical 100-bar graph, (1.4ms to 0.33ms on my laptop) thus bringing closer my secret dream of drawing live audio data with awesome graphs (kidding). I guess, LGI does something not very optimal there, like gobject-introspecting the method every time, or something. There might be some untapped optimization potential, possibly in upstream, like a method cache, from which everything in awesome could benefit, even though I imagine not many things are so call-intensive as the graph widget, to really feel the difference. commit 538df25bdf02b53d0bbe81809eef2e6fdb05ff6f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 07:32:37 2021 +0700 Use 1-based index in the inner graph draw loop It's used there only once for a calculation and then several times to index some tables, and index 0 is bad for lua tables performance-wise. commit 7412546916cd0a0a490130a120255ffb719398b8 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:55:12 2021 +0700 Leave graph.max_value uninitialized at construction This is a user-visible change, but the previous behavior was probably not what user expected anyway, namely with graph.scale set the graph mysteriously couldn't zoom in onto values that are smaller than 1. commit eb81d7929a161e79c13262cfa73ac2e6a820e8ed Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:48:29 2021 +0700 Allow graph.max_value be nil This makes its behave symmetrically to min_value. Now it defaults to 1, when graph.scale is not set, and to the maximum value in the graph data, when graph.scale is set. commit f622a8430cbfde7007b58d7f4c9c76b08c91e997 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 12:01:13 2021 +0700 Calculate max values num in graph:add_value() more precisely At most so many values were kept in the widget values array, as there are pixels of usable widget width (i.e. sans border). This is exactly right for the surely most common case of graphs with 1px-wide bars, but is more than needed for graphs with step_width+step_spacing>1, and not enough for graphs with subpixel bars. Moreover this made graph autoscaling behave weirdly in graphs with thicker bars, because old values survived longer in the array and kept affecting min_value/max_value calculations even after they were shifted off-screen. The maximum number of kept values is now calculated exactly as the number of values that can be displayed at once, taking step_width and step_spacing into account. It might be worth adding a max_values_num property to the graph to let user override this calculation, in case they draw shapes in step_shape() outside of its supposed rectangle, so our assumptions of visibility are wrong, or the widget often gets resized, resulting in the annoyance of visibly truncated series. commit 0aa63249a18641251a1bf77423739164f0eab39f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 02:30:13 2021 +0700 Fix border_color/border_width bugs in graph widget add_value() wrongly assumed that border_width is always set, when border_color is, so if user set only the latter, an error occurred. add_value() assumed that border_width is always < width which led to awesome hanging up in an infinite loop, should that fail to be the case. The graph border_width property was driven by the border_color property, instead of the other way round. If border_color wasn't set, the border wasn't drawn all, even if user sets non-zero border_width, which is a confusing behavior, in contrast to all other widgets, which simply use a plethora of fallback colors to draw the border, when needed. This also meant that, despite being documented, the beautiful.graph_border_color fallback color could not and was not ever used. If the border_color was set though, an unset border_width was immediately assumed to be 1 instead of 0, also contrary to what other widgets do. Theme colors shouldn't influence widget layout, so this commit fixes this behavior to: 1) border_width always defaults to 0, if unset. 2) border_color falls back to beautiful.graph_border_color or white. Maybe one should also add a beautiful.graph_border_width theme variable, like some widgets do. commit dad4a3253b8fef971fb82720d2a407fcc0a879ad Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 03:11:27 2021 +0700 Implement multiple data series drawing for non-stacked graph The widget can already hold and draw multiple data groups, so it makes little sense to limit drawing only to the first group when graph.stack = false, probably leading to user's confusion. Now user can call add_value(v, group) with group > 1 and the data series will be drawn (over groups with lesser id-s) with graph.stack_colors[group] color, if stack_colors is set, or with graph.color, if stack_colors isn't set. Thus nothing visibly changes for all current users of non-stacked graphs with only single data series, but the new functionality is now there for those who need it. One can't fully emulate this functionality, e.g. with multiple graphs widgets in a wibox.layout.stack, because data series in a single widget can enjoy a common auto-scaling. commit 3e645c976184ab5f50d625271c25062db9ab7ed9 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:59:12 2021 +0700 Don't draw a stacked graph data group if there's no color for it If stack_colors[idx] is nil for the group `idx`, don't draw it at all (as opposed, e.g. to the effect of drawing it with a transparent color, or as though it had all values == 0, which both produce different results). This code is still subtly different from prior behavior, because now user can have a non-contiguous stack_colors table and turn the display of any data group on and off at will, whereas the code two commits earlier would just stop rendering as soon as ipairs(stack_colors) does, i.e. no data series were drawn past the first nil hole. This commit only looks large, but is mostly a whitespace change due to nesting from a pair of wrapping ifs. commit 8c0273786de1ade66bdd977699f98b588da9f487 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:32:42 2021 +0700 Unify stacked and non-stacked graph rendering Now stacked graphs support everything the nonstacked do, like step_width ~= 1 and custom step_shape-s. Technical notes: This code no longer calls cr:stroke() for every single value in a stacked graph, but rather paints a whole series and then cr:stroke/fill()-s it at all once with its color, improving performance considerably. Additionally the graph no longer relies on the presence of the stack_colors property to render series. It draws all data that it has, and, if there's no corresponding color in stack_colors, falls back to graph.color. This will be changed in the next commit to the prior behavior of only drawing those series for which there's a color in the stack_colors table, but that would've made this commit's diff much larger due to required nesting ifs over large code chunks. This commit also takes care to properly handle NaNs and negative values. Those are currently (almost) guaranteed to not occur in the values array due to clipping in graph:add_value(), but I intend to eventually drop the clipping, because it does nothing that draw() really relies upon, and allowing unclipped and NaN values is more flexible and less surprising, e.g. when user dynamically toggles graph.scale. Eventually I intend to implement drawing negative values in non-stacked graphs and possibly even in the stacked ones. commit 38c6589c51b2aa057d7c2f40182ecf32b5b2ff5e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 10:04:15 2021 +0700 Convert graph:draw() to use `self` commit f21f1b2550426df1c4ea8abde079b1ccef84b394 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 12:27:26 2021 +0700 Fix scaling for stacked graphs Stacked graphs have cumulative heights, values from all groups that end up in the same graph bar are effectively summed. Previous code didn't take that into account when calculating min/max_value for the purposes of rescaling, but simply examined every value separately. This code adds up all values up front and finds min/max among those, to determine proper scaling that would fit the graph exactly. commit 1ce4c7bf63bdd6c99c195d668347bec49355e921 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 09:49:35 2021 +0700 Draw shaped graph bars only up to the baseline Prior to this commit, every step_shape bar was, for some reason, drawn with height = full widget's height, regardless of the value it represents. So for the most bars their lower end was far outside the box and therefore clipped off. So e.g. one couldn't see the lower round edges of rounded rectangles. This commit fixes that by appropriately varying the height that is passed into step_shape(), so that lower edges of the bars always exactly touch the lower edge of the graph. If the user wants to restore the prior behavior, they can simply ignore the passed-in height in step_shape() and just always draw their shape with height = full widget height. But they couldn't replicate the effect of this commit in their rc.lua without essentially writing their own graph widget. commit e25185b08da23745b5815f85f6e9e43222ed2294 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 08:42:54 2021 +0700 Avoid incremental transform shifting during graph bar rendering Instead of letting addition errors and cognitive load accumulate simply use the same offset calculation that is used for lines and reset the transform back to the top-left corner after every drawn bar. It's conceptually simpler, and will be useful later for stacked graphs and more. It also fixes a discrepancy between plain and step_shape rendering. Prior to this commit, bar coloring occurred with a disturbed transform, which was centered at the last drawn bar, wherever it happened to be. This meant that nontrivial colors, like gradients and images, were arbitrarily offset when step_shape was used. commit f3e98866d8583c35d0b557c09f529299edc9ba2c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:45:29 2021 +0700 Refactor out graph bar height expression into a variable commit f54528d6892f5aaa4595917853c9e2130f743fdb Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:20:25 2021 +0700 Draw graph bars with lines only when step_width == 1 Before this commit graph bars of step_width < 1 were drawn with lines of width 1, but that's a bit unfortunate, because it led to much overpainting, while still ending up with a graphically incorrect result. Let's draw such bars with rectangles and let cairo do things with subpixels, getting more graphically accurate result, while still keeping the common case of step_width == 1 (supposedly) fast-drawn with lines. commit b6515dca67c18b78eb5657378131a62ab43d4cfc Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:54:07 2021 +0700 Fix unsharp edges when drawing graph bars with rectangles The 0.5 offset is only helpful for 1-width strokes, not for fills. commit 7f3db8f07e82f821bcd1dc418d66564f537609d6 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:21:01 2021 +0700 Do coordinate transformations even if the value isn't drawn The graph presently doesn't draw values < 0, but there was a discrepancy between drawing with and without step_shape. The default line/rectangle drawing reserves an empty place where the skipped value should've been, but no such empty place is reserved, when step_shape is used. This commit fixes that. Skipped values always have their empty space now. commit 7b731ebfe96af7a9dba9c57371da6761cf22c00c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:03:30 2021 +0700 Fix graph bar scaling when min_value is not 0 Previous calculation lead to underscaling of values so that even value = max_value wasn't drawn to full height. commit dca59fd8b7fff0e7e646f2dfc6bd92047028e5a8 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:18:40 2021 +0700 Calculate the horizontal graph bar offset only when needed commit 940ddf4b9825e9c3a91d276208c089d8c74e49db Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:17:23 2021 +0700 Call cr:move_to() only when drawing the graph with lines It's redundant otherwise and might even interfere with user's drawing in step_shape(). commit b5d5cd10ca29fb29cd751faaa95137c2fe89ff63 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:04:06 2021 +0700 Fix wrong horizontal offset calculations in widget.graph Indices in the loop are zero-based, so things like i-1 and i>1 aren't necessary and actually wouldn't have been correct even in a 1-based loop. step_shape = gears.shape.rectangle and step_shape = nil, which is supposed to default to rectangle according to docs, were drawn differently due to mismatched offset calculations, which led to problems especially noticeable when step_spacing is not zero, e.g: 1) whole graph shifted left too much, drawn partially off-screen. 2) whole graph shifted right too much, and the first bar placed closer to the rest than others. commit dc369fa198281e000afc1bd17326f3a264adf7d1 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 02:09:21 2021 +0700 Fix the "border_width affects graph bar width" bug Graph bars were drawn with lines of border_width thickness instead of 1, i.e. possibly too fat to be readable. commit 1ba73378cf29ed27a6ecd18716394779ceb543d9 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 00:13:03 2021 +0700 Simplify internal values representation in graph even more New values in graph._private.values were pushed to the end, and old values were removed from the beginning of the table. In draw() however newer values are drawn first, at the left edge of the graph, necessitating annoying idx = #values - idx conversions everywhere. In particular in stacked graph that means that values that are drawn together may have different indices. One has to shift the values array in add_value() either way, so one might as well insert new values at the beginning of the array and simplify the rest of the code. This doesn't seem like much yet, but it will prove to be much more convenient and less error-prone in what I'm going to do later. commit f70523ee68dbd6b67050ace4b82903a9b22440c7 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 13:35:10 2021 +0700 Simplify internal values representation in graph widget graph._private.values was either a table of values or a table of tables of values, depending on whether "stack" property is enabled, ... or a mix of both if user forgot to pass the second "group" parameter to graph:add_value(). This resulted in pretty fragile and confusing behavior, e.g. leaving user to wonder, why they see nothing more after setting stack=true for a graph in a previously working rc.lua, and demanding a very careful use of add_value() from the user, or else some values or even entire stacks would mysteriously vanish and errors in draw() get triggered, sometimes after a period of seemingly normal functioning. Now `values` is always a table of tables and the group parameter in add_value() defaults to 1, thus making the non-stacked graph a trivial variant of the stacked graph.
2021-05-20 17:14:03 +02:00
-- Set initial values for properties.
gtable.crush(_graph._private, prop_defaults, true)
_graph._private.values = {}
-- Copy methods and properties over
gtable.crush(_graph, graph, true)
-- Except those, which don't belong in the widget instance
rawset(_graph, "new", nil)
rawset(_graph, "mt", nil)
2016-05-23 07:30:44 +02:00
return _graph
end
function graph.mt:__call(...)
return graph.new(...)
end
graph: complete widget overhaul and bug fixes Squashed commit of the following: commit 69821f51fe1e8652715658543e50631ece495090 Author: Alex Belykh <albel727@ngs.ru> Date: Mon May 10 19:17:55 2021 +0700 Refactor property handling in wibox.widget.graph In addition, baseline_value, step_width and step_spacing properties are all set to their default non-nil numeric values during instance creation now. commit 842eb429bf5df4b2ba46b2e6f7646afe89b1a3c3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 9 06:18:45 2021 +0700 Make graph:draw_values() private There's no documentation nor confidence that it's a good API, so I'm hiding it for now. commit 720746780574e4bad14a71fc4174955139c2eb50 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 04:04:13 2021 +0700 Document graph_should_draw_data_group() for posterity commit 7c73f3754ce6ba86b118f1170a6f9e406ab4841d Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:59:21 2021 +0700 Make graph:should_draw_data_group() private There's no confidence whether it's a good API, so I'm hiding it for now. commit b3539e20adb6423118af726ae4f8751ef864b5d2 Author: Alex Belykh <albel727@ngs.ru> Date: Fri May 7 03:36:44 2021 +0700 Make graph:preprocess_values() private There's no confidence whether it's a good API, so I'm hiding it for now. commit 2df4400e780a6260f8d0ab1c60734fbb11bf3c4f Author: Alex Belykh <albel727@ngs.ru> Date: Thu May 6 10:05:23 2021 +0700 Add spec/wibox/widget/graph_spec.lua commit ce01a9771ab439b8df841f1565aa4aa8a44a5e0d Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 08:16:18 2021 +0700 Improve graph.capacity documentation commit e28fade8f655802f5bf89154169f68d256b8e332 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 07:36:01 2021 +0700 Document graph:compute_drawn_values_num() commit dd68332292465422d08b48e9f0229f143beccbc6 Author: Alex Belykh <albel727@ngs.ru> Date: Wed May 5 05:24:23 2021 +0700 Document graph:pick_data_group_color() method commit ca6dc55f77aca88a6e9cc90bb6fd0045739f41f5 Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 04:13:58 2021 +0700 Guard against setting data groups sparsely in graph:add_value() commit cdea5f126f7984e72924f6dffbd312c4fe3b032f Author: Alex Belykh <albel727@ngs.ru> Date: Sun May 2 02:36:12 2021 +0700 Refactor scaling and baseline choice out of graph:draw() commit 10135deafe8a9390ba66ab657ebb1fb41d549e8c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 21:34:48 2021 +0700 Document graph.nan_color and graph.stack properties commit 0d73bb84ac66b87e16aee472bd1f299549f66a1c Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 20:49:24 2021 +0700 Use a better example in docs for the graph.stack property commit c27d160a0826d033d49d6141608c40aa8a0642fa Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 16:05:38 2021 +0700 Document graph.baseline_value property commit 459bc8176c13d4ea32af0dcdc2399d3b929b1c6a Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:53:47 2021 +0700 Document graph.clamp_bars property commit e732ee0b3b007d213e689b98cc00ae7cb141ae08 Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:34 2021 +0700 Improve graph:add_value() and :clear() documentation commit ae73e3aa9f8d60409cbe4cb4da9612f31af4f38e Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:28:11 2021 +0700 Improve graph min_value/max_value/scale documentation commit a7350996a9def773b91b1631e6c0507e33d8f93b Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 15:27:36 2021 +0700 Cleanup graph examples to show only relevant things in docs commit 30740c26f4f41c16f4c8a3c339bcb7bd299e14cd Author: Alex Belykh <albel727@ngs.ru> Date: Sat May 1 14:04:36 2021 +0700 Rename graph.stack_colors property to `group_colors` The colors are used for stacked and non-stacked graphs alike, so the name didn't make sense. Also document and clarify some color stuff. commit 1ea01bfd410971cf0d0c25fd74cd764d5eb7279d Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 21:25:41 2021 +0700 Add an example file for code coverage of some fringe graph cases commit eae68e39553f88a6275a0c65bf040b5d4c262b54 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 20:23:32 2021 +0700 Add step_shape to the graph/nan_color example for code coverage commit 8027147bc2eab34a97b49a428fb8cef27f122bed Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:55:18 2021 +0700 Add example files for graph.nan_color/nan_indication properties commit 89a25a165c52b2c15b6c4d95ecb777a9067e66f9 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:33:07 2021 +0700 Make widget graph:add_value(v) default v to NaN 0 as a default makes no sense and silences the error that is failing to specify a value, to which the user should rather be alerted. The graph widget has sane NaN handling now and will do just that. commit 2835d552440be48d356a7f7b1d64963696dd550b Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 30 19:20:48 2021 +0700 Implement graph.nan_color and nan_indication properties Now, whenever there's a NaN among values and graph.nan_indication is set, the corresponding area, where the value bar should have been drawn, is filled with the nan_color from top to bottom. The painting is done after all data is rendered, to make sure that it won't be overpainted and go unnoticed. NaNs among graph's values are inevitably a sign of some error and it is arguably not a sane default behavior to simply not render them at all. The user should take immediate note of any errors by default, instead of wondering at a mysteriously empty graph widget, which is why nan_indication is set to true by default. If the user wants to silence NaNs, they can always simply set it to false. nan_color if unset, defaults to a noticeable yellow-black pattern. As a direct and planned consequence, stacked graphs with negative values in them trigger the NaN handling too. But silencing NaN handling for those graphs is also possible and semi-sane, because then NaNs and negatives are ignored and the graph behaves (almost) like they were zeros. commit 9c1f8f08b400e01e19c534f3810c9c9ab485760c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 26 08:10:19 2021 +0700 Refactor out graph_map_value_to_widget_coordinates() function commit 1905991ddecabdae3e15ffb8ee5b18a1bee1a711 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 28 13:39:29 2021 +0700 Add an example file for graph.baseline_value property commit b31b39d66f4c037a9484b71a3f910703ae01fac0 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 07:03:49 2021 +0700 Add an example file for graph.clamp_bars=false/true comparison commit a1fc850e3047f9ee2c9ddeffacfbabf3ded34171 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 04:06:45 2021 +0700 Add an example file for graphs with negative values and shapes commit 2f012f8ff1bb5094c14e724a61e59a2a3a296bc8 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 22 01:06:05 2021 +0700 Add an example file for a graph displaying negative values commit 7b14fbd59a4c4c51962fd3110a1a900dd5f4eb98 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 11:18:26 2021 +0700 Add an example file for graphs with step_width < 1 commit dfd9a54544b637e2fbe3d1325f9cc24b3f4d2313 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 10:17:38 2021 +0700 Add an example file for graph.stack=false/true comparison Should also demonstrate that multiple data groups are possible for non-stacked graphs too. commit 5b532106c3fd255f9b66a66cfb88d977b489ab5d Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 09:34:14 2021 +0700 Add an example file for disabling graph data groups with nil colors commit ac6f1083c02fcb428f089a616f9b0ebaa5496b6c Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:57:25 2021 +0700 Add an example file for step_shape/width/spacing on stacked graphs commit b3703db6a5139e73e72c1cfd198ece2cd9be7df2 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 21 08:43:56 2021 +0700 Add an example file for scale=false/true on stacked graph commit b000aed4a12cb44fb501e1c8432a71a14b2f8f3f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 20 08:22:19 2021 +0700 Document deprecation of graph's width and height commit cb0ad617bd6c83a499896797318e3b0b588c3566 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 07:53:37 2021 +0700 Deprecate width and height properties of wibox.widget.graph commit 8c64e14ad91a074b784166a7054e53444cccb9d2 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 19 04:37:59 2021 +0700 Refactor graph:draw() to separate concerns Firstly, summation of group values for stacked graphs is extracted into graph:preprocess_values(). This makes graph:draw() smaller and exposes the fact, that stacked graphs are almost entirely just a form of a more general concept: pre-render data manipulation. It's something a user might want to customize, and now they can do it by monkey-patching the method. Secondly, the logic for deciding, if and with which color a data group should be drawn, is extracted into graph:should_draw_data_group() and get_data_group_color() methods respectively. This makes it customizable too and makes more obvious the fact, that it is a general thing that has nothing to do with stacked graphs in particular. With this there are only 5 lines remaining in graph:draw(), that could be said to be specific to stacked graphs, all pertaining to the `prev_y` variable, which merely preserves the bar coordinates from a previous data group. commit 2a32b6305d893f413303728a0a3da1dcd9a5591e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 22:52:37 2021 +0700 Pointlessly optimize graph:add_value() 1) Purge old values first, then insert the new one, if needed. Since Lua grows arrays in power of two steps, and our guess_capacity() also takes care to return even integers, it would be a shame to overstep the capacity by one by inserting first and thus getting the table size doubled only to never use the most of it. 2) Use a removal procedure that doesn't call the slow length operator at all. Previous code called it twice (once in table.remove) for every removed value. commit 680ea8f5d81e9040afb4662b3229e937e4b89e02 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:23:38 2021 +0700 Set graph data series color before the painting loop commit b970f0f221ae76250f2a94433ff79d653caa6e21 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:05:37 2021 +0700 Drop a large no longer necessary `if` in graph:draw_values() This commit is almost purely a whitespace change. commit 6b4f0541c70ec99d7f6e276bae64939f337120d6 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 16:00:50 2021 +0700 Extract values rendering code into graph:draw_values() method This is to avoid ungodly if nesting levels, which are needed to bail out of drawing values early and skip to drawing the border. The next commit will get rid of the if. commit e0950e4f99d1ea1c4b8cea2cc67629fa40030c2a Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 14:50:20 2021 +0700 Track graph draw() usage to guess necessary array capacity The last step of divorcing add_values() from width calculations, while ensuring that user doesn't observe a lack of data to draw. The widget now tracks an approximate number of values that it has been asked to draw() in _private.last_drawn_values_num, and truncates its values array based on that (if not overridden by the capacity property). commit 2e3d542282f35e7c0fed454da0f2eada0fcf3b78 Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 13:35:12 2021 +0700 Add `capacity` property to graph widget This is the second step of divorcing add_values() from calculations involving widths. How many values are stored and how many values are drawn are distinct concepts. There isn't even a single definitive "how many values are drawn" because the widget could be rendered in several places with distinct widths simultaneously, and in every such place the data must be drawn appropriately, e.g. with auto-scaling, that takes only the visible data points into account. And the draw() method can do that now. The default capacity value of nil uses heuristics based on widget's _private.width to guess some capacity that would tolerate some dynamic widget resizing. If the user finds the heuristic failing them, they can set the capacity property to the desired maximum number of values that will be kept in the _private.values array (in each data group). commit 08d4a80686f7fe363551c35589a460839f60853e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 03:27:18 2021 +0700 Extract values size computation into compute_drawn_values_num() This is the first step of making add_values() not concern itself (directly) with widget dimensions. The user can monkey-patch the method to override how many values are (logically) drawn, because it affects graph auto-scaling and can be useful for rendering of step_shape-s that paint outside of their step_width. I didn't make it a full-blown function-valued property, because it seems a fringe functionality, which I'm too lazy to document. commit 3c5617c98ab1147618ed22e8414a77fc56b45f6e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 02:32:29 2021 +0700 Copy graph widget prototype methods over in bulk commit 169538837ece41a03d02ba8fbc0e4097062b67a3 Author: Alex Belykh <albel727@ngs.ru> Date: Sun Apr 25 05:58:01 2021 +0700 Remove graph.baseline_y property After giving it some thought I've concluded, that it is unlikely to be useful as it is now. I'd better reserve the name for some more sensible functionality, like fixing the baseline_value defined axis at the given widget coordinate, adjusting the scaling accordingly, so min_value/max_value won't generally correspond to widget top and bottom anymore. commit 202ffaa9a0b820c7c471e1121d01b91f8f76cd38 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 12:06:55 2021 +0700 Add baseline_y and baseline_value properties to graph widget Now values in graph have a *baseline*, tunable with baseline_y and baseline_value properties. It is the (for now invisible) horizontal line from which all bars in the graph grow. I call it a "baseline" instead of "axis", because it doesn't actually have to correspond to the 0 value. But it can, and actually does now by default. baseline_y and baseline_value properties determine the vertical placement of the baseline in the widget. baseline_y is the position of the baseline in normalized widget coordinates, e.g. 0 corresponds to widget's top, 1 - to widget's bottom, and 0.5 - to widget's middle. So if e.g. one sets baseline_y = 0, then no matter how the widget is resized and how its values are scaled, bars in it will be drawn starting from the top. baseline_value is the position of the baseline in value coordinates. So if one sets baseline_value = 10, then no matter what happens, bars for values greater than 10 will be drawn as growing up from this line, and bars for values smaller as growing down. Where and if it will be seen in the widget, depends on the scaling. baseline_value = 0 is the familiar zero axis. baseline_y is unset by default and has priority, i.e. if it's set, then baseline_value is ignored. baseline_value defaults to 0. Prior to this commit, bars were always drawn from the bottom edge of the widget, or, in the new terms, the widget had baseline_y = 1. But it was indistinguishable from baseline_value = 0, because the widget clamped all negative values to 0, so the difference between the two could never be observed. baseline_value = 0 is clearly more useful and familiar to people, so now it is in force by default. commit df7e19abaebf0a871b5ca217b4cc3365533267a6 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 11:18:12 2021 +0700 Move value clamping from graph:add_value() into draw() Previously all values were forcibly clamped between 0 (not tunable at all) and max_value (1 by default). Thus graphs with negative values were not an option. NaN values were conflated with zeros, even though it may be useful to maintain the distinction and render them differently. And if user happened to dynamically change the max_value property, they discovered, that their data got truncated. Preserving user data as it was given is more flexible and makes things easier to debug. This commit does exactly that. So what happens now to the newly allowed values during draw()? There are 3 new cases to consider: 1) NaNs 2) Negatives 3) Values outside of min_value..max_value range NaNs are presently simply not drawn. An empty place is reserved where the value should've been, but nothing else is done. This is distinct from how a zero is handled, e.g. if step_shape is set, it is called with the height parameter = 0, which can still end up drawing something, depending on the shape. One could also consider addition of nan_color/nan_shape properties which could be drawn to alert user to a gap in their data. Negative values necessitate introduction of the concept of the zero axis, from which they will be drawn DOWN, in contrast to positive values. Where should this axis be drawn? How should step_shape-s with inherent direction, e.g. arrows, be drawn for negatives? This will be addressed in the next commit. Values outside of min/max_value range can imply shapes that go beyond widget dimensions. An arrow shape that goes beyond the roof is visibly different from an arrow that saturates and merely touches the widget top, and the user may reasonably desire either behavior. Therefore this commit introduces a boolean clamp_bars property, which defaults to true for reasons of robustness and backward compatibility with the behaviour prior to this commit. The property of course affects only the display of the values, the actual user data stays intact. commit c9c24fb1ab741cd6bb8074971d568e0646c51532 Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:29:01 2021 +0700 Use rectangles for drawing graph bars of all sizes I have benchmarked various bar drawing strategies and graph sizes and found out that drawing graph bars with rectangles is as fast or even marginally faster than using fat lines. Thus the code that tries to special-case 1px bars as lines seems to be not worth the additional complexity it introduces, and this commit removes it. Incidentally, I've also established, that rectangles are more robust. If one draws graph bars and feeds cairo very large coordinates (~2^62), then corresponding rectangles fail to render, but all the other rectangles in the same series get rendered successfully, whereas a single large coordinate in a move_to/line_to() sequence is enough to cause all lines in the batch to fail to render. commit bc7f4be5e06405b5aa7fb443a5ace2b9b9575b1f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 09:01:42 2021 +0700 Cache cairo methods used in the inner loop of graph:draw() I have benchmarked the draw() method, both on cairo xcb and on argb32 image surfaces, and have discovered that cairo context method calls are unnaturally slow, to the point, that almost nothing else in the function matters. For example, drawing bars with vertical lines made the whole draw() function almost 2 times slower than doing the same with rectangles, merely because the former requires two method calls (move_to/line_to), and the latter only one. Simply caching hotpath cairo methods in local variables, reduced the difference between the drawing methods to a negligible one and made the whole function 4 times faster on a typical 100-bar graph, (1.4ms to 0.33ms on my laptop) thus bringing closer my secret dream of drawing live audio data with awesome graphs (kidding). I guess, LGI does something not very optimal there, like gobject-introspecting the method every time, or something. There might be some untapped optimization potential, possibly in upstream, like a method cache, from which everything in awesome could benefit, even though I imagine not many things are so call-intensive as the graph widget, to really feel the difference. commit 538df25bdf02b53d0bbe81809eef2e6fdb05ff6f Author: Alex Belykh <albel727@ngs.ru> Date: Thu Apr 15 07:32:37 2021 +0700 Use 1-based index in the inner graph draw loop It's used there only once for a calculation and then several times to index some tables, and index 0 is bad for lua tables performance-wise. commit 7412546916cd0a0a490130a120255ffb719398b8 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:55:12 2021 +0700 Leave graph.max_value uninitialized at construction This is a user-visible change, but the previous behavior was probably not what user expected anyway, namely with graph.scale set the graph mysteriously couldn't zoom in onto values that are smaller than 1. commit eb81d7929a161e79c13262cfa73ac2e6a820e8ed Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 21:48:29 2021 +0700 Allow graph.max_value be nil This makes its behave symmetrically to min_value. Now it defaults to 1, when graph.scale is not set, and to the maximum value in the graph data, when graph.scale is set. commit f622a8430cbfde7007b58d7f4c9c76b08c91e997 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 12:01:13 2021 +0700 Calculate max values num in graph:add_value() more precisely At most so many values were kept in the widget values array, as there are pixels of usable widget width (i.e. sans border). This is exactly right for the surely most common case of graphs with 1px-wide bars, but is more than needed for graphs with step_width+step_spacing>1, and not enough for graphs with subpixel bars. Moreover this made graph autoscaling behave weirdly in graphs with thicker bars, because old values survived longer in the array and kept affecting min_value/max_value calculations even after they were shifted off-screen. The maximum number of kept values is now calculated exactly as the number of values that can be displayed at once, taking step_width and step_spacing into account. It might be worth adding a max_values_num property to the graph to let user override this calculation, in case they draw shapes in step_shape() outside of its supposed rectangle, so our assumptions of visibility are wrong, or the widget often gets resized, resulting in the annoyance of visibly truncated series. commit 0aa63249a18641251a1bf77423739164f0eab39f Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 02:30:13 2021 +0700 Fix border_color/border_width bugs in graph widget add_value() wrongly assumed that border_width is always set, when border_color is, so if user set only the latter, an error occurred. add_value() assumed that border_width is always < width which led to awesome hanging up in an infinite loop, should that fail to be the case. The graph border_width property was driven by the border_color property, instead of the other way round. If border_color wasn't set, the border wasn't drawn all, even if user sets non-zero border_width, which is a confusing behavior, in contrast to all other widgets, which simply use a plethora of fallback colors to draw the border, when needed. This also meant that, despite being documented, the beautiful.graph_border_color fallback color could not and was not ever used. If the border_color was set though, an unset border_width was immediately assumed to be 1 instead of 0, also contrary to what other widgets do. Theme colors shouldn't influence widget layout, so this commit fixes this behavior to: 1) border_width always defaults to 0, if unset. 2) border_color falls back to beautiful.graph_border_color or white. Maybe one should also add a beautiful.graph_border_width theme variable, like some widgets do. commit dad4a3253b8fef971fb82720d2a407fcc0a879ad Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 03:11:27 2021 +0700 Implement multiple data series drawing for non-stacked graph The widget can already hold and draw multiple data groups, so it makes little sense to limit drawing only to the first group when graph.stack = false, probably leading to user's confusion. Now user can call add_value(v, group) with group > 1 and the data series will be drawn (over groups with lesser id-s) with graph.stack_colors[group] color, if stack_colors is set, or with graph.color, if stack_colors isn't set. Thus nothing visibly changes for all current users of non-stacked graphs with only single data series, but the new functionality is now there for those who need it. One can't fully emulate this functionality, e.g. with multiple graphs widgets in a wibox.layout.stack, because data series in a single widget can enjoy a common auto-scaling. commit 3e645c976184ab5f50d625271c25062db9ab7ed9 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:59:12 2021 +0700 Don't draw a stacked graph data group if there's no color for it If stack_colors[idx] is nil for the group `idx`, don't draw it at all (as opposed, e.g. to the effect of drawing it with a transparent color, or as though it had all values == 0, which both produce different results). This code is still subtly different from prior behavior, because now user can have a non-contiguous stack_colors table and turn the display of any data group on and off at will, whereas the code two commits earlier would just stop rendering as soon as ipairs(stack_colors) does, i.e. no data series were drawn past the first nil hole. This commit only looks large, but is mostly a whitespace change due to nesting from a pair of wrapping ifs. commit 8c0273786de1ade66bdd977699f98b588da9f487 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 01:32:42 2021 +0700 Unify stacked and non-stacked graph rendering Now stacked graphs support everything the nonstacked do, like step_width ~= 1 and custom step_shape-s. Technical notes: This code no longer calls cr:stroke() for every single value in a stacked graph, but rather paints a whole series and then cr:stroke/fill()-s it at all once with its color, improving performance considerably. Additionally the graph no longer relies on the presence of the stack_colors property to render series. It draws all data that it has, and, if there's no corresponding color in stack_colors, falls back to graph.color. This will be changed in the next commit to the prior behavior of only drawing those series for which there's a color in the stack_colors table, but that would've made this commit's diff much larger due to required nesting ifs over large code chunks. This commit also takes care to properly handle NaNs and negative values. Those are currently (almost) guaranteed to not occur in the values array due to clipping in graph:add_value(), but I intend to eventually drop the clipping, because it does nothing that draw() really relies upon, and allowing unclipped and NaN values is more flexible and less surprising, e.g. when user dynamically toggles graph.scale. Eventually I intend to implement drawing negative values in non-stacked graphs and possibly even in the stacked ones. commit 38c6589c51b2aa057d7c2f40182ecf32b5b2ff5e Author: Alex Belykh <albel727@ngs.ru> Date: Fri Apr 16 10:04:15 2021 +0700 Convert graph:draw() to use `self` commit f21f1b2550426df1c4ea8abde079b1ccef84b394 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 12:27:26 2021 +0700 Fix scaling for stacked graphs Stacked graphs have cumulative heights, values from all groups that end up in the same graph bar are effectively summed. Previous code didn't take that into account when calculating min/max_value for the purposes of rescaling, but simply examined every value separately. This code adds up all values up front and finds min/max among those, to determine proper scaling that would fit the graph exactly. commit 1ce4c7bf63bdd6c99c195d668347bec49355e921 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 09:49:35 2021 +0700 Draw shaped graph bars only up to the baseline Prior to this commit, every step_shape bar was, for some reason, drawn with height = full widget's height, regardless of the value it represents. So for the most bars their lower end was far outside the box and therefore clipped off. So e.g. one couldn't see the lower round edges of rounded rectangles. This commit fixes that by appropriately varying the height that is passed into step_shape(), so that lower edges of the bars always exactly touch the lower edge of the graph. If the user wants to restore the prior behavior, they can simply ignore the passed-in height in step_shape() and just always draw their shape with height = full widget height. But they couldn't replicate the effect of this commit in their rc.lua without essentially writing their own graph widget. commit e25185b08da23745b5815f85f6e9e43222ed2294 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 08:42:54 2021 +0700 Avoid incremental transform shifting during graph bar rendering Instead of letting addition errors and cognitive load accumulate simply use the same offset calculation that is used for lines and reset the transform back to the top-left corner after every drawn bar. It's conceptually simpler, and will be useful later for stacked graphs and more. It also fixes a discrepancy between plain and step_shape rendering. Prior to this commit, bar coloring occurred with a disturbed transform, which was centered at the last drawn bar, wherever it happened to be. This meant that nontrivial colors, like gradients and images, were arbitrarily offset when step_shape was used. commit f3e98866d8583c35d0b557c09f529299edc9ba2c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:45:29 2021 +0700 Refactor out graph bar height expression into a variable commit f54528d6892f5aaa4595917853c9e2130f743fdb Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 06:20:25 2021 +0700 Draw graph bars with lines only when step_width == 1 Before this commit graph bars of step_width < 1 were drawn with lines of width 1, but that's a bit unfortunate, because it led to much overpainting, while still ending up with a graphically incorrect result. Let's draw such bars with rectangles and let cairo do things with subpixels, getting more graphically accurate result, while still keeping the common case of step_width == 1 (supposedly) fast-drawn with lines. commit b6515dca67c18b78eb5657378131a62ab43d4cfc Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:54:07 2021 +0700 Fix unsharp edges when drawing graph bars with rectangles The 0.5 offset is only helpful for 1-width strokes, not for fills. commit 7f3db8f07e82f821bcd1dc418d66564f537609d6 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:21:01 2021 +0700 Do coordinate transformations even if the value isn't drawn The graph presently doesn't draw values < 0, but there was a discrepancy between drawing with and without step_shape. The default line/rectangle drawing reserves an empty place where the skipped value should've been, but no such empty place is reserved, when step_shape is used. This commit fixes that. Skipped values always have their empty space now. commit 7b731ebfe96af7a9dba9c57371da6761cf22c00c Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 05:03:30 2021 +0700 Fix graph bar scaling when min_value is not 0 Previous calculation lead to underscaling of values so that even value = max_value wasn't drawn to full height. commit dca59fd8b7fff0e7e646f2dfc6bd92047028e5a8 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:18:40 2021 +0700 Calculate the horizontal graph bar offset only when needed commit 940ddf4b9825e9c3a91d276208c089d8c74e49db Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:17:23 2021 +0700 Call cr:move_to() only when drawing the graph with lines It's redundant otherwise and might even interfere with user's drawing in step_shape(). commit b5d5cd10ca29fb29cd751faaa95137c2fe89ff63 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 04:04:06 2021 +0700 Fix wrong horizontal offset calculations in widget.graph Indices in the loop are zero-based, so things like i-1 and i>1 aren't necessary and actually wouldn't have been correct even in a 1-based loop. step_shape = gears.shape.rectangle and step_shape = nil, which is supposed to default to rectangle according to docs, were drawn differently due to mismatched offset calculations, which led to problems especially noticeable when step_spacing is not zero, e.g: 1) whole graph shifted left too much, drawn partially off-screen. 2) whole graph shifted right too much, and the first bar placed closer to the rest than others. commit dc369fa198281e000afc1bd17326f3a264adf7d1 Author: Alex Belykh <albel727@ngs.ru> Date: Mon Apr 12 02:09:21 2021 +0700 Fix the "border_width affects graph bar width" bug Graph bars were drawn with lines of border_width thickness instead of 1, i.e. possibly too fat to be readable. commit 1ba73378cf29ed27a6ecd18716394779ceb543d9 Author: Alex Belykh <albel727@ngs.ru> Date: Wed Apr 14 00:13:03 2021 +0700 Simplify internal values representation in graph even more New values in graph._private.values were pushed to the end, and old values were removed from the beginning of the table. In draw() however newer values are drawn first, at the left edge of the graph, necessitating annoying idx = #values - idx conversions everywhere. In particular in stacked graph that means that values that are drawn together may have different indices. One has to shift the values array in add_value() either way, so one might as well insert new values at the beginning of the array and simplify the rest of the code. This doesn't seem like much yet, but it will prove to be much more convenient and less error-prone in what I'm going to do later. commit f70523ee68dbd6b67050ace4b82903a9b22440c7 Author: Alex Belykh <albel727@ngs.ru> Date: Tue Apr 13 13:35:10 2021 +0700 Simplify internal values representation in graph widget graph._private.values was either a table of values or a table of tables of values, depending on whether "stack" property is enabled, ... or a mix of both if user forgot to pass the second "group" parameter to graph:add_value(). This resulted in pretty fragile and confusing behavior, e.g. leaving user to wonder, why they see nothing more after setting stack=true for a graph in a previously working rc.lua, and demanding a very careful use of add_value() from the user, or else some values or even entire stacks would mysteriously vanish and errors in draw() get triggered, sometimes after a period of seemingly normal functioning. Now `values` is always a table of tables and the group parameter in add_value() defaults to 1, thus making the non-stacked graph a trivial variant of the stacked graph.
2021-05-20 17:14:03 +02:00
-- Setup default impls for property accessors that haven't been implemented explicitly.
build_properties(graph, properties)
2016-05-23 07:30:44 +02:00
return setmetatable(graph, graph.mt)
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80