Finished Graph Elements
- Added Graph - Added BarChart - Added LineChart
This commit is contained in:
78
src/elements/BarChart.lua
Normal file
78
src/elements/BarChart.lua
Normal 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
|
||||||
@@ -1,62 +1,188 @@
|
|||||||
local elementManager = require("elementManager")
|
local elementManager = require("elementManager")
|
||||||
local VisualElement = elementManager.getElement("elements/VisualElement")
|
local VisualElement = elementManager.getElement("VisualElement")
|
||||||
---@configDescription
|
local tHex = require("libraries/colorHex")
|
||||||
|
---@configDescription A point based graph element
|
||||||
---@configDefault false
|
---@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)
|
local Graph = setmetatable({}, VisualElement)
|
||||||
Graph.__index = Graph
|
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})
|
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, "maxValue", {default = 100, type = "number", canTriggerRender = true})
|
||||||
Graph.defineProperty(Graph, "graphColor", {default = colors.yellow, type = "color", canTriggerRender = true})
|
---@property series table {} The series of the graph
|
||||||
Graph.defineProperty(Graph, "graphSymbol", {default = "\127", type = "string", canTriggerRender = true}) -- Default: "|"
|
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()
|
function Graph.new()
|
||||||
local self = setmetatable({}, Graph):__init()
|
local self = setmetatable({}, Graph):__init()
|
||||||
return self
|
return self
|
||||||
end
|
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)
|
function Graph:init(props, basalt)
|
||||||
VisualElement.init(self, props, basalt)
|
VisualElement.init(self, props, basalt)
|
||||||
self.set("type", "Graph")
|
self.set("type", "Graph")
|
||||||
|
self.set("width", 20)
|
||||||
|
self.set("height", 10)
|
||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
function Graph:setPoint(index, value)
|
--- @shortDescription Adds a series to the graph
|
||||||
local data = self.get("data")
|
--- @param name string The name of the series
|
||||||
data[index] = value
|
--- @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()
|
self:updateRender()
|
||||||
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
function Graph:addPoint(value)
|
--- @shortDescription Removes a series from the graph
|
||||||
local data = self.get("data")
|
--- @param name string The name of the series
|
||||||
table.insert(data, value)
|
--- @return Graph self The graph instance
|
||||||
while #data > self.get("width") do
|
function Graph:removeSeries(name)
|
||||||
table.remove(data, 1)
|
local series = self.get("series")
|
||||||
|
for i, s in ipairs(series) do
|
||||||
|
if s.name == name then
|
||||||
|
table.remove(series, i)
|
||||||
|
break
|
||||||
|
end
|
||||||
end
|
end
|
||||||
self:updateRender()
|
self:updateRender()
|
||||||
|
return self
|
||||||
end
|
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()
|
function Graph:render()
|
||||||
VisualElement.render(self)
|
VisualElement.render(self)
|
||||||
|
|
||||||
local data = self.get("data")
|
|
||||||
local width = self.get("width")
|
local width = self.get("width")
|
||||||
local height = self.get("height")
|
local height = self.get("height")
|
||||||
local minVal = self.get("minValue")
|
local minVal = self.get("minValue")
|
||||||
local maxVal = self.get("maxValue")
|
local maxVal = self.get("maxValue")
|
||||||
local symbol = self.get("graphSymbol")
|
local series = self.get("series")
|
||||||
local graphColor = self.get("graphColor")
|
|
||||||
|
|
||||||
for x = 1, width do
|
for _, s in pairs(series) do
|
||||||
if data[x] then
|
if(s.visible)then
|
||||||
local normalizedValue = (data[x] - minVal) / (maxVal - minVal)
|
local dataCount = #s.data
|
||||||
local y = math.floor(height - (normalizedValue * (height-1)))
|
local spacing = (width - 1) / math.max((dataCount - 1), 1)
|
||||||
y = math.max(1, math.min(y, height))
|
|
||||||
|
|
||||||
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
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
80
src/elements/LineChart.lua
Normal file
80
src/elements/LineChart.lua
Normal 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
|
||||||
Reference in New Issue
Block a user