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 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
|
||||
|
||||
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