1113 lines
26 KiB
Lua
1113 lines
26 KiB
Lua
|
local c = {}
|
||
|
|
||
|
local Super = require 'Super'
|
||
|
|
||
|
local _CR = require 'CR'
|
||
|
local util = require 'util'
|
||
|
local schema = require 'default_patterns'
|
||
|
|
||
|
local Arc = require 'Arc'
|
||
|
local Dial = require 'Dial'
|
||
|
local Poly = require 'Poly'
|
||
|
local Bar = require 'Bar'
|
||
|
local Text = require 'Text'
|
||
|
local CriticalText = require 'CriticalText'
|
||
|
local TextColumn = require 'TextColumn'
|
||
|
local Table = require 'Table'
|
||
|
local Plot = require 'Plot'
|
||
|
local LabelPlot = require 'LabelPlot'
|
||
|
local Image = require 'Image'
|
||
|
local ScaledImage = require 'ScaledImage'
|
||
|
|
||
|
local _CAIRO_NEW_PATH = cairo_new_path
|
||
|
local _CAIRO_RECTANGLE = cairo_rectangle
|
||
|
local _CAIRO_COPY_PATH = cairo_copy_path
|
||
|
local _CAIRO_SET_FONT_FACE = cairo_set_font_face
|
||
|
local _CAIRO_SET_FONT_SIZE = cairo_set_font_size
|
||
|
local _CAIRO_FONT_EXTENTS = cairo_font_extents
|
||
|
local _CAIRO_TOY_FONT_FACE_CREATE = cairo_toy_font_face_create
|
||
|
|
||
|
local _UNPACK = unpack
|
||
|
local _MATH_ATAN2 = math.atan2
|
||
|
local _MATH_SIN = math.sin
|
||
|
local _MATH_COS = math.cos
|
||
|
local _MATH_CEIL = math.ceil
|
||
|
local _MATH_LOG = math.log
|
||
|
local _MATH_RAD = math.rad
|
||
|
|
||
|
--Box(x, y, [width], [height])
|
||
|
|
||
|
local BOX_WIDTH = 0
|
||
|
local BOX_HEIGHT = 0
|
||
|
|
||
|
local Box = function(arg)
|
||
|
|
||
|
local width = arg.width or BOX_WIDTH
|
||
|
local height = arg.height or BOX_HEIGHT
|
||
|
|
||
|
local obj = {
|
||
|
x = arg.x,
|
||
|
y = arg.y,
|
||
|
width = width,
|
||
|
height = height,
|
||
|
right_x = width + arg.x,
|
||
|
bottom_y = height + arg.y
|
||
|
}
|
||
|
|
||
|
return obj
|
||
|
end
|
||
|
|
||
|
--Arc(x, y, radius, [thickness], [theta0], [theta1], [arc_pattern], [cap])
|
||
|
|
||
|
local ARC_CAP = CAIRO_LINE_CAP_BUTT
|
||
|
local ARC_THICKNESS = 2
|
||
|
local ARC_THETA0 = 0
|
||
|
local ARC_THETA1 = 360
|
||
|
local ARC_PATTERN = schema.dark_grey
|
||
|
|
||
|
local initArc = function(arg)
|
||
|
|
||
|
local x = arg.x
|
||
|
local y = arg.y
|
||
|
local radius = arg.radius
|
||
|
local thickness = arg.thickness or ARC_THICKNESS
|
||
|
|
||
|
local obj = {
|
||
|
x = x,
|
||
|
y = y,
|
||
|
thickness = thickness,
|
||
|
cap = arg.cap or ARC_CAP,
|
||
|
path = Arc.create_path(x, y, radius, _MATH_RAD(arg.theta0 or ARC_THETA0),
|
||
|
_MATH_RAD(arg.theta1 or ARC_THETA1)),
|
||
|
source = Super.Pattern{
|
||
|
pattern = arg.arc_pattern or ARC_PATTERN,
|
||
|
p1 = {x = x, y = y},
|
||
|
p2 = {x = x, y = y},
|
||
|
r1 = radius - thickness * 0.5,
|
||
|
r2 = radius + thickness * 0.5
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return obj
|
||
|
end
|
||
|
|
||
|
--[[
|
||
|
Dial([x], [y], radius, [thickness], [theta0], [theta1], [arc_pattern], [cap],
|
||
|
[dial_pattern], [critical_pattern], [critical_limit])
|
||
|
]]
|
||
|
|
||
|
local DIAL_THICKNESS = 4
|
||
|
local DIAL_THETA0 = 90
|
||
|
local DIAL_THETA1 = 360
|
||
|
local DIAL_ARC_PATTERN = schema.grey_rounded
|
||
|
local DIAL_DIAL_PATTERN = schema.blue_rounded
|
||
|
local DIAL_CRITICAL_PATTERN = schema.red_rounded
|
||
|
local DIAL_CRITICAL_LIMIT = '>80'
|
||
|
|
||
|
local initDial = function(arg)
|
||
|
|
||
|
local x = arg.x
|
||
|
local y = arg.y
|
||
|
local radius = arg.radius
|
||
|
local thickness = arg.thickness or DIAL_THICKNESS
|
||
|
local theta0 = arg.theta0 or DIAL_THETA0
|
||
|
local theta1 = arg.theta1 or DIAL_THETA1
|
||
|
|
||
|
local obj = initArc{
|
||
|
x = x,
|
||
|
y = y,
|
||
|
radius = radius,
|
||
|
thickness = thickness,
|
||
|
theta0 = theta0,
|
||
|
theta1 = theta1,
|
||
|
arc_pattern = arg.arc_pattern or DIAL_ARC_PATTERN,
|
||
|
cap = arg.cap
|
||
|
}
|
||
|
|
||
|
local inner_radius = radius - thickness * 0.5
|
||
|
local outer_radius = radius + thickness * 0.5
|
||
|
|
||
|
obj.indicator_source = Super.Pattern{
|
||
|
pattern = arg.dial_pattern or DIAL_DIAL_PATTERN,
|
||
|
p1 = {x = x, y = y},
|
||
|
p2 = {x = x, y = y},
|
||
|
r1 = inner_radius,
|
||
|
r2 = outer_radius
|
||
|
}
|
||
|
|
||
|
obj.critical = Super.Critical{
|
||
|
critical_pattern = arg.critical_pattern or DIAL_CRITICAL_PATTERN,
|
||
|
critical_limit = arg.critical_limit or DIAL_CRITICAL_LIMIT,
|
||
|
p1 = {x = x, y = y},
|
||
|
p2 = {x = x, y = y},
|
||
|
r1 = inner_radius,
|
||
|
r2 = outer_radius
|
||
|
}
|
||
|
|
||
|
theta0 = _MATH_RAD(theta0)
|
||
|
theta1 = _MATH_RAD(theta1)
|
||
|
|
||
|
obj._make_dial_path = util.memoize(
|
||
|
function(percent)
|
||
|
obj.dial_angle = (1 - percent) * theta0 + percent * theta1
|
||
|
return Arc.create_path(x, y, radius, theta0, obj.dial_angle)
|
||
|
end
|
||
|
)
|
||
|
|
||
|
Dial.set(obj, 0)
|
||
|
|
||
|
return obj
|
||
|
end
|
||
|
|
||
|
--[[
|
||
|
Dial([x], [y], inner_radius, outer_radius, spacing, num_dials, [theta0], [theta1],
|
||
|
[arc_pattern], [cap], [dial_pattern], [critical_pattern], [critical_limit])
|
||
|
]]
|
||
|
local initCompoundDial = function(arg)
|
||
|
|
||
|
local inner_radius = arg.inner_radius
|
||
|
local outer_radius = arg.outer_radius
|
||
|
local spacing = arg.spacing
|
||
|
local num_dials = arg.num_dials
|
||
|
|
||
|
local side_length = outer_radius * 2
|
||
|
|
||
|
local obj = {
|
||
|
width = side_length,
|
||
|
height = side_length,
|
||
|
num_dials = num_dials,
|
||
|
dials = {}
|
||
|
}
|
||
|
|
||
|
local thickness = ((outer_radius - inner_radius) - (num_dials - 1) * spacing) / num_dials
|
||
|
|
||
|
for i = 1, obj.num_dials do
|
||
|
local r = inner_radius + thickness * 0.5 + (i - 1) * (spacing + thickness)
|
||
|
obj.dials[i] = initDial{
|
||
|
x = arg.x,
|
||
|
y = arg.y,
|
||
|
radius = r,
|
||
|
thickness = thickness,
|
||
|
theta0 = arg.theta0,
|
||
|
theta1 = arg.theta1,
|
||
|
arc_pattern = arg.arc_pattern,
|
||
|
dial_pattern = arg.dial_pattern,
|
||
|
critical_pattern = arg.critical_pattern,
|
||
|
critical_limit = arg.critical_limit,
|
||
|
cap = arg.cap
|
||
|
}
|
||
|
end
|
||
|
|
||
|
return obj
|
||
|
end
|
||
|
|
||
|
--Poly([thickness], [line_pattern], [cap], [join], [closed], ... points)
|
||
|
|
||
|
local POLY_THICKNESS = 1
|
||
|
local POLY_CAP = CAIRO_LINE_CAP_BUTT
|
||
|
local POLY_JOIN = CAIRO_LINE_JOIN_MITER
|
||
|
local POLY_LINE_PATTERN = schema.mid_grey
|
||
|
|
||
|
local initPoly = function(arg)
|
||
|
|
||
|
local obj = {
|
||
|
thickness = arg.thickness or POLY_THICKNESS,
|
||
|
cap = arg.cap or POLY_CAP,
|
||
|
join = arg.join or POLY_JOIN,
|
||
|
points = {}
|
||
|
}
|
||
|
|
||
|
local points = {}
|
||
|
|
||
|
for i = 1, #arg do points[i] = arg[i] end
|
||
|
|
||
|
obj.path = Poly.create_path(arg.closed, _UNPACK(points))
|
||
|
|
||
|
obj.source = Super.Pattern{
|
||
|
pattern = line_pattern or POLY_LINE_PATTERN,
|
||
|
p1 = points[1],
|
||
|
p2 = points[#points]
|
||
|
}
|
||
|
|
||
|
return obj
|
||
|
end
|
||
|
|
||
|
--Line(p1, p2, [thickness], [line_pattern], [cap])
|
||
|
|
||
|
local LINE_THICKNESS = 1
|
||
|
local LINE_PATTERN = schema.dark_grey
|
||
|
local LINE_CAP = CAIRO_LINE_CAP_BUTT
|
||
|
|
||
|
local initLine = function(arg)
|
||
|
|
||
|
local p1 = arg.p1
|
||
|
local p2 = arg.p2
|
||
|
local thickness = arg.thickness or LINE_THICKNESS
|
||
|
local line_pattern = arg.line_pattern or LINE_PATTERN
|
||
|
local cap = arg.cap or LINE_CAP
|
||
|
|
||
|
local obj = initPoly{
|
||
|
thickness = thickness,
|
||
|
line_pattern = line_pattern,
|
||
|
cap = cap,
|
||
|
p1,
|
||
|
p2
|
||
|
}
|
||
|
|
||
|
return obj
|
||
|
end
|
||
|
|
||
|
--[[
|
||
|
Bar(p1, p2, [thickness], [line_pattern], [indicator_pattern], [critical_pattern],
|
||
|
[critical_limit], [cap])
|
||
|
]]
|
||
|
|
||
|
local BAR_THICKNESS = 10
|
||
|
local BAR_CRITICAL_LIMIT = '>80'
|
||
|
local BAR_CAP = CAIRO_LINE_CAP_BUTT
|
||
|
local BAR_LINE_PATTERN = schema.grey_rounded
|
||
|
local BAR_INDICATOR_PATTERN = schema.blue_rounded
|
||
|
local BAR_CRITICAL_PATTERN = schema.red_rounded
|
||
|
|
||
|
local initBar = function(arg)
|
||
|
|
||
|
local p1 = arg.p1
|
||
|
local p2 = arg.p2
|
||
|
local thickness = arg.thickness or BAR_THICKNESS
|
||
|
|
||
|
local obj = initLine{
|
||
|
p1 = p1,
|
||
|
p2 = p2,
|
||
|
thickness = thickness,
|
||
|
line_pattern = line_pattern,
|
||
|
cap = arg.cap or BAR_CAP
|
||
|
}
|
||
|
|
||
|
local p1_x = p1.x
|
||
|
local p1_y = p1.y
|
||
|
local p2_x = p2.x
|
||
|
local p2_y = p2.y
|
||
|
|
||
|
local theta = _MATH_ATAN2(p2_y - p1_y, p2_x - p1_x)
|
||
|
local delta_x = 0.5 * thickness * _MATH_SIN(theta) --and yes, these are actually flipped
|
||
|
local delta_y = 0.5 * thickness * _MATH_COS(theta)
|
||
|
local p1_pattern = {x = p1_x + delta_x, y = p1_y + delta_y}
|
||
|
local p2_pattern = {x = p1_x - delta_x, y = p1_y - delta_y}
|
||
|
|
||
|
--override pattern from superclasses
|
||
|
obj.source = Super.Pattern{
|
||
|
pattern = arg.line_pattern or BAR_LINE_PATTERN,
|
||
|
p1 = p1_pattern,
|
||
|
p2 = p2_pattern
|
||
|
}
|
||
|
|
||
|
obj.indicator_source = Super.Pattern{
|
||
|
pattern = arg.indicator_pattern or BAR_INDICATOR_PATTERN,
|
||
|
p1 = p1_pattern,
|
||
|
p2 = p2_pattern
|
||
|
}
|
||
|
|
||
|
obj.critical = Super.Critical{
|
||
|
critical_pattern = arg.critical_pattern or BAR_CRITICAL_PATTERN,
|
||
|
critical_limit = arg.critical_limit or BAR_CRITICAL_LIMIT,
|
||
|
p1 = p1_pattern,
|
||
|
p2 = p2_pattern
|
||
|
}
|
||
|
|
||
|
obj.midpoint = {}
|
||
|
|
||
|
obj._make_bar_path = util.memoize(
|
||
|
function(percent)
|
||
|
local mp = obj.midpoint
|
||
|
mp.x = (p2_x - p1_x) * percent + p1_x
|
||
|
mp.y = (p2_y - p1_y) * percent + p1_y
|
||
|
return Poly.create_path(nil, p1, mp)
|
||
|
end
|
||
|
)
|
||
|
|
||
|
Bar.set(obj, 0)
|
||
|
|
||
|
return obj
|
||
|
end
|
||
|
|
||
|
--[[
|
||
|
CompoundBar([x], [y], width, [length], [spacing], [num_bars], [line_pattern],
|
||
|
[indicator_pattern], [critical_pattern], [critical_limit], [cap], [is_vertical])
|
||
|
]]
|
||
|
local initCompoundBar = function(arg)
|
||
|
|
||
|
local x = arg.x
|
||
|
local y = arg.y
|
||
|
local thickness = arg.thickness
|
||
|
local length = arg.length
|
||
|
local spacing = arg.spacing
|
||
|
local num_bars = arg.num_bars
|
||
|
local is_vertical = arg.is_vertical
|
||
|
|
||
|
local width = is_vertical and spacing * (num_bars -1) or length
|
||
|
local height = is_vertical and length or spacing * (num_bars -1)
|
||
|
|
||
|
local obj = Box{
|
||
|
x = x,
|
||
|
y = y,
|
||
|
width = width,
|
||
|
height = height
|
||
|
}
|
||
|
|
||
|
obj.bars = {
|
||
|
n = num_bars
|
||
|
}
|
||
|
|
||
|
for i = 1, num_bars do
|
||
|
local p1, p2
|
||
|
local var_dim = spacing * (i - 1)
|
||
|
|
||
|
if is_vertical then
|
||
|
var_dim = x + var_dim
|
||
|
p1 = {x = var_dim, y = y}
|
||
|
p2 = {x = var_dim, y = obj.bottom_y}
|
||
|
else
|
||
|
var_dim = y + var_dim
|
||
|
p1 = {x = x, y = var_dim}
|
||
|
p2 = {x = obj.right_x, y = var_dim}
|
||
|
end
|
||
|
|
||
|
obj.bars[i] = initBar{
|
||
|
x = x,
|
||
|
y = y,
|
||
|
p1 = p1,
|
||
|
p2 = p2,
|
||
|
thickness = arg.thickness,
|
||
|
line_pattern = arg.line_pattern,
|
||
|
indicator_pattern = arg.indicator_pattern,
|
||
|
critical_pattern = arg.critical_pattern,
|
||
|
critical_limit = arg.critical_limit,
|
||
|
cap = arg.cap
|
||
|
}
|
||
|
end
|
||
|
|
||
|
return obj
|
||
|
end
|
||
|
|
||
|
--Rect(x, y, [width], [height], [thickness], [join], [line_pattern])
|
||
|
|
||
|
local RECT_LINE_PATTERN = schema.mid_grey
|
||
|
local RECT_LINE_THICKNESS = 1
|
||
|
local RECT_LINE_JOIN = CAIRO_LINE_JOIN_MITER
|
||
|
|
||
|
local RECT_CREATE_PATH = function(x, y, w, h)
|
||
|
_CAIRO_NEW_PATH(_CR)
|
||
|
_CAIRO_RECTANGLE(_CR, x, y, w, h)
|
||
|
return _CAIRO_COPY_PATH(_CR)
|
||
|
end
|
||
|
|
||
|
local initRect = function(arg)
|
||
|
|
||
|
local x = arg.x
|
||
|
local y = arg.y
|
||
|
local width = arg.width
|
||
|
local height = arg.height
|
||
|
|
||
|
local obj = Box{
|
||
|
x = x,
|
||
|
y = y,
|
||
|
width = width,
|
||
|
height = height
|
||
|
}
|
||
|
|
||
|
obj.path = RECT_CREATE_PATH(x, y, width, height)
|
||
|
|
||
|
obj.thickness = arg.thickness or RECT_LINE_THICKNESS
|
||
|
obj.join = arg.join or RECT_LINE_JOIN
|
||
|
|
||
|
obj.source = Super.Pattern{
|
||
|
pattern = arg.line_pattern or RECT_LINE_PATTERN,
|
||
|
{x = x, y = y},
|
||
|
{x = x, y = y + height}
|
||
|
}
|
||
|
|
||
|
return obj
|
||
|
end
|
||
|
|
||
|
--FillRect(x, y, [width], [height], [thickness], [join], [line_pattern], [fill_pattern])
|
||
|
local initFillRect = function(arg)
|
||
|
|
||
|
local x = arg.x
|
||
|
local y = arg.y
|
||
|
local width = arg.width
|
||
|
local height = arg.height
|
||
|
|
||
|
local obj = initRect{
|
||
|
x = x,
|
||
|
y = y,
|
||
|
width = width,
|
||
|
height = height,
|
||
|
join = arg.join,
|
||
|
thickness = arg.thickness,
|
||
|
line_pattern = arg.line_pattern
|
||
|
}
|
||
|
obj.fill_source = Super.Pattern{
|
||
|
pattern = arg.fill_pattern,
|
||
|
p1 = {x = x, y = y},
|
||
|
p2 = {x = x, y = y + height}
|
||
|
}
|
||
|
|
||
|
return obj
|
||
|
end
|
||
|
|
||
|
--Image([path], x, y)
|
||
|
|
||
|
local initImage = function(arg)
|
||
|
|
||
|
local path = arg.path
|
||
|
local x = arg.x
|
||
|
local y = arg.y
|
||
|
|
||
|
local obj = {x = x, y = y}
|
||
|
|
||
|
local width, height
|
||
|
|
||
|
if path then Image.update(obj, path) end
|
||
|
|
||
|
obj.width = width or 0
|
||
|
obj.height = height or 0
|
||
|
obj.path = path
|
||
|
|
||
|
return obj
|
||
|
end
|
||
|
|
||
|
--ScaledImage([path], x, y, [width], [height])
|
||
|
|
||
|
local initScaledImage = function(arg)
|
||
|
|
||
|
local path = arg.path
|
||
|
local x = arg.x
|
||
|
local y = arg.y
|
||
|
|
||
|
local obj = Box{x = x, y = y}
|
||
|
|
||
|
local img_width
|
||
|
local img_height
|
||
|
|
||
|
if path then ScaledImage.update(obj, path) end
|
||
|
|
||
|
obj.width = arg.width or 0
|
||
|
obj.height = arg.height or 0
|
||
|
|
||
|
return obj
|
||
|
end
|
||
|
|
||
|
--[[
|
||
|
Text(x, y, [text], [font_size], [x_align], [y_align], [text_color], [font], [slant],
|
||
|
[weight], [append_front], [append_end])
|
||
|
]]
|
||
|
|
||
|
local TEXT_STRING = '<null>'
|
||
|
local TEXT_FONT_SIZE = 13
|
||
|
local TEXT_X_ALIGN = 'left'
|
||
|
local TEXT_Y_ALIGN = 'center'
|
||
|
local TEXT_FONT = 'Neuropolitical'
|
||
|
local TEXT_FONT_SLANT = CAIRO_FONT_SLANT_NORMAL
|
||
|
local TEXT_FONT_WEIGHT = CAIRO_FONT_WEIGHT_NORMAL
|
||
|
local TEXT_COLOR = schema.light_grey
|
||
|
|
||
|
local fe = cairo_font_extents_t:create()
|
||
|
tolua.takeownership(fe)
|
||
|
|
||
|
local initText = function(arg)
|
||
|
|
||
|
local font_size = arg.font_size or TEXT_FONT_SIZE
|
||
|
|
||
|
local font_face = _CAIRO_TOY_FONT_FACE_CREATE(
|
||
|
arg.font or TEXT_FONT,
|
||
|
arg.slant or TEXT_FONT_SLANT,
|
||
|
arg.weight or TEXT_FONT_WEIGHT
|
||
|
)
|
||
|
local source = Super.Pattern{
|
||
|
pattern = arg.text_color or TEXT_COLOR
|
||
|
}
|
||
|
|
||
|
_CAIRO_SET_FONT_SIZE(_CR, font_size)
|
||
|
_CAIRO_SET_FONT_FACE(_CR, font_face)
|
||
|
_CAIRO_FONT_EXTENTS(_CR, fe)
|
||
|
|
||
|
local delta_y
|
||
|
local y_align = arg.y_align or TEXT_Y_ALIGN
|
||
|
|
||
|
if y_align == 'bottom' then delta_y = -fe.descent
|
||
|
elseif y_align == 'top' then delta_y = fe.height
|
||
|
elseif y_align == 'center' then delta_y = 0.92 * fe.height * 0.5 - fe.descent
|
||
|
end
|
||
|
|
||
|
local obj = {
|
||
|
x = arg.x,
|
||
|
y = arg.y + delta_y,
|
||
|
x_ref = arg.x,
|
||
|
y_ref = arg.y + delta_y,
|
||
|
delta_y = delta_y,
|
||
|
height = fe.ascent,
|
||
|
font_size = font_size,
|
||
|
x_align = arg.x_align or TEXT_X_ALIGN,
|
||
|
y_align = y_align,
|
||
|
font_face = font_face,
|
||
|
source = source,
|
||
|
current_source = source, --hack to integrate critical
|
||
|
append_front = arg.append_front,
|
||
|
append_end = arg.append_end
|
||
|
}
|
||
|
|
||
|
Text.set(obj, _CR, (arg.text or TEXT_STRING))
|
||
|
|
||
|
return obj
|
||
|
end
|
||
|
|
||
|
--[[
|
||
|
CriticalText(x, y, [text], [font_size], [x_align], [y_align], [text_color], [font],
|
||
|
[slant], [weight], [append_front], [append_end], [critical_color], [critical_limit])
|
||
|
]]
|
||
|
|
||
|
local CRITICALTEXT_COLOR = schema.blue
|
||
|
local CRITICALTEXT_CRITICAL_COLOR = schema.red
|
||
|
|
||
|
local initCriticalText = function(arg)
|
||
|
|
||
|
local obj = initText{
|
||
|
x = arg.x,
|
||
|
y = arg.y,
|
||
|
text = arg.text,
|
||
|
font_size = arg.font_size,
|
||
|
x_align = arg.x_align,
|
||
|
y_align = arg.y_align,
|
||
|
text_color = arg.text_color or CRITICALTEXT_COLOR,
|
||
|
font = arg.font,
|
||
|
slant = arg.slant,
|
||
|
weight = arg.weight,
|
||
|
append_front = arg.append_front,
|
||
|
append_end = arg.append_end
|
||
|
}
|
||
|
|
||
|
obj.critical = Super.Critical{
|
||
|
critical_color = arg.critical_color or CRITICALTEXT_CRITICAL_COLOR,
|
||
|
critical_limit = arg.critical_limit
|
||
|
}
|
||
|
|
||
|
CriticalText.set(obj, _CR, '0')
|
||
|
|
||
|
return obj
|
||
|
end
|
||
|
|
||
|
--[[
|
||
|
TextColumn(x, y, [spacing], [max_length], [font_size], [x_align], [y_align],
|
||
|
[text_color], [font], [slant], [weight], [append_front], [append_end],
|
||
|
[num_rows], ... text list)
|
||
|
]]
|
||
|
|
||
|
local TEXTCOLUMN_SPACING = 20
|
||
|
--~ local TEXTCOLUMN_MAX_LENGTH = -1
|
||
|
local TEXTCOLUMN_NUM_ROWS = 1
|
||
|
|
||
|
local initTextColumn = function(arg)
|
||
|
|
||
|
local obj = {
|
||
|
rows = {
|
||
|
n = (#arg == 0) and (arg.num_rows or TEXTCOLUMN_NUM_ROWS) or #arg
|
||
|
},
|
||
|
spacing = arg.spacing or TEXTCOLUMN_SPACING,
|
||
|
max_length = arg.max_length-- or TEXTCOLUMN_MAX_LENGTH
|
||
|
}
|
||
|
|
||
|
for i = 1, obj.rows.n do
|
||
|
obj.rows[i] = initText{
|
||
|
x = arg.x,
|
||
|
y = arg.y + obj.spacing * (i - 1),
|
||
|
font_size = arg.font_size,
|
||
|
x_align = arg.x_align,
|
||
|
y_align = arg.y_align,
|
||
|
text_color = arg.text_color,
|
||
|
font = arg.font,
|
||
|
slant = arg.slant,
|
||
|
weight = arg.weight,
|
||
|
append_front = arg.append_front,
|
||
|
append_end = arg.append_end
|
||
|
}
|
||
|
TextColumn.set(obj, _CR, i, (#arg == 0) and 'row'..i or arg[i])
|
||
|
end
|
||
|
|
||
|
return obj
|
||
|
end
|
||
|
|
||
|
--[[
|
||
|
Table(x, y, width, height, [num_rows], [max_length], [line_pattern],
|
||
|
[body_color], [header_color], [separator_pattern], ... header list)
|
||
|
]]
|
||
|
|
||
|
local TABLE_FONT = "Neuropolitical"
|
||
|
|
||
|
local TABLE_HEADER_FONT_SIZE = 11
|
||
|
local TABLE_HEADER_FONT_SLANT = CAIRO_FONT_SLANT_NORMAL
|
||
|
local TABLE_HEADER_FONT_WEIGHT = CAIRO_FONT_WEIGHT_NORMAL
|
||
|
|
||
|
local TABLE_BODY_FONT_SIZE = 11
|
||
|
local TABLE_BODY_FONT_SLANT = CAIRO_FONT_SLANT_NORMAL
|
||
|
local TABLE_BODY_FONT_WEIGHT = CAIRO_FONT_WEIGHT_NORMAL
|
||
|
|
||
|
local TABLE_X_ALIGN = 'center'
|
||
|
local TABLE_Y_ALIGN = 'center'
|
||
|
|
||
|
local TABLE_JOIN = CAIRO_LINE_JOIN_MITER
|
||
|
|
||
|
local TABLE_TOP_PAD = 15
|
||
|
local TABLE_BOTTOM_PAD = 15
|
||
|
local TABLE_LEFT_PAD = 5
|
||
|
local TABLE_RIGHT_PAD = 5
|
||
|
local TABLE_HEADER_PAD_FACTOR = 1.25
|
||
|
|
||
|
local TABLE_SEPARATOR_THICKNESS = 1
|
||
|
local TABLE_SEPARATOR_CAP = CAIRO_LINE_CAP_BUTT
|
||
|
|
||
|
local TABLE_NUM_ROWS = 5
|
||
|
local TABLE_MAX_LENGTH = 8
|
||
|
local TABLE_HEADER_COLOR = schema.blue
|
||
|
local TABLE_BODY_COLOR = schema.light_grey
|
||
|
local TABLE_LINE_PATTERN = schema.dark_grey
|
||
|
local TABLE_SEPARATOR_PATTERN = schema.dark_grey
|
||
|
|
||
|
local initTable = function(arg)
|
||
|
|
||
|
local x = arg.x
|
||
|
local y = arg.y
|
||
|
local width = arg.width
|
||
|
local height = arg.height
|
||
|
local num_rows = arg.num_rows or TABLE_NUM_ROWS
|
||
|
|
||
|
local obj = initRect{
|
||
|
x = x + 0.5,
|
||
|
y = y + 0.5,
|
||
|
width = width,
|
||
|
height = height,
|
||
|
join = TABLE_JOIN,
|
||
|
line_pattern = arg.line_pattern or TABLE_LINE_PATTERN
|
||
|
}
|
||
|
|
||
|
obj.table = Box{
|
||
|
x = x + TABLE_LEFT_PAD,
|
||
|
y = y + TABLE_TOP_PAD,
|
||
|
width = width - TABLE_LEFT_PAD - TABLE_RIGHT_PAD,
|
||
|
height = height - TABLE_TOP_PAD - TABLE_BOTTOM_PAD
|
||
|
}
|
||
|
|
||
|
local tbl = obj.table
|
||
|
tbl.num_rows = num_rows
|
||
|
tbl.num_columns = #arg
|
||
|
|
||
|
tbl.columns = {}
|
||
|
|
||
|
local column_width = tbl.width / tbl.num_columns
|
||
|
local spacing = tbl.height / (TABLE_HEADER_PAD_FACTOR + tbl.num_rows - 1)
|
||
|
|
||
|
for i = 1, tbl.num_columns do
|
||
|
local column_x = tbl.x + column_width * (i - 0.5)
|
||
|
|
||
|
tbl.columns[i] = initTextColumn{
|
||
|
x = column_x,
|
||
|
y = tbl.y + spacing * TABLE_HEADER_PAD_FACTOR,
|
||
|
spacing = spacing,
|
||
|
max_length = arg.max_length or TABLE_MAX_LENGTH,
|
||
|
font_size = TABLE_BODY_FONT_SIZE,
|
||
|
x_align = TABLE_X_ALIGN,
|
||
|
y_align = TABLE_Y_ALIGN,
|
||
|
text_color = arg.body_color or TABLE_BODY_COLOR,
|
||
|
font = TABLE_FONT,
|
||
|
slant = TABLE_BODY_FONT_SLANT,
|
||
|
slant = TABLE_BODY_FONT_WEIGHT,
|
||
|
num_rows = num_rows
|
||
|
}
|
||
|
tbl.columns[i].header = initText{
|
||
|
x = column_x,
|
||
|
y = tbl.y,
|
||
|
text = arg[i],
|
||
|
font_size = TABLE_HEADER_FONT_SIZE,
|
||
|
x_align = TABLE_X_ALIGN,
|
||
|
y_align = TABLE_Y_ALIGN,
|
||
|
text_color = arg.header_color or TABLE_HEADER_COLOR,
|
||
|
font = TABLE_FONT,
|
||
|
slant = TABLE_HEADER_FONT_SLANT,
|
||
|
weight = TABLE_HEADER_FONT_WEIGHT
|
||
|
}
|
||
|
end
|
||
|
|
||
|
tbl.separators = {
|
||
|
n = tbl.num_columns - 1
|
||
|
}
|
||
|
|
||
|
for i = 1, tbl.separators.n do
|
||
|
local sep_x = tbl.x + column_width * i
|
||
|
tbl.separators[i] = initLine{
|
||
|
thickness = TABLE_SEPARATOR_THICKNESS,
|
||
|
line_pattern = arg.separator_pattern or TABLE_SEPARATOR_PATTERN,
|
||
|
cap = TABLE_SEPARATOR_CAP,
|
||
|
p1 = {x = sep_x, y = tbl.y},
|
||
|
p2 = {x = sep_x, y = tbl.bottom_y}
|
||
|
}
|
||
|
end
|
||
|
|
||
|
return obj
|
||
|
end
|
||
|
|
||
|
|
||
|
--[[
|
||
|
Plot([x], [y], width, height, [seconds], [num_x_intrvl], [num_y_intrvl],
|
||
|
[outline_pattern], [intrvl_pattern], [data_line_pattern], [data_fill_pattern])
|
||
|
]]
|
||
|
|
||
|
local PLOT_SECONDS = 60
|
||
|
local PLOT_NUM_X_INTERVAL = 6
|
||
|
local PLOT_NUM_Y_INTERVAL = 4
|
||
|
local PLOT_OUTLINE_PATTERN = schema.dark_grey
|
||
|
local PLOT_INTRVL_PATTERN = schema.dark_grey
|
||
|
local PLOT_DATA_LINE_PATTERN = schema.transparent_blue
|
||
|
local PLOT_DATA_FILL_PATTERN = schema.transparent_blue
|
||
|
|
||
|
local initPlot = function(arg)
|
||
|
|
||
|
local x = arg.x
|
||
|
local y = arg.y
|
||
|
local width = arg.width
|
||
|
local height = arg.height
|
||
|
|
||
|
local obj = Box{
|
||
|
x = x,
|
||
|
y = y,
|
||
|
width = width,
|
||
|
height = height
|
||
|
}
|
||
|
|
||
|
local p1 = {x = x, y = y}
|
||
|
local p2 = {x = x + width, y = y}
|
||
|
|
||
|
--allocate outline objects
|
||
|
obj.outline = {
|
||
|
source = Super.Pattern{
|
||
|
pattern = arg.outline_pattern or PLOT_OUTLINE_PATTERN,
|
||
|
p1 = p1,
|
||
|
p2 = p2
|
||
|
},
|
||
|
}
|
||
|
|
||
|
obj.intrvls = {
|
||
|
source = Super.Pattern{
|
||
|
pattern = arg.intrvl_pattern or PLOT_INTRVL_PATTERN,
|
||
|
p1 = p1,
|
||
|
p2 = p2
|
||
|
},
|
||
|
x = {
|
||
|
n = arg.num_x_intrvl or PLOT_NUM_X_INTERVAL
|
||
|
},
|
||
|
y = {
|
||
|
n = arg.num_y_intrvl or PLOT_NUM_Y_INTERVAL
|
||
|
},
|
||
|
|
||
|
}
|
||
|
|
||
|
local data_fill_pattern = arg.data_fill_pattern or PLOT_DATA_FILL_PATTERN
|
||
|
local seconds = arg.seconds or PLOT_SECONDS
|
||
|
|
||
|
obj.data = {
|
||
|
line_source = Super.Pattern{
|
||
|
pattern = arg.data_line_pattern or PLOT_DATA_LINE_PATTERN,
|
||
|
p1 = p1,
|
||
|
p2 = p2
|
||
|
},
|
||
|
seconds = seconds,
|
||
|
n = seconds * CONSTRUCTION_GLOBAL.UPDATE_INTERVAL,
|
||
|
fill_source = data_fill_pattern and Super.Pattern{
|
||
|
pattern = data_fill_pattern,
|
||
|
p1 = p1,
|
||
|
p2 = p2
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Plot.position_x_intrvls(obj)
|
||
|
Plot.position_y_intrvls(obj)
|
||
|
Plot.position_graph_outline(obj)
|
||
|
|
||
|
return obj
|
||
|
end
|
||
|
|
||
|
--[[
|
||
|
LabelPlot(x, y, width, height, [seconds], [x_label_func], [y_label_func],
|
||
|
[num_x_intrvl], [num_y_intrvl], [outline_pattern], [intrvl_pattern],
|
||
|
[data_line_pattern], [data_fill_pattern], [label_color])
|
||
|
]]
|
||
|
|
||
|
local LABELPLOT_LABEL_SIZE = 8
|
||
|
local LABELPLOT_LABEL_FONT = "Neuropolitical"
|
||
|
local LABELPLOT_LABEL_SLANT = CAIRO_FONT_SLANT_NORMAL
|
||
|
local LABELPLOT_LABEL_WEIGHT = CAIRO_FONT_WEIGHT_NORMAL
|
||
|
local LABELPLOT_LABEL_COLOR = schema.mid_grey
|
||
|
local LABELPLOT_SECONDS = 60
|
||
|
local LABELPLOT_NUM_X_INTERVAL = 6
|
||
|
local LABELPLOT_NUM_Y_INTERVAL = 4
|
||
|
|
||
|
local initLabelPlot = function(arg)
|
||
|
|
||
|
local x = arg.x
|
||
|
local y = arg.y
|
||
|
local width = arg.width
|
||
|
local height = arg.height
|
||
|
local seconds = arg.seconds or LABELPLOT_SECONDS
|
||
|
local x_label_func = arg.x_label_func
|
||
|
local y_label_func = arg.y_label_func
|
||
|
local num_x_intrvl = arg.num_x_intrvl or LABELPLOT_NUM_X_INTERVAL
|
||
|
local num_y_intrvl = arg.num_y_intrvl or LABELPLOT_NUM_Y_INTERVAL
|
||
|
local label_color = arg.label_color or LABELPLOT_LABEL_COLOR
|
||
|
local x_input_factor = arg.x_input_factor
|
||
|
local y_input_factor = arg.y_input_factor
|
||
|
|
||
|
local obj = Box{
|
||
|
x = x,
|
||
|
y = y,
|
||
|
width = width,
|
||
|
height = height
|
||
|
}
|
||
|
|
||
|
obj.plot = initPlot{
|
||
|
x = x,
|
||
|
y = y,
|
||
|
width = width,
|
||
|
height = height,
|
||
|
seconds = seconds,
|
||
|
num_x_intrvl = num_x_intrvl,
|
||
|
num_y_intrvl = num_y_intrvl,
|
||
|
outline_pattern = arg.outline_pattern,
|
||
|
intrvl_pattern = arg.intrvl_pattern,
|
||
|
data_line_pattern = arg.data_line_pattern,
|
||
|
data_fill_pattern = arg.data_fill_pattern
|
||
|
}
|
||
|
|
||
|
obj.labels = {
|
||
|
x = {
|
||
|
n = num_x_intrvl + 1
|
||
|
},
|
||
|
y = {
|
||
|
n = num_y_intrvl + 1
|
||
|
}
|
||
|
}
|
||
|
|
||
|
--x labels
|
||
|
local labels_x = obj.labels.x
|
||
|
|
||
|
labels_x._func = x_label_func or function(fraction)
|
||
|
return util.round((1-fraction) * seconds)..'s'
|
||
|
end
|
||
|
|
||
|
for i = 1, labels_x.n do
|
||
|
labels_x[i] = initText{
|
||
|
x = 0,
|
||
|
y = obj.bottom_y,
|
||
|
font_size = LABELPLOT_LABEL_SIZE,
|
||
|
x_align = 'center',
|
||
|
y_align = 'bottom',
|
||
|
text_color = label_color
|
||
|
}
|
||
|
end
|
||
|
|
||
|
LabelPlot.populate_x_labels(obj, _CR, x_input_factor)
|
||
|
|
||
|
--y labels
|
||
|
local labels_y = obj.labels.y
|
||
|
|
||
|
labels_y._func = y_label_func or function(fraction)
|
||
|
return (fraction * 100)..'%'
|
||
|
end
|
||
|
|
||
|
for i = 1, labels_y.n do
|
||
|
labels_y[i] = initText{
|
||
|
x = x,
|
||
|
y = 0,
|
||
|
font_size = LABELPLOT_LABEL_SIZE,
|
||
|
x_align = 'left',
|
||
|
y_align = 'center',
|
||
|
text_color = label_color,
|
||
|
}
|
||
|
end
|
||
|
|
||
|
LabelPlot.populate_y_labels(obj, _CR, y_input_factor)
|
||
|
|
||
|
LabelPlot.position_x_intrvls(obj.plot)
|
||
|
LabelPlot.position_y_intrvls(obj.plot)
|
||
|
|
||
|
LabelPlot.position_graph_outline(obj.plot)
|
||
|
|
||
|
LabelPlot.position_x_labels(obj)
|
||
|
LabelPlot.position_y_labels(obj)
|
||
|
|
||
|
return obj
|
||
|
end
|
||
|
|
||
|
--[[
|
||
|
ScalePlot(x, y, width, height, [seconds], [x_label_func], [y_label_func],
|
||
|
[scale_function], [num_x_intrvl], [num_y_intrvl], [outline_pattern], [intrvl_pattern],
|
||
|
[data_line_pattern], [data_fill_pattern], [label_color])
|
||
|
]]
|
||
|
|
||
|
local SCALEPLOT_THRESHOLD = 0.9 --trip point to go to next scale domain
|
||
|
local SCALEPLOT_BASE = 2 --base for log scale
|
||
|
local SCALEPLOT_INITIAL = 1 --initial scale domain value
|
||
|
|
||
|
--~ local SCALEPLOT_SCALE_FUNCTION = function(x)
|
||
|
--~ local domain = (x > 0) and _MATH_CEIL(_MATH_LOG(x / SCALEPLOT_THRESHOLD) /
|
||
|
--~ _MATH_LOG(SCALEPLOT_BASE) - SCALEPLOT_INITIAL + 1) or 1
|
||
|
--~ domain = (domain < 1) and 1 or domain
|
||
|
--~ local factor = SCALEPLOT_BASE ^ -(SCALEPLOT_INITIAL + domain - 1)
|
||
|
--~ return domain, factor
|
||
|
--~ end
|
||
|
|
||
|
local SCALEPLOT_Y_LABEL_FUNCTION = function(kilobytes)
|
||
|
local new_unit = util.get_unit_base_K(kilobytes)
|
||
|
local converted_bytes = util.convert_bytes(kilobytes, 'KiB', new_unit)
|
||
|
local precision = 0
|
||
|
if converted_bytes < 10 then precision = 1 end
|
||
|
|
||
|
return util.round_to_string(converted_bytes, precision)..' '..new_unit..'/s'
|
||
|
end
|
||
|
|
||
|
local initScalePlot = function(arg)
|
||
|
|
||
|
local base = arg.scaleplot_base or SCALEPLOT_BASE
|
||
|
local initial = arg.scaleplot_initial or SCALEPLOT_INITIAL
|
||
|
|
||
|
local obj = initLabelPlot{
|
||
|
x = arg.x,
|
||
|
y = arg.y,
|
||
|
width = arg.width,
|
||
|
height = arg.height,
|
||
|
seconds = arg.seconds,
|
||
|
x_label_func = arg.x_label_func,
|
||
|
y_label_func = arg.y_label_func or SCALEPLOT_Y_LABEL_FUNCTION,
|
||
|
num_x_intrvl = arg.num_x_intrvl,
|
||
|
num_y_intrvl = arg.num_y_intrvl,
|
||
|
outline_pattern = arg.outline_pattern,
|
||
|
intrvl_pattern = arg.intrvl_pattern,
|
||
|
data_line_pattern = arg.data_line_pattern,
|
||
|
data_fill_pattern = arg.data_fill_pattern,
|
||
|
label_color = arg.label_color,
|
||
|
y_input_factor = base ^ initial
|
||
|
}
|
||
|
|
||
|
obj.scale = {
|
||
|
--~ _func = scale_function or SCALEPLOT_SCALE_FUNCTION,
|
||
|
_func = function(x)
|
||
|
local threshold = arg.scaleplot_threshold or SCALEPLOT_THRESHOLD
|
||
|
|
||
|
local domain = 1
|
||
|
if x > 0 then
|
||
|
domain = _MATH_CEIL(_MATH_LOG(x / threshold) / _MATH_LOG(base) - initial + 1)
|
||
|
end
|
||
|
|
||
|
if domain < 1 then domain = 1 end
|
||
|
return domain, base ^ -(initial + domain - 1)
|
||
|
end,
|
||
|
factor = 0.5,
|
||
|
domain = 1,
|
||
|
timers = {}
|
||
|
}
|
||
|
|
||
|
obj.scale.previous_domain, obj.scale.previous_factor = obj.scale._func(0)
|
||
|
|
||
|
return obj
|
||
|
end
|
||
|
|
||
|
--Header(x, y, width, header)
|
||
|
|
||
|
local HEADER_HEIGHT = 45
|
||
|
local HEADER_FONT_SIZE = 15
|
||
|
local HEADER_FONT_SLANT = CAIRO_FONT_SLANT_NORMAL
|
||
|
local HEADER_FONT_WEIGHT = CAIRO_FONT_WEIGHT_BOLD
|
||
|
local HEADER_X_ALIGN = 'left'
|
||
|
local HEADER_Y_ALIGN = 'top'
|
||
|
local HEADER_COLOR = schema.white
|
||
|
local HEADER_UNDERLINE_OFFSET = -20
|
||
|
local HEADER_UNDERLINE_THICKNESS = 3
|
||
|
local HEADER_UNDERLINE_COLOR = schema.white
|
||
|
local HEADER_UNDERLINE_CAP = CAIRO_LINE_CAP_ROUND
|
||
|
|
||
|
local initHeader = function(arg)
|
||
|
|
||
|
local x = arg.x
|
||
|
local y = arg.y
|
||
|
local width = arg.width
|
||
|
|
||
|
local bottom_y = y + HEADER_HEIGHT
|
||
|
local underline_y = bottom_y + HEADER_UNDERLINE_OFFSET
|
||
|
|
||
|
local obj = {
|
||
|
text = initText{
|
||
|
x = x,
|
||
|
y = y,
|
||
|
text = arg.header,
|
||
|
font_size = HEADER_FONT_SIZE,
|
||
|
x_align = HEADER_X_ALIGN,
|
||
|
y_align = HEADER_Y_ALIGN,
|
||
|
text_color = HEADER_COLOR,
|
||
|
slant = HEADER_FONT_SLANT,
|
||
|
weight = HEADER_FONT_WEIGHT
|
||
|
},
|
||
|
bottom_y = bottom_y,
|
||
|
underline = initLine{
|
||
|
p1 = {x = x, y = underline_y},
|
||
|
p2 = {x = x + width, y = underline_y},
|
||
|
thickness = HEADER_UNDERLINE_THICKNESS,
|
||
|
line_pattern = HEADER_UNDERLINE_COLOR,
|
||
|
cap = HEADER_UNDERLINE_CAP
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return obj
|
||
|
end
|
||
|
|
||
|
--Panel{x, y, width, height}
|
||
|
|
||
|
local PANEL_LINE_PATTERN = schema.dark_grey
|
||
|
local PANEL_FILL_PATTERN = schema.transparent_black
|
||
|
--~ local PANEL_GLOSS_HEIGHT = 20
|
||
|
|
||
|
local initPanel = function(arg)
|
||
|
|
||
|
--~ PANEL_FILL_PATTERN.color_stops[2].stop = PANEL_GLOSS_HEIGHT / arg.height
|
||
|
|
||
|
local obj = initFillRect{
|
||
|
x = arg.x + 0.5,
|
||
|
y = arg.y + 0.5,
|
||
|
width = arg.width,
|
||
|
height = arg.height,
|
||
|
line_pattern = PANEL_LINE_PATTERN,
|
||
|
fill_pattern = PANEL_FILL_PATTERN,
|
||
|
}
|
||
|
|
||
|
return obj
|
||
|
end
|
||
|
|
||
|
c.Arc = initArc
|
||
|
c.Dial = initDial
|
||
|
c.CompoundDial = initCompoundDial
|
||
|
c.Poly = initPoly
|
||
|
c.Line = initLine
|
||
|
c.Bar = initBar
|
||
|
c.CompoundBar = initCompoundBar
|
||
|
c.Rect = initRect
|
||
|
c.FillRect = initFillRect
|
||
|
c.Image = initImage
|
||
|
c.ScaledImage = initScaledImage
|
||
|
c.Text = initText
|
||
|
c.CriticalText = initCriticalText
|
||
|
c.TextColumn = initTextColumn
|
||
|
c.Table = initTable
|
||
|
c.Plot = initPlot
|
||
|
c.LabelPlot = initLabelPlot
|
||
|
c.ScalePlot = initScalePlot
|
||
|
c.Header = initHeader
|
||
|
c.Panel = initPanel
|
||
|
|
||
|
return c
|