You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2268 lines
62 KiB
2268 lines
62 KiB
--[[
|
|
Name: LibGraph-2.0
|
|
Revision: $Rev: 62 $
|
|
Author(s): Cryect (cryect@gmail.com), Xinhuan
|
|
Website: http://www.wowace.com/
|
|
Documentation: http://www.wowace.com/wiki/GraphLib
|
|
SVN: http://svn.wowace.com/root/trunk/GraphLib/
|
|
Description: Allows for easy creation of graphs
|
|
]]
|
|
|
|
--Thanks to Nelson Minar for catching several errors where width was being used instead of height (damn copy and paste >_>)
|
|
|
|
local major = "LibGraph-2.0"
|
|
local minor = 90000 + tonumber(("$Revision: 62 $"):match("(%d+)"))
|
|
|
|
|
|
--Search for just Addon\\ at the front since the interface part often gets trimmed
|
|
--Do this before anything else, so if it errors, any existing loaded copy of LibGraph-2.0
|
|
--doesn't get modified with a newer revision (this one)
|
|
local TextureDirectory
|
|
do
|
|
local path = string.match(debugstack(1, 1, 0), "AddOns[\\/](.+)LibGraph%-2%.0%.lua")
|
|
|
|
if path then
|
|
TextureDirectory = path
|
|
else
|
|
error(major.." cannot determine the folder it is located in because the path is too long and got truncated in the debugstack(1, 1, 0) function call")
|
|
end
|
|
end
|
|
|
|
if not LibStub then error(major .. " requires LibStub") end
|
|
|
|
local lib, oldLibMinor = LibStub:NewLibrary(major, minor)
|
|
if not lib then return end
|
|
|
|
local GraphFunctions = {}
|
|
|
|
local gsub = gsub
|
|
local ipairs = ipairs
|
|
local pairs = pairs
|
|
local sqrt = sqrt
|
|
local table = table
|
|
local tinsert = tinsert
|
|
local tremove = table.remove
|
|
local type = type
|
|
local math_max = math.max
|
|
local math_min = math.min
|
|
local math_ceil = math.ceil
|
|
local math_pi = math.pi
|
|
local math_floor = math.floor
|
|
local math_pow = math.pow
|
|
local math_random = math.random
|
|
local math_cos = math.cos
|
|
local math_sin = math.sin
|
|
local math_deg = math.deg
|
|
local math_atan = math.atan
|
|
local math_abs = math.abs
|
|
local math_fmod = math.fmod
|
|
local math_huge = math.huge
|
|
|
|
local CreateFrame = CreateFrame
|
|
local GetCursorPosition = GetCursorPosition
|
|
local GetTime = GetTime
|
|
local MouseIsOver = MouseIsOver
|
|
local UnitHealth = UnitHealth
|
|
|
|
local UIParent = UIParent
|
|
|
|
local DEFAULT_CHAT_FRAME = DEFAULT_CHAT_FRAME
|
|
|
|
-- lib upgrade stuff
|
|
lib.RegisteredGraphRealtime = lib.RegisteredGraphRealtime or {}
|
|
lib.RegisteredGraphLine = lib.RegisteredGraphLine or {}
|
|
lib.RegisteredGraphScatterPlot = lib.RegisteredGraphScatterPlot or {}
|
|
lib.RegisteredGraphPieChart = lib.RegisteredGraphPieChart or {}
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
--Graph Creation Functions
|
|
--------------------------------------------------------------------------------
|
|
|
|
--Realtime Graph
|
|
local function SetupGraphRealtimeFunctions(graph, upgrade)
|
|
local self = lib
|
|
|
|
--Set the various functions
|
|
graph.SetXAxis = GraphFunctions.SetXAxis
|
|
graph.SetYMax = GraphFunctions.SetYMax
|
|
graph.AddTimeData = GraphFunctions.AddTimeData
|
|
graph.OnUpdate = GraphFunctions.OnUpdateGraphRealtime
|
|
graph.CreateGridlines = GraphFunctions.CreateGridlines
|
|
graph.RefreshGraph = GraphFunctions.RefreshRealtimeGraph
|
|
graph.SetAxisDrawing = GraphFunctions.SetAxisDrawing
|
|
graph.SetGridSpacing = GraphFunctions.SetGridSpacing
|
|
graph.SetAxisColor = GraphFunctions.SetAxisColor
|
|
graph.SetGridColor = GraphFunctions.SetGridColor
|
|
graph.SetGridColorSecondary = GraphFunctions.SetGridColorSecondary
|
|
graph.SetGridSecondaryMultiple = GraphFunctions.SetGridSecondaryMultiple
|
|
graph.SetFilterRadius = GraphFunctions.SetFilterRadius
|
|
--graph.SetAutoscaleYAxis = GraphFunctions.SetAutoscaleYAxis
|
|
graph.SetBarColors = GraphFunctions.SetBarColors
|
|
graph.SetMode = GraphFunctions.SetMode
|
|
graph.SetAutoScale = GraphFunctions.SetAutoScale
|
|
if not upgrade then
|
|
-- This is the original frame:SetWidth() and frame:SetHeight()
|
|
-- standard frame functions
|
|
graph.OldSetWidth = graph.SetWidth
|
|
graph.OldSetHeight = graph.SetHeight
|
|
end
|
|
graph.SetWidth = GraphFunctions.RealtimeSetWidth
|
|
graph.SetHeight = GraphFunctions.RealtimeSetHeight
|
|
graph.SetBarColors = GraphFunctions.RealtimeSetColors
|
|
graph.GetMaxValue = GraphFunctions.GetMaxValue
|
|
graph.GetValue = GraphFunctions.RealtimeGetValue
|
|
graph.SetUpdateLimit = GraphFunctions.SetUpdateLimit
|
|
graph.SetDecay = GraphFunctions.SetDecay
|
|
graph.SetMinMaxY = GraphFunctions.SetMinMaxY
|
|
graph.AddBar = GraphFunctions.AddBar
|
|
graph.SetYLabels = GraphFunctions.SetYLabels
|
|
|
|
|
|
graph.DrawLine = self.DrawLine
|
|
graph.DrawHLine = self.DrawHLine
|
|
graph.DrawVLine = self.DrawVLine
|
|
graph.HideLines = self.HideLines
|
|
graph.HideFontStrings = GraphFunctions.HideFontStrings
|
|
graph.FindFontString = GraphFunctions.FindFontString
|
|
graph.SetBars = GraphFunctions.SetBars
|
|
|
|
|
|
--Set the update function
|
|
graph:SetScript("OnUpdate", graph.OnUpdate)
|
|
end
|
|
|
|
function lib:CreateGraphRealtime(name, parent, relative, relativeTo, offsetX, offsetY, Width, Height)
|
|
local graph
|
|
local i
|
|
graph = CreateFrame("Frame", name, parent, BackdropTemplateMixin and "BackdropTemplate")
|
|
|
|
Width = math_floor(Width)
|
|
|
|
|
|
graph:SetPoint(relative, parent, relativeTo, offsetX, offsetY)
|
|
graph:SetWidth(Width)
|
|
graph:SetHeight(Height)
|
|
graph:Show()
|
|
|
|
--Create the bars
|
|
graph.Bars = {}
|
|
graph.BarsUsing = {}
|
|
graph.BarNum = Width
|
|
graph.Height = Height
|
|
for i = 1, Width do
|
|
local bar
|
|
bar = CreateFrame("StatusBar", name.."Bar"..i, graph, BackdropTemplateMixin and "BackdropTemplate")--graph:CreateTexture(nil, "ARTWORK")
|
|
bar:SetPoint("BOTTOMLEFT", graph, "BOTTOMLEFT", i - 1, 0)
|
|
bar:SetHeight(Height)
|
|
bar:SetWidth(1)
|
|
bar:SetOrientation("VERTICAL")
|
|
bar:SetMinMaxValues(0, 1)
|
|
bar:SetStatusBarTexture("Interface\\Buttons\\WHITE8X8")
|
|
bar:GetStatusBarTexture():SetHorizTile(false)
|
|
bar:GetStatusBarTexture():SetVertTile(false)
|
|
|
|
local t = bar:GetStatusBarTexture()
|
|
t:SetGradientAlpha("VERTICAL", 0.2, 0.0, 0.0, 0.5, 1.0, 0.0, 0.0, 1.0)
|
|
|
|
bar:Show()
|
|
tinsert(graph.Bars, bar)
|
|
tinsert(graph.BarsUsing, bar)
|
|
end
|
|
|
|
|
|
SetupGraphRealtimeFunctions(graph)
|
|
|
|
|
|
--Initialize Data
|
|
graph.GraphType = "REALTIME"
|
|
graph.YMax = 60
|
|
graph.YMin = 0
|
|
graph.XMax = -0.75
|
|
graph.XMin = -10
|
|
graph.TimeRadius = 0.5
|
|
graph.Mode = "FAST"
|
|
graph.Filter = "RECT"
|
|
graph.AxisColor = {1.0, 1.0, 1.0, 1.0}
|
|
graph.GridColor = {0.5, 0.5, 0.5, 0.5}
|
|
graph.BarColorTop = {1.0, 0.0, 0.0, 1.0}
|
|
graph.BarColorBot = {0.2, 0.0, 0.0, 0.5}
|
|
graph.AutoScale = false
|
|
graph.Data = {}
|
|
graph.MinMaxY = 0
|
|
graph.CurVal = 0
|
|
graph.LastDataTime = GetTime()
|
|
|
|
graph.Textures = {}
|
|
graph.TexturesUsed = {}
|
|
|
|
graph.LimitUpdates = 0
|
|
graph.NextUpdate = 0
|
|
|
|
graph.BarHeight = {}
|
|
graph.LastShift = GetTime()
|
|
graph.BarWidth = (graph.XMax - graph.XMin) / graph.BarNum
|
|
graph.DecaySet = 0.8
|
|
graph.Decay = math_pow(graph.DecaySet, graph.BarWidth)
|
|
graph.ExpNorm = 1 / (1 - graph.Decay)
|
|
|
|
graph.FilterOverlap = math_max(math_ceil((graph.TimeRadius + graph.XMax) / graph.BarWidth), 0)
|
|
for i = 1, graph.BarNum do
|
|
graph.BarHeight[i] = 0
|
|
end
|
|
|
|
graph.TextFrame = CreateFrame("Frame", nil, graph)
|
|
graph.TextFrame:SetAllPoints(graph)
|
|
graph.TextFrame:SetFrameLevel(graph:GetFrameLevel() + 2)
|
|
|
|
tinsert(self.RegisteredGraphRealtime, graph)
|
|
return graph
|
|
end
|
|
|
|
--Line Graph
|
|
local function SetupGraphLineFunctions(graph)
|
|
local self = lib
|
|
|
|
--Set the various functions
|
|
graph.SetXAxis = GraphFunctions.SetXAxis
|
|
graph.SetYAxis = GraphFunctions.SetYAxis
|
|
graph.AddDataSeries = GraphFunctions.AddDataSeries
|
|
graph.AddFilledDataSeries = GraphFunctions.AddFilledDataSeries
|
|
graph.ResetData = GraphFunctions.ResetData
|
|
graph.RefreshGraph = GraphFunctions.RefreshLineGraph
|
|
graph.CreateGridlines = GraphFunctions.CreateGridlines
|
|
graph.SetAxisDrawing = GraphFunctions.SetAxisDrawing
|
|
graph.SetGridSpacing = GraphFunctions.SetGridSpacing
|
|
graph.SetAxisColor = GraphFunctions.SetAxisColor
|
|
graph.SetGridColor = GraphFunctions.SetGridColor
|
|
graph.SetGridColorSecondary = GraphFunctions.SetGridColorSecondary
|
|
graph.SetGridSecondaryMultiple = GraphFunctions.SetGridSecondaryMultiple
|
|
graph.SetAutoScale = GraphFunctions.SetAutoScale
|
|
graph.SetYLabels = GraphFunctions.SetYLabels
|
|
graph.OnUpdate = GraphFunctions.OnUpdateGraph
|
|
|
|
graph.SetLineTexture = GraphFunctions.SetLineTexture
|
|
graph.SetBorderSize = GraphFunctions.SetBorderSize
|
|
|
|
graph.LockXMin = GraphFunctions.LockXMin
|
|
graph.LockXMax = GraphFunctions.LockXMax
|
|
graph.LockYMin = GraphFunctions.LockYMin
|
|
graph.LockYMax = GraphFunctions.LockYMax
|
|
|
|
|
|
graph.DrawLine = self.DrawLine
|
|
graph.DrawHLine = self.DrawHLine
|
|
graph.DrawVLine = self.DrawVLine
|
|
graph.HideLines = self.HideLines
|
|
graph.DrawBar = self.DrawBar
|
|
graph.HideBars = self.HideBars
|
|
graph.HideFontStrings = GraphFunctions.HideFontStrings
|
|
graph.FindFontString = GraphFunctions.FindFontString
|
|
|
|
--Set the update function
|
|
graph:SetScript("OnUpdate", graph.OnUpdate)
|
|
end
|
|
|
|
--TODO: Clip lines with the bounds
|
|
function lib:CreateGraphLine(name, parent, relative, relativeTo, offsetX, offsetY, Width, Height)
|
|
local graph
|
|
local i
|
|
graph = CreateFrame("Frame", name, parent, BackdropTemplateMixin and "BackdropTemplate")
|
|
|
|
|
|
graph:SetPoint(relative, parent, relativeTo, offsetX, offsetY)
|
|
graph:SetWidth(Width)
|
|
graph:SetHeight(Height)
|
|
graph:Show()
|
|
|
|
|
|
SetupGraphLineFunctions(graph)
|
|
|
|
|
|
graph.NeedsUpdate = false
|
|
|
|
|
|
--Initialize Data
|
|
graph.GraphType = "LINE"
|
|
graph.YMax = 1
|
|
graph.YMin = -1
|
|
graph.XMax = 1
|
|
graph.XMin = -1
|
|
graph.AxisColor = {1.0, 1.0, 1.0, 1.0}
|
|
graph.GridColor = {0.5, 0.5, 0.5, 0.5}
|
|
graph.XGridInterval = 0.25
|
|
graph.YGridInterval = 0.25
|
|
graph.XAxisDrawn = true
|
|
graph.YAxisDrawn = true
|
|
|
|
graph.LockOnXMin = false
|
|
graph.LockOnXMax = false
|
|
graph.LockOnYMin = false
|
|
graph.LockOnYMax = false
|
|
graph.Data = {}
|
|
graph.FilledData = {}
|
|
graph.Textures = {}
|
|
graph.TexturesUsed = {}
|
|
graph.TextFrame = CreateFrame("Frame", nil, graph)
|
|
graph.TextFrame:SetAllPoints(graph)
|
|
|
|
|
|
tinsert(self.RegisteredGraphLine, graph)
|
|
return graph
|
|
end
|
|
|
|
|
|
--Scatter Plot
|
|
local function SetupGraphScatterPlotFunctions(graph)
|
|
local self = lib
|
|
|
|
--Set the various functions
|
|
graph.SetXAxis = GraphFunctions.SetXAxis
|
|
graph.SetYAxis = GraphFunctions.SetYAxis
|
|
graph.AddDataSeries = GraphFunctions.AddDataSeries
|
|
graph.ResetData = GraphFunctions.ResetData
|
|
graph.RefreshGraph = GraphFunctions.RefreshScatterPlot
|
|
graph.CreateGridlines = GraphFunctions.CreateGridlines
|
|
graph.OnUpdate = GraphFunctions.OnUpdateGraph
|
|
|
|
graph.LinearRegression = GraphFunctions.LinearRegression
|
|
graph.SetAxisDrawing = GraphFunctions.SetAxisDrawing
|
|
graph.SetGridSpacing = GraphFunctions.SetGridSpacing
|
|
graph.SetAxisColor = GraphFunctions.SetAxisColor
|
|
graph.SetGridColor = GraphFunctions.SetGridColor
|
|
graph.SetGridColorSecondary = GraphFunctions.SetGridColorSecondary
|
|
graph.SetGridSecondaryMultiple = GraphFunctions.SetGridSecondaryMultiple
|
|
graph.SetLinearFit = GraphFunctions.SetLinearFit
|
|
graph.SetAutoScale = GraphFunctions.SetAutoScale
|
|
graph.SetYLabels = GraphFunctions.SetYLabels
|
|
|
|
graph.LockXMin = GraphFunctions.LockXMin
|
|
graph.LockXMax = GraphFunctions.LockXMax
|
|
graph.LockYMin = GraphFunctions.LockYMin
|
|
graph.LockYMax = GraphFunctions.LockYMax
|
|
|
|
graph.DrawLine = self.DrawLine
|
|
graph.DrawHLine = self.DrawHLine
|
|
graph.DrawVLine = self.DrawVLine
|
|
graph.HideLines = self.HideLines
|
|
graph.HideTextures = GraphFunctions.HideTextures
|
|
graph.FindTexture = GraphFunctions.FindTexture
|
|
graph.HideFontStrings = GraphFunctions.HideFontStrings
|
|
graph.FindFontString = GraphFunctions.FindFontString
|
|
|
|
--Set the update function
|
|
graph:SetScript("OnUpdate", graph.OnUpdate)
|
|
end
|
|
|
|
function lib:CreateGraphScatterPlot(name, parent, relative, relativeTo, offsetX, offsetY, Width, Height)
|
|
local graph
|
|
local i
|
|
graph = CreateFrame("Frame",name, parent, BackdropTemplateMixin and "BackdropTemplate")
|
|
|
|
|
|
graph:SetPoint(relative, parent, relativeTo, offsetX, offsetY)
|
|
graph:SetWidth(Width)
|
|
graph:SetHeight(Height)
|
|
graph:Show()
|
|
|
|
|
|
SetupGraphScatterPlotFunctions(graph)
|
|
|
|
|
|
graph.NeedsUpdate = false
|
|
|
|
--Initialize Data
|
|
graph.GraphType = "SCATTER"
|
|
graph.YMax = 1
|
|
graph.YMin = -1
|
|
graph.XMax = 1
|
|
graph.XMin = -1
|
|
graph.AxisColor = {1.0, 1.0, 1.0, 1.0}
|
|
graph.GridColor = {0.5, 0.5, 0.5, 0.5}
|
|
graph.XGridInterval = 0.25
|
|
graph.YGridInterval = 0.25
|
|
graph.XAxisDrawn = true
|
|
graph.YAxisDrawn = true
|
|
graph.AutoScale = false
|
|
graph.LinearFit = false
|
|
graph.LockOnXMin = false
|
|
graph.LockOnXMax = false
|
|
graph.LockOnYMin = false
|
|
graph.LockOnYMax = false
|
|
graph.Data = {}
|
|
graph.Textures = {}
|
|
graph.TexturesUsed = {}
|
|
|
|
graph.TextFrame = CreateFrame("Frame", nil, graph)
|
|
graph.TextFrame:SetAllPoints(graph)
|
|
|
|
tinsert(self.RegisteredGraphScatterPlot, graph)
|
|
return graph
|
|
end
|
|
|
|
--Pie Chart
|
|
local function SetupGraphPieChartFunctions(graph)
|
|
local self = lib
|
|
|
|
--Set the various functions
|
|
graph.AddPie = GraphFunctions.AddPie
|
|
graph.CompletePie = GraphFunctions.CompletePie
|
|
graph.ResetPie = GraphFunctions.ResetPie
|
|
|
|
graph.DrawLine = self.DrawLine
|
|
graph.DrawHLine = self.DrawHLine
|
|
graph.DrawVLine = self.DrawVLine
|
|
graph.DrawLinePie = GraphFunctions.DrawLinePie
|
|
graph.HideLines = self.HideLines
|
|
graph.HideTextures = GraphFunctions.HideTextures
|
|
graph.FindTexture = GraphFunctions.FindTexture
|
|
graph.OnUpdate = GraphFunctions.PieChart_OnUpdate
|
|
graph.SetSelectionFunc = GraphFunctions.SetSelectionFunc
|
|
|
|
graph:SetScript("OnUpdate", graph.OnUpdate)
|
|
end
|
|
|
|
function lib:CreateGraphPieChart(name, parent, relative, relativeTo, offsetX, offsetY, Width, Height)
|
|
local graph
|
|
local i
|
|
graph = CreateFrame("Frame",name, parent, BackdropTemplateMixin and "BackdropTemplate")
|
|
|
|
|
|
graph:SetPoint(relative, parent, relativeTo, offsetX, offsetY)
|
|
graph:SetWidth(Width)
|
|
graph:SetHeight(Height)
|
|
graph:Show()
|
|
|
|
|
|
SetupGraphPieChartFunctions(graph)
|
|
|
|
|
|
--Initialize Data
|
|
graph.GraphType = "PIE"
|
|
graph.PieUsed = 0
|
|
graph.PercentOn = 0
|
|
graph.Remaining = 0
|
|
graph.Textures = {}
|
|
graph.Ratio = Width / Height
|
|
graph.Radius = 0.88 * (Width / 2)
|
|
graph.Radius = graph.Radius * graph.Radius
|
|
graph.Sections = {}
|
|
graph.Textures = {}
|
|
graph.TexturesUsed = {}
|
|
graph.LastSection = nil
|
|
graph.onColor = 1
|
|
graph.TotalSections = 0
|
|
|
|
tinsert(self.RegisteredGraphPieChart, graph)
|
|
return graph
|
|
end
|
|
|
|
|
|
-------------------------------------------------------------------------------
|
|
--Functions for Realtime Graphs
|
|
-------------------------------------------------------------------------------
|
|
|
|
--AddTimeData - Adds a data value to the realtime graph at this moment in time
|
|
function GraphFunctions:AddTimeData(value)
|
|
if type(value) ~= "number" then
|
|
return
|
|
end
|
|
|
|
local t = {}
|
|
t.Time = GetTime()
|
|
self.LastDataTime = t.Time
|
|
t.Value = value
|
|
tinsert(self.Data, t)
|
|
end
|
|
|
|
--RefreshRealtimeGraph - Refreshes the gridlines for the realtime graph
|
|
function GraphFunctions:RefreshRealtimeGraph()
|
|
self:HideLines(self)
|
|
self:CreateGridlines()
|
|
end
|
|
|
|
--SetFilterRadius - controls the radius of the filter
|
|
function GraphFunctions:SetFilterRadius(radius)
|
|
self.TimeRadius = radius
|
|
end
|
|
|
|
--SetAutoscaleYAxis - If enabled the maximum y axis is adjusted to be 25% more than the max value
|
|
function GraphFunctions:SetAutoscaleYAxis(scale)
|
|
self.AutoScale = scale
|
|
end
|
|
|
|
--SetBarColors -
|
|
function GraphFunctions:SetBarColors(BotColor, TopColor)
|
|
local Temp
|
|
if BotColor.r then
|
|
Temp = BotColor
|
|
BotColor = {Temp.r, Temp.g, Temp.b, Temp.a}
|
|
end
|
|
if TopColor.r then
|
|
Temp = TopColor
|
|
TopColor = {Temp.r, Temp.g, Temp.b, Temp.a}
|
|
end
|
|
for i = 1, self.BarNum do
|
|
local t = self.Bars[i]:GetStatusBarTexture()
|
|
t:SetGradientAlpha("VERTICAL", BotColor[1], BotColor[2], BotColor[3], BotColor[4], TopColor[1], TopColor[2], TopColor[3], TopColor[4])
|
|
end
|
|
end
|
|
|
|
function GraphFunctions:SetMode(mode)
|
|
self.Mode = mode
|
|
|
|
if mode ~= "SLOW" then
|
|
self.LastShift = GetTime() + self.XMin
|
|
end
|
|
end
|
|
|
|
function GraphFunctions:RealtimeSetColors(BotColor, TopColor)
|
|
local Temp
|
|
if BotColor.r then
|
|
Temp = BotColor
|
|
BotColor = {Temp.r, Temp.g, Temp.b, Temp.a}
|
|
end
|
|
if TopColor.r then
|
|
Temp = TopColor
|
|
TopColor = {Temp.r, Temp.g, Temp.b, Temp.a}
|
|
end
|
|
self.BarColorBot = BotColor
|
|
self.BarColorTop = TopColor
|
|
for _, v in pairs(self.Bars) do
|
|
v:GetStatusBarTexture():SetGradientAlpha("VERTICAL", self.BarColorBot[1], self.BarColorBot[2], self.BarColorBot[3], self.BarColorBot[4], self.BarColorTop[1], self.BarColorTop[2], self.BarColorTop[3], self.BarColorTop[4])
|
|
end
|
|
end
|
|
|
|
function GraphFunctions:RealtimeSetWidth(Width)
|
|
Width = math_floor(Width)
|
|
|
|
if Width == self.BarNum then
|
|
return
|
|
end
|
|
|
|
self.BarNum = Width
|
|
for i = 1, Width do
|
|
if type(self.Bars[i]) == "nil" then
|
|
local bar
|
|
bar = CreateFrame("StatusBar", self:GetName().."Bar"..i, self)
|
|
bar:SetPoint("BOTTOMLEFT", self, "BOTTOMLEFT", i - 1, 0)
|
|
bar:SetHeight(self.Height)
|
|
bar:SetWidth(1)
|
|
bar:SetOrientation("VERTICAL")
|
|
bar:SetMinMaxValues(0, 1)
|
|
bar:SetStatusBarTexture("Interface\\Buttons\\WHITE8X8")
|
|
bar:GetStatusBarTexture():SetHorizTile(false)
|
|
bar:GetStatusBarTexture():SetVertTile(false)
|
|
|
|
local t = bar:GetStatusBarTexture()
|
|
t:SetGradientAlpha("VERTICAL", self.BarColorBot[1], self.BarColorBot[2], self.BarColorBot[3], self.BarColorBot[4], self.BarColorTop[1], self.BarColorTop[2], self.BarColorTop[3], self.BarColorTop[4])
|
|
|
|
tinsert(self.Bars, bar)
|
|
else
|
|
self.Bars[i]:SetPoint("BOTTOMLEFT", self, "BOTTOMLEFT", i - 1, 0)
|
|
end
|
|
self.BarHeight[i] = 0
|
|
end
|
|
|
|
local SizeOfBarsUsed = table.maxn(self.BarsUsing)
|
|
|
|
if Width > SizeOfBarsUsed then
|
|
for i = SizeOfBarsUsed + 1, Width do
|
|
tinsert(self.BarsUsing, self.Bars[i])
|
|
self.Bars[i]:Show()
|
|
end
|
|
elseif Width < SizeOfBarsUsed then
|
|
for i = Width + 1, SizeOfBarsUsed do
|
|
tremove(self.BarsUsing, Width + 1)
|
|
self.Bars[i]:Hide()
|
|
end
|
|
end
|
|
|
|
self.BarWidth = (self.XMax - self.XMin) / self.BarNum
|
|
self.Decay = math_pow(self.DecaySet, self.BarWidth)
|
|
self.ExpNorm = 1 / (1 - self.Decay) / 0.95 --Actually a finite geometric series
|
|
|
|
|
|
self:OldSetWidth(Width)
|
|
self:RefreshGraph()
|
|
end
|
|
|
|
function GraphFunctions:RealtimeSetHeight(Height)
|
|
self.Height = Height
|
|
|
|
for i = 1, self.BarNum do
|
|
--self.Bars[i]:Hide()
|
|
self.Bars[i]:SetValue(0)
|
|
self.Bars[i]:SetHeight(self.Height)
|
|
end
|
|
|
|
self:OldSetHeight(Height)
|
|
self:RefreshGraph()
|
|
end
|
|
|
|
function GraphFunctions:GetMaxValue()
|
|
--Is there any data that could possibly be not zero?
|
|
if self.LastDataTime < (self.LastShift + self.XMin - self.TimeRadius) then
|
|
return 0
|
|
end
|
|
|
|
local MaxY = 0
|
|
|
|
for i = 1, self.BarNum do
|
|
MaxY = math_max(MaxY, self.BarHeight[i])
|
|
end
|
|
|
|
return MaxY
|
|
end
|
|
|
|
|
|
|
|
function GraphFunctions:RealtimeGetValue(Time)
|
|
local Bar
|
|
if Time < self.XMin or Time > self.XMax then
|
|
return 0
|
|
end
|
|
|
|
Bar = math_min(math_max(math_floor(self.BarNum * (Time - self.XMin) / (self.XMax - self.XMin) + 0.5), 1), self.BarNum)
|
|
|
|
return self.BarHeight[Bar]
|
|
end
|
|
|
|
function GraphFunctions:SetUpdateLimit(Time)
|
|
self.LimitUpdates = Time
|
|
end
|
|
|
|
function GraphFunctions:SetDecay(decay)
|
|
self.DecaySet = decay
|
|
self.Decay = math_pow(self.DecaySet, self.BarWidth)
|
|
self.ExpNorm = 1 / (1 - self.Decay) / 0.95 --Actually a finite geometric series (divide 0.96 instead of 1 since seems doesn't quite work right)
|
|
end
|
|
|
|
function GraphFunctions:AddBar(value)
|
|
for i = 1, self.BarNum - 1 do
|
|
self.BarHeight[i] = self.BarHeight[i + 1]
|
|
end
|
|
self.BarHeight[self.BarNum] = value
|
|
self.AddedBar = true
|
|
end
|
|
|
|
function GraphFunctions:SetBars()
|
|
local YHeight = self.YMax - self.YMin
|
|
|
|
for i, bar in pairs(self.BarsUsing) do
|
|
local h
|
|
h = (self.BarHeight[i] - self.YMin) / YHeight
|
|
|
|
bar:SetValue(h)
|
|
end
|
|
end
|
|
|
|
|
|
-------------------------------------------------------------------------------
|
|
--Functions for Line Graph Data
|
|
-------------------------------------------------------------------------------
|
|
|
|
function GraphFunctions:AddDataSeries(points, color, n2, linetexture)
|
|
local data
|
|
--Make sure there is data points
|
|
if not points then
|
|
return
|
|
end
|
|
|
|
data = points
|
|
if n2 == nil then
|
|
n2 = false
|
|
end
|
|
if n2 or (table.getn(points) == 2 and table.getn(points[1]) ~= 2) then
|
|
data = {}
|
|
for k, v in ipairs(points[1]) do
|
|
tinsert(data, {v, points[2][k]})
|
|
end
|
|
end
|
|
|
|
if linetexture then
|
|
if not linetexture:find("\\") and not linetexture:find("//") then
|
|
linetexture = TextureDirectory..linetexture
|
|
end
|
|
end
|
|
|
|
tinsert(self.Data,{Points = data; Color = color; LineTexture=linetexture})
|
|
|
|
self.NeedsUpdate = true
|
|
end
|
|
|
|
function GraphFunctions:AddFilledDataSeries(points, color, n2)
|
|
local data
|
|
--Make sure there is data points
|
|
if not points or #points == 0 then
|
|
return
|
|
end
|
|
|
|
data = points
|
|
if n2 == nil then
|
|
n2 = false
|
|
end
|
|
|
|
if n2 or (table.getn(points) == 2 and table.getn(points[1]) ~= 2) then
|
|
data = {}
|
|
for k, v in ipairs(points[1]) do
|
|
tinsert(data, {v, points[2][k]})
|
|
end
|
|
end
|
|
|
|
tinsert(self.FilledData, {Points = data; Color = color})
|
|
|
|
self.NeedsUpdate = true
|
|
end
|
|
|
|
|
|
function GraphFunctions:ResetData()
|
|
self.Data = {}
|
|
|
|
if self.FilledData then
|
|
self.FilledData = {}
|
|
end
|
|
|
|
self.NeedsUpdate = true
|
|
end
|
|
|
|
function GraphFunctions:SetLinearFit(fit)
|
|
self.LinearFit = fit
|
|
|
|
self.NeedsUpdate = true
|
|
end
|
|
|
|
|
|
|
|
function GraphFunctions:HideTextures()
|
|
local k = #self.TexturesUsed
|
|
while k > 0 do
|
|
self.Textures[#self.Textures + 1] = self.TexturesUsed[k]
|
|
self.TexturesUsed[k]:Hide()
|
|
self.TexturesUsed[k] = nil
|
|
k = k - 1
|
|
end
|
|
end
|
|
|
|
--Make sure to show a texture after you grab it or its free for anyone else to grab
|
|
function GraphFunctions:FindTexture()
|
|
local t
|
|
if #self.Textures > 0 then
|
|
t = self.Textures[#self.Textures]
|
|
self.TexturesUsed[#self.TexturesUsed + 1] = t
|
|
self.Textures[#self.Textures] = nil
|
|
return t
|
|
end
|
|
local g = self:CreateTexture(nil, "BACKGROUND")
|
|
self.TexturesUsed[#self.TexturesUsed + 1] = g
|
|
return g
|
|
end
|
|
|
|
function GraphFunctions:HideFontStrings()
|
|
if not self.FontStrings then
|
|
self.FontStrings = {}
|
|
end
|
|
for k, t in pairs(self.FontStrings) do
|
|
t:Hide()
|
|
end
|
|
end
|
|
|
|
--Make sure to show a fontstring after you grab it or its free for anyone else to grab
|
|
function GraphFunctions:FindFontString()
|
|
for k, t in pairs(self.FontStrings) do
|
|
if not t:IsShown() then
|
|
return t
|
|
end
|
|
end
|
|
local g
|
|
|
|
if self.TextFrame then
|
|
g = self.TextFrame:CreateFontString(nil, "OVERLAY")
|
|
else
|
|
g = self:CreateFontString(nil, "OVERLAY")
|
|
end
|
|
tinsert(self.FontStrings, g)
|
|
return g
|
|
end
|
|
|
|
--Linear Regression via Least Squares
|
|
function GraphFunctions:LinearRegression(data)
|
|
local alpha, beta
|
|
local n, SX, SY, SXX, SXY = 0, 0, 0, 0, 0
|
|
|
|
for k, v in pairs(data) do
|
|
n = n + 1
|
|
|
|
SX = SX + v[1]
|
|
SXX = SXX + v[1] * v[1]
|
|
SY = SY + v[2]
|
|
SXY = SXY + v[1] * v[2]
|
|
end
|
|
|
|
beta = (n * SXY - SX * SY) / (n * SXX - SX * SX)
|
|
alpha = (SY - beta * SX) / n
|
|
|
|
return alpha, beta
|
|
end
|
|
|
|
|
|
-------------------------------------------------------------------------------
|
|
--Functions for Pie Chart
|
|
-------------------------------------------------------------------------------
|
|
|
|
local PiePieces = {
|
|
"1-2",
|
|
"1-4",
|
|
"1-8",
|
|
"1-16",
|
|
"1-32",
|
|
"1-64",
|
|
"1-128"
|
|
}
|
|
|
|
--26 Colors
|
|
local ColorTable = {
|
|
{0.9, 0.1, 0.1},
|
|
{0.1, 0.9, 0.1},
|
|
{0.1, 0.1, 0.9},
|
|
{0.9, 0.9, 0.1},
|
|
{0.9, 0.1, 0.9},
|
|
{0.1, 0.9, 0.9},
|
|
{0.9, 0.9, 0.9},
|
|
{0.5, 0.1, 0.1},
|
|
{0.1, 0.5, 0.1},
|
|
{0.1, 0.1, 0.5},
|
|
{0.5, 0.5, 0.1},
|
|
{0.5, 0.1, 0.5},
|
|
{0.1, 0.5, 0.5},
|
|
{0.5, 0.5, 0.5},
|
|
{0.75, 0.15, 0.15},
|
|
{0.15, 0.75, 0.15},
|
|
{0.15, 0.15, 0.75},
|
|
{0.75, 0.75, 0.15},
|
|
{0.75, 0.15, 0.75},
|
|
{0.15, 0.75, 0.75},
|
|
{0.9, 0.5, 0.1},
|
|
{0.1, 0.5, 0.9},
|
|
{0.9, 0.1, 0.5},
|
|
{0.5, 0.9, 0.1},
|
|
{0.5, 0.1, 0.9},
|
|
{0.1, 0.9, 0.5},
|
|
}
|
|
|
|
function GraphFunctions:AddPie(Percent, Color)
|
|
local PiePercent = self.PercentOn
|
|
|
|
local CurPiece = 50
|
|
local Angle = 180
|
|
local CurAngle = PiePercent * 360 / 100
|
|
|
|
self.TotalSections = self.TotalSections + 1
|
|
if type(self.Sections[self.TotalSections]) ~= "table" then
|
|
self.Sections[self.TotalSections] = {}
|
|
end
|
|
|
|
local Section = self.Sections[self.TotalSections]
|
|
Section.Textures = {}
|
|
|
|
if type(Color) ~= "table" then
|
|
if self.onColor <= table.maxn(ColorTable) then
|
|
Color = ColorTable[self.onColor]
|
|
else
|
|
Color = {math_random(), math_random(), math_random()}
|
|
end
|
|
self.onColor = self.onColor + 1
|
|
end
|
|
|
|
if PiePercent == 0 then
|
|
self:DrawLinePie(0)
|
|
end
|
|
|
|
Percent = Percent + self.Remaining
|
|
local LastPiece = 0
|
|
for k, v in pairs(PiePieces) do
|
|
if (Percent + 0.1) > CurPiece then
|
|
local t = self:FindTexture()
|
|
t:SetTexture(TextureDirectory..v)
|
|
t:ClearAllPoints()
|
|
t:SetPoint("CENTER", self, "CENTER", 0, 0)
|
|
t:SetHeight(self:GetHeight())
|
|
t:SetWidth(self:GetWidth())
|
|
GraphFunctions:RotateTexture(t, CurAngle)
|
|
t:Show()
|
|
|
|
t:SetVertexColor(Color[1], Color[2], Color[3], 1.0)
|
|
Percent = Percent - CurPiece
|
|
PiePercent = PiePercent + CurPiece
|
|
CurAngle = CurAngle + Angle
|
|
|
|
tinsert(Section.Textures, t)
|
|
|
|
if k == 7 then
|
|
LastPiece = 0.09
|
|
end
|
|
end
|
|
CurPiece = CurPiece / 2
|
|
Angle = Angle / 2
|
|
end
|
|
|
|
--Finish adding section data
|
|
Section.Color = Color
|
|
Section.Angle = CurAngle
|
|
|
|
self:DrawLinePie((PiePercent + LastPiece) * 360 / 100)
|
|
self.PercentOn = PiePercent
|
|
self.Remaining = Percent
|
|
|
|
return Color
|
|
end
|
|
|
|
function GraphFunctions:CompletePie(Color)
|
|
local Percent = 100 - self.PercentOn
|
|
local PiePercent = self.PercentOn
|
|
|
|
local CurPiece = 50
|
|
local Angle = 180
|
|
local CurAngle = PiePercent * 360 / 100
|
|
|
|
self.TotalSections = self.TotalSections + 1
|
|
if not self.Sections[self.TotalSections] then
|
|
self.Sections[self.TotalSections] = {}
|
|
end
|
|
|
|
local Section = self.Sections[self.TotalSections]
|
|
Section.Textures = {}
|
|
|
|
if type(Color) ~= "table" then
|
|
if self.onColor <= table.maxn(ColorTable) then
|
|
Color = ColorTable[self.onColor]
|
|
else
|
|
Color = {math_random(), math_random(), math_random()}
|
|
end
|
|
self.onColor = self.onColor + 1
|
|
end
|
|
|
|
Percent = Percent + self.Remaining
|
|
if PiePercent ~= 0 then
|
|
for k, v in pairs(PiePieces) do
|
|
if (Percent + 0.1) > CurPiece then
|
|
local t = self:FindTexture()
|
|
t:SetTexture(TextureDirectory..v)
|
|
t:ClearAllPoints()
|
|
t:SetPoint("CENTER", self, "CENTER", 0, 0)
|
|
t:SetHeight(self:GetHeight())
|
|
t:SetWidth(self:GetWidth())
|
|
GraphFunctions:RotateTexture(t, CurAngle)
|
|
t:Show()
|
|
|
|
t:SetVertexColor(Color[1], Color[2], Color[3], 1.0)
|
|
Percent = Percent - CurPiece
|
|
PiePercent = PiePercent + CurPiece
|
|
CurAngle = CurAngle + Angle
|
|
|
|
tinsert(Section.Textures, t)
|
|
end
|
|
CurPiece = CurPiece / 2
|
|
Angle = Angle / 2
|
|
end
|
|
else--Special case if its by itself
|
|
local t = self:FindTexture()
|
|
t:SetTexture(TextureDirectory.."1-1")
|
|
t:ClearAllPoints()
|
|
t:SetPoint("CENTER", self, "CENTER", 0, 0)
|
|
t:SetHeight(self:GetHeight())
|
|
t:SetWidth(self:GetWidth())
|
|
GraphFunctions:RotateTexture(t, CurAngle)
|
|
t:Show()
|
|
|
|
t:SetVertexColor(Color[1], Color[2], Color[3], 1.0)
|
|
tinsert(Section.Textures, t)
|
|
end
|
|
|
|
--Finish adding section data
|
|
Section.Color = Color
|
|
Section.Angle = 360
|
|
|
|
self.PercentOn = PiePercent
|
|
self.Remaining = Percent
|
|
|
|
return Color
|
|
end
|
|
|
|
function GraphFunctions:ResetPie()
|
|
self:HideTextures()
|
|
self:HideLines(self)
|
|
|
|
self.PieUsed = 0
|
|
self.PercentOn = 0
|
|
self.Remaining = 0
|
|
self.onColor = 1
|
|
self.LastSection = nil
|
|
self.TotalSections = 0
|
|
--self.Sections = {}
|
|
end
|
|
|
|
function GraphFunctions:DrawLinePie(angle)
|
|
local sx, sy, ex, ey
|
|
local Radian = math_pi * (90 - angle) / 180
|
|
local w, h
|
|
w = self:GetWidth() / 2
|
|
h = self:GetHeight() / 2
|
|
|
|
|
|
sx = w
|
|
sy = h
|
|
|
|
ex = sx + 0.88 * w * math_cos(Radian)
|
|
ey = sx + 0.88 * h * math_sin(Radian)
|
|
|
|
self:DrawLine(self, sx, sy, ex, ey, 34, {0.0, 0.0, 0.0, 1.0}, "OVERLAY")
|
|
end
|
|
|
|
--Used to rotate the pie slices
|
|
function GraphFunctions:RotateTexture(texture, angle)
|
|
local Radian = math_pi * (45 - angle) / 180
|
|
local Radian2 = math_pi * (45 + 90 - angle) / 180
|
|
local Radius = 0.70710678118654752440084436210485
|
|
|
|
local tx, ty, tx2, ty2
|
|
tx = Radius * math_cos(Radian)
|
|
ty = Radius * math_sin(Radian)
|
|
tx2 = -ty
|
|
ty2 = tx
|
|
|
|
texture:SetTexCoord(0.5 - tx, 0.5 - ty, 0.5 + tx2, 0.5 + ty2, 0.5 - tx2, 0.5 - ty2, 0.5 + tx, 0.5 + ty)
|
|
end
|
|
|
|
function GraphFunctions:SetSelectionFunc(f)
|
|
self.SelectionFunc = f
|
|
end
|
|
|
|
--TODO: Pie chart pieces need to be clickable
|
|
function GraphFunctions:PieChart_OnUpdate()
|
|
if (MouseIsOver(self)) then
|
|
local sX, sY = self:GetCenter()
|
|
local Scale = self:GetEffectiveScale()
|
|
local mX, mY = GetCursorPosition()
|
|
local dX, dY
|
|
|
|
dX = mX / Scale - sX
|
|
dY = mY / Scale - sY
|
|
|
|
local Angle = 90-math_deg(math_atan(dY / dX))
|
|
dY = dY * self.Ratio
|
|
local Dist = dX * dX + dY * dY
|
|
|
|
if dX < 0 then
|
|
Angle = Angle + 180
|
|
end
|
|
|
|
--Are we on the Pie Chart?
|
|
if Dist < self.Radius then
|
|
--What section are we on?
|
|
for k = 1, self.TotalSections do
|
|
local v = self.Sections[k]
|
|
if Angle < v.Angle then
|
|
local Color
|
|
if k ~= self.LastSection then
|
|
if self.LastSection then
|
|
local Section = self.Sections[self.LastSection]
|
|
for _, t in pairs(Section.Textures) do
|
|
Color = Section.Color
|
|
t:SetVertexColor(Color[1], Color[2], Color[3], 1.0)
|
|
end
|
|
end
|
|
|
|
if self.SelectionFunc then
|
|
self:SelectionFunc(k)
|
|
end
|
|
end
|
|
|
|
local ColorAdd = 0.15 * math_abs(math_fmod(GetTime(), 3) - 1.5) - 0.1125
|
|
|
|
Color = {}
|
|
Color[1] = v.Color[1]+ColorAdd
|
|
Color[2] = v.Color[2]+ColorAdd
|
|
Color[3] = v.Color[3]+ColorAdd
|
|
|
|
for _, t in pairs(v.Textures) do
|
|
t:SetVertexColor(Color[1], Color[2], Color[3], 1.0)
|
|
end
|
|
|
|
self.LastSection = k
|
|
|
|
return
|
|
end
|
|
end
|
|
elseif self.LastSection then
|
|
local Section = self.Sections[self.LastSection]
|
|
for _, t in pairs(Section.Textures) do
|
|
local Color = Section.Color
|
|
t:SetVertexColor(Color[1], Color[2], Color[3], 1.0)
|
|
end
|
|
self.LastSection = nil
|
|
if self.SelectionFunc then
|
|
self:SelectionFunc(nil)
|
|
end
|
|
end
|
|
else
|
|
if self.LastSection then
|
|
local Section = self.Sections[self.LastSection]
|
|
for _, t in pairs(Section.Textures) do
|
|
local Color = Section.Color
|
|
t:SetVertexColor(Color[1], Color[2], Color[3], 1.0)
|
|
end
|
|
self.LastSection = nil
|
|
if self.SelectionFunc then
|
|
self:SelectionFunc(nil)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
-------------------------------------------------------------------------------
|
|
--Axis Setting Functions
|
|
-------------------------------------------------------------------------------
|
|
|
|
function GraphFunctions:SetYMax(ymax)
|
|
if ymax == self.YMax then
|
|
return
|
|
end
|
|
|
|
self.YMax = ymax
|
|
|
|
self.NeedsUpdate = true
|
|
end
|
|
|
|
function GraphFunctions:SetYAxis(ymin, ymax)
|
|
if self.YMin == ymin and self.YMax == ymax then
|
|
return
|
|
end
|
|
|
|
self.YMin = ymin
|
|
self.YMax = ymax
|
|
|
|
self.NeedsUpdate = true
|
|
end
|
|
|
|
function GraphFunctions:SetMinMaxY(val)
|
|
if self.MinMaxY == val then
|
|
return
|
|
end
|
|
|
|
self.MinMaxY = val
|
|
self.NeedsUpdate = true
|
|
end
|
|
|
|
function GraphFunctions:SetXAxis(xmin, xmax)
|
|
if self.XMin == xmin and self.XMax == xmax then
|
|
return
|
|
end
|
|
|
|
self.XMin = xmin
|
|
self.XMax = xmax
|
|
|
|
self.NeedsUpdate = true
|
|
|
|
if self.GraphType == "REALTIME" then
|
|
self.BarWidth = (xmax - xmin) / self.BarNum
|
|
self.Decay = math_pow(self.DecaySet, self.BarWidth)
|
|
self.FilterOverlap = math_max(math_ceil((self.TimeRadius + xmax) / self.BarWidth), 0)
|
|
self.LastShift = GetTime() + xmin
|
|
end
|
|
end
|
|
|
|
function GraphFunctions:SetAutoScale(auto)
|
|
self.AutoScale = auto
|
|
|
|
self.NeedsUpdate = true
|
|
end
|
|
|
|
--The various Lock Functions let you use Autoscale but holds the locked points in place
|
|
function GraphFunctions:LockXMin(state)
|
|
if state == nil then
|
|
self.LockOnXMin = not self.LockOnXMin
|
|
return
|
|
end
|
|
self.LockOnXMin = state
|
|
end
|
|
|
|
function GraphFunctions:LockXMax(state)
|
|
if state == nil then
|
|
self.LockOnXMax = not self.LockOnXMax
|
|
return
|
|
end
|
|
self.LockOnXMax = state
|
|
end
|
|
|
|
function GraphFunctions:LockYMin(state)
|
|
if state == nil then
|
|
self.LockOnYMin = not self.LockOnYMin
|
|
return
|
|
end
|
|
self.LockOnYMin = state
|
|
end
|
|
|
|
function GraphFunctions:LockYMax(state)
|
|
if state == nil then
|
|
self.LockOnYMax = not self.LockOnYMax
|
|
return
|
|
end
|
|
self.LockOnYMax = state
|
|
end
|
|
|
|
|
|
-------------------------------------------------------------------------------
|
|
--Grid & Axis Drawing Functions
|
|
-------------------------------------------------------------------------------
|
|
|
|
function GraphFunctions:SetAxisDrawing(xaxis, yaxis)
|
|
if xaxis == self.XAxisDrawn and self.YAxisDrawn == yaxis then
|
|
return
|
|
end
|
|
|
|
self.XAxisDrawn = xaxis
|
|
self.YAxisDrawn = yaxis
|
|
|
|
self.NeedsUpdate = true
|
|
end
|
|
|
|
function GraphFunctions:SetGridSpacing(xspacing, yspacing)
|
|
if xspacing == self.XGridInterval and self.YGridInterval == yspacing then
|
|
return
|
|
end
|
|
|
|
self.XGridInterval = xspacing
|
|
self.YGridInterval = yspacing
|
|
|
|
self.NeedsUpdate = true
|
|
end
|
|
|
|
function GraphFunctions:SetAxisColor(color)
|
|
if self.AxisColor[1] == color[1] and self.AxisColor[2] == color[2] and self.AxisColor[3] == color[3] and self.AxisColor[4] == color[4] then
|
|
return
|
|
end
|
|
|
|
self.AxisColor = color
|
|
|
|
self.NeedsUpdate = true
|
|
end
|
|
|
|
function GraphFunctions:SetGridColor(color)
|
|
if self.GridColor[1] == color[1] and self.GridColor[2] == color[2] and self.GridColor[3] == color[3] and self.GridColor[4] == color[4] then
|
|
return
|
|
end
|
|
|
|
self.GridColor = color
|
|
|
|
self.NeedsUpdate = true
|
|
end
|
|
|
|
function GraphFunctions:SetGridColorSecondary(color)
|
|
if self.GridColorSecondary ~= nil and self.GridColorSecondary[1] == color[1] and self.GridColorSecondary[2] == color[2] and self.GridColorSecondary[3] == color[3] and self.GridColorSecondary[4] == color[4] then
|
|
return
|
|
end
|
|
|
|
self.GridColorSecondary = color
|
|
|
|
self.NeedsUpdate = true
|
|
end
|
|
|
|
function GraphFunctions:SetGridSecondaryMultiple(XAxis, YAxis)
|
|
if type(XAxis) ~= "number" then
|
|
XAxis = 1
|
|
end
|
|
if type(YAxis) ~= "number" then
|
|
YAxis = 1
|
|
end
|
|
self.GridSecondaryX = XAxis
|
|
self.GridSecondaryY = YAxis
|
|
|
|
self.NeedsUpdate = true
|
|
end
|
|
|
|
function GraphFunctions:SetYLabels(Left, Right)
|
|
self.YLabelsLeft = Left
|
|
self.YLabelsRight = Right
|
|
end
|
|
|
|
function GraphFunctions:SetLineTexture(texture)
|
|
if (type(texture) ~= "string") then
|
|
return assert(false, "Parameter 1 for SetLineTexture must be a string")
|
|
end
|
|
|
|
--full path
|
|
if (texture:find("\\") or texture:find("//")) then
|
|
self.CustomLine = texture
|
|
--using an image inside lib-graph folder
|
|
else
|
|
self.CustomLine = TextureDirectory..texture
|
|
end
|
|
end
|
|
|
|
function GraphFunctions:SetBorderSize(border, size)
|
|
border = string.lower(border)
|
|
|
|
if (type(size) ~= "number") then
|
|
return assert(false, "Parameter 2 for SetBorderSize must be a number")
|
|
end
|
|
|
|
if (border == "left") then
|
|
self.CustomLeftBorder = size
|
|
return true
|
|
elseif (border == "right") then
|
|
self.CustomRightBorder = size
|
|
return true
|
|
elseif (border == "top") then
|
|
self.CustomTopBorder = size
|
|
return true
|
|
elseif (border == "bottom") then
|
|
self.CustomBottomBorder = size
|
|
return true
|
|
end
|
|
|
|
return assert(false, "Usage: GraphObject:SetBorderSize (LEFT RIGHT TOP BOTTOM, SIZE)")
|
|
end
|
|
|
|
function GraphFunctions:CreateGridlines()
|
|
local Width = self:GetWidth()
|
|
local Height = self:GetHeight()
|
|
local NoSecondary = (self.GridSecondaryY == nil) or (self.GridSecondaryX == nil) or (type(self.GridColorSecondary) ~= "table")
|
|
local F
|
|
self:HideLines(self)
|
|
self:HideFontStrings()
|
|
|
|
if self.YGridInterval then
|
|
local LowerYGridLine, UpperYGridLine, TopSpace
|
|
LowerYGridLine = self.YMin / self.YGridInterval
|
|
LowerYGridLine = math_max(math_floor(LowerYGridLine), math_ceil(LowerYGridLine))
|
|
UpperYGridLine = self.YMax / self.YGridInterval
|
|
UpperYGridLine = math_min(math_floor(UpperYGridLine), math_ceil(UpperYGridLine))
|
|
--UpperYGridLine = math_min(UpperYGridLine, self.YGridMax or 16)
|
|
TopSpace = Height * (1 - (UpperYGridLine * self.YGridInterval - self.YMin) / (self.YMax - self.YMin))
|
|
|
|
for i = LowerYGridLine, UpperYGridLine do
|
|
if i ~= 0 or not self.YAxisDrawn then
|
|
local YPos, T
|
|
YPos = Height * (i * self.YGridInterval - self.YMin) / (self.YMax - self.YMin)
|
|
if NoSecondary or math_fmod(i, self.GridSecondaryY) == 0 then
|
|
T = self:DrawLine(self, 0, YPos, Width, YPos, 24, self.GridColor, "BACKGROUND")
|
|
else
|
|
T = self:DrawLine(self, 0, YPos, Width, YPos, 24, self.GridColorSecondary, "BACKGROUND")
|
|
end
|
|
|
|
if ((i ~= UpperYGridLine) or (TopSpace > 12)) and (NoSecondary or math_fmod(i, self.GridSecondaryY) == 0) then
|
|
if self.YLabelsLeft then
|
|
F = self:FindFontString()
|
|
F:SetFontObject("GameFontHighlightSmall")
|
|
F:SetTextColor(1, 1, 1)
|
|
F:ClearAllPoints()
|
|
F:SetPoint("BOTTOMLEFT", T, "LEFT", 2, 2)
|
|
F:SetText(i * self.YGridInterval)
|
|
F:Show()
|
|
end
|
|
|
|
if self.YLabelsRight then
|
|
F = self:FindFontString()
|
|
F:SetFontObject("GameFontHighlightSmall")
|
|
F:SetTextColor(1, 1, 1)
|
|
F:ClearAllPoints()
|
|
F:SetPoint("BOTTOMRIGHT", T, "RIGHT", -2, 2)
|
|
F:SetText(i * self.YGridInterval)
|
|
F:Show()
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
if self.XGridInterval then
|
|
local LowerXGridLine, UpperXGridLine
|
|
LowerXGridLine = self.XMin / self.XGridInterval
|
|
LowerXGridLine = math_max(math_floor(LowerXGridLine), math_ceil(LowerXGridLine))
|
|
UpperXGridLine = self.XMax / self.XGridInterval
|
|
UpperXGridLine = math_min(math_floor(UpperXGridLine), math_ceil(UpperXGridLine))
|
|
--UpperXGridLine = math_min(UpperXGridLine, self.XGridMax or 16)
|
|
|
|
for i = LowerXGridLine, UpperXGridLine do
|
|
if i ~= 0 or not self.XAxisDrawn then
|
|
local XPos
|
|
XPos = Width * (i * self.XGridInterval - self.XMin) / (self.XMax - self.XMin)
|
|
if NoSecondary or math_fmod(i, self.GridSecondaryX) == 0 then
|
|
self:DrawLine(self, XPos, 0, XPos, Height, 24, self.GridColor, "BACKGROUND")
|
|
else
|
|
self:DrawLine(self, XPos, 0, XPos, Height, 24, self.GridColorSecondary, "BACKGROUND")
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
if self.YAxisDrawn and self.YMax >= 0 and self.YMin <= 0 then
|
|
local YPos, T
|
|
|
|
YPos = Height * (-self.YMin) / (self.YMax - self.YMin)
|
|
T = self:DrawLine(self, 0, YPos, Width, YPos, 24, self.AxisColor, "BACKGROUND")
|
|
|
|
if self.YLabelsLeft then
|
|
F = self:FindFontString()
|
|
F:SetFontObject("GameFontHighlightSmall")
|
|
F:SetTextColor(1, 1, 1)
|
|
F:ClearAllPoints()
|
|
F:SetPoint("BOTTOMLEFT", T, "LEFT", 2, 2)
|
|
F:SetText(0)
|
|
F:Show()
|
|
end
|
|
if self.YLabelsRight then
|
|
F = self:FindFontString()
|
|
F:SetFontObject("GameFontHighlightSmall")
|
|
F:SetTextColor(1, 1, 1)
|
|
F:ClearAllPoints()
|
|
F:SetPoint("BOTTOMRIGHT", T, "RIGHT", -2, 2)
|
|
F:SetText(0)
|
|
F:Show()
|
|
end
|
|
end
|
|
|
|
if self.XAxisDrawn and self.XMax >= 0 and self.XMin <= 0 then
|
|
local XPos
|
|
|
|
XPos = Width * (-self.XMin) / (self.XMax - self.XMin)
|
|
self:DrawLine(self, XPos, 0, XPos, Height, 24, self.AxisColor, "BACKGROUND")
|
|
end
|
|
end
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
--Refresh functions
|
|
--------------------------------------------------------------------------------
|
|
|
|
function GraphFunctions:OnUpdateGraph()
|
|
if self.NeedsUpdate and self.RefreshGraph then
|
|
self:RefreshGraph()
|
|
self.NeedsUpdate = false
|
|
end
|
|
end
|
|
|
|
--Performs a convolution in realtime allowing to graph Framerate, DPS, or any other data you want graphed in realtime
|
|
function GraphFunctions:OnUpdateGraphRealtime()
|
|
local CurTime = GetTime()
|
|
local BarsChanged
|
|
|
|
if self.NextUpdate > CurTime or (self.Mode == "RAW" and not (self.NeedsUpdate or self.AddedBar)) then
|
|
return
|
|
end
|
|
|
|
self.NextUpdate = CurTime + self.LimitUpdates
|
|
|
|
--Slow Mode performs an entire convolution every frame
|
|
if self.Mode == "SLOW" then
|
|
--Initialize Bar Data
|
|
self.BarHeight = {}
|
|
for i = 1, self.BarNum do
|
|
self.BarHeight[i] = 0
|
|
end
|
|
local k, v
|
|
local BarTimeRadius = (self.XMax - self.XMin) / self.BarNum
|
|
local DataValue = 1 / (2 * self.TimeRadius)
|
|
|
|
if self.Filter == "RECT" then
|
|
--Take the convolution of the dataset on to the bars wtih a rectangular filter
|
|
local DataValue = 1 / (2 * self.TimeRadius)
|
|
for k, v in pairs(self.Data) do
|
|
if v.Time < (CurTime + self.XMin - self.TimeRadius) then
|
|
tremove(self.Data, k)
|
|
else
|
|
local DataTime = v.Time - CurTime
|
|
local LowestBar = math_max(math_floor((DataTime - self.XMin - self.TimeRadius) / BarTimeRadius), 1)
|
|
local HighestBar = math_min(math_ceil((DataTime - self.XMin + self.TimeRadius) / BarTimeRadius), self.BarNum)
|
|
for i = LowestBar, HighestBar do
|
|
self.BarHeight[i] = self.BarHeight[i] + v.Value * DataValue
|
|
end
|
|
end
|
|
end
|
|
elseif self.Filter == "TRI" then
|
|
--Needs optimization badly
|
|
--Take the convolution of the dataset on to the bars wtih a triangular filter
|
|
local DataValue = 1 / (self.TimeRadius)
|
|
for k, v in pairs(self.Data) do
|
|
local Temp
|
|
if v.Time < (CurTime + self.XMin - self.TimeRadius) then
|
|
tremove(self.Data, k)
|
|
else
|
|
local DataTime = v.Time - CurTime
|
|
local LowestBar = math_max(math_floor((DataTime - self.XMin - self.TimeRadius) / BarTimeRadius), 1)
|
|
local HighestBar = math_min(math_ceil((DataTime - self.XMin + self.TimeRadius) / BarTimeRadius), self.BarNum)
|
|
|
|
for i = LowestBar, HighestBar do
|
|
self.BarHeight[i] = self.BarHeight[i] + v.Value * DataValue * math_abs(BarTimeRadius * i + self.XMin - DataTime)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
BarsChanged = true
|
|
elseif self.Mode == "FAST" then
|
|
local ShiftBars = math_floor((CurTime - self.LastShift) / self.BarWidth)
|
|
|
|
if ShiftBars > 0 and not (self.LastDataTime < (self.LastShift + self.XMin - self.TimeRadius * 2)) then
|
|
local RecalcBars = self.BarNum - (ShiftBars + self.FilterOverlap) + 1
|
|
|
|
for i = 1, self.BarNum do
|
|
if i < RecalcBars then
|
|
self.BarHeight[i] = self.BarHeight[i + ShiftBars]
|
|
else
|
|
self.BarHeight[i] = 0
|
|
end
|
|
end
|
|
|
|
local BarTimeRadius = (self.XMax - self.XMin) / self.BarNum
|
|
local DataValue = 1 / (2 * self.TimeRadius)
|
|
local TimeDiff = CurTime-self.LastShift
|
|
|
|
CurTime = self.LastShift + ShiftBars * self.BarWidth
|
|
self.LastShift = CurTime
|
|
|
|
if self.Filter == "RECT" then
|
|
--Take the convolution of the dataset on to the bars wtih a rectangular filter
|
|
local DataValue = 1 / (2 * self.TimeRadius)
|
|
for k, v in pairs(self.Data) do
|
|
if v.Time < (CurTime + self.XMax - self.TimeRadius - TimeDiff) then
|
|
tremove(self.Data, k)
|
|
else
|
|
local DataTime = v.Time - CurTime
|
|
local LowestBar = math_max(math_max(math_floor((DataTime - self.XMin - self.TimeRadius) / BarTimeRadius), RecalcBars), 1)
|
|
local HighestBar = math_min(math_ceil((DataTime - self.XMin + self.TimeRadius) / BarTimeRadius), self.BarNum)
|
|
if LowestBar <= HighestBar then
|
|
for i = LowestBar, HighestBar do
|
|
self.BarHeight[i] = self.BarHeight[i] + v.Value * DataValue
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
BarsChanged = true
|
|
else
|
|
CurTime = self.LastShift + ShiftBars * self.BarWidth
|
|
self.LastShift = CurTime
|
|
end
|
|
elseif self.Mode == "EXP" then
|
|
local ShiftBars = math_floor((CurTime - self.LastShift) / self.BarWidth)
|
|
|
|
if ShiftBars > 0 then
|
|
local RecalcBars = self.BarNum - ShiftBars + 1
|
|
|
|
for i = 1, self.BarNum do
|
|
if i < RecalcBars then
|
|
self.BarHeight[i] = self.BarHeight[i + ShiftBars]
|
|
end
|
|
end
|
|
|
|
--Now to calculate the new bars
|
|
local Total
|
|
local Weight = 1 / self.TimeRadius / self.ExpNorm
|
|
|
|
for i = RecalcBars, self.BarNum do
|
|
Total = 0
|
|
|
|
--Implement an EXPFAST which does this only once instead of for each bar
|
|
for k, v in pairs(self.Data) do
|
|
Total = Total + v.Value * Weight
|
|
if v.Time < (self.LastShift - self.TimeRadius) then
|
|
tremove(self.Data, k)
|
|
end
|
|
end
|
|
|
|
self.CurVal = self.Decay * self.CurVal + Total
|
|
|
|
self.BarHeight[i] = self.CurVal
|
|
|
|
self.LastShift = self.LastShift + self.BarWidth
|
|
end
|
|
|
|
if self.CurVal < 0.1 then
|
|
self.CurVal = 0
|
|
end
|
|
BarsChanged = true
|
|
else
|
|
self.LastShift = self.LastShift + self.BarWidth * ShiftBars
|
|
end
|
|
elseif self.Mode == "EXPFAST" then
|
|
local ShiftBars = math_floor((CurTime - self.LastShift) / self.BarWidth)
|
|
local RecalcBars = self.BarNum - ShiftBars + 1
|
|
|
|
if ShiftBars > 0 and not (self.LastDataTime < (self.LastShift + self.XMin-self.TimeRadius)) then
|
|
for i = 1, self.BarNum do
|
|
if i < RecalcBars then
|
|
self.BarHeight[i] = self.BarHeight[i + ShiftBars]
|
|
end
|
|
end
|
|
|
|
--Now to calculate the new bars
|
|
local Total
|
|
local Weight = 1 / self.TimeRadius / self.ExpNorm
|
|
|
|
Total = 0
|
|
|
|
--Implement an EXPFAST which does this only once instead of for each bar
|
|
for k, v in pairs(self.Data) do
|
|
Total = Total + v.Value * Weight
|
|
if v.Time < (self.LastShift - self.TimeRadius) then
|
|
tremove(self.Data, k)
|
|
end
|
|
end
|
|
|
|
--self.LastShift = self.LastShift+self.BarWidth*ShiftBars
|
|
|
|
if self.CurVal ~= 0 or Total ~= 0 then
|
|
for i = RecalcBars, self.BarNum do
|
|
self.CurVal = self.Decay * self.CurVal + Total
|
|
self.BarHeight[i] = self.CurVal
|
|
end
|
|
self.LastDataTime = self.LastShift + self.BarWidth * ShiftBars
|
|
else
|
|
for i = RecalcBars, self.BarNum do
|
|
self.BarHeight[i] = 0
|
|
end
|
|
end
|
|
|
|
if self.CurVal < 0.1 then
|
|
self.CurVal = 0
|
|
end
|
|
BarsChanged = true
|
|
end
|
|
self.LastShift = self.LastShift + self.BarWidth * ShiftBars
|
|
elseif self.Mode == "RAW" then
|
|
--Do nothing really
|
|
--Using .AddedBar so we cut down on updating the grid
|
|
self.AddedBar = false
|
|
BarsChanged = true
|
|
end
|
|
|
|
|
|
if BarsChanged then
|
|
if self.AutoScale then
|
|
local MaxY = 0
|
|
|
|
for i = 1, self.BarNum do
|
|
MaxY = math_max(MaxY, self.BarHeight[i])
|
|
end
|
|
MaxY = 1.25 * MaxY
|
|
|
|
MaxY = math_max(MaxY, self.MinMaxY)
|
|
|
|
if MaxY ~= 0 and math_abs(self.YMax - MaxY) > 0.01 then
|
|
self.YMax = MaxY
|
|
self.NeedsUpdate = true
|
|
|
|
local Spacing
|
|
if self.YMax < 25 then
|
|
Spacing = -1
|
|
else
|
|
Spacing = math.log(self.YMax / 100) / math.log(2)
|
|
end
|
|
|
|
self.YGridInterval = 25 * math.pow(2, math.floor(Spacing))
|
|
end
|
|
end
|
|
self:SetBars()
|
|
end
|
|
|
|
if self.NeedsUpdate then
|
|
self.NeedsUpdate = false
|
|
self:RefreshGraph()
|
|
end
|
|
end
|
|
|
|
--Line Graph
|
|
function GraphFunctions:RefreshLineGraph()
|
|
self:HideLines(self)
|
|
self:HideBars(self)
|
|
|
|
if self.AutoScale and self.Data then
|
|
local MinX, MaxX, MinY, MaxY = math_huge, -math_huge, math_huge, -math_huge
|
|
--Go through line data first
|
|
for k1, series in pairs(self.Data) do
|
|
for k2, point in pairs(series.Points) do
|
|
MinX = math_min(point[1], MinX)
|
|
MaxX = math_max(point[1], MaxX)
|
|
MinY = math_min(point[2], MinY)
|
|
MaxY = math_max(point[2], MaxY)
|
|
end
|
|
end
|
|
|
|
--Now through the Filled Lines
|
|
for k1, series in pairs(self.FilledData) do
|
|
for k2, point in pairs(series.Points) do
|
|
MinX = math_min(point[1], MinX)
|
|
MaxX = math_max(point[1], MaxX)
|
|
MinY = math_min(point[2], MinY)
|
|
MaxY = math_max(point[2], MaxY)
|
|
end
|
|
end
|
|
|
|
local XBorder, YBorder
|
|
|
|
XBorder = 0.1 * (MaxX - MinX)
|
|
YBorder = 0.1 * (MaxY - MinY)
|
|
|
|
if not self.LockOnXMin then
|
|
if (self.CustomLeftBorder) then
|
|
self.XMin = MinX + self.CustomLeftBorder --custom size of left border
|
|
else
|
|
self.XMin = MinX - XBorder
|
|
end
|
|
end
|
|
|
|
if not self.LockOnXMax then
|
|
if (self.CustomRightBorder) then
|
|
self.XMax = MaxX + self.CustomRightBorder --custom size of right border
|
|
else
|
|
self.XMax = MaxX + XBorder
|
|
end
|
|
end
|
|
|
|
if not self.LockOnYMin then
|
|
if (self.CustomBottomBorder) then
|
|
self.YMin = MinY + self.CustomBottomBorder --custom size of bottom border
|
|
else
|
|
self.YMin = MinY - YBorder
|
|
end
|
|
end
|
|
|
|
if not self.LockOnYMax then
|
|
if (self.CustomTopBorder) then
|
|
self.YMax = MaxY + self.CustomTopBorder --custom size of top border
|
|
else
|
|
self.YMax = MaxY + YBorder
|
|
end
|
|
end
|
|
|
|
end
|
|
|
|
self:CreateGridlines()
|
|
|
|
local Width = self:GetWidth()
|
|
local Height = self:GetHeight()
|
|
|
|
for k1, series in pairs(self.Data) do
|
|
local LastPoint
|
|
LastPoint = nil
|
|
|
|
for k2, point in pairs(series.Points) do
|
|
if LastPoint then
|
|
local TPoint = {x = point[1]; y = point[2]}
|
|
|
|
TPoint.x = Width * (TPoint.x - self.XMin) / (self.XMax - self.XMin)
|
|
TPoint.y = Height * (TPoint.y - self.YMin) / (self.YMax - self.YMin)
|
|
|
|
--tercioo: send the data index to DrawLine so custom draw functions know what they are drawing
|
|
self:DrawLine(self, LastPoint.x, LastPoint.y, TPoint.x, TPoint.y, 32, series.Color, nil, series.LineTexture, k1)
|
|
|
|
LastPoint = TPoint
|
|
else
|
|
LastPoint = {x = point[1]; y = point[2]}
|
|
LastPoint.x = Width * (LastPoint.x - self.XMin) / (self.XMax - self.XMin)
|
|
LastPoint.y = Height * (LastPoint.y - self.YMin) / (self.YMax - self.YMin)
|
|
end
|
|
end
|
|
end
|
|
|
|
--Filled Line Graphs
|
|
for k1, series in pairs(self.FilledData) do
|
|
local LastPoint
|
|
LastPoint = nil
|
|
|
|
for k2, point in pairs(series.Points) do
|
|
if LastPoint then
|
|
local TPoint = {x = point[1]; y = point[2]}
|
|
|
|
TPoint.x = Width * (TPoint.x - self.XMin) / (self.XMax - self.XMin)
|
|
TPoint.y = Height * (TPoint.y - self.YMin ) /(self.YMax - self.YMin)
|
|
|
|
self:DrawBar(self, LastPoint.x, LastPoint.y, TPoint.x, TPoint.y, series.Color, k1)
|
|
|
|
LastPoint = TPoint
|
|
else
|
|
LastPoint = {x = point[1]; y = point[2]}
|
|
LastPoint.x = Width * (LastPoint.x - self.XMin) / (self.XMax - self.XMin)
|
|
LastPoint.y = Height * (LastPoint.y - self.YMin) / (self.YMax - self.YMin)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
--Scatter Plot Refresh
|
|
function GraphFunctions:RefreshScatterPlot()
|
|
self:HideLines(self)
|
|
|
|
if self.AutoScale and self.Data then
|
|
local MinX, MaxX, MinY, MaxY = math_huge, -math_huge, math_huge, -math_huge
|
|
for k1, series in pairs(self.Data) do
|
|
for k2, point in pairs(series.Points) do
|
|
MinX = math_min(point[1], MinX)
|
|
MaxX = math_max(point[1], MaxX)
|
|
MinY = math_min(point[2], MinY)
|
|
MaxY = math_max(point[2], MaxY)
|
|
end
|
|
end
|
|
|
|
local XBorder, YBorder
|
|
|
|
XBorder = 0.1 * (MaxX - MinX)
|
|
YBorder = 0.1 * (MaxY - MinY)
|
|
|
|
if not self.LockOnXMin then
|
|
self.XMin = MinX - XBorder
|
|
end
|
|
if not self.LockOnXMax then
|
|
self.XMax = MaxX + XBorder
|
|
end
|
|
if not self.LockOnYMin then
|
|
self.YMin = MinY - YBorder
|
|
end
|
|
if not self.LockOnYMax then
|
|
self.YMax = MaxY + YBorder
|
|
end
|
|
end
|
|
|
|
self:CreateGridlines()
|
|
|
|
local Width = self:GetWidth()
|
|
local Height = self:GetHeight()
|
|
|
|
self:HideTextures()
|
|
for k1, series in pairs(self.Data) do
|
|
local MinX, MaxX = self.XMax, self.XMin
|
|
for k2, point in pairs(series.Points) do
|
|
local x, y
|
|
MinX = math_min(point[1],MinX)
|
|
MaxX = math_max(point[1],MaxX)
|
|
x = Width * (point[1] - self.XMin) / (self.XMax - self.XMin)
|
|
y = Height * (point[2] - self.YMin) / (self.YMax - self.YMin)
|
|
|
|
local g = self:FindTexture()
|
|
g:SetTexture("Spells\\GENERICGLOW2_64")
|
|
g:SetWidth(6)
|
|
g:SetHeight(6)
|
|
g:ClearAllPoints()
|
|
g:SetPoint("CENTER", self, "BOTTOMLEFT", x, y)
|
|
g:SetVertexColor(series.Color[1], series.Color[2], series.Color[3], series.Color[4])
|
|
g:Show()
|
|
end
|
|
|
|
if self.LinearFit then
|
|
local alpha, beta = self:LinearRegression(series.Points)
|
|
local sx, sy, ex, ey
|
|
|
|
sx = MinX
|
|
sy = beta * sx + alpha
|
|
ex = MaxX
|
|
ey = beta*ex+alpha
|
|
|
|
sx = Width * (sx - self.XMin) / (self.XMax - self.XMin)
|
|
sy = Height * (sy - self.YMin) / (self.YMax - self.YMin)
|
|
ex = Width * (ex - self.XMin) / (self.XMax - self.XMin)
|
|
ey = Height * (ey - self.YMin) / (self.YMax - self.YMin)
|
|
|
|
self:DrawLine(self, sx, sy, ex, ey, 32, series.Color)
|
|
end
|
|
end
|
|
end
|
|
|
|
--Copied from Blizzard's TaxiFrame code and modifed for IMBA then remodified for GraphLib
|
|
|
|
-- The following function is used with permission from Daniel Stephens <iriel@vigilance-committee.org>
|
|
local TAXIROUTE_LINEFACTOR = 128 / 126 -- Multiplying factor for texture coordinates
|
|
local TAXIROUTE_LINEFACTOR_2 = TAXIROUTE_LINEFACTOR / 2 -- Half of that
|
|
|
|
-- T - Texture
|
|
-- C - Canvas Frame (for anchoring)
|
|
-- sx, sy - Coordinate of start of line
|
|
-- ex, ey - Coordinate of end of line
|
|
-- w - Width of line
|
|
-- relPoint - Relative point on canvas to interpret coords (Default BOTTOMLEFT)
|
|
function lib:DrawLine(C, sx, sy, ex, ey, w, color, layer, linetexture)
|
|
local relPoint = "BOTTOMLEFT"
|
|
|
|
if sx == ex then
|
|
if sy == ey then
|
|
return
|
|
else
|
|
return self:DrawVLine(C, sx, sy, ey, w, color, layer)
|
|
end
|
|
elseif sy == ey then
|
|
return self:DrawHLine(C, sx, ex, sy, w, color, layer)
|
|
end
|
|
|
|
if not C.GraphLib_Lines then
|
|
C.GraphLib_Lines = {}
|
|
C.GraphLib_Lines_Used = {}
|
|
end
|
|
|
|
local T = tremove(C.GraphLib_Lines) or C:CreateTexture(nil, "ARTWORK")
|
|
|
|
if linetexture then --this data series texture
|
|
T:SetTexture(linetexture)
|
|
elseif C.CustomLine then --overall chart texture
|
|
T:SetTexture(C.CustomLine)
|
|
else --no texture assigned, use default
|
|
T:SetTexture(TextureDirectory.."line")
|
|
end
|
|
|
|
tinsert(C.GraphLib_Lines_Used, T)
|
|
|
|
T:SetDrawLayer(layer or "ARTWORK")
|
|
|
|
T:SetVertexColor(color[1], color[2], color[3], color[4])
|
|
-- Determine dimensions and center point of line
|
|
local dx, dy = ex - sx, ey - sy
|
|
local cx, cy = (sx + ex) / 2, (sy + ey) / 2
|
|
|
|
-- Normalize direction if necessary
|
|
if (dx < 0) then
|
|
dx, dy = -dx, -dy
|
|
end
|
|
|
|
-- Calculate actual length of line
|
|
local l = sqrt((dx * dx) + (dy * dy))
|
|
|
|
-- Sin and Cosine of rotation, and combination (for later)
|
|
local s, c = -dy / l, dx / l
|
|
local sc = s * c
|
|
|
|
-- Calculate bounding box size and texture coordinates
|
|
local Bwid, Bhgt, BLx, BLy, TLx, TLy, TRx, TRy, BRx, BRy
|
|
if (dy >= 0) then
|
|
Bwid = ((l * c) - (w * s)) * TAXIROUTE_LINEFACTOR_2
|
|
Bhgt = ((w * c) - (l * s)) * TAXIROUTE_LINEFACTOR_2
|
|
BLx, BLy, BRy = (w / l) * sc, s * s, (l / w) * sc
|
|
BRx, TLx, TLy, TRx = 1 - BLy, BLy, 1 - BRy, 1 - BLx
|
|
TRy = BRx
|
|
else
|
|
Bwid = ((l * c) + (w * s)) * TAXIROUTE_LINEFACTOR_2
|
|
Bhgt = ((w * c) + (l * s)) * TAXIROUTE_LINEFACTOR_2
|
|
BLx, BLy, BRx = s * s, -(l / w) * sc, 1 + (w / l) * sc
|
|
BRy, TLx, TLy, TRy = BLx, 1 - BRx, 1 - BLx, 1 - BLy
|
|
TRx = TLy
|
|
end
|
|
|
|
-- Thanks Blizzard for adding (-)10000 as a hard-cap and throwing errors!
|
|
-- The cap was added in 3.1.0 and I think it was upped in 3.1.1
|
|
-- (way less chance to get the error)
|
|
if TLx > 10000 then TLx = 10000 elseif TLx < -10000 then TLx = -10000 end
|
|
if TLy > 10000 then TLy = 10000 elseif TLy < -10000 then TLy = -10000 end
|
|
if BLx > 10000 then BLx = 10000 elseif BLx < -10000 then BLx = -10000 end
|
|
if BLy > 10000 then BLy = 10000 elseif BLy < -10000 then BLy = -10000 end
|
|
if TRx > 10000 then TRx = 10000 elseif TRx < -10000 then TRx = -10000 end
|
|
if TRy > 10000 then TRy = 10000 elseif TRy < -10000 then TRy = -10000 end
|
|
if BRx > 10000 then BRx = 10000 elseif BRx < -10000 then BRx = -10000 end
|
|
if BRy > 10000 then BRy = 10000 elseif BRy < -10000 then BRy = -10000 end
|
|
|
|
-- Set texture coordinates and anchors
|
|
T:ClearAllPoints()
|
|
T:SetTexCoord(TLx, TLy, BLx, BLy, TRx, TRy, BRx, BRy)
|
|
T:SetPoint("BOTTOMLEFT", C, relPoint, cx - Bwid, cy - Bhgt)
|
|
T:SetPoint("TOPRIGHT", C, relPoint, cx + Bwid, cy + Bhgt)
|
|
T:Show()
|
|
return T
|
|
end
|
|
|
|
--Thanks to Celandro
|
|
function lib:DrawVLine(C, x, sy, ey, w, color, layer)
|
|
local relPoint = "BOTTOMLEFT"
|
|
|
|
if not C.GraphLib_Lines then
|
|
C.GraphLib_Lines = {}
|
|
C.GraphLib_Lines_Used = {}
|
|
end
|
|
|
|
local T = tremove(C.GraphLib_Lines) or C:CreateTexture(nil, "ARTWORK")
|
|
T:SetTexture(TextureDirectory.."sline")
|
|
tinsert(C.GraphLib_Lines_Used, T)
|
|
|
|
T:SetDrawLayer(layer or "ARTWORK")
|
|
|
|
T:SetVertexColor(color[1], color[2], color[3], color[4])
|
|
|
|
if sy > ey then
|
|
sy, ey = ey, sy
|
|
end
|
|
|
|
-- Set texture coordinates and anchors
|
|
T:ClearAllPoints()
|
|
T:SetTexCoord(1, 0, 0, 0, 1, 1, 0, 1)
|
|
T:SetPoint("BOTTOMLEFT", C, relPoint, x - w / 2, sy)
|
|
T:SetPoint("TOPRIGHT", C, relPoint, x + w / 2, ey)
|
|
T:Show()
|
|
return T
|
|
end
|
|
|
|
function lib:DrawHLine(C, sx, ex, y, w, color, layer)
|
|
local relPoint = "BOTTOMLEFT"
|
|
|
|
if not C.GraphLib_Lines then
|
|
C.GraphLib_Lines = {}
|
|
C.GraphLib_Lines_Used = {}
|
|
end
|
|
|
|
local T = tremove(C.GraphLib_Lines) or C:CreateTexture(nil, "ARTWORK")
|
|
T:SetTexture(TextureDirectory.."sline")
|
|
tinsert(C.GraphLib_Lines_Used, T)
|
|
|
|
T:SetDrawLayer(layer or "ARTWORK")
|
|
|
|
T:SetVertexColor(color[1], color[2], color[3], color[4])
|
|
|
|
if sx > ex then
|
|
sx, ex = ex, sx
|
|
end
|
|
|
|
-- Set texture coordinates and anchors
|
|
T:ClearAllPoints()
|
|
T:SetTexCoord(0, 0, 0, 1, 1, 0, 1, 1)
|
|
T:SetPoint("BOTTOMLEFT", C, relPoint, sx, y - w / 2)
|
|
T:SetPoint("TOPRIGHT", C, relPoint, ex, y + w / 2)
|
|
T:Show()
|
|
return T
|
|
end
|
|
|
|
function lib:HideLines(C)
|
|
if C.GraphLib_Lines then
|
|
for i = #C.GraphLib_Lines_Used, 1, -1 do
|
|
C.GraphLib_Lines_Used[i]:Hide()
|
|
tinsert(C.GraphLib_Lines, tremove(C.GraphLib_Lines_Used))
|
|
end
|
|
end
|
|
end
|
|
|
|
--Two parts to each bar
|
|
function lib:DrawBar(C, sx, sy, ex, ey, color, level)
|
|
local Bar, Tri, barNum, MinY, MaxY
|
|
|
|
--Want sx <= ex if not then flip them
|
|
if sx > ex then
|
|
sx, ex = ex, sx
|
|
sy, ey = ey, sy
|
|
end
|
|
|
|
if not C.GraphLib_Bars then
|
|
C.GraphLib_Bars = {}
|
|
C.GraphLib_Tris = {}
|
|
C.GraphLib_Bars_Used = {}
|
|
C.GraphLib_Tris_Used = {}
|
|
C.GraphLib_Frames = {}
|
|
end
|
|
|
|
if (#C.GraphLib_Bars) > 0 then
|
|
Bar = C.GraphLib_Bars[#C.GraphLib_Bars]
|
|
tremove(C.GraphLib_Bars, #C.GraphLib_Bars)
|
|
Bar:Show()
|
|
|
|
Tri = C.GraphLib_Tris[#C.GraphLib_Tris]
|
|
tremove(C.GraphLib_Tris, #C.GraphLib_Tris)
|
|
Tri:Show()
|
|
end
|
|
|
|
if not Bar then
|
|
Bar = C:CreateTexture(nil, "ARTWORK")
|
|
Bar:SetColorTexture(1, 1, 1, 1)
|
|
|
|
Tri = C:CreateTexture(nil, "ARTWORK")
|
|
Tri:SetTexture(TextureDirectory.."triangle")
|
|
end
|
|
|
|
tinsert(C.GraphLib_Bars_Used, Bar)
|
|
tinsert(C.GraphLib_Tris_Used, Tri)
|
|
|
|
if level then
|
|
if type(C.GraphLib_Frames[level]) == "nil" then
|
|
local newLevel = C:GetFrameLevel() + level
|
|
C.GraphLib_Frames[level] = CreateFrame("Frame", nil, C)
|
|
C.GraphLib_Frames[level]:SetFrameLevel(newLevel)
|
|
C.GraphLib_Frames[level]:SetAllPoints(C)
|
|
|
|
if C.TextFrame and C.TextFrame:GetFrameLevel() <= newLevel then
|
|
C.TextFrame:SetFrameLevel(newLevel + 1)
|
|
self.NeedsUpdate = true
|
|
end
|
|
end
|
|
|
|
Bar:SetParent(C.GraphLib_Frames[level])
|
|
Tri:SetParent(C.GraphLib_Frames[level])
|
|
end
|
|
|
|
Bar:SetVertexColor(color[1], color[2], color[3], color[4])
|
|
Tri:SetVertexColor(color[1], color[2], color[3], color[4])
|
|
|
|
|
|
if sy < ey then
|
|
Tri:SetTexCoord(0, 0, 0, 1, 1, 0, 1, 1)
|
|
MinY = sy
|
|
MaxY = ey
|
|
else
|
|
Tri:SetTexCoord(1, 0, 1, 1, 0, 0, 0, 1)
|
|
MinY = ey
|
|
MaxY = sy
|
|
end
|
|
|
|
--Has to be at least 1 wide
|
|
if MinY <= 1 then
|
|
MinY = 1
|
|
end
|
|
|
|
|
|
Bar:ClearAllPoints()
|
|
Bar:SetPoint("BOTTOMLEFT", C, "BOTTOMLEFT", sx, 0)
|
|
|
|
local Width = ex - sx
|
|
if Width < 1 then
|
|
Width = 1
|
|
end
|
|
Bar:SetWidth(Width)
|
|
Bar:SetHeight(MinY)
|
|
|
|
|
|
if (MaxY-MinY) >= 1 then
|
|
Tri:ClearAllPoints()
|
|
Tri:SetPoint("BOTTOMLEFT", C, "BOTTOMLEFT", sx, MinY)
|
|
Tri:SetWidth(Width)
|
|
Tri:SetHeight(MaxY - MinY)
|
|
else
|
|
Tri:Hide()
|
|
end
|
|
end
|
|
|
|
|
|
function lib:HideBars(C)
|
|
if not C.GraphLib_Bars then
|
|
return
|
|
end
|
|
|
|
while (#C.GraphLib_Bars_Used) > 0 do
|
|
C.GraphLib_Bars[#C.GraphLib_Bars + 1] = C.GraphLib_Bars_Used[#C.GraphLib_Bars_Used]
|
|
C.GraphLib_Bars[#C.GraphLib_Bars]:Hide()
|
|
C.GraphLib_Bars_Used[#C.GraphLib_Bars_Used] = nil
|
|
|
|
C.GraphLib_Tris[#C.GraphLib_Tris + 1] = C.GraphLib_Tris_Used[#C.GraphLib_Tris_Used]
|
|
C.GraphLib_Tris[#C.GraphLib_Tris]:Hide()
|
|
C.GraphLib_Tris_Used[#C.GraphLib_Tris_Used] = nil
|
|
end
|
|
end
|
|
|
|
-- lib upgrade stuff, overwrite the old function references in
|
|
-- existing graphs with the ones in this newer library
|
|
for _, graph in ipairs(lib.RegisteredGraphRealtime) do
|
|
SetupGraphRealtimeFunctions(graph, true)
|
|
end
|
|
for _, graph in ipairs(lib.RegisteredGraphLine) do
|
|
SetupGraphLineFunctions(graph)
|
|
end
|
|
for _, graph in ipairs(lib.RegisteredGraphScatterPlot) do
|
|
SetupGraphScatterPlotFunctions(graph)
|
|
end
|
|
for _, graph in ipairs(lib.RegisteredGraphPieChart) do
|
|
SetupGraphPieChartFunctions(graph)
|
|
end
|
|
|
|
---------------------------------------------------
|
|
--Test Functions, for reference for addon authors to test, use and copy
|
|
--To test the library do /script LibStub("LibGraph-2.0"):TestGraph2Lib()
|
|
local function TestRealtimeGraph()
|
|
local Graph = LibStub(major)
|
|
local g = Graph:CreateGraphRealtime("TestRealtimeGraph", UIParent, "CENTER", "CENTER", -90, 90, 150, 150)
|
|
g:SetAutoScale(true)
|
|
g:SetGridSpacing(1.0, 10.0)
|
|
g:SetYMax(120)
|
|
g:SetXAxis(-11, -1)
|
|
g:SetFilterRadius(1)
|
|
g:SetBarColors({0.2, 0.0, 0.0, 0.4}, {1.0, 0.0, 0.0, 1.0})
|
|
|
|
local f = CreateFrame("Frame")
|
|
f:SetScript("OnUpdate", function()
|
|
g:AddTimeData(1)
|
|
end)
|
|
f:Show()
|
|
DEFAULT_CHAT_FRAME:AddMessage("Testing Realtime Graph")
|
|
end
|
|
|
|
local function TestRealtimeGraphRaw()
|
|
local Graph = LibStub(major)
|
|
local g = Graph:CreateGraphRealtime("TestRealtimeGraph", UIParent, "TOP", "TOP", 0, 0, 150, 150)
|
|
g:SetAutoScale(true)
|
|
g:SetGridSpacing(1.0, 10.0)
|
|
g:SetYMax(120)
|
|
g:SetXAxis(-10, 0)
|
|
g:SetMode("RAW")
|
|
g:SetBarColors({0.2, 0.0, 0.0, 0.4}, {1.0, 0.0, 0.0, 1.0})
|
|
|
|
local f = CreateFrame("Frame")
|
|
f.frames = 0
|
|
f.NextUpdate = GetTime()
|
|
f:SetScript("OnUpdate",function()
|
|
if f.NextUpdate > GetTime() then
|
|
return
|
|
end
|
|
|
|
g:AddBar(UnitHealth("player"))
|
|
f.NextUpdate = f.NextUpdate + g.BarWidth
|
|
end)
|
|
f:Show()
|
|
DEFAULT_CHAT_FRAME:AddMessage("Testing 0")
|
|
end
|
|
|
|
local function TestLineGraph()
|
|
local Graph = LibStub(major)
|
|
local g = Graph:CreateGraphLine("TestLineGraph", UIParent, "CENTER", "CENTER", 90, 90, 150, 150)
|
|
g:SetXAxis(-1, 1)
|
|
g:SetYAxis(-1, 1)
|
|
g:SetGridSpacing(0.25, 0.25)
|
|
g:SetGridColor({0.5, 0.5, 0.5, 0.5})
|
|
g:SetAxisDrawing(true, true)
|
|
g:SetAxisColor({1.0, 1.0, 1.0, 1.0})
|
|
g:SetAutoScale(true)
|
|
|
|
local Data1 = {{0.05, 0.05}, {0.2, 0.3}, {0.4, 0.2}, {0.9, 0.6}}
|
|
local Data2 = {{0.05, 0.8}, {0.3, 0.1}, {0.5, 0.4}, {0.95, 0.05}}
|
|
|
|
g:AddDataSeries(Data1,{1.0, 0.0, 0.0, 0.8})
|
|
g:AddDataSeries(Data2,{0.0, 1.0, 0.0, 0.8})
|
|
DEFAULT_CHAT_FRAME:AddMessage("Testing Line Graph")
|
|
end
|
|
|
|
local function TestScatterPlot()
|
|
local Graph = LibStub(major)
|
|
local g = Graph:CreateGraphScatterPlot("TestScatterPlot", UIParent, "CENTER", "CENTER", 90, -90, 150, 150)
|
|
g:SetXAxis(-1, 1)
|
|
g:SetYAxis(-1, 1)
|
|
g:SetGridSpacing(0.25, 0.25)
|
|
g:SetGridColor({0.5, 0.5, 0.5, 0.5})
|
|
g:SetAxisDrawing(true, true)
|
|
g:SetAxisColor({1.0, 1.0, 1.0, 1.0})
|
|
g:SetLinearFit(true)
|
|
g:SetAutoScale(true)
|
|
|
|
local Data1 = {{0.05, 0.05}, {0.2, 0.3}, {0.4, 0.2}, {0.9, 0.6}}
|
|
local Data2 = {{0.05, 0.8}, {0.3, 0.1}, {0.5, 0.4}, {0.95, 0.05}}
|
|
|
|
g:AddDataSeries(Data1,{1.0, 0.0, 0.0, 0.8})
|
|
g:AddDataSeries(Data2,{0.0, 1.0, 0.0, 0.8})
|
|
DEFAULT_CHAT_FRAME:AddMessage("Testing Scatter Plot")
|
|
end
|
|
|
|
local function TestPieChart()
|
|
local Graph = LibStub(major)
|
|
local g = Graph:CreateGraphPieChart("TestPieChart", UIParent, "CENTER", "CENTER", -90, -90, 150, 150)
|
|
g:AddPie(35, {1.0, 0.0, 0.0})
|
|
g:AddPie(21, {0.0, 1.0, 0.0})
|
|
g:AddPie(10, {1.0, 1.0, 1.0})
|
|
g:CompletePie({0.2, 0.2, 1.0})
|
|
DEFAULT_CHAT_FRAME:AddMessage("Testing Pie Chart")
|
|
end
|
|
|
|
function lib:TestGraph2Lib()
|
|
DEFAULT_CHAT_FRAME:AddMessage("Testing "..major..", "..gsub(minor, "%$", ""))
|
|
TestRealtimeGraph()
|
|
TestLineGraph()
|
|
TestScatterPlot()
|
|
TestPieChart()
|
|
end
|
|
|