CoreScriptsRoot/Modules/Stats/BarGraph.lua
--[[
  Filename: BarGraph.lua
  Written by: dbanks
  Description: A simple bar graph.
--]]

--[[ Services ]]--
local CoreGuiService = game:GetService('CoreGui')

--[[ Globals ]]--
local BarZIndex = 1
local LineZIndex = 2


--[[ Modules ]]--
local StatsUtils = require(CoreGuiService.RobloxGui.Modules.Stats.StatsUtils)

--[[ Classes ]]--
local BarGraphClass = {}
BarGraphClass.__index = BarGraphClass

function BarGraphClass.new(showExtras) 
  local self = {}
  setmetatable(self, BarGraphClass)

  self._barFrame = Instance.new("Frame")
  self._barFrame.Name = "PS_BarFrame"
  self._barFrame.BackgroundTransparency = 1.0

  self._lineFrame = Instance.new("Frame")
  self._lineFrame.Name = "PS_LineFrame"
  self._lineFrame.BackgroundTransparency = 1.0

  self._showExtras = showExtras

  -- All of the values we are showing in the bar graph, in order.
  self._values = {}
  self._bars = {}
  -- Average of these values.
  self._average = 0
  -- Suggested max for these values.
  self._target = 0

  if self._showExtras then
    self:_addGraphTarget()
    self:_addGraphAverage()
  end

  return self
end

function BarGraphClass:SetZIndex(zIndex)
  self._barFrame.ZIndex = zIndex
  self._lineFrame.ZIndex = zIndex + 1
  if self._showExtras then
    self._targetLine.ZIndex = self._lineFrame.ZIndex
    self._averageLine.ZIndex = self._lineFrame.ZIndex
  end
end

function BarGraphClass:PlaceInParent(parent, size, position) 
  self._barFrame.Position = position
  self._barFrame.Size = size
  self._barFrame.Parent = parent
  self._lineFrame.Position = position
  self._lineFrame.Size = size
  self._lineFrame.Parent = parent
end

function BarGraphClass:SetAxisMax(axisMax) 
  self._axisMax = axisMax
end

function BarGraphClass:SetValues(values) 
  self._values = values
end

function BarGraphClass:SetAverage(average) 
  self._average = average
end

function BarGraphClass:SetTarget(target) 
  -- Set the target value, move corresponding graph line 
  -- accordingly (if present).
  self._target = target
  self:_moveGraphTarget()
end

function BarGraphClass:_updateBarCount(newBarCount)
  -- Make sure we have exactly this number of bars in _bars.
  -- Reuse old ones if possible.
  -- If we have more old ones than we need, delete them.
  local newBars = {}
  local currentBarCount = table.getn(self._bars)
  
  for i = 1, currentBarCount, 1 do
    if (i <= currentBarCount) then
      table.insert(newBars, self._bars[i])
    else
      self._bars[i].Destroy()
    end
  end
  
  for i = currentBarCount + 1, newBarCount, 1 do 
    table.insert(newBars, self:_makeNthBar(i))
  end
  
  self._bars = newBars
end
  

function BarGraphClass:Render()    
  local numValues = table.getn(self._values)
  
  self:_updateBarCount(numValues)
  
  for i, value in ipairs(self._values) do
    self:_updateBar(i, value, numValues)
  end  

  if self._showExtras then
    self:_moveGraphAverage()
  end
end


function BarGraphClass:_addGraphTarget() 
  -- Add the line used to mark target value.
  local line = Instance.new("ImageLabel")
  line.Name = "TargetLine"
  line.Size = UDim2.new(1, 0, 0, StatsUtils.GraphTargetLineInnerThickness)

  line.Image = 'rbxasset://textures/ui/PerformanceStats/TargetLine.png'
  line.BackgroundTransparency = 1
  line.Parent = self._lineFrame
  line.ZIndex = self._lineFrame.ZIndex 
  line.BorderSizePixel = 0

  line.Changed:connect(function()
      self:_updateTargetLineImageSize()
    end)

  self._targetLine = line
  self:_updateTargetLineImageSize()
end

function BarGraphClass:_updateTargetLineImageSize()
  self._targetLine.ImageRectSize = self._targetLine.AbsoluteSize
end

function BarGraphClass:_addGraphAverage() 
  -- Add the line used to mark average of current values.
  local line = Instance.new("Frame")
  line.Name = "AverageLine"
  line.Size = UDim2.new(1, 0, 0, StatsUtils.GraphAverageLineInnerThickness)

  line.Parent = self._lineFrame
  line.ZIndex = self._lineFrame.ZIndex

  StatsUtils.StyleAverageLine(line)

  self._averageLine = line
end

function BarGraphClass:_moveGraphTarget() 
  -- Update position of graph target line, if present.
  if self._targetLine == nil then 
    return
  end
  self._targetLine.Position = UDim2.new(0, 
    0, (
      self._axisMax - self._target)/self._axisMax,
    -StatsUtils.GraphTargetLineInnerThickness/2)
end

function BarGraphClass:_moveGraphAverage()  
  -- Update position of graph average line, if present.
  if self._averageLine == nil then 
    return
  end

  -- Never let it go above axis max.
  local adjustedAverage = math.min(self._average, self._axisMax)

  self._averageLine.Position = UDim2.new(0, 
    0, 
    (self._axisMax - adjustedAverage)/self._axisMax,
    -StatsUtils.GraphAverageLineTotalThickness/2)
end

function BarGraphClass:_makeNthBar(index)
  -- Make the nth bar in the bar graph.
  local realIndex = index-1
  local bar = Instance.new("Frame")
  bar.Name = string.format("Bar_%d", realIndex)
  bar.Parent = self._barFrame  
  bar.ZIndex = self._barFrame.ZIndex
  bar.BorderSizePixel = 0
  return bar
end

function BarGraphClass:_updateBar(i, value, numValues) 
  -- Update nth bar in graph: size, position, and color.
  local bar = self._bars[i]
  local realIndex = i-1

  -- Don't let it go off the chart.
  local clampedValue = math.max(0, math.min(value, self._axisMax))

  bar.Position = UDim2.new(realIndex/numValues, 0,
    (self._axisMax - clampedValue)/self._axisMax, 0)
  bar.Size = UDim2.new(1/numValues, 0, 
    clampedValue/self._axisMax, 0)

  bar.BackgroundColor3 = StatsUtils.GetColorForValue(value, self._target)
end

return BarGraphClass