local detailsFramework = _G [ " DetailsFramework " ]
if ( not detailsFramework or not DetailsFrameworkCanLoad ) then
return
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
---@class chart_backdropindicator : frame a frame which is used to indicate a specific time frame in the chart with a colored texture
---@field fieldTexture texture the texture which indicates the amount of time the effect was active, it is painted over the background texture
---@field fieldLabel fontstring the label showing the name of the indicator, example: bloodlust, heroism, etc
---@field indicatorTexture texture a small squere texture located in the top right of the chart frame, it is used to indicate the color of the indicator
---@field indicatorLabel fontstring the label showing the name of the indicator within the indicatorTexture
---@field bInUse boolean if the indicator is in use or not
---@field startTime number
---@field endTime number
---@field labelText string
---@field color color
---@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_chartline : line
---@field thickness number
---@class df_chartshared: table
---@field fillOrder table<number, number[]> the order of the lines to be filled, this table is shared by all charts
---@field bFillChart boolean if the chart lines should be filled or not
---@field fillChartLineThickness number the thickness of the fill line
---@field bRunningInBackground boolean true if there is a proccess happening asynchronously
---@field waitForBackgroundProcessTicker timer a ticker to check if all background process finished
---@field amountOfBackgroundProcess number the amount of background processes happening
---@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 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 backdropIndicators chart_backdropindicator[]
---@field nextBackdropIndicator number tell which is the next backdrop indicator to be used
---@field SetFillChart fun(self: df_chartshared, bFill: boolean, lineThickness:number?) set if the chart lines should be filled or not
---@field GetFillState fun(self: df_chartshared) : boolean, number return if the chart lines should be filled or not
---@field ShrinkData fun(self: df_chartmulti|df_chart, data: table, skrinkBy: number, bJustDrop: boolean?) : table
---@field HasBackgroundProcess fun(self: df_chartmulti|df_chart) : boolean return true if there is a proccess happening asynchronously
---@field SetBackgroundProcessState fun(self: df_chartmulti|df_chart, bRunning: boolean) set if there is a proccess happening asynchronously
---@field CreateBackdropIndicator fun(self: df_chartmulti|df_chart, index: number) : chart_backdropindicator create a new backdrop indicator
---@field GetBackdropIndicator fun(self: df_chartmulti|df_chart) : chart_backdropindicator get a backdrop indicator by index
---@field ResetBackdropIndicators fun(self: df_chartmulti|df_chart) reset all backdrop indicators
---@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) create the x and y axis lines with their labels, offsets are the distance from left and bottom
---@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
self.backdropIndicators = { }
self.nextBackdropIndicator = 1
end
---@class df_chart: frame, df_data, df_value, df_chartshared
---@field _dataInfo df_data
---@field average number
---@field depth number
---@field color number[] red green blue alpha
---@field height number
---@field nextLine number
---@field minValue number
---@field maxValue number
---@field data number[]
---@field lines df_chartline[]
---@field fixedLineWidth number
---@field chartName string
---@field dataPoint_OnEnterFunc fun(self: df_chart, onEnterFunc: function, ...) set the function to be called when the mouse hover over a data point in the chart
---@field dataPoint_OnEnterPayload any[] set the payload to be passed to the function set by DataPoint_OnEnterFunc
---@field dataPoint_OnLeaveFunc fun(self: df_chart, onLeaveFunc: function, ...) set the function to be called when the mouse leaves a data point in the chart
---@field dataPoint_OnLeavePayload any[] set the payload to be passed to the function set by DataPoint_OnLeaveFunc
---@field GetOnEnterLeaveFunctions fun(self: df_chart) : function, any[], function, any[] return the functions and payloads set by DataPoint_OnEnterFunc and DataPoint_OnLeaveFunc
---@field ChartFrameConstructor fun(self: df_chart) set the default values for the chart frame
---@field GetLine fun(self: df_chart) : df_chartline return a line and also internally handle next line
---@field GetLines fun(self: df_chart) : df_chartline[] 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 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, plotFrameHeightScaled: number) : number
---@field UpdateFrameSizeCache fun(self: df_chart)
---@field Plot fun(self: df_chart, yPointScale: number|nil, bUpdateLabels: boolean|nil, lineId:number?) 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, smoothingMethod: string|nil, smoothnessLevel:number, 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)
---@field SetChartsMinMaxValues fun(self: df_chartmulti, minValue: number, maxValue: number)
---@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 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
---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
---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
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
---@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
local createAxesLines = function ( self , xOffset , yOffset , whichSide , thickness , amountYLabels , amountXLabels , red , green , blue , alpha )
if ( self.axisCreated ) then
return false
end
local plotFrame = self.plotFrame
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 = 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 = 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 )
--set the thickness of the both axis lines
self : SetAxesThickness ( thickness )
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 if the chart lines should be filled or not, when filled, an extra line is drawn at the bottom of the chart to close the fill
---@param self df_chartshared
---@param bFill boolean
---@param lineThickness number?
SetFillChart = function ( self , bFill , lineThickness )
lineThickness = lineThickness or 1
self.bFillChart = bFill
self.fillChartLineThickness = lineThickness
end ,
---return if the chart lines should be filled or not
---@param self df_chartshared
---@return boolean
---@return number
GetFillState = function ( self )
return self.bFillChart , self.fillChartLineThickness
end ,
---receives a table containing the data to be plotted in the chart, returns a new table with the data reduced by the skrinkBy value
---if bJustDrop is true, the data will be reduced by dropping values, if false, the data will be reduced by averaging the values
---@param self df_chart|df_chartmulti
---@param data table
---@param skrinkBy number
---@param bJustDrop boolean
---@return table
ShrinkData = function ( self , data , skrinkBy , bJustDrop )
local newData = { }
local dataSize = # data
local tinsert = table.insert
if ( bJustDrop ) then
if ( true ) then
--make a for loop to drop the values by random, for example, is shrink is 3 and index is 9, it will drop at random two values of: 9 10 or 11
else
--it will shrink the data by dropping values each skrinkBy indexes
for i = 1 , dataSize , skrinkBy do
tinsert ( newData , data [ i ] )
end
end
else
--it will shrink the data by making an average of the values and add to newTable, shrinkBy controls how many values will be averaged
for i = 1 , dataSize , skrinkBy do
local sum = 0
for o = 0 , skrinkBy - 1 do
sum = sum + ( data [ i + o ] or 0 ) --attempt to perform arithmetic on field '?' (a nil value)
end
tinsert ( newData , sum / skrinkBy )
end
end
return newData
end ,
---return if there is a proccess happening asynchronously
---@param self df_chartmulti
---@return boolean
HasBackgroundProcess = function ( self )
return self.bRunningInBackground
end ,
---set if there is a proccess happening asynchronously
---@param self df_chartmulti
---@param bRunning boolean
SetBackgroundProcessState = function ( self , bRunning )
if ( bRunning ) then
self.amountOfBackgroundProcess = self.amountOfBackgroundProcess + 1
self.bRunningInBackground = bRunning
else
self.amountOfBackgroundProcess = self.amountOfBackgroundProcess - 1
if ( self.amountOfBackgroundProcess == 0 ) then
self.bRunningInBackground = false
end
end
end ,
---set the color of both axis lines
---@param self 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_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_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
---@param ... any
SetXAxisData = function ( self , ... )
setXAxisData ( self , ... )
end ,
---@param self df_chartmulti
---@param dataType x_axisdatatype
SetXAxisDataType = function ( self , dataType )
setXAxisDataType ( self , dataType )
end ,
---create a new backdrop indicator, this is called from the function GetBackdropIndicator
---@param self df_chartmulti
---@return chart_backdropindicator
CreateBackdropIndicator = function ( self , nextIndicatorIndex )
---@type chart_backdropindicator
local newBackdropIndicator = CreateFrame ( " frame " , " $parentBackdropIndicator " .. nextIndicatorIndex , self.plotFrame )
--make the backdrop indicators bebelow the plot frame
newBackdropIndicator : SetFrameLevel ( self.plotFrame : GetFrameLevel ( ) - 1 )
newBackdropIndicator.fieldTexture = newBackdropIndicator : CreateTexture ( nil , " overlay " )
newBackdropIndicator.fieldTexture : SetAllPoints ( )
newBackdropIndicator.fieldLabel = newBackdropIndicator : CreateFontString ( nil , " overlay " , " GameFontNormal " )
newBackdropIndicator.fieldLabel : SetTextColor ( 1 , 1 , 1 , 0.3 )
newBackdropIndicator.fieldLabel : SetJustifyH ( " left " )
newBackdropIndicator.fieldLabel : SetJustifyV ( " top " )
detailsFramework : SetFontSize ( newBackdropIndicator.fieldLabel , 10 )
newBackdropIndicator.fieldLabel : SetPoint ( " topleft " , newBackdropIndicator.fieldTexture , " topleft " , 2 , - 2 )
newBackdropIndicator.indicatorTexture = newBackdropIndicator : CreateTexture ( nil , " overlay " )
newBackdropIndicator.indicatorTexture : SetSize ( 10 , 10 )
newBackdropIndicator.indicatorLabel = newBackdropIndicator : CreateFontString ( nil , " overlay " , " GameFontNormal " )
newBackdropIndicator.indicatorLabel : SetTextColor ( 1 , 1 , 1 , 0.837 )
newBackdropIndicator.indicatorLabel : SetJustifyH ( " left " )
newBackdropIndicator.indicatorLabel : SetPoint ( " left " , newBackdropIndicator.indicatorTexture , " right " , 2 , 0 )
return newBackdropIndicator
end ,
---reset the backdrop indicators by hidding all of them
---@param self df_chartmulti
ResetBackdropIndicators = function ( self )
for i = 1 , # self.backdropIndicators do
local thisBackdropIndicator = self.backdropIndicators [ i ]
thisBackdropIndicator : Hide ( )
thisBackdropIndicator.bInUse = false
end
self.nextBackdropIndicator = 1
end ,
---get a backdrop indicator, if it doesn't exist, create a new one
---@param self df_chartmulti
---@return chart_backdropindicator
GetBackdropIndicator = function ( self )
local nextIndicator = self.nextBackdropIndicator
if ( not self.backdropIndicators [ nextIndicator ] ) then
self.backdropIndicators [ nextIndicator ] = self : CreateBackdropIndicator ( nextIndicator )
end
self.nextBackdropIndicator = nextIndicator + 1
return self.backdropIndicators [ nextIndicator ]
end ,
---add a backdrop indicator to the chart
---@param self df_chartmulti
---@param label string this is a text to be displayed on the left side of the indicator and on the top right corner of the chart panel
---@param timeStart number the start time of the indicator
---@param timeEnd number the end time of the indicator
---@param red number|nil
---@param green number|nil
---@param blue number|nil
---@param alpha number|nil
AddBackdropIndicator = function ( self , label , timeStart , timeEnd , red , green , blue , alpha )
assert ( type ( label ) == " string " , " AddBackdropIndicator: label must be a string. " )
assert ( type ( timeStart ) == " number " , " AddBackdropIndicator: timeStart must be a number. " )
assert ( type ( timeEnd ) == " number " , " AddBackdropIndicator: timeEnd must be a number. " )
red , green , blue , alpha = detailsFramework : ParseColors ( red , green , blue , alpha )
local backdropIndicator = self : GetBackdropIndicator ( )
backdropIndicator.bInUse = true
backdropIndicator.startTime = timeStart
backdropIndicator.endTime = timeEnd
backdropIndicator.labelText = label
backdropIndicator.color = { red , green , blue , alpha }
return true
end ,
---when Plot() is called, this function will be called to show the backdrop indicators
---it gets the x_axisdatatype or if not existant defaults to "time", calculate the area in pixels using the plot area width and the plot area 'time'
---then set the texture color, label texts and show the small squere indicators in the top right of the plot area
---@param self df_chartmulti
ShowBackdropIndicators = function ( self )
--get the x axis data type
local xDataType = self.xAxisDataType or " time "
--get the max value of the data type
local dataSize = self.xAxisDataNumber or self.GetDataSize and self : GetDataSize ( ) or 0
--frame width in pixels
local frameWidth = self.plotFrame : GetWidth ( )
for i = 1 , self.nextBackdropIndicator - 1 do
local thisIndicator = self.backdropIndicators [ i ]
if ( not thisIndicator.bInUse ) then
break
end
local startTime = thisIndicator.startTime
local endTime = thisIndicator.endTime
local labelText = thisIndicator.labelText
local color = thisIndicator.color
--set the point where the indicator will be placed
local startX = startTime / dataSize * frameWidth
local endX = endTime / dataSize * frameWidth
thisIndicator : SetPoint ( " topleft " , self.plotFrame , " topleft " , startX , 0 )
thisIndicator : SetPoint ( " bottomright " , self.plotFrame , " bottomleft " , endX , 0 )
thisIndicator.fieldLabel : SetText ( labelText )
thisIndicator.fieldTexture : SetColorTexture ( unpack ( color ) )
thisIndicator.indicatorLabel : SetText ( labelText )
thisIndicator.indicatorTexture : SetColorTexture ( unpack ( color ) )
local stringWidth = thisIndicator.indicatorLabel : GetStringWidth ( )
local squareWidth = thisIndicator.indicatorTexture : GetWidth ( )
if ( i == 1 ) then
local space = stringWidth + squareWidth
thisIndicator.indicatorTexture : SetPoint ( " topright " , self.plotFrame , " topright " , - space + 2 , - 30 )
else
local space = stringWidth + squareWidth + 10
thisIndicator.indicatorTexture : SetPoint ( " left " , self.backdropIndicators [ i - 1 ] . indicatorTexture , " left " , - space , 0 )
end
thisIndicator : Show ( )
end
end ,
}
local fillerLines_InAvailable = { }
local fillerLines_InUse = { }
---@class df_chartlazypayload : table
---@field self df_chartmulti
---@field currentDataIndex number
---@field executionsPerFrame number
---@field dataSize number
---@field currentXPoint number
---@field currentYPoint number
---@field eachLineWidth number
---@field plotFrameHeightScaled number
---@field r number
---@field g number
---@field b number
---@field lineId number
---@field bUpdateLabels boolean
---@field bFillChart boolean
---@field fillLineThickness number
--this is the function which is called by the schedules lazy execution system
local lazyChartUpdate = function ( payload , iterationCount , maxIterations )
---@cast payload df_chartlazypayload
local self = payload.self
---@cast self df_chart
local currentDataIndex = payload.currentDataIndex
local dataSize = payload.dataSize
local currentXPoint = payload.currentXPoint
local currentYPoint = payload.currentYPoint
local eachLineWidth = payload.eachLineWidth
local plotFrameHeightScaled = payload.plotFrameHeightScaled
local r = payload.r
local g = payload.g
local b = payload.b
local lineId = payload.lineId
local bUpdateLabels = payload.bUpdateLabels
local bFillChart = payload.bFillChart
local fillLineThickness = payload.fillLineThickness
local executionsPerFrame = payload.executionsPerFrame
currentDataIndex = currentDataIndex + executionsPerFrame
for i = 1 , payload.executionsPerFrame do
local value , dataIndex = self : GetDataNextValue ( )
if ( not value ) then
--the data stream has ended
return true
end
local line = self : GetLine ( )
line : SetColorTexture ( r , g , b )
if ( line.thickness ~= self.lineThickness ) then
line : SetThickness ( self.lineThickness )
line.thickness = self.lineThickness
end
--get the start points
local startX = currentXPoint
local startY = currentYPoint
currentXPoint = currentXPoint + eachLineWidth
--end point
local endX = currentXPoint
currentYPoint = self : CalcYAxisPointForValue ( value , plotFrameHeightScaled )
local endY = currentYPoint
local length = detailsFramework : GetVectorLength ( endX - startX , endY - startY )
--make sure the magnitude of the difference between previous point to current point is at least 1.5
if ( length < 1.5 ) then
local diffX = endX - startX
local diffY = endY - startY
local diffLength = detailsFramework : GetVectorLength ( diffX , diffY )
local scaleFactor = 1.5 / diffLength
diffX = diffX * scaleFactor
diffY = diffY * scaleFactor
endX = endX + diffX
endY = endY + diffY
end
--the start point starts where the latest point finished
line : SetStartPoint ( " bottomleft " , startX , startY )
line : SetEndPoint ( " bottomleft " , endX , endY )
if ( bFillChart ) then
if ( lineId ) then
local fillLine = table.remove ( fillerLines_InAvailable )
if ( not fillLine ) then
fillLine = self.plotFrame : CreateLine ( nil , " overlay " )
fillLine : SetThickness ( fillLineThickness )
fillerLines_InUse [ # fillerLines_InUse + 1 ] = fillLine
else
fillerLines_InUse [ # fillerLines_InUse + 1 ] = fillLine
end
fillLine : SetStartPoint ( " bottomleft " , endX , endY )
fillLine : SetEndPoint ( " bottomleft " , endX , 0 )
fillLine : SetDrawLayer ( " overlay " , self.depth )
fillLine : SetColorTexture ( r , g , b , 0.15 + ( self.depth / 10 ) )
fillLine : Show ( )
end
end
end
payload.currentXPoint = currentXPoint
payload.currentYPoint = currentYPoint
end
detailsFramework.ChartFrameMixin = {
---set the default values for the chart frame
---@param self df_chart
ChartFrameConstructor = function ( self )
self.nextLine = 1
self.minValue = 0
self.maxValue = 1
self.lineThickness = 2
self.data = { }
self.lines = { }
self.color = { 1 , 1 , 1 , 1 }
self.amountOfBackgroundProcess = 0
--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
---@param self df_chart
---@param r number
---@param g number
---@param b number
---@param a number|nil
SetColor = function ( self , r , g , b , a )
r , g , b , a = detailsFramework : ParseColors ( r , g , b , a )
self.color [ 1 ] = r
self.color [ 2 ] = g
self.color [ 3 ] = b
self.color [ 4 ] = a or 1
end ,
---internally handle next line
---@param self df_chart
---@return df_chartline
GetLine = function ( self )
---@type df_chartline
local line = self.lines [ self.nextLine ]
if ( not line ) then
---@type line
local newLine = self.plotFrame : CreateLine ( nil , " overlay " , nil , 5 )
---@cast newLine df_chartline
self.lines [ self.nextLine ] = newLine
line = newLine
end
self.nextLine = self.nextLine + 1
line : Show ( )
return line
end ,
---return all lines created for this chart
---@param self df_chart
---@return df_chartline[]
GetLines = function ( self )
return self.lines
end ,
---return the amount of lines in use
---@param self df_chart
---@return number
GetAmountLines = function ( self )
return self.nextLine - 1
end ,
---hide all lines already created
---@param self df_chart
HideLines = function ( self )
local allLines = self : GetLines ( )
for i = 1 , # allLines do
local line = allLines [ i ]
line : Hide ( )
end
end ,
---hide all lines and reset the next line to 1
---@param self df_chart
Reset = function ( self )
self : HideLines ( )
self : ResetMinMaxValues ( )
self.nextLine = 1
self.xAxisDataNumber = 0
self : ResetBackdropIndicators ( )
end ,
---@param self df_chart
---@param value number
SetLineThickness = function ( self , value )
assert ( type ( value ) == " number " , " number expected on :SetLineThickness(number) " )
self.lineThickness = value
end ,
---calculate the width of each drawn line
---@param self df_chart
GetLineWidth = function ( self )
--self:SetLineWidth(nil) to erase the fixed value
if ( self.fixedLineWidth ) then
return self.fixedLineWidth
else
local amountData = self : GetDataSize ( )
local frameWidth = self.plotFrame : GetWidth ( )
return frameWidth / amountData
end
end ,
---set the line width to a fixed value
---@param self df_chart
---@param width number
SetLineWidth = function ( self , width )
assert ( type ( width ) == " number " , " number expected on :SetLineWidth(number) " )
self.fixedLineWidth = width
end ,
---@param self df_chart
---@param value number
---@param plotFrameHeightScaled number
---@return number
CalcYAxisPointForValue = function ( self , value , plotFrameHeightScaled )
return value / self.maxValue * ( plotFrameHeightScaled )
end ,
---@param self df_chart
UpdateFrameSizeCache = function ( self )
self.width = self : GetWidth ( )
self.height = self : GetHeight ( )
end ,
---@param self df_chart
OnSizeChanged = function ( self )
self : UpdateFrameSizeCache ( )
end ,
---when the mouse hover over a data point in the chart, this function will be called
---@param self df_chart
SetOnEnterFunction = function ( self , onEnterFunc , ... )
self.dataPoint_OnEnterFunc = onEnterFunc
self.dataPoint_OnEnterPayload = { ... }
end ,
---when the mouse leaves a data point in the chart, this function will be called
---@param self df_chart
SetOnLeaveFunction = function ( self , onLeaveFunc , ... )
self.dataPoint_OnLeaveFunc = onLeaveFunc
self.dataPoint_OnLeavePayload = { ... }
end ,
---get the data point on enter and on leave function
---@param self df_chart
---@return function onEnterFunc
---@return any[] onEnterPayload
---@return function onLeaveFunc
---@return any[] onLeavePayload
GetOnEnterLeaveFunctions = function ( self )
return self.dataPoint_OnEnterFunc , self.dataPoint_OnEnterPayload , self.dataPoint_OnLeaveFunc , self.dataPoint_OnLeavePayload
end ,
---this function will draw the chart lines
---@param self df_chart
---@param yPointScale number|nil
---@param bUpdateLabels boolean|nil
Plot = function ( self , yPointScale , bUpdateLabels , lineId )
lineId = lineId or 1
self : UpdateFrameSizeCache ( )
--max amount of data is the max amount of point the chart will have
local dataSize = self : GetDataSize ( )
--calculate where the first point height will be
local firstValue = self : GetDataFirstValue ( )
assert ( firstValue , " Can't Plot(), chart has no data, use Chart:SetData(table) " )
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 ( )
self : ResetDataIndex ( )
local r , g , b = unpack ( self.color )
local bFillChart , fillLineThickness = self : GetFillState ( )
local payload = {
executionsPerFrame = 50 ,
self = self ,
currentDataIndex = 1 ,
dataSize = dataSize ,
currentXPoint = currentXPoint ,
currentYPoint = currentYPoint ,
eachLineWidth = eachLineWidth ,
plotFrameHeightScaled = plotFrameHeightScaled ,
r = r ,
g = g ,
b = b ,
lineId = lineId ,
bUpdateLabels = bUpdateLabels ,
bFillChart = bFillChart ,
fillLineThickness = fillLineThickness ,
}
for i = # fillerLines_InUse , 1 , - 1 do
local line = table.remove ( fillerLines_InUse , i )
fillerLines_InAvailable [ # fillerLines_InAvailable + 1 ] = line
line : Hide ( )
end
detailsFramework.Schedules . LazyExecute ( lazyChartUpdate , payload )
self : ShowBackdropIndicators ( )
if ( bUpdateLabels or bUpdateLabels == nil ) then
updateLabelValues ( self )
end
end ,
}
--https://en.wikipedia.org/wiki/Local_regression
local calcLOESS = function ( data , span , mainFrame , chartFrame )
local lazyLOESSUpdate = function ( payload , iterationCount , maxIterations )
local data = payload.data
local span = payload.span
local lastDataIndex = payload.lastDataIndex
local result = payload.result
local halfSpan = payload.halfSpan
local sumTotal = payload.sumTotal
local currentDataIndex = payload.currentDataIndex
payload.currentDataIndex = currentDataIndex + payload.executionsPerFrame
local max = math.max
local min = math.min
local abs = math.abs
local tinsert = table.insert
for i = currentDataIndex , currentDataIndex + payload.executionsPerFrame do
--define the local neighborhood
local neighborhood = { }
for o = max ( 1 , i - halfSpan ) , min ( lastDataIndex , i + halfSpan ) do
tinsert ( neighborhood , { x = o , y = data [ o ] } )
end
sumTotal = sumTotal + data [ i ]
--calculate weights based on distance from target point
local weights = { }
for _ , point in ipairs ( neighborhood ) do
local distance = abs ( i - point.x )
local weight = ( 1 - ( distance / ( halfSpan + 1 ) ) ^ 3 ) ^ 3
weights [ point.x ] = weight
end
--fit a weighted linear regression to the neighborhood
local sum_w = 0
local sum_wx = 0
local sum_wy = 0
local sum_wxx = 0
local sum_wxy = 0
for _ , point in ipairs ( neighborhood ) do
local w = weights [ point.x ]
sum_w = sum_w + w
sum_wx = sum_wx + w * point.x
sum_wy = sum_wy + w * point.y
sum_wxx = sum_wxx + w * point.x * point.x
sum_wxy = sum_wxy + w * point.x * point.y
end
local denominator = sum_w * sum_wxx - sum_wx * sum_wx
local intercept = ( sum_wy * sum_wxx - sum_wx * sum_wxy ) / denominator
local slope = ( sum_w * sum_wxy - sum_wx * sum_wy ) / denominator
--predict the smoothed value at the target point
result [ i ] = max ( 0 , intercept + slope * i )
--check if can finishe the execution
if ( i == lastDataIndex ) then
return true
end
end
payload.sumTotal = sumTotal
end
local result = { }
local dataSize = # data
local halfSpan = math.floor ( span / 2 )
local payload = {
currentDataIndex = 1 ,
sumTotal = 0 ,
lastDataIndex = dataSize ,
executionsPerFrame = 100 ,
data = data ,
span = span ,
result = result ,
halfSpan = halfSpan ,
}
---@type df_schedule
local schedules = detailsFramework.Schedules
local onEndLazyExecution = function ( payload )
chartFrame : SetDataRaw ( payload.result )
chartFrame.average = payload.sumTotal / dataSize
local minValue , maxValue = chartFrame : GetDataMinMaxValues ( )
chartFrame : SetMinMaxValues ( minValue , maxValue )
--clear the lines
chartFrame : HideLines ( )
mainFrame : SetBackgroundProcessState ( false )
end
mainFrame : SetBackgroundProcessState ( true )
schedules.LazyExecute ( lazyLOESSUpdate , payload , 999 , onEndLazyExecution )
end
--simple moving average
---@param data table
---@param averageSize number
---@param mainFrame df_chartmulti
---@param chartFrame df_chart
---@param bAddZeroPadding boolean?
local calcSMA = function ( data , averageSize , mainFrame , chartFrame , bAddZeroPadding )
if ( bAddZeroPadding ) then
--fill the start of the data with zeros
for i = 1 , averageSize - 1 do
--insert at index 1 a zero
table.insert ( data , 1 , 0 )
end
end
local lazySMAUpdate = function ( payload , iterationCount , maxIterations )
local averageSize = payload.averageSize
local result = payload.result
local data = payload.data
local lastDataIndex = payload.lastDataIndex
local sum = payload.sum
local sumTotal = payload.sumTotal
local bAddZeroPadding = payload.bAddZeroPadding
local currentDataIndex = payload.currentDataIndex
payload.currentDataIndex = currentDataIndex + payload.executionsPerFrame
local tinsert = table.insert
for i = currentDataIndex , currentDataIndex + payload.executionsPerFrame do
sum = sum + data [ i ]
sumTotal = sumTotal + data [ i ]
if ( i >= averageSize ) then
if ( i > averageSize ) then
sum = sum - data [ i - averageSize ]
end
tinsert ( result , max ( 0 , sum / averageSize ) )
end
--check if can finishe the execution
if ( i == lastDataIndex ) then
if ( bAddZeroPadding ) then
--remove from the data the zeros added at the start
for o = 1 , averageSize - 1 do
--remove from the data the zero added at the first index
table.remove ( data , 1 )
end
end
return true
end
end
payload.sumTotal = sumTotal
payload.sum = sum
end
--return result
local result = { }
local dataSize = # data
local payload = {
sum = 0 ,
sumTotal = 0 ,
currentDataIndex = 1 ,
lastDataIndex = dataSize ,
executionsPerFrame = 300 ,
data = data ,
result = result ,
averageSize = averageSize ,
bAddZeroPadding = bAddZeroPadding ,
}
---@type df_schedule
local schedules = detailsFramework.Schedules
local onEndLazyExecution = function ( payload )
chartFrame : SetDataRaw ( payload.result )
chartFrame.average = payload.sumTotal / dataSize
local minValue , maxValue = chartFrame : GetDataMinMaxValues ( )
chartFrame : SetMinMaxValues ( minValue , maxValue )
--clear the lines
chartFrame : HideLines ( )
mainFrame : SetBackgroundProcessState ( false )
end
mainFrame : SetBackgroundProcessState ( true )
schedules.LazyExecute ( lazySMAUpdate , payload , 999 , onEndLazyExecution )
end
---create a chart frame object
---@param parent frame
---@param name string|nil
---@return df_chart
local createChartFrame = function ( parent , name )
---@type df_chart
local chartFrame = CreateFrame ( " frame " , name , parent , " BackdropTemplate " )
detailsFramework : Mixin ( chartFrame , detailsFramework.DataMixin )
detailsFramework : Mixin ( chartFrame , detailsFramework.ValueMixin )
detailsFramework : Mixin ( chartFrame , detailsFramework.ChartFrameMixin )
detailsFramework : Mixin ( chartFrame , detailsFramework.ChartFrameSharedMixin )
chartFrame : DataConstructor ( )
chartFrame : ValueConstructor ( )
chartFrame : ChartFrameConstructor ( )
--when a new data is set, starting an background process to smooth the data
local onSetDataCallback = function ( data , payload )
local smoothnessMethod = payload.smoothnessMethod or " "
local smoothnessLevel = payload.smoothnessLevel
local mainFrame = payload.mainFrame
smoothnessLevel = smoothnessLevel or 0
smoothnessMethod = string.lower ( smoothnessMethod )
if ( smoothnessMethod == " loess " ) then
calcLOESS ( data , smoothnessLevel , mainFrame , chartFrame )
elseif ( smoothnessMethod == " sma " ) then
calcSMA ( data , smoothnessLevel , mainFrame , chartFrame )
elseif ( smoothnessMethod == " smaz " ) then
local bAddZeroPadding = true
calcSMA ( data , smoothnessLevel , mainFrame , chartFrame , bAddZeroPadding )
else
chartFrame : SetDataRaw ( data )
local minValue , maxValue = chartFrame : GetDataMinMaxValues ( )
chartFrame : SetMinMaxValues ( minValue , maxValue )
--clear the lines
chartFrame : HideLines ( )
end
end
chartFrame : AddDataChangeCallback ( onSetDataCallback )
createPlotFrame ( chartFrame ) --creates chartFrame.plotFrame
return chartFrame
end
detailsFramework.MultiChartFrameMixin = {
MultiChartFrameConstructor = function ( self )
self.nextChartselframe = 1
self.biggestDataValue = 0
self.lineThickness = 2
self.nextChartFrame = 1
self.chartFrames = { }
self.lineNameIndicators = { }
self.amountOfBackgroundProcess = 0
chartFrameSharedConstructor ( self )
end ,
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 smoothingMethod string|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 , smoothingMethod , 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.chartName = name or " "
local payload = {
smoothnessMethod = smoothingMethod or " sma " ,
smoothnessLevel = smoothnessLevel or 3 ,
mainFrame = self ,
}
--setting the data will start a background process to smooth the data
chartFrame : SetData ( data , payload )
end ,
---internally handle next line
---@param self df_chartmulti
---@return df_chart
GetChart = function ( self )
local chartFrame = self.chartFrames [ self.nextChartFrame ]
if ( not chartFrame ) then
chartFrame = createChartFrame ( self , " $parentChartFrame " .. self.nextChartFrame )
chartFrame : SetAllPoints ( )
chartFrame : UpdateFrameSizeCache ( )
self.chartFrames [ self.nextChartFrame ] = chartFrame
end
self.nextChartFrame = self.nextChartFrame + 1
chartFrame : Show ( )
return chartFrame
end ,
---get all charts added to the multi chart frame
---@param self df_chartmulti
---@return df_chart[]
GetCharts = function ( self )
return self.chartFrames
end ,
---get the amount of charts added to the multi chart frame
---@param self df_chartmulti
---@return number
GetAmountCharts = function ( self )
return self.nextChartFrame - 1
end ,
---hide all charts
---@param self df_chartmulti
HideCharts = function ( self )
local charts = self : GetCharts ( )
for i = 1 , # charts do
local chartFrame = charts [ i ]
chartFrame : Hide ( )
end
end ,
---reset the multi chart frame
---@param self df_chartmulti
Reset = function ( self )
self : HideCharts ( )
self : ResetMinMaxValues ( )
self : ResetBackdropIndicators ( )
self.nextChartFrame = 1
self.biggestDataValue = 0
self.xAxisDataNumber = 0
end ,
---set the min and max values of all charts
---@param self df_chartmulti
---@param minValue number
---@param maxValue number
SetChartsMinMaxValues = function ( self , minValue , maxValue )
local allCharts = self : GetCharts ( )
for i = 1 , self : GetAmountCharts ( ) do
local chartFrame = allCharts [ i ]
chartFrame : SetMinMaxValues ( minValue , maxValue )
end
end ,
---set the max data size of all charts
---@param self df_chartmulti
---@param dataSize number
SetMaxDataSize = function ( self , dataSize )
self.biggestDataValue = math.max ( self.biggestDataValue , dataSize )
end ,
---get the max data size of all charts
---@param self df_chartmulti
---@return number
GetMaxDataSize = function ( self )
return self.biggestDataValue
end ,
---@param self df_chartmulti
---@param value number
SetLineThickness = function ( self , value )
assert ( type ( value ) == " number " , " number expected on :SetLineThickness(number) " )
self.lineThickness = value
end ,
---@param self df_chartmulti
UpdateChartNamesIndicator = function ( self )
local allCharts = self : GetCharts ( )
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 ,
---@param self df_chartmulti
WaitForBackgroundProcess = function ( self )
--start a ticker to check if the background process is done
if ( not self.waitForBackgroundProcessTicker ) then
self.waitForBackgroundProcessTicker = C_Timer.NewTicker ( 0.1 , function ( )
if ( not self : HasBackgroundProcess ( ) ) then
self.waitForBackgroundProcessTicker : Cancel ( )
self.waitForBackgroundProcessTicker = nil
self : Plot ( )
end
end )
end
end ,
---draw all the charts added to the multi chart frame
---@param multiChartFrame df_chartmulti
Plot = function ( multiChartFrame )
--check if there is a background process ongoing
if ( multiChartFrame : HasBackgroundProcess ( ) ) then
multiChartFrame : WaitForBackgroundProcess ( )
return
end
local allCharts = multiChartFrame : GetCharts ( )
local bFillChart , fillLineThickness = multiChartFrame : GetFillState ( )
---@type table<number, {average: number, chartIndex: number}>
local biggestAverage = { }
--set the min/max values of the multi chart frame
for i = 1 , multiChartFrame : GetAmountCharts ( ) do
local chartFrame = allCharts [ i ]
multiChartFrame : SetMaxValueIfBigger ( chartFrame : GetMaxValue ( ) )
multiChartFrame : SetMinValueIfLower ( chartFrame : GetMinValue ( ) )
local dataAmount = chartFrame : GetDataSize ( )
multiChartFrame : SetMaxDataSize ( dataAmount )
if ( bFillChart ) then
chartFrame : SetFillChart ( true , fillLineThickness )
end
--get the average of this chart
biggestAverage [ i ] = { average = chartFrame.average , chartIndex = i }
end
--sort the averages by the biggest average placing the biggest average in the first position
table.sort ( biggestAverage , function ( a , b ) return a.average > b.average end )
local minValue , multiChartMaxValue = multiChartFrame : GetMinMaxValues ( )
local plotAreaWidth = multiChartFrame.plotFrame : GetWidth ( ) --if there's no axis, the plotFrame has no width
local maxDataSize = multiChartFrame : GetMaxDataSize ( ) --it's not clearing when a new boss is selected
local eachLineWidth = plotAreaWidth / maxDataSize
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 )
for o = 1 , # biggestAverage do
local thisAverageTable = biggestAverage [ o ]
if ( thisAverageTable.chartIndex == i ) then
chartFrame.depth = o
break
end
end
--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 , i )
end
multiChartFrame : ShowBackdropIndicators ( )
updateLabelValues ( multiChartFrame )
multiChartFrame : UpdateChartNamesIndicator ( )
end ,
}
---create a chart frame object with support to multi lines
---@param parent frame
---@param name string|nil
---@return df_chartmulti
function detailsFramework : CreateGraphicMultiLineFrame ( parent , name )
name = name or ( " DetailsMultiChartFrameID " .. math.random ( 1 , 10000000 ) )
---@type df_chartmulti
local chartFrame = CreateFrame ( " frame " , name , parent , " BackdropTemplate " )
detailsFramework : Mixin ( chartFrame , detailsFramework.ValueMixin )
detailsFramework : Mixin ( chartFrame , detailsFramework.MultiChartFrameMixin )
detailsFramework : Mixin ( chartFrame , detailsFramework.ChartFrameSharedMixin )
chartFrame : ValueConstructor ( )
chartFrame : MultiChartFrameConstructor ( )
createPlotFrame ( chartFrame ) --creates chartFrame.plotFrame
return chartFrame
end