Add graph.step_hook property for more flexible drawing
There is a limit to how much one can do with step_shape callback. By its nature it can only have three parameters, and that list can't be extended, because the user is expected to set the property to a function from gears.shape, all of which have different meanings for parameters past the first 3. Moreover step_shape requires the current coordinate system to be modified accordingly because it draws the shape at (0,0). Lastly it's not expected to handle NaN heights and thus is never called for NaN values. This makes it hard to implement things like the following: 1) drawing steps which depend on knowing their position relative to other steps. (e.g. connecting data points with bezier curves) 2) drawing steps while appropriately handling NaN values in any way other than not drawing anything, which might be still wrong or not sufficient. (e.g. interpolating data points requires to know *where* there are gaps in data, not simply continuing with the next present value) 3) drawing steps that need the knowledge of the exact value that is being drawn (e.g. drawing value tooltips over bars) The step_hook callback (name bikeshedding welcome) is designed to solve the problems (for now only the first two of the 3). Whenever it's set, it takes precedence over step_shape property and is used to draw steps. No coordinate transformation before calling it takes place like for step_shape(). The (0, 0) is always the top-left corner of the graph drawing area (sans borders), when it's called. In contrast to step_shape() which only accepts three parameters (cairo, width, height), step_hook() accepts (cairo, x, y, baseline_y, step_width, options). (x, y) is what would be (0, 0) in step_shape, i.e. the coordinates of the bar top. The y parameter can be NaN, and step_hook() is expected to handle that. baseline_y is the y coordinate of the bar bottom. (baseline_y - y) is what is known as `height` in step_shape. But note that baseline_y is never NaN, so even in the NaN case step_hook() can at least know where the baseline is. step_width is the bar's width, just like in step_shape. options is the same table that is passed to the group_start()/group_finish() callbacks, it contains some useful data for nontrivial drawing needs and it could be extended later with more useful data at leisure, e.g. with `value`, if such need arises, without fear of coming in conflict with other user's parameters.
This commit is contained in:
parent
ff882f1d80
commit
3e3b0ca9de
|
@ -174,6 +174,8 @@ local graph = { mt = {} }
|
|||
|
||||
--- The step shape.
|
||||
--
|
||||
-- If `step_hook` property is also set, this property is ignored.
|
||||
--
|
||||
--@DOC_wibox_widget_graph_step_shape_EXAMPLE@
|
||||
--
|
||||
-- @property step_shape
|
||||
|
@ -297,6 +299,59 @@ local graph = { mt = {} }
|
|||
-- @tparam @{draw_callback_options} options Additional info (@{draw_callback_options})
|
||||
-- @see group_finish
|
||||
|
||||
--- Control what is done to draw each value.
|
||||
--
|
||||
-- This property can be set to a `step_hook_callback` function or nil.
|
||||
--
|
||||
-- When `step_hook` is nil (default), the default implementation
|
||||
-- is used, which draws the values as `step_shape` bars.
|
||||
--
|
||||
-- @DOC_wibox_widget_graph_bezier_curve_EXAMPLE@
|
||||
--
|
||||
-- @property step_hook
|
||||
-- @within Advanced drawing
|
||||
-- @tparam function|nil step_hook
|
||||
-- @propemits true false
|
||||
|
||||
--- User callback for drawing a value.
|
||||
--
|
||||
-- This function is called in succession to draw the values of a data group,
|
||||
-- starting with the newest value.
|
||||
--
|
||||
-- Prior to this, the `group_start_callback` will be called once for
|
||||
-- the data group. And after `step_hook` was called for all values
|
||||
-- necessary, the `group_finish_callback` will be called once.
|
||||
--
|
||||
-- A drawing session consists of repeating the above steps for each drawn
|
||||
-- data group. The order of drawing of data groups is unspecified.
|
||||
--
|
||||
-- This function can assume, that nothing except itself interferes with
|
||||
-- the widget or cairo context state between the paired
|
||||
-- `group_start_callback`/`group_finish_callback` calls.
|
||||
--
|
||||
-- The coordinate system of the cairo context during all calls is the
|
||||
-- one with (0,0) and (width, height) corresponding to the top-left
|
||||
-- and the bottom-right corner of the graph's value drawing area respectively.
|
||||
--
|
||||
-- The parameters of this callback are pretty straightforward.
|
||||
-- It deserves a note though, that the baseline\_y parameter could vary between
|
||||
-- successive calls, because e.g. for stacked graphs, baseline\_y corresponds
|
||||
-- to the tops of the bars of the lower data group.
|
||||
--
|
||||
-- It's also not necessary that `baseline_y >= value_y`, the opposite holds e.g.
|
||||
-- for graphs with values smaller than `baseline_value`.
|
||||
--
|
||||
-- @callback step_hook_callback
|
||||
-- @within Advanced drawing
|
||||
-- @tparam cairo.Context cr Cairo context
|
||||
-- @tparam number x The horizontal coordinate of the left edge of the bar.
|
||||
-- @tparam number value_y The vertical coordinate corresponding to the drawn value.
|
||||
-- Can be a NaN.
|
||||
-- @tparam number baseline_y The vertical coordinate corresponding to the baseline.
|
||||
-- @tparam number step_width Same as `step_width` (but never nil).
|
||||
-- @tparam @{draw_callback_options} options Additional info (@{draw_callback_options}).
|
||||
-- @see step_hook
|
||||
|
||||
--- The graph foreground color
|
||||
-- Used, when the `color` property isn't set.
|
||||
--
|
||||
|
@ -321,7 +376,7 @@ local properties = { "width", "height", "border_color", "stack",
|
|||
"step_spacing", "step_width", "border_width",
|
||||
"clamp_bars", "baseline_value",
|
||||
"capacity", "nan_color", "nan_indication",
|
||||
"group_start", "group_finish",
|
||||
"group_start", "group_finish", "step_hook",
|
||||
"group_colors",
|
||||
}
|
||||
|
||||
|
@ -597,19 +652,13 @@ end
|
|||
local function graph_draw_values(self, cr, width, 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
|
||||
|
@ -662,6 +711,29 @@ local function graph_draw_values(self, cr, width, height, drawn_values_num)
|
|||
end
|
||||
end
|
||||
|
||||
-- The user callback for drawing each data bar
|
||||
local step_hook = self._private.step_hook
|
||||
if not step_hook then
|
||||
local step_shape = self._private.step_shape
|
||||
if step_shape then
|
||||
-- Preserve the transform centered at the top-left corner of the graph
|
||||
local pristine_transform = cr:get_matrix()
|
||||
local cairo_translate = cr.translate
|
||||
local cairo_set_matrix = cr.set_matrix
|
||||
step_hook = function(cr, x, value_y, baseline_y, step_width, _options) --luacheck: ignore 431 432 212/_.*
|
||||
local step_height = baseline_y - value_y
|
||||
if not (step_height == step_height) then
|
||||
return -- is NaN
|
||||
end
|
||||
-- Shift to the bar beginning
|
||||
cairo_translate(cr, x, value_y)
|
||||
step_shape(cr, step_width, step_height)
|
||||
-- Undo the shift
|
||||
cairo_set_matrix(cr, pristine_transform)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local nan_x = self._private.nan_indication and {}
|
||||
local prev_y = self._private.stack and {}
|
||||
|
||||
|
@ -682,24 +754,21 @@ local function graph_draw_values(self, cr, width, height, drawn_values_num)
|
|||
-- The coordinate of the i-th bar's left edge
|
||||
local x = (i-1)*(step_width + step_spacing) + offset_x
|
||||
|
||||
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
|
||||
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
|
||||
if not_nan then
|
||||
prev_y[i] = value_y
|
||||
end
|
||||
end
|
||||
|
||||
if step_shape then
|
||||
-- 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
|
||||
cairo_rectangle(cr, x, value_y, step_width, base_y - value_y)
|
||||
end
|
||||
if step_hook then
|
||||
-- step_hook() is expected to handle NaNs itself
|
||||
step_hook(cr, x, value_y, base_y, step_width, options)
|
||||
elseif not_nan then
|
||||
cairo_rectangle(cr, x, value_y, step_width, base_y - value_y)
|
||||
end
|
||||
|
||||
if not not_nan and nan_x then
|
||||
|
|
Loading…
Reference in New Issue