diff --git a/Definitions.lua b/Definitions.lua index dca03186..ae0cb051 100644 --- a/Definitions.lua +++ b/Definitions.lua @@ -6,6 +6,50 @@ ---| "TOOLBAR" ---| "STATUSBAR" +---@alias detailsevent +---| "DETAILS_INSTANCE_OPEN" +---| "DETAILS_INSTANCE_CLOSE" +---| "DETAILS_INSTANCE_SIZECHANGED" +---| "DETAILS_INSTANCE_STARTRESIZE" +---| "DETAILS_INSTANCE_ENDRESIZE" +---| "DETAILS_INSTANCE_STARTSTRETCH" +---| "DETAILS_INSTANCE_ENDSTRETCH" +---| "DETAILS_INSTANCE_CHANGESEGMENT" +---| "DETAILS_INSTANCE_CHANGEATTRIBUTE" +---| "DETAILS_INSTANCE_CHANGEMODE" +---| "DETAILS_INSTANCE_NEWROW" +---| "DETAILS_OPTIONS_MODIFIED" +---| "DETAILS_DATA_RESET" +---| "DETAILS_DATA_SEGMENTREMOVED" +---| "COMBAT_ENCOUNTER_START" +---| "COMBAT_ENCOUNTER_END" +---| "COMBAT_PLAYER_ENTER" +---| "COMBAT_PLAYER_LEAVE" +---| "COMBAT_PLAYER_TIMESTARTED" +---| "COMBAT_BOSS_WIPE" +---| "COMBAT_BOSS_DEFEATED" +---| "COMBAT_BOSS_FOUND" +---| "COMBAT_INVALID" +---| "COMBAT_PREPOTION_UPDATED" +---| "COMBAT_CHARTTABLES_CREATING" +---| "COMBAT_CHARTTABLES_CREATED" +---| "COMBAT_ENCOUNTER_PHASE_CHANGED" +---| "COMBAT_ARENA_START" +---| "COMBAT_ARENA_END" +---| "COMBAT_MYTHICDUNGEON_START" +---| "COMBAT_MYTHICDUNGEON_END" +---| "GROUP_ONENTER" +---| "GROUP_ONLEAVE" +---| "ZONE_TYPE_CHANGED" +---| "REALM_CHANNEL_ENTER" +---| "REALM_CHANNEL_LEAVE" +---| "COMM_EVENT_RECEIVED" +---| "COMM_EVENT_SENT" +---| "UNIT_SPEC" +---| "UNIT_TALENTS" +---| "PLAYER_TARGET" +---| "DETAILS_PROFILE_APPLYED" + ---@alias containertype number this container type is the number used to identify the actorcontainer type when using combat:GetContainer(containertype), can be 1, 2, 3, or 4. ---@class details @@ -18,8 +62,19 @@ ---@field CreateEventListener fun(self: details) : table ---@class detailseventlistener : table ----@field RegisterEvent fun(self: detailseventlistener, event: "DETAILS_INSTANCE_OPEN"|"DETAILS_INSTANCE_CLOSE"|"DETAILS_INSTANCE_SIZECHANGED"|"DETAILS_INSTANCE_STARTRESIZE"|"DETAILS_INSTANCE_ENDRESIZE"|"DETAILS_INSTANCE_STARTSTRETCH"|"DETAILS_INSTANCE_ENDSTRETCH"|"DETAILS_INSTANCE_CHANGESEGMENT"|"DETAILS_INSTANCE_CHANGEATTRIBUTE"|"DETAILS_INSTANCE_CHANGEMODE"|"DETAILS_INSTANCE_NEWROW"|"DETAILS_OPTIONS_MODIFIED"|"DETAILS_DATA_RESET"|"DETAILS_DATA_SEGMENTREMOVED"|"COMBAT_ENCOUNTER_START"|"COMBAT_ENCOUNTER_END"|"COMBAT_PLAYER_ENTER"|"COMBAT_PLAYER_LEAVE"|"COMBAT_PLAYER_TIMESTARTED"|"COMBAT_BOSS_WIPE"|"COMBAT_BOSS_DEFEATED"|"COMBAT_BOSS_FOUND"|"COMBAT_INVALID"|"COMBAT_PREPOTION_UPDATED"|"COMBAT_CHARTTABLES_CREATING"|"COMBAT_CHARTTABLES_CREATED"|"COMBAT_ENCOUNTER_PHASE_CHANGED"|"COMBAT_ARENA_START"|"COMBAT_ARENA_END"|"COMBAT_MYTHICDUNGEON_START"|"COMBAT_MYTHICDUNGEON_END"|"GROUP_ONENTER"|"GROUP_ONLEAVE"|"ZONE_TYPE_CHANGED"|"REALM_CHANNEL_ENTER"|"REALM_CHANNEL_LEAVE"|"COMM_EVENT_RECEIVED"|"COMM_EVENT_SENT"|"UNIT_SPEC"|"UNIT_TALENTS"|"PLAYER_TARGET"|"DETAILS_PROFILE_APPLYED", callback: function) ----@field UnregisterEvent fun(self: detailseventlistener, event: "DETAILS_INSTANCE_OPEN"|"DETAILS_INSTANCE_CLOSE"|"DETAILS_INSTANCE_SIZECHANGED"|"DETAILS_INSTANCE_STARTRESIZE"|"DETAILS_INSTANCE_ENDRESIZE"|"DETAILS_INSTANCE_STARTSTRETCH"|"DETAILS_INSTANCE_ENDSTRETCH"|"DETAILS_INSTANCE_CHANGESEGMENT"|"DETAILS_INSTANCE_CHANGEATTRIBUTE"|"DETAILS_INSTANCE_CHANGEMODE"|"DETAILS_INSTANCE_NEWROW"|"DETAILS_OPTIONS_MODIFIED"|"DETAILS_DATA_RESET"|"DETAILS_DATA_SEGMENTREMOVED"|"COMBAT_ENCOUNTER_START"|"COMBAT_ENCOUNTER_END"|"COMBAT_PLAYER_ENTER"|"COMBAT_PLAYER_LEAVE"|"COMBAT_PLAYER_TIMESTARTED"|"COMBAT_BOSS_WIPE"|"COMBAT_BOSS_DEFEATED"|"COMBAT_BOSS_FOUND"|"COMBAT_INVALID"|"COMBAT_PREPOTION_UPDATED"|"COMBAT_CHARTTABLES_CREATING"|"COMBAT_CHARTTABLES_CREATED"|"COMBAT_ENCOUNTER_PHASE_CHANGED"|"COMBAT_ARENA_START"|"COMBAT_ARENA_END"|"COMBAT_MYTHICDUNGEON_START"|"COMBAT_MYTHICDUNGEON_END"|"GROUP_ONENTER"|"GROUP_ONLEAVE"|"ZONE_TYPE_CHANGED"|"REALM_CHANNEL_ENTER"|"REALM_CHANNEL_LEAVE"|"COMM_EVENT_RECEIVED"|"COMM_EVENT_SENT"|"UNIT_SPEC"|"UNIT_TALENTS"|"PLAYER_TARGET"|"DETAILS_PROFILE_APPLYED") +---@field RegisterEvent fun(self: detailseventlistener, event: detailsevent, callback: function) +---@field UnregisterEvent fun(self: detailseventlistener, event: detailsevent) + +---@class deathtable : table +---@field key1 any[] what happened to the player before death +---@field key2 number unix time +---@field key3 string player name +---@field key4 string player class +---@field key5 number max health +---@field key6 string time of death as string +---@field dead boolean just a boolean to indicate this is a death table +---@field last_cooldown {key1: unixtime, key2: spellid} +---@field dead_at number combatElapsedTime ---@class customspellinfo : {name: string, isPassive: boolean, itemId: number, icon: string|number} ---@class customiteminfo: {itemId: number, isPassive: boolean} @@ -43,6 +98,8 @@ ---@field totals {key1: table, key2: table, key3: table, key3: table} ---@field totals_grupo {key1: table, key2: table, key3: table, key3: table} ---@field __destroyed boolean +---@field PhaseData table +---@field is_boss table ---@field GetTimeData fun(dataName: string) : table ---@field GetPhases fun(combat: combat) : table ---@field GetCombatTime fun(combat) : number diff --git a/Libs/DF/button.lua b/Libs/DF/button.lua index ee0ffb93..eae2f3f1 100644 --- a/Libs/DF/button.lua +++ b/Libs/DF/button.lua @@ -1317,7 +1317,7 @@ detailsFramework.CloseButtonMixin = { ---@param parent frame ---@param frameName string|nil ---@return df_closebutton -function detailsFramework:CreateCloseButton(parent, frameName) +function detailsFramework:CreateCloseButton(parent, frameName) --make documentation ---@type df_closebutton local closeButton = CreateFrame("button", frameName, parent, "UIPanelCloseButton") closeButton:SetFrameLevel(parent:GetFrameLevel() + 1) diff --git a/Libs/DF/charts.examples.lua b/Libs/DF/charts.examples.lua new file mode 100644 index 00000000..589dcf09 --- /dev/null +++ b/Libs/DF/charts.examples.lua @@ -0,0 +1,109 @@ + +--documentation: see the header of the file charts.lua + + +--1º example: making a simple chart, just copy and paste this code into a lua file and run it +do + local ChartFrameTest = ChartFrameExample1 or DetailsFramework:CreateGraphicLineFrame(UIParent, "ChartFrameExample1") + ChartFrameTest:SetPoint("left", UIParent, "left", 10, 0) --set the position of the chart + ChartFrameTest:SetSize(800, 600) --set the size of the chart + DetailsFramework:ApplyStandardBackdrop(ChartFrameTest) --apply a backdrop to this example hence see the frame size + + --set the data (required) + local data = {1, 2, 30, 25, 6, 5, 4, 8, 7, 4, 1, 12, 15, 24, 18, 17, 14, 15, 8, 4, 14, 42, 22, 25, 30, 35, 39, 8, 7, 4, 1, 2, 5, 4, 8, 7, 4, 12, 12, 4} + local smoothnessLevel = 1 --(optional, default: 1) + ChartFrameTest:SetData(data, smoothnessLevel) + --draw the chart + ChartFrameTest:Plot() +end + +--2º example: setting the color, thickness and scale of the line: +do + local ChartFrameTest = ChartFrameExample2 or DetailsFramework:CreateGraphicLineFrame(UIParent, "ChartFrameExample2") + ChartFrameTest:SetPoint("left", UIParent, "left", 10, 0) --set the position of the chart + ChartFrameTest:SetSize(800, 600) --set the size of the chart + DetailsFramework:ApplyStandardBackdrop(ChartFrameTest) --apply a backdrop to this example hence see the frame size + + --set the line thickness (optional, default: 2) + local lineThickness = 3 + ChartFrameTest:SetLineThickness(lineThickness) + + --set the chart color (optional, default: "white") + local lineColor = {r = 1, g = 1, b = 0} --set it to "yellow" + ChartFrameTest:SetColor(lineColor) --using {r = 1, g = 1, b = 0} + ChartFrameTest:SetColor("yellow") --using the color name + ChartFrameTest:SetColor(1, 1, 0) --passing the rgb directly + ChartFrameTest:SetColor({1, 1, 0}) --using an index table + + --set the data (required) + local data = {1, 2, 30, 25, 6, 5, 4, 8, 7, 4, 1, 12, 15, 24 ,18, 17 ,14, 15, 8 , 4, 14, 42, 22, 25, 30, 35, 39, 8, 7, 4, 1, 2, 5, 4 ,8, 7 ,4, 12, 12 , 4} + local smoothnessLevel = 1 --(optional, default: 1) + ChartFrameTest:SetData(data, smoothnessLevel) + + --height modifier, if for some reason need to scale the chart height + local heightScale = 1 --(optional, default: 1) + --draw the chart + ChartFrameTest:Plot(heightScale) +end + +--3º example: setting the axes lines and labels +do + local ChartFrameTest = ChartFrameExample3 or DetailsFramework:CreateGraphicLineFrame(UIParent, "ChartFrameExample3") + ChartFrameTest:SetPoint("left", UIParent, "left", 10, 0) + ChartFrameTest:SetSize(800, 600) + DetailsFramework:ApplyStandardBackdrop(ChartFrameTest) + + --create guide lines in the left and bottom of the chart + local xOffset = 48 --pixels from the left border of the chart + local yOffset = 28 --pixels from the bottom border of the chart + local whichSide = "left" --which side of vertical line should be placed + local thickness = 1 + local amountYLabels = 10 --amounf of texts indicating the scale of the chart + local amountXLabels = 10 + local r, g, b, a = 1, 1, 1, 1 + ChartFrameTest:CreateAxesLines(xOffset, yOffset, whichSide, thickness, amountYLabels, amountXLabels, r, g, b, a) + + --the labels in the bottom line can be 'time', 'number' or 'value' + ChartFrameTest:SetXAxisDataType("time") + --set the data to be used in the bottom line labels, how the data is formatted depends on the type set above + ChartFrameTest:SetXAxisData(10) --with type 'time' the chart interprets this as seconds and shows 1:00 to 10:00 + + ChartFrameTest:SetXAxisDataType("number") + ChartFrameTest:SetXAxisData(600) --the chart interprets this as a 'number' type and displays it as 60, 120, 180. + + ChartFrameTest:SetXAxisDataType("value") + ChartFrameTest:SetXAxisData("hello", "world", 1, 2, 3, 4, "chart", 0, 1, 0) --and 'value' show the values passed + + --setting the data, doesn't matter if it is set at the top or right before Plot() + local data = {1, 2, 30, 25, 6, 5, 4, 8, 7, 4, 1, 12, 15, 24 ,18, 17 ,14, 15, 8 , 4, 14, 42, 22, 25, 30, 35, 39, 8, 7, 4, 1, 2, 5, 4 ,8, 7 ,4, 12, 12 , 4} + ChartFrameTest:SetData(data) --smoothnessLevel is absent here, it'll use 1 as default + ChartFrameTest:Plot() +end + +--4º example: a multi line chart is a chart which supports multiple lines, each line can have a different color, name, smoothnessLevel and thickness +do + local ChartFrameTest = ChartFrameExample4 or DetailsFramework:CreateGraphicMultiLineFrame(UIParent, "ChartFrameExample4") + ChartFrameTest:SetPoint("left", UIParent, "left", 10, 0) + ChartFrameTest:SetSize(800, 600) + DetailsFramework:ApplyStandardBackdrop(ChartFrameTest) + + --when using multi-line, the Reset() function instructs the chart to discard the previous data as new data is about to be added + ChartFrameTest:Reset() + + --smoothnessLevel, name, red, green, blue, alpha + local smoothnessLevel = 2 --(optional, default: 0) + local line1Name, line2Name, line3Name = "Line 1", "Line 2", "Line 3" --show the line name at the top right corner (optional, default none) + local line1Color, line2Color, line3Color = "lime", "purple", "orange" --(optional, default "white") + + --add data into the chart (it plots a line for each data added when :Plot() is called) + local data1 = {1, 2, 30, 25, 6, 5, 4, 8, 7, 4, 1, 12, 15, 24 ,18, 17 ,14, 15, 8 , 4, 14, 42, 22, 25, 30, 35, 39, 8, 7, 4, 1, 2, 5, 4} + ChartFrameTest:AddData(data1, smoothnessLevel, line1Name, line1Color) + + local data2 = {3, 5, 20, 25, 6, 5, 15, 18, 12, 14, 11, 8, 7, 8 ,7, 4 ,1, 25, 26 , 30, 28, 20, 22, 25, 20, 15, 10, 8, 7, 4, 1, 2, 5, 4} + ChartFrameTest:AddData(data2, smoothnessLevel, line2Name, line2Color) + + local data3 = {5, 7, 15, 30, 6, 2, 10, 13, 10, 5, 11, 8, 7, 5, 3, 1, 1, 8, 10 , 12, 15, 20, 25, 25, 20, 17, 12, 7, 7, 6, 4, 5, 6, 5} + ChartFrameTest:AddData(data3, smoothnessLevel, line3Name, line3Color) + + ChartFrameTest:Plot() +end \ No newline at end of file diff --git a/Libs/DF/charts.lua b/Libs/DF/charts.lua index 453bc836..b640e237 100644 --- a/Libs/DF/charts.lua +++ b/Libs/DF/charts.lua @@ -6,48 +6,95 @@ end local CreateFrame = CreateFrame local unpack = unpack +local wipe = table.wipe local _ +---@class chart_guideline : fontstring +---@field circleTexture texture +---@field guideLine line + +---@class chart_nameindicator : frame +---@field Texture texture +---@field Label fontstring + +---@alias x_axisdatatype +---| "time" when setting the text into the labels, it will be converted into a time format +---| "number" same as timer, but the number is not comverted to time +---| "value" a fixed table with values is passed by the SetXAxisData() function + ---@class df_chartshared: table ---@field yAxisLine line the vertical line which can be anchored in the left or right side of the frame, if the chart is a multi chart, this line is shared by all charts ----@field xAxisLine line ----@field lineThickness number ----@field yAxisLabels fontstring[] ----@field xAxisLabels fontstring[] ----@field SetAxisColor fun(self: df_chartmulti, red: number|string|table|nil, green: number|nil, blue: number|nil, alpha: number|nil) : boolean set the color of both axis lines ----@field SetAxisThickness fun(self: df_chartmulti, thickness: number) : boolean set the thickness of both axis lines +---@field xAxisLine line the horizontal line which can be anchored in the top or bottom side of the frame, if the chart is a multi chart, this line is shared by all charts +---@field xAxisDataNumber any if the data type of the x axis is "number" or "time" +---@field xAxisDataValues table if the data type of the x axis is "value" +---@field xAxisDataType x_axisdatatype the data type of the x axis, if time, the x axis will be a time axis, if value, the x axis will be a value axis +---@field yAxisLabels chart_guideline[] the vertical axis labels to indicate the values of the chart data +---@field xAxisLabels fontstring[] the horizontal axis labels to indicate the values of the chart data +---@field plotFrame frame the plot frame which is the frame that will hold the chart lines +---@field lineThickness number the thickness of the chart lines +---@field chartLeftOffset number the offset of the left side of the chart frame to the plot frame +---@field chartBottomOffset number the offset of the bottom side of the chart frame to the plot frame +---@field xAxisLabelsYOffset number default: -6, the offset of the horizontal axis labels to the horizontal axis line (y coordinate) +---@field smoothnessLevel number default: 0, the smoothness level of the chart lines, 0 is no smoothness +---@field SetAxesColor fun(self: df_chartmulti, red: number|string|table|nil, green: number|nil, blue: number|nil, alpha: number|nil) : boolean set the color of both axis lines +---@field SetAxesThickness fun(self: df_chartmulti, thickness: number) : boolean set the thickness of both axis lines +---@field CreateAxesLines fun(self: df_chartmulti|df_chart, xOffset: number, yOffset: number, whichSide: "left"|"right", thickness: number, amountYLabels: number, amountXLabels: number, red: any, green: number|nil, blue: number|nil, alpha: number|nil) +---@field SetXAxisDataType fun(self: df_chartmulti|df_chart, dataType: x_axisdatatype) : boolean set the data type of the x axis, if time, the x axis will be a time axis, if value, the x axis will be a value axis +---@field SetXAxisData fun(self: df_chartmulti|df_chart, data: any) set the data of the x axis, if time, the x axis will be a time axis, if value, the x axis will be a value axis +---@field SharedContrustor fun(self: df_chartmulti|df_chart) set default values for fields used on both chart types +---@field IsMultiChart fun(self: df_chartmulti|df_chart) : boolean return true if the chart is a multi chart + +---@param self df_chart|df_chartmulti +local chartFrameSharedConstructor = function(self) + self.xAxisDataType = "number" + self.lineThickness = 2 + self.xAxisDataNumber = 0 + self.xAxisDataValues = {} + self.xAxisLabels = {} + self.yAxisLabels = {} + self.chartLeftOffset = 0 + self.chartBottomOffset = 0 + self.xAxisLabelsYOffset = -6 + self.smoothnessLevel = 0 +end ---@class df_chart: frame, df_data, df_value, df_chartshared ---@field _dataInfo df_data ---@field color number[] red green blue alpha +---@field height number ---@field nextLine number ---@field minValue number ---@field maxValue number ---@field data number[] ---@field lines line[] +---@field fixedLineWidth number +---@field chartName string ---@field ChartFrameConstructor fun(self: df_chart) set the default values for the chart frame ---@field GetLine fun(self: df_chart) : line return a line and also internally handle next line ---@field GetLines fun(self: df_chart) : line[] return a table with all lines already created ---@field GetLineWidth fun(self: df_chart) : number calculate the width of each drawn line ---@field SetLineWidth fun(self: df_chart, width: number) set the line width to a fixed value ----@field Plot fun(self: df_chart) draw the graphic using lines and following the data set by SetData() ---@field GetAmountLines fun(self: df_chart) : number return the amount of lines in use ---@field OnSizeChanged fun(self: df_chart) ---@field HideLines fun(self: df_chart) hide all lines already created ---@field Reset fun(self: df_chart) hide all lines and reset the next line to 1 ---@field SetColor fun(self: df_chart, r: number|string|table|nil, g: number|nil, b: number|nil, a: number|nil) set the color for the lines +---@field GetColor fun(self: df_chart) : red, green, blue, alpha ---@field SetLineThickness fun(self: df_chart, thickness: number) set the line thickness ----@field CalcYAxisPointForValue fun(self: df_chart, value: number) +---@field CalcYAxisPointForValue fun(self: df_chart, value: number, plotFrameHeightScaled: number) : number ---@field UpdateFrameSizeCache fun(self: df_chart) +---@field Plot fun(self: df_chart, yPointScale: number|nil, bUpdateLabels: boolean|nil) draw the graphic using lines and following the data set by SetData() or AddData() in multi chart ---@class df_chartmulti : df_chart, df_chartshared ---@field chartFrames df_chart[] ---@field nextChartselframe number ---@field biggestDataValue number +---@field nextChartFrame number +---@field lineNameIndicators chart_nameindicator[] ---@field MultiChartFrameConstructor fun(self: df_chartmulti) ---@field GetCharts fun(self: df_chartmulti) : df_chart[] ---@field GetChart fun(self: df_chartmulti) : df_chart ----@field AddData fun(self: df_chartmulti, data: table, red: number|string|table|nil, green: number|nil, blue: number|nil, alpha: number|nil) +---@field AddData fun(self: df_chartmulti, data: table, name: string, red: any, green: number|nil, blue: number|nil, alpha: number|nil) ---@field GetAmountCharts fun(self: df_chartmulti): number ---@field HideCharts fun(self: df_chartmulti) ---@field Reset fun(self: df_chartmulti) @@ -55,44 +102,75 @@ local _ ---@field SetMaxDataSize fun(self: df_chartmulti, dataSize: number) ---@field GetMaxDataSize fun(self: df_chartmulti) ---@field SetLineThickness fun(self: df_chart, thickness: number) set the line thickness for all chart frames ----@field Plot fun(self: df_chartmulti) +---@field UpdateChartNamesIndicator fun(self: df_chartmulti) if the chart names has been passed while adding data, this function will update the chart names indicator +---@field Plot fun(self: df_chartmulti) draw the graphic using lines and following the data set by SetData() or AddData() in multi chart -detailsFramework.ChartFrameSharedMixin = { - ---set the color of both axis lines - ---@param self df_chart|df_chartmulti - ---@param red any - ---@param green number|nil - ---@param blue number|nil - ---@param alpha number|nil - ---@return boolean bColorChanged return true if the color was set, false if the axis lines are not created yet - SetAxisColor = function(self, red, green, blue, alpha) - if (not self.yAxisLine) then - return false - end - red, green, blue, alpha = detailsFramework:ParseColors(red, green, blue, alpha) - self.yAxisLine:SetColorTexture(red, green, blue, alpha) - self.xAxisLine:SetColorTexture(red, green, blue, alpha) - return true - end, +---create the plot frame which is the frame that will hold the chart lines +---@param self df_chartmulti|df_chart +---@return frame +local createPlotFrame = function(self) + local plotFrame = CreateFrame("frame", "$parentPlotFrame", self, "BackdropTemplate") + plotFrame:SetAllPoints() + self.plotFrame = plotFrame + return plotFrame +end - ---set the thickness of both axis lines - ---@param self df_chart|df_chartmulti - ---@param thickness number - ---@return boolean bThicknessChanged return true if the thickness was set, false if the axis lines are not created yet - SetAxisThickness = function(self, thickness) - if (not self.yAxisLine) then - return false - end - self.yAxisLine:SetThickness(thickness) - self.xAxisLine:SetThickness(thickness) - return true - end, -} +---generate the vertical axis labels to indicate the values of the chart data +---@param parent frame +---@param amountLabels number +---@param labelsTable chart_guideline[] +---@param red number +---@param green number +---@param blue number +---@param alpha number +local createVerticalAxisLabels = function(parent, amountLabels, labelsTable, red, green, blue, alpha) + for i = 1, amountLabels do + ---@type fontstring + local label = parent:CreateFontString("$parentYAxisLabel" .. i, "overlay", "GameFontNormal") + ---@cast label chart_guideline ---> functions shared by both single and multi chart frames + label:SetJustifyH("right") + label:SetTextColor(red, green, blue, alpha) + detailsFramework:SetFontSize(label, 11) + table.insert(labelsTable, label) + + local circleTexture = parent:CreateTexture("$parentYAxisLabel" .. i .. "CircleTexture", "border") + circleTexture:SetSize(4, 4) + circleTexture:SetTexture([[Interface\CHARACTERFRAME\TempPortraitAlphaMaskSmall]]) + circleTexture:SetVertexColor(red, green, blue, alpha) + circleTexture:SetPoint("right", label, "right", 5, 0) + + local guideLine = parent:CreateLine("$parentYAxisLabel" .. i .. "GuideLine", "border") + guideLine:SetThickness(1) + guideLine:SetColorTexture(red, green, blue, 0.05) + + label.circleTexture = circleTexture + label.guideLine = guideLine + end +end + +---generate the horizontal axis labels to indicate the values of the chart data +---@param parent frame +---@param amountLabels number +---@param labelsTable fontstring[] +---@param red number +---@param green number +---@param blue number +---@param alpha number +local createHorizontalAxisLabels = function(parent, amountLabels, labelsTable, red, green, blue, alpha) + for i = 1, amountLabels do + local label = parent:CreateFontString("$parentXAxisLabel" .. i, "overlay", "GameFontNormal") + label:SetJustifyH("left") + label:SetTextColor(red, green, blue, alpha) + detailsFramework:SetFontSize(label, 11) + table.insert(labelsTable, label) + end +end ---create the x and y axis lines with their labels ---@param self df_chart|df_chartmulti +---@param xOffset number +---@param yOffset number ---@param whichSide "left"|"right" ---@param thickness number ---@param amountYLabels number @@ -102,58 +180,217 @@ detailsFramework.ChartFrameSharedMixin = { ---@param blue number|nil ---@param alpha number|nil ---@return boolean -local createAxysLines = function(self, whichSide, thickness, amountYLabels, amountXLabels, red, green, blue, alpha) +local createAxesLines = function(self, xOffset, yOffset, whichSide, thickness, amountYLabels, amountXLabels, red, green, blue, alpha) if (self.axisCreated) then return false end - self.yAxisLabels = {} - self.xAxisLabels = {} + local plotFrame = self.plotFrame - --this is the vertical line which can be anchored in the left or right side of the frame, it separates the chart lines from the measurements texts + self.chartLeftOffset = xOffset or 48 + self.chartBottomOffset = yOffset or 28 + whichSide = whichSide or "left" + thickness = thickness or 1 + amountYLabels = amountYLabels or 10 + amountXLabels = amountXLabels or 10 + red = red or 1 + green = green or 1 + blue = blue or 1 + alpha = alpha or 1 + + --adjust the plotFrame size and point taking in consideration of the left and bottom offsets, this is done to free space for the axis labels + plotFrame:SetSize(self:GetWidth() - self.chartLeftOffset - 10, self:GetHeight() - self.chartBottomOffset - 20) + plotFrame:ClearAllPoints() + plotFrame:SetPoint("topleft", self, "topleft", self.chartLeftOffset, -1) + plotFrame:SetPoint("bottomright", self, "bottomright", -1, self.chartBottomOffset) + + --this is the vertical line which can be anchored in the left or right side of the frame, it separates the chart lines from the labels ---@type line - local yAxisLine = self:CreateLine("$parentYAxisLine", "overlay") + local yAxisLine = plotFrame:CreateLine("$parentYAxisLine", "overlay") self.yAxisLine = yAxisLine - --and the horizontal line which is always anchored in the bottom of the frame ---@type line - local xAxisLine = self:CreateLine("$parentXAxisLine", "overlay") + local xAxisLine = plotFrame:CreateLine("$parentXAxisLine", "overlay") self.xAxisLine = xAxisLine + --vertical axis point + if (whichSide == "left") then + yAxisLine:SetStartPoint("topleft", plotFrame, 0, -1) + yAxisLine:SetEndPoint("bottomleft", plotFrame, 0, self.chartBottomOffset * -1) + else + yAxisLine:SetStartPoint("topright", plotFrame, 0, -1) + yAxisLine:SetEndPoint("bottomleft", plotFrame, 0, self.chartBottomOffset) + end + + --horizontal axis point + xAxisLine:SetStartPoint("bottomleft", plotFrame, self.chartLeftOffset * -1, 0) + xAxisLine:SetEndPoint("bottomright", plotFrame, -1, 0) + red, green, blue, alpha = detailsFramework:ParseColors(red, green, blue, alpha) + self:SetAxesColor(red, green, blue, alpha) - self:SetAxisColor(red, green, blue, alpha) - self:SetAxisThickness(thickness) + --set the thickness of the both axis lines + self:SetAxesThickness(thickness) - --create the labels in the vertical axis line - for i = 1, amountYLabels do - local label = self:CreateFontString("$parentYAxisLabel" .. i, "overlay", "GameFontNormal") - label:SetJustifyH("right") - label:SetTextColor(red, green, blue, alpha) - table.insert(self.yAxisLabels, label) - end - - --create the labels in the horizontal axis line - for i = 1, amountXLabels do - local label = self:CreateFontString("$parentXAxisLabel" .. i, "overlay", "GameFontNormal") - label:SetJustifyH("left") - label:SetTextColor(red, green, blue, alpha) - table.insert(self.xAxisLabels, label) - end - - yAxisLine:SetStartPoint("topleft", self, "topleft", 0, 0) - yAxisLine:SetEndPoint("bottomleft", self, "bottomleft", 0, 0) - yAxisLine:Hide() - - xAxisLine:SetStartPoint("bottomleft", self, "bottomleft", 0, 0) - xAxisLine:SetEndPoint("bottomright", self, "bottomright", 0, 0) - xAxisLine:Hide() + createVerticalAxisLabels(plotFrame, amountYLabels, self.yAxisLabels, red, green, blue, alpha) + createHorizontalAxisLabels(plotFrame, amountXLabels, self.xAxisLabels, red, green, blue, alpha) self.axisCreated = true - return true end +---@param self df_chartmulti|df_chart +---@param ... any +local setXAxisData = function(self, ...) + --when the data type is set to time, the x axis data is a number which represents the biggest time in seconds of all charts added + if (self.xAxisDataType == "time" or self.xAxisDataType == "number") then + self.xAxisDataNumber = math.max(self.xAxisDataNumber, select(1, ...)) + else + wipe(self.xAxisDataValues) + self.xAxisDataValues = {...} + end +end + +---@param self df_chartmulti|df_chart +---@param dataType x_axisdatatype +local setXAxisDataType = function(self, dataType) + assert(type(dataType) == "string", "string expected on :SetXAxisDataType(string)") + self.xAxisDataType = dataType + + if (dataType == "time" or dataType == "number") then + self.xAxisDataNumber = 0 + + elseif (dataType == "value") then + wipe(self.xAxisDataValues) + end +end + +---updates the values of the labels on the axes to reflect the data shown +---@param self df_chart|df_chartmulti +local updateLabelValues = function(self) + local maxValue = self:GetMaxValue() + local height = self.plotFrame:GetHeight() + local verticalLabelCount = #self.yAxisLabels + local heightStep = height / verticalLabelCount + + --update the labels in the vertical axis line + for i = 1, verticalLabelCount do + local label = self.yAxisLabels[i] + local value = maxValue * (i / verticalLabelCount) + label:ClearAllPoints() + label:SetPoint("topright", self.yAxisLine, "bottomleft", -6, heightStep * i + self.chartBottomOffset) + label:SetText(detailsFramework.FormatNumber(value)) + + label.circleTexture:ClearAllPoints() + label.circleTexture:SetPoint("center", self.yAxisLine, "bottomleft", -2, heightStep * i - 5 + self.chartBottomOffset) + + label.guideLine:SetStartPoint("center", label.circleTexture, 0, 0) + label.guideLine:SetEndPoint("bottomright", self.plotFrame, 0, heightStep * i - 5) + end + + --update the labels in the horizontal axis line + local xAxisDataType = self.xAxisDataType + local horizontalLabelCount = #self.xAxisLabels + local width = self.plotFrame:GetWidth() + local widthStep = width / horizontalLabelCount + + for i = horizontalLabelCount, 1, -1 do + local label = self.xAxisLabels[i] + label:ClearAllPoints() + label:SetJustifyH("right") + + --set the point of each x axis label + label:SetPoint("topright", self.plotFrame, "bottomleft", widthStep * i, self.xAxisLabelsYOffset or -6) + + --get the type set for the x axis labels and format the value accordingly + if (xAxisDataType == "time" or xAxisDataType == "number") then + local maxNumberValue = self.xAxisDataNumber + local thisValue = maxNumberValue * (i / horizontalLabelCount) + if (xAxisDataType == "time") then + label:SetText(detailsFramework:IntegerToTimer(thisValue)) + else + label:SetText(detailsFramework.FormatNumber(thisValue)) + end + + elseif (xAxisDataType == "value") then + label:SetText(self.xAxisDataValues[i]) + end + end +end + +detailsFramework.ChartFrameSharedMixin = { + ---set the color of both axis lines + ---@param self df_chart|df_chartmulti + ---@param red any + ---@param green number|nil + ---@param blue number|nil + ---@param alpha number|nil + ---@return boolean bColorChanged return true if the color was set, false if the axis lines are not created yet + SetAxesColor = function(self, red, green, blue, alpha) + if (not self.yAxisLine) then + return false + end + + --set the color of both axis lines + red, green, blue, alpha = detailsFramework:ParseColors(red, green, blue, alpha) + self.yAxisLine:SetColorTexture(red, green, blue, alpha) + self.xAxisLine:SetColorTexture(red, green, blue, alpha) + + --iterage over all labels and set their color + for i = 1, #self.yAxisLabels do + self.yAxisLabels[i]:SetTextColor(red, green, blue, alpha) + end + + for i = 1, #self.xAxisLabels do + self.xAxisLabels[i]:SetTextColor(red, green, blue, alpha) + end + + return true + end, + + ---set the thickness of both axis lines + ---@param self df_chart|df_chartmulti + ---@param thickness number + ---@return boolean bThicknessChanged return true if the thickness was set, false if the axis lines are not created yet + SetAxesThickness = function(self, thickness) + if (not self.yAxisLine) then + return false + end + self.yAxisLine:SetThickness(thickness) + self.xAxisLine:SetThickness(thickness) + return true + end, + + ---create the x and y axis lines with their labels + ---@param self df_chart|df_chartmulti + ---@param xOffset number + ---@param yOffset number + ---@param whichSide "left"|"right" + ---@param thickness number + ---@param amountYLabels number + ---@param amountXLabels number + ---@param red any + ---@param green number|nil + ---@param blue number|nil + ---@param alpha number|nil + ---@return boolean + CreateAxesLines = function(self, xOffset, yOffset, whichSide, thickness, amountYLabels, amountXLabels, red, green, blue, alpha) + return createAxesLines(self, xOffset, yOffset, whichSide, thickness, amountYLabels, amountXLabels, red, green, blue, alpha) + end, + + ---@param self df_chartmulti|df_chart + ---@param ... any + SetXAxisData = function(self, ...) + setXAxisData(self, ...) + end, + + ---@param self df_chartmulti|df_chart + ---@param dataType x_axisdatatype + SetXAxisDataType = function(self, dataType) + setXAxisDataType(self, dataType) + end, +} + detailsFramework.ChartFrameMixin = { ---set the default values for the chart frame ---@param self df_chart @@ -161,13 +398,28 @@ detailsFramework.ChartFrameMixin = { self.nextLine = 1 self.minValue = 0 self.maxValue = 1 - self.lineThickness = 1 + self.lineThickness = 2 self.data = {} self.lines = {} self.color = {1, 1, 1, 1} - --OnSizeChanged self:SetScript("OnSizeChanged", self.OnSizeChanged) + + chartFrameSharedConstructor(self) + end, + + IsMultiChart = function(self) + return false + end, + + ---get the chart color + ---@param self df_chart + ---@return number red + ---@return number green + ---@return number blue + ---@return number alpha + GetColor = function(self) + return unpack(self.color) end, ---set the color for the lines @@ -192,7 +444,7 @@ detailsFramework.ChartFrameMixin = { if (not line) then ---@type line - line = self:CreateLine(nil, "overlay") + line = self.plotFrame:CreateLine(nil, "overlay", nil, 5) self.lines[self.nextLine] = line end @@ -247,7 +499,7 @@ detailsFramework.ChartFrameMixin = { return self.fixedLineWidth else local amountData = self:GetDataSize() - local frameWidth = self:GetWidth() + local frameWidth = self.plotFrame:GetWidth() return frameWidth / amountData end end, @@ -262,8 +514,10 @@ detailsFramework.ChartFrameMixin = { ---@param self df_chart ---@param value number - CalcYAxisPointForValue = function(self, value) - return value / self.maxValue * self.height + ---@param plotFrameHeightScaled number + ---@return number + CalcYAxisPointForValue = function(self, value, plotFrameHeightScaled) + return value / self.maxValue * (plotFrameHeightScaled) end, ---@param self df_chart @@ -278,11 +532,11 @@ detailsFramework.ChartFrameMixin = { end, ---@param self df_chart - Plot = function(self) + ---@param yPointScale number|nil + ---@param bUpdateLabels boolean|nil + Plot = function(self, yPointScale, bUpdateLabels) --debug --self:SetData({38, 26, 12, 63, 100, 96, 42, 94, 25, 75, 61, 54, 71, 40, 34, 100, 66, 90, 39, 13, 99, 18, 72, 18, 83, 45, 56, 24, 33, 85, 95, 71, 15, 66, 19, 58, 52, 9, 83, 99, 100, 4, 3, 56, 6, 80, 94, 7, 40, 55, 98, 92, 20, 9, 35, 89, 72, 7, 13, 81, 29, 78, 55, 70, 12, 33, 39, 3, 84, 31, 10, 53, 51, 69, 66, 58, 71, 60, 31, 71, 27, 76, 21, 75, 15, 89, 2, 81, 72, 78, 74, 80, 97, 10, 59, 0, 31, 5, 1, 82, 71, 89, 78, 94, 74, 20, 65, 72, 56, 40, 92, 91, 40, 79, 4, 56, 18, 88, 88, 20, 20, 10, 47, 26, 80, 26, 75, 21, 57, 10, 67, 66, 84, 83, 14, 47, 83, 9, 7, 73, 63, 32, 64, 20, 40, 3, 46, 54, 17, 37, 82, 66, 65, 22, 12, 1, 100, 41, 1, 72, 38, 41, 71, 69, 88, 34, 10, 50, 9, 25, 19, 27, 3, 13, 40, 75, 3, 11, 93, 58, 81, 80, 93, 25, 74, 68, 91, 87, 79, 48, 66, 53, 64, 18, 51, 19, 32, 4, 21, 43}) - local currentXPoint = 0 - local currentYPoint = 0 self:UpdateFrameSizeCache() @@ -293,7 +547,9 @@ detailsFramework.ChartFrameMixin = { local firstValue = self:GetDataFirstValue() assert(firstValue, "Can't Plot(), chart has no data, use Chart:SetData(table)") - currentYPoint = self:CalcYAxisPointForValue(firstValue) + local plotFrameHeightScaled = self.plotFrame:GetHeight() * (yPointScale or 1) + local currentXPoint = 0 + local currentYPoint = self:CalcYAxisPointForValue(firstValue, plotFrameHeightScaled) --calculate the width space which line should have local eachLineWidth = self:GetLineWidth() @@ -302,8 +558,13 @@ detailsFramework.ChartFrameMixin = { for i = 1, maxLines do local line = self:GetLine() + line:SetColorTexture(unpack(self.color)) - line:SetThickness(self.lineThickness) + + if (line.thickness ~= self.lineThickness) then + line:SetThickness(self.lineThickness) + line.thickness = self.lineThickness + end --the start point starts where the latest point finished line:SetStartPoint("bottomleft", currentXPoint, currentYPoint) @@ -313,9 +574,13 @@ detailsFramework.ChartFrameMixin = { --end point local value = self:GetDataNextValue() - currentYPoint = self:CalcYAxisPointForValue(value) + currentYPoint = self:CalcYAxisPointForValue(value, plotFrameHeightScaled) line:SetEndPoint("bottomleft", currentXPoint, currentYPoint) end + + if (bUpdateLabels or bUpdateLabels == nil) then + updateLabelValues(self) + end end, } @@ -337,7 +602,36 @@ local createChartFrame = function(parent, name) chartFrame:ChartFrameConstructor() --when a new data is set, update the min and max values - local onSetDataCallback = function() + local onSetDataCallback = function(data, smoothnessLevel) + local newData = {} + + smoothnessLevel = smoothnessLevel or 0 + + if (smoothnessLevel > 0) then + smoothnessLevel = smoothnessLevel + 2 + + for i = 1, #data do + local thisValue = 0 + local amountDataAdded = 0 + + --calculate the sum within the window + for o = i - math.floor(smoothnessLevel / 2), i + math.floor(smoothnessLevel / 2) do + if o >= 1 and o <= #data then + thisValue = thisValue + data[o] + amountDataAdded = amountDataAdded + 1 + end + end + + --calculate the average and store in the smoothedData value + local average = thisValue / amountDataAdded + table.insert(newData, average) + end + else + newData = data + end + + chartFrame:SetDataRaw(newData) + local minValue, maxValue = chartFrame:GetDataMinMaxValues() chartFrame:SetMinMaxValues(minValue, maxValue) --clear the lines @@ -345,6 +639,7 @@ local createChartFrame = function(parent, name) end chartFrame:AddDataChangeCallback(onSetDataCallback) + createPlotFrame(chartFrame) --creates chartFrame.plotFrame return chartFrame end @@ -358,26 +653,36 @@ detailsFramework.MultiChartFrameMixin = { MultiChartFrameConstructor = function(self) self.nextChartselframe = 1 self.biggestDataValue = 0 - self.lineThickness = 1 + self.lineThickness = 2 + self.nextChartFrame = 1 self.chartFrames = {} + self.lineNameIndicators = {} + + chartFrameSharedConstructor(self) end, - CreateAxisLines = function(self, whichSide, thickness, amountYLabels, amountXLabels, red, green, blue, alpha) - createAxysLines(self, whichSide, thickness, amountYLabels, amountXLabels, red, green, blue, alpha) + IsMultiChart = function(self) + return true end, ---add a new chart data and create a new chart frame if necessary to the multi chart ---@param self df_chartmulti ---@param data table - ---@param red number|string|table|nil + ---@param smoothnessLevel number|nil + ---@param name string|nil + ---@param red any ---@param green number|nil ---@param blue number|nil ---@param alpha number|nil - AddData = function(self, data, red, green, blue, alpha) + AddData = function(self, data, smoothnessLevel, name, red, green, blue, alpha) assert(type(data) == "table", "MultiChartFrame:AddData() usage: AddData(table)") local chartFrame = self:GetChart() + + red, green, blue, alpha = detailsFramework:ParseColors(red, green, blue, alpha) chartFrame:SetColor(red, green, blue, alpha) - chartFrame:SetData(data) + chartFrame:SetData(data, smoothnessLevel) + + chartFrame.chartName = name or "" self:SetMaxValueIfBigger(chartFrame:GetMaxValue()) self:SetMinValueIfLower(chartFrame:GetMinValue()) @@ -466,23 +771,94 @@ detailsFramework.MultiChartFrameMixin = { self.lineThickness = value end, - ---draw all the charts added to the multi chart frame ---@param self df_chartmulti - Plot = function(self) - local minValue, maxValue = self:GetMinMaxValues() - self:SetChartsMinMaxValues(minValue, maxValue) - - local plotAreaWidth = self:GetWidth() - local maxDataSize = self:GetMaxDataSize() - local eachLineWidth = plotAreaWidth / maxDataSize - + UpdateChartNamesIndicator = function(self) local allCharts = self:GetCharts() - for i = 1, self:GetAmountCharts() do - local chartFrame = allCharts[i] - chartFrame:SetLineThickness(self.lineThickness) - chartFrame:SetLineWidth(eachLineWidth) - chartFrame:Plot() + local allChartsAmount = self:GetAmountCharts() + + --hide all indicators already created + for i = 1, #self.lineNameIndicators do + local thisIndicator = self.lineNameIndicators[i] + thisIndicator:Hide() end + + local nameIndicatorIndex = 1 + + for i = allChartsAmount, 1, -1 do + local chartFrame = allCharts[i] + local chartName = chartFrame.chartName + local red, green, blue, alpha = chartFrame:GetColor() + + ---@type chart_nameindicator + local thisIndicator = self.lineNameIndicators[nameIndicatorIndex] + if (not thisIndicator) then + ---@type chart_nameindicator + thisIndicator = CreateFrame("frame", "$parentLineNameIndicator" .. i, self) + thisIndicator:SetSize(60, 12) + thisIndicator:Hide() + if (nameIndicatorIndex == 1) then + thisIndicator:SetPoint("topright", self, "topright", nameIndicatorIndex * -10, -10) + end + + thisIndicator.Texture = thisIndicator:CreateTexture("$parentTexture", "overlay") + thisIndicator.Texture:SetSize(12, 12) + + thisIndicator.Label = thisIndicator:CreateFontString("$parentLabel", "overlay", "GameFontNormal") + detailsFramework:SetFontSize(thisIndicator.Label, 11) + detailsFramework:SetFontColor(thisIndicator.Label, "white") + + thisIndicator.Texture:SetPoint("left", thisIndicator, "left", 0, 0) + thisIndicator.Label:SetPoint("left", thisIndicator.Texture, "right", 2, 0) + self.lineNameIndicators[nameIndicatorIndex] = thisIndicator + end + + thisIndicator.Texture:SetColorTexture(red, green, blue, alpha) + thisIndicator.Label:SetText(chartName) + local textWidth = thisIndicator.Label:GetStringWidth() + thisIndicator:SetWidth(math.max(textWidth + thisIndicator.Texture:GetWidth() + 4, 85)) + + if (nameIndicatorIndex > 1) then + local previousIndicator = self.lineNameIndicators[nameIndicatorIndex-1] + thisIndicator:SetPoint("topright", previousIndicator, "topleft", -2, 0) + end + + nameIndicatorIndex = nameIndicatorIndex + 1 + + if (chartName ~= "") then + thisIndicator:Show() + end + end + end, + + ---draw all the charts added to the multi chart frame + ---@param multiChartFrame df_chartmulti + Plot = function(multiChartFrame) + local minValue, multiChartMaxValue = multiChartFrame:GetMinMaxValues() + local plotAreaWidth = multiChartFrame.plotFrame:GetWidth() --if there's no axis, the plotFrame has no width + local maxDataSize = multiChartFrame:GetMaxDataSize() + local eachLineWidth = plotAreaWidth / maxDataSize + local allCharts = multiChartFrame:GetCharts() + + for i = 1, multiChartFrame:GetAmountCharts() do + local chartFrame = allCharts[i] + chartFrame.chartLeftOffset = multiChartFrame.chartLeftOffset + chartFrame.chartBottomOffset = multiChartFrame.chartLeftOffset + + chartFrame.plotFrame:ClearAllPoints() + chartFrame.plotFrame:SetAllPoints(multiChartFrame.plotFrame) + + chartFrame:SetLineThickness(multiChartFrame.lineThickness) + chartFrame:SetLineWidth(eachLineWidth) + + --get the percentage of how small this data is compared to the biggest data + --this percentage is then used to scale down the to fit correctly the fontStrings showing the value metrics + local yPointScale = chartFrame.maxValue / multiChartMaxValue + local bUpdateLabels = false + chartFrame:Plot(yPointScale, bUpdateLabels) + end + + updateLabelValues(multiChartFrame) + multiChartFrame:UpdateChartNamesIndicator() end, } @@ -503,5 +879,6 @@ function detailsFramework:CreateGraphicMultiLineFrame(parent, name) chartFrame:ValueConstructor() chartFrame:MultiChartFrameConstructor() + createPlotFrame(chartFrame) --creates chartFrame.plotFrame return chartFrame end \ No newline at end of file diff --git a/Libs/DF/fw.lua b/Libs/DF/fw.lua index 3ba7decc..62f0a706 100644 --- a/Libs/DF/fw.lua +++ b/Libs/DF/fw.lua @@ -1,6 +1,6 @@ -local dversion = 439 +local dversion = 441 local major, minor = "DetailsFramework-1.0", dversion local DF, oldminor = LibStub:NewLibrary(major, minor) diff --git a/Libs/DF/load.xml b/Libs/DF/load.xml index b1bae6a3..9e5dcd29 100644 --- a/Libs/DF/load.xml +++ b/Libs/DF/load.xml @@ -21,6 +21,7 @@