awesome/lib/wibox/widget/graph.lua

921 lines
31 KiB
Lua

---------------------------------------------------------------------------
--- A graph widget.
--
-- 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@
-- @author Julien Danjou <julien@danjou.info>
-- @copyright 2009 Julien Danjou
-- @widgetmod wibox.widget.graph
-- @supermodule wibox.widget.base
---------------------------------------------------------------------------
local setmetatable = setmetatable
local ipairs = ipairs
local math = math
local math_max = math.max
local math_min = math.min
local table = table
local color = require("gears.color")
local gdebug = require("gears.debug")
local gtable = require("gears.table")
local base = require("wibox.widget.base")
local beautiful = require("beautiful")
local graph = { mt = {} }
--- Set the graph border_width.
--
--@DOC_wibox_widget_graph_border_width_EXAMPLE@
--
-- @property border_width
-- @tparam number border_width
-- @propemits true false
-- @see border_color
--- Set the graph border color.
--
--@DOC_wibox_widget_graph_border_color_EXAMPLE@
--
-- @property border_color
-- @tparam gears.color border_color The border color to set.
-- @propbeautiful
-- @propemits true false
-- @see gears.color
--- Set the graph foreground color.
--
-- This color is used, when `group_colors` isn't set.
--
--@DOC_wibox_widget_graph_color_EXAMPLE@
--
-- @property color
-- @tparam color color The graph color.
-- @usebeautiful beautiful.graph_fg
-- @propemits true false
-- @see gears.color
--- Set the graph background color.
--
--@DOC_wibox_widget_graph_background_color_EXAMPLE@
--
-- @property background_color
-- @tparam gears.color background_color The graph background color.
-- @usebeautiful beautiful.graph_bg
-- @propemits true false
-- @see gears.color
--- Set the colors for data groups.
--
-- 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.
--
-- @DOC_wibox_widget_graph_stacked_group_disable_EXAMPLE@
--
-- @property group_colors
-- @tparam table colors A table with colors for data groups.
-- @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
-- automatically scales down to make all data fit.
-- If `scale` and `max_value` are unset, `max_value` defaults to 1.
--
-- @DOC_wibox_widget_graph_max_value_EXAMPLE@
--
-- @property max_value
-- @tparam number max_value
-- @propemits true false
--- 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.
--
-- @DOC_wibox_widget_graph_min_value_EXAMPLE@
--
-- @property min_value
-- @tparam number min_value
-- @propemits true false
--- Set the graph to automatically scale its values. Default is false.
--
-- 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@
--
-- @property scale
-- @tparam boolean scale
-- @propemits true false
--- 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@
--
-- @property clamp_bars
-- @tparam boolean clamp_bars
-- @propemits true false
--- The value corresponding to the starting point of graph bars. Default is 0.
--
-- @DOC_wibox_widget_graph_baseline_value_EXAMPLE@
--
-- @property baseline_value
-- @tparam number baseline_value
-- @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
-- @propemits true false
--- Set the spacing between the steps.
--
--@DOC_wibox_widget_graph_step_spacing_EXAMPLE@
--
-- @property step_spacing
-- @tparam[opt=0] number step_spacing
-- @propemits true false
--- The step shape.
--
--@DOC_wibox_widget_graph_step_shape_EXAMPLE@
--
-- @property step_shape
-- @tparam[opt=rectangle] gears.shape|function step_shape
-- @propemits true false
-- @see gears.shape
--- Set the graph to draw stacks. Default is false.
--
-- 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 boolean stack
-- @propemits true false
--- 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.
--
-- @DOC_wibox_widget_graph_nan_color_EXAMPLE@
--
-- @property nan_indication
-- @tparam boolean nan_indication
-- @propemits true false
--- The color of NaN indication.
--
-- 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 gears.color nan_color The color of NaN indication.
-- @propemits true false
-- @see gears.color
--- The graph foreground color
-- Used, when the `color` property isn't set.
--
-- @beautiful beautiful.graph_fg
-- @param color
--- The graph background color.
-- Used, when the `background_color` property isn't set.
--
-- @beautiful beautiful.graph_bg
-- @param color
--- The graph border color.
-- Used, when the `border_color` property isn't set.
--
-- @beautiful beautiful.graph_border_color
-- @param color
local properties = { "width", "height", "border_color", "stack",
"stack_colors", "color", "background_color",
"max_value", "scale", "min_value", "step_shape",
"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
-- 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
-- Set up the nan_color fallback.
prop_fallbacks.nan_color = build_fallback_nan_color()
--
-- Module and prototype methods.
--
local function graph_gather_drawn_values_num_stats(self, new_value)
if not (new_value >= 0) then
return -- is negative or NaN
end
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
--- 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 gears.color The color to paint the data group's values with.
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
--- 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
end
-- 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
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
end
-- 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
end
summed_values[idx] = acc
end
end
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
if v > max_value then
max_value = v
end
if min_value > v then
min_value = v
end
end
end
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
-- 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
-- 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
end
if not not_nan and nan_x then
-- Keep the coordinate to draw NaN indication later
table.insert(nan_x, x)
end
end
-- Paint the data series
cr:fill()
end
end
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()
end
end
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)
-- 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
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))
cr:stroke()
end
end
function graph:fit(_, width, height)
return width, height
end
--- 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
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.
--
-- @method add_value
-- @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.
function graph:add_value(value, group)
value = value or 0/0 -- default to NaN
group = group or 1
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))
end
end
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
if capacity then
table.insert(values, 1, value)
end
self:emit_signal("widget::redraw_needed")
return self
end
--- Clear the graph.
--
-- Removes all values from all data groups.
--
-- @method clear
function graph:clear()
self._private.values = {}
self:emit_signal("widget::redraw_needed")
return self
end
--- 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).
-- @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
--- Set the graph height.
--
-- This property is deprecated. Use a `wibox.container.constraint` widget or
-- `forced_height`.
---
-- @deprecatedproperty height
-- @tparam number height The height to set.
-- @renamedin 5.0 forced_height
-- @propemits true false
function graph:set_height(height)
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
end
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
end
--- Set the graph width.
--
-- 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
function graph:set_width(width)
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
end
end
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)
end
return self
end
end
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
--- Create a graph widget.
--
-- @tparam table args Standard widget() arguments.
-- @treturn wibox.widget.graph A new graph widget.
-- @constructorfct wibox.widget.graph
function graph.new(args)
args = args or {}
local _graph = base.make_widget(nil, nil, {enable_properties = true})
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
if width < 5 or height < 5 then return end
_graph._private.forced_width = width
_graph._private.forced_height = height
end
-- 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)
return _graph
end
function graph.mt:__call(...)
return graph.new(...)
end
-- Setup default impls for property accessors that haven't been implemented explicitly.
build_properties(graph, properties)
return setmetatable(graph, graph.mt)
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80