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

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