--[[ Name: LibGraph-2.0 Revision: $Rev: 56 $ 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: 58 $"):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 = "Interface\\AddOns\\"..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") --beta doing some errors here if (Details) then TextureDirectory = [[Interface\AddOns\Details\Libs\LibGraph-2.0]] end end end TextureDirectory = "Interface\\Addons\\Details\\Libs\\LibGraph-2.0" 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 = tremove 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 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