Finished Graph Elements

- Added Graph
- Added BarChart
- Added LineChart
This commit is contained in:
Robert Jelic
2025-03-04 01:53:14 +01:00
parent ac68ddd41b
commit 49a5e4bfde
3 changed files with 308 additions and 24 deletions

78
src/elements/BarChart.lua Normal file
View File

@@ -0,0 +1,78 @@
local elementManager = require("elementManager")
local VisualElement = elementManager.getElement("VisualElement")
local BaseGraph = elementManager.getElement("Graph")
local tHex = require("libraries/colorHex")
--- @configDescription A bar chart element based on the graph element
---@configDefault false
--- This is the bar chart class. It is based on the graph element. It draws bar based points.
--- @class BarChart : Graph
local BarChart = setmetatable({}, BaseGraph)
BarChart.__index = BarChart
--- Creates a new BarChart instance
--- @shortDescription Creates a new BarChart instance
--- @return BarChart self The newly created BarChart instance
--- @private
function BarChart.new()
local self = setmetatable({}, BarChart):__init()
return self
end
--- @shortDescription Initializes the BarChart instance
--- @param props table The properties to initialize the element with
--- @param basalt table The basalt instance
--- @return BarChart self The initialized instance
--- @protected
function BarChart:init(props, basalt)
BaseGraph.init(self, props, basalt)
self.set("type", "BarChart")
return self
end
--- @shortDescription Renders the BarChart
--- @protected
function BarChart:render()
VisualElement.render(self)
local width = self.get("width")
local height = self.get("height")
local minVal = self.get("minValue")
local maxVal = self.get("maxValue")
local series = self.get("series")
local activeSeriesCount = 0
local seriesList = {}
for _, s in pairs(series) do
if(s.visible)then
if #s.data > 0 then
activeSeriesCount = activeSeriesCount + 1
table.insert(seriesList, s)
end
end
end
local barGroupWidth = activeSeriesCount
local spacing = 1
local totalGroups = math.min(seriesList[1] and seriesList[1].pointCount or 0, math.floor((width + spacing) / (barGroupWidth + spacing)))
for groupIndex = 1, totalGroups do
local groupX = ((groupIndex-1) * (barGroupWidth + spacing)) + 1
for seriesIndex, s in ipairs(seriesList) do
local value = s.data[groupIndex]
if value then
local x = groupX + (seriesIndex - 1)
local normalizedValue = (value - minVal) / (maxVal - minVal)
local y = math.floor(height - (normalizedValue * (height-1)))
y = math.max(1, math.min(y, height))
for barY = y, height do
self:blit(x, barY, s.symbol, tHex[s.fgColor], tHex[s.bgColor])
end
end
end
end
end
return BarChart

View File

@@ -1,62 +1,188 @@
local elementManager = require("elementManager")
local VisualElement = elementManager.getElement("elements/VisualElement")
---@configDescription
local VisualElement = elementManager.getElement("VisualElement")
local tHex = require("libraries/colorHex")
---@configDescription A point based graph element
---@configDefault false
---@class Graph : VisualElement
--- This is the base class for all graph elements. It is a point based graph.
--- @class Graph : VisualElement
local Graph = setmetatable({}, VisualElement)
Graph.__index = Graph
Graph.defineProperty(Graph, "data", {default = {}, type = "table", canTriggerRender = true})
---@property minValue number 0 The minimum value of the graph
Graph.defineProperty(Graph, "minValue", {default = 0, type = "number", canTriggerRender = true})
---@property maxValue number 100 The maximum value of the graph
Graph.defineProperty(Graph, "maxValue", {default = 100, type = "number", canTriggerRender = true})
Graph.defineProperty(Graph, "graphColor", {default = colors.yellow, type = "color", canTriggerRender = true})
Graph.defineProperty(Graph, "graphSymbol", {default = "\127", type = "string", canTriggerRender = true}) -- Default: "|"
---@property series table {} The series of the graph
Graph.defineProperty(Graph, "series", {default = {}, type = "table", canTriggerRender = true})
--- Creates a new Graph instance
--- @shortDescription Creates a new Graph instance
--- @return Graph self The newly created Graph instance
--- @private
function Graph.new()
local self = setmetatable({}, Graph):__init()
return self
end
--- @shortDescription Initializes the Graph instance
--- @param props table The properties to initialize the element with
--- @param basalt table The basalt instance
--- @return Graph self The initialized instance
--- @protected
function Graph:init(props, basalt)
VisualElement.init(self, props, basalt)
self.set("type", "Graph")
self.set("width", 20)
self.set("height", 10)
return self
end
function Graph:setPoint(index, value)
local data = self.get("data")
data[index] = value
--- @shortDescription Adds a series to the graph
--- @param name string The name of the series
--- @param symbol string The symbol of the series
--- @param bgCol number The background color of the series
--- @param fgCol number The foreground color of the series
--- @param pointCount number The number of points in the series
function Graph:addSeries(name, symbol, bgCol, fgCol, pointCount)
local series = self.get("series")
table.insert(series, {
name = name,
symbol = symbol or " ",
bgColor = bgCol or colors.white,
fgColor = fgCol or colors.black,
pointCount = pointCount or self.get("width"),
data = {},
visible = true
})
self:updateRender()
return self
end
function Graph:addPoint(value)
local data = self.get("data")
table.insert(data, value)
while #data > self.get("width") do
table.remove(data, 1)
--- @shortDescription Removes a series from the graph
--- @param name string The name of the series
--- @return Graph self The graph instance
function Graph:removeSeries(name)
local series = self.get("series")
for i, s in ipairs(series) do
if s.name == name then
table.remove(series, i)
break
end
end
self:updateRender()
return self
end
--- @shortDescription Gets a series from the graph
--- @param name string The name of the series
--- @return table? series The series
function Graph:getSeries(name)
local series = self.get("series")
for _, s in ipairs(series) do
if s.name == name then
return s
end
end
return nil
end
--- @shortDescription Changes the visibility of a series
--- @param name string The name of the series
--- @param visible boolean Whether the series should be visible
--- @return Graph self The graph instance
function Graph:changeSeriesVisibility(name, visible)
local series = self.get("series")
for _, s in ipairs(series) do
if s.name == name then
s.visible = visible
break
end
end
self:updateRender()
return self
end
--- @shortDescription Adds a point to a series
--- @param name string The name of the series
--- @param value number The value of the point
--- @return Graph self The graph instance
function Graph:addPoint(name, value)
local series = self.get("series")
for _, s in ipairs(series) do
if s.name == name then
table.insert(s.data, value)
while #s.data > s.pointCount do
table.remove(s.data, 1)
end
break
end
end
self:updateRender()
return self
end
--- @shortDescription Focuses a series
--- @param name string The name of the series
--- @return Graph self The graph instance
function Graph:focusSeries(name)
local series = self.get("series")
for index, s in ipairs(series) do
if s.name == name then
table.remove(series, index)
table.insert(series, s)
break
end
end
self:updateRender()
return self
end
--- @shortDescription Sets the point count of a series
--- @param name string The name of the series
--- @param count number The number of points in the series
--- @return Graph self The graph instance
function Graph:setSeriesPointCount(name, count)
local series = self.get("series")
for _, s in ipairs(series) do
if s.name == name then
s.pointCount = count
while #s.data > count do
table.remove(s.data, 1)
end
break
end
end
self:updateRender()
return self
end
--- @shortDescription Renders the graph
--- @protected
function Graph:render()
VisualElement.render(self)
local data = self.get("data")
local width = self.get("width")
local height = self.get("height")
local minVal = self.get("minValue")
local maxVal = self.get("maxValue")
local symbol = self.get("graphSymbol")
local graphColor = self.get("graphColor")
local series = self.get("series")
for x = 1, width do
if data[x] then
local normalizedValue = (data[x] - minVal) / (maxVal - minVal)
local y = math.floor(height - (normalizedValue * (height-1)))
y = math.max(1, math.min(y, height))
for _, s in pairs(series) do
if(s.visible)then
local dataCount = #s.data
local spacing = (width - 1) / math.max((dataCount - 1), 1)
self:textFg(x, y, symbol, graphColor)
for i, value in ipairs(s.data) do
local x = math.floor(((i-1) * spacing) + 1)
local normalizedValue = (value - minVal) / (maxVal - minVal)
local y = math.floor(height - (normalizedValue * (height-1)))
y = math.max(1, math.min(y, height))
self:blit(x, y, s.symbol, tHex[s.bgColor], tHex[s.fgColor])
end
end
end
end

View File

@@ -0,0 +1,80 @@
local elementManager = require("elementManager")
local VisualElement = elementManager.getElement("VisualElement")
local Graph = elementManager.getElement("Graph")
local tHex = require("libraries/colorHex")
--- @configDescription A line chart element based on the graph element
---@configDefault false
--- This is the line chart class. It is based on the graph element. It draws lines between points.
--- @class LineChart : Graph
local LineChart = setmetatable({}, Graph)
LineChart.__index = LineChart
--- Creates a new LineChart instance
--- @shortDescription Creates a new LineChart instance
--- @return LineChart self The newly created LineChart instance
--- @private
function LineChart.new()
local self = setmetatable({}, LineChart):__init()
return self
end
--- @shortDescription Initializes the LineChart instance
--- @param props table The properties to initialize the element with
--- @param basalt table The basalt instance
--- @return LineChart self The initialized instance
--- @protected
function LineChart:init(props, basalt)
Graph.init(self, props, basalt)
self.set("type", "LineChart")
return self
end
local function drawLine(self, x1, y1, x2, y2, symbol, bgColor, fgColor)
local dx = x2 - x1
local dy = y2 - y1
local steps = math.max(math.abs(dx), math.abs(dy))
for i = 0, steps do
local t = steps == 0 and 0 or i / steps
local x = math.floor(x1 + dx * t)
local y = math.floor(y1 + dy * t)
if x >= 1 and x <= self.get("width") and y >= 1 and y <= self.get("height") then
self:blit(x, y, symbol, tHex[bgColor], tHex[fgColor])
end
end
end
--- @shortDescription Renders the LineChart
--- @protected
function LineChart:render()
VisualElement.render(self)
local width = self.get("width")
local height = self.get("height")
local minVal = self.get("minValue")
local maxVal = self.get("maxValue")
local series = self.get("series")
for _, s in pairs(series) do
if(s.visible)then
local lastX, lastY
local dataCount = #s.data
local spacing = (width - 1) / math.max((dataCount - 1), 1)
for i, value in ipairs(s.data) do
local x = math.floor(((i-1) * spacing) + 1)
local normalizedValue = (value - minVal) / (maxVal - minVal)
local y = math.floor(height - (normalizedValue * (height-1)))
y = math.max(1, math.min(y, height))
if lastX then
drawLine(self, lastX, lastY, x, y, s.symbol, s.bgColor, s.fgColor)
end
lastX, lastY = x, y
end
end
end
end
return LineChart