AuctioneerSuite/Auc-Stat-Histogram/StatHistogram.lua
2026-04-13 17:48:13 -04:00

843 lines
32 KiB
Lua

--[[
Auctioneer - Histogram Statistics module
Version: 5.7.4568 (KillerKoala)
Revision: $Id: StatHistogram.lua 4840 2010-08-04 21:44:00Z Nechckn $
URL: http://auctioneeraddon.com/
This is an addon for World of Warcraft that adds statistical history to the auction data that is collected
when the auction is scanned, so that you can easily determine what price
you will be able to sell an item for at auction or at a vendor whenever you
mouse-over an item in the game
License:
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program(see GPL.txt); if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
Note:
This AddOn's source code is specifically designed to work with
World of Warcraft's interpreted AddOn system.
You have an implicit license to use this AddOn with these facilities
since that is its designated purpose as per:
http://www.fsf.org/licensing/licenses/gpl-faq.html#InterpreterIncompat
--]]
if not AucAdvanced then return end
local libType, libName = "Stat", "Histogram"
local lib,parent,private = AucAdvanced.NewModule(libType, libName)
if not lib then return end
local aucPrint,decode,_,_,replicate,empty,get,set,default,debugPrint,fill, _TRANS = AucAdvanced.GetModuleLocals()
local tonumber,pairs,type,setmetatable=tonumber,pairs,type,setmetatable
local strsplit,strjoin = strsplit,strjoin
local min,max,abs,ceil,floor = min,max,abs,ceil,floor
local concat = table.concat
local wipe,unpack = wipe,unpack
local GetFaction = AucAdvanced.GetFaction
local data
local totaldata
local stattable = {}
local PDcurve = {}
local newstats = {}
local array = {}
local frame
local pricecache
function lib.CommandHandler(command, ...)
if (not data) then private.makeData() end
local serverKey = GetFaction()
local _,_,keyText = AucAdvanced.SplitServerKey(serverKey)
if (command == "help") then
aucPrint(_TRANS('SHTG_Help_SlashHelp1') )--Help for Auctioneer - Histogram
local line = AucAdvanced.Config.GetCommandLead(libType, libName)
aucPrint(line, "help}} - ".._TRANS('SHTG_Help_SlashHelp2') ) -- this Histogram help
aucPrint(line, "clear}} - ".._TRANS('SHTG_Help_SlashHelp3'):format(keyText) ) --clear current %s Histogram price database
elseif (command == "clear") then
lib.ClearData(serverKey)
end
end
function lib.Processor(callbackType, ...)
if (not data) then private.makeData() end
if (callbackType == "tooltip") then
lib.ProcessTooltip(...)
elseif (callbackType == "config") then
--Called when you should build your Configator tab.
private.SetupConfigGui(...)
elseif (callbackType == "load") then
lib.OnLoad(...)
elseif (callbackType == "scanstats") then
pricecache = nil
elseif callbackType == "auctionclose" then
pricecache = nil -- not actually needed, just conserving RAM
end
end
lib.Processors = {}
function lib.Processors.tooltip(callbackType, ...)
if (not data) then private.makeData() end
lib.ProcessTooltip(...)
end
function lib.Processors.config(callbackType, ...)
if (not data) then private.makeData() end
--Called when you should build your Configator tab.
private.SetupConfigGui(...)
end
function lib.Processors.load(callbackType, ...)
if (not data) then private.makeData() end
lib.OnLoad(...)
end
function lib.Processors.scanstats(callbackType, ...)
if (not data) then private.makeData() end
pricecache = nil
end
function lib.Processors.auctionclose(callbackType, ...)
if (not data) then private.makeData() end
pricecache = nil -- not actually needed, just conserving RAM
end
function private.GetPriceData()
if not stattable["count"] then
debugPrint("GetPriceData: No stattable", libType.."-"..libName, "Warning")
return
end
local median = 0
local Qone = 0
local Qthree = 0
local percent40, percent30 = 0, 0
local count = stattable["count"]
local refactored = false
local recount = 0
--now find the Q1, median, and Q3 values
if stattable["min"] == stattable["max"] then
--no need to do fancy median calculations
median = stattable["min"]*stattable["step"]
--count = value at the only index we have
count = stattable[stattable["min"]]
stattable["count"] = count
else
for i = stattable["min"], stattable["max"] do
recount = recount + (stattable[i] or 0)
if Qone == 0 and count > 4 then --Q1 is meaningless with very little data
if recount >= count/4 then
Qone = i*stattable["step"]
end
end
if percent30 == 0 then
if recount >= count*0.3 then
percent30 = i*stattable["step"]
end
end
if percent40 == 0 then
if recount >= count*0.4 then
percent40 = i*stattable["step"]
end
end
if median == 0 then
if recount >= count/2 then
median = i*stattable["step"]
end
end
if Qthree == 0 and count > 4 then--Q3 is meaningless with very little data
if recount >= count * 3/4 then
Qthree = i*stattable["step"]
end
end
end
if count ~= recount then
count = recount --We've just rechecked the count, so save the correct value
stattable["count"] = count
refactored = true
end
end
local step = stattable["step"]
if count > 20 then --we've seen enough to get a fairly decent price to base the precision on
if (step > (median/85)) and (step > 1) then
private.refactor(median*3, 300)
refactored = true
elseif step < (median/115) then
private.refactor(median*3, 300)
refactored = true
end
end
return median, Qone, Qthree, step, count, refactored, percent40, percent30
end
function lib.GetPrice(link, faction)
if not get("stat.histogram.enable") then return end
wipe(stattable)
local linkType,itemId,property,factor = AucAdvanced.DecodeLink(link)
if (linkType ~= "item") then return end
if (factor and factor ~= 0) then property = property.."x"..factor end
if not faction then faction = GetFaction() end
if (not data[faction]) or (not data[faction][itemId]) or (not data[faction][itemId][property]) then
debugPrint("GetPrice: No data", libType.."-"..libName, "Info")
return
end
if pricecache and pricecache[faction] and pricecache[faction][itemId] and pricecache[faction][itemId][property] then
return unpack(pricecache[faction][itemId][property])
end
private.UnpackStats(data[faction][itemId][property])
local median, Qone, Qthree, step, count, refactored, percent40, percent30 = private.GetPriceData()
if refactored then
--data has been refactored, so we need to repack it
data[faction][itemId][property] = private.PackStats()
--get the updated data
median, Qone, Qthree, step, count = private.GetPriceData()
end
--we're done with the data, so clear the table
wipe(stattable)
if not pricecache then pricecache = {} end
if not pricecache[faction] then pricecache[faction] = {} end
if not pricecache[faction][itemId] then pricecache[faction][itemId] = {} end
pricecache[faction][itemId][property] = {median or false, Qone or false, Qthree or false, step or false, count or false}
return median, Qone, Qthree, step, count, percent40, percent30
end
function lib.GetPriceColumns()
return "Median", "Q1", "Q3", "step", "Seen"
end
function lib.GetPriceArray(link, faction)
if not get("stat.histogram.enable") then return end
--make sure that array is empty
wipe(array)
local median, Qone, Qthree, step, count = lib.GetPrice(link, faction)
--these are the two values that GetMarketPrice cares about
array.price = median
array.seen = count
--additional data
array.Qone = Qone
array.Qthree = Qthree
array.step = step
-- Return a temporary array. Data in this array is
-- only valid until this function is called again.
return array
end
function private.ItemPDF(price)
if not PDcurve["step"] then return 0 end
local index = floor(price/PDcurve["step"])
if (index >= PDcurve["min"]) and (index <= PDcurve["max"]) then
return PDcurve[index]
else
return 0
end
end
function lib.GetItemPDF(link, faction)
if not get("stat.histogram.enable") then return end
wipe(PDcurve)
wipe(stattable)
local linkType,itemId,property,factor = AucAdvanced.DecodeLink(link)
if (linkType ~= "item") then return end
if (factor and factor ~= 0) then property = property.."x"..factor end
if not faction then faction = GetFaction() end
if not data[faction] then return end
if not data[faction][itemId] then return end
if not data[faction][itemId][property] then return end
local median, Qone, Qthree, step, count, refactored
if pricecache and pricecache[faction] and pricecache[faction][itemId] and pricecache[faction][itemId][property] then
median, Qone, Qthree, step, count = unpack(pricecache[faction][itemId][property])
end
private.UnpackStats(data[faction][itemId][property])
if not count or count == 0 then
median, Qone, Qthree, step, count, refactored = private.GetPriceData()
end
if refactored then
--data has been refactored, so we need to repack it
data[faction][itemId][property] = private.PackStats()
--get the updated data
median, Qone, Qthree, step, count = private.GetPriceData()
end
if not count or count == 0 then
return
end
if not pricecache then pricecache = {} end
if not pricecache[faction] then pricecache[faction] = {} end
if not pricecache[faction][itemId] then pricecache[faction][itemId] = {} end
pricecache[faction][itemId][property] = {median or false, Qone or false, Qthree or false, step or false, count or false}
local curcount = 0
local area = 0
local targetarea = min(1, count/30) --if count is less than thirty, we're not very sure about the price
PDcurve["step"] = step
PDcurve["min"] = stattable["min"]-1
PDcurve["max"] = stattable["max"]+1
for i = stattable["min"], stattable["max"] do
curcount = curcount + stattable[i]
if count == stattable[i] then
PDcurve[i] = 1
else
PDcurve[i] = 1-(abs(2*curcount - count)/count)
end
area = area + step*PDcurve[i]
end
local areamultiplier = 1
if area > 0 then
areamultiplier = targetarea/area
end
for i = PDcurve["min"], PDcurve["max"] do
PDcurve[i]= (PDcurve[i] or 0) * areamultiplier
end
return private.ItemPDF, PDcurve["min"]*PDcurve["step"], PDcurve["max"]*PDcurve["step"], targetarea
end
lib.ScanProcessors = {}
function lib.ScanProcessors.create(operation, itemData, oldData)
if not get("stat.histogram.enable") then return end
if (not data) then private.makeData() end
-- This function is responsible for processing and storing the stats after each scan
-- Note: itemData gets reused over and over again, so do not make changes to it, or use
-- it in places where you rely on it. Make a deep copy of it if you need it after this
-- function returns.
-- We're only interested in items with buyouts.
local buyout = itemData.buyoutPrice
if not buyout or buyout == 0 then return end
if (itemData.stackSize > 1) then
buyout = buyout/itemData.stackSize
end
local priceindex
-- Get the signature of this item and find it's stats.
local linkType,itemId,property,factor = AucAdvanced.DecodeLink(itemData.link)
if (linkType ~= "item") then return end
if (factor and factor ~= 0) then property = property.."x"..factor end
wipe(stattable)
local faction = GetFaction()
if not data[faction] then data[faction] = {} end
if not data[faction][itemId] then data[faction][itemId] = {} end
if data[faction][itemId][property] then
private.UnpackStats(data[faction][itemId][property])
end
if not stattable["count"] then
--start out with first 20 prices pushing max to 100. This should help prevent losing data due to the first price being way too low
--also keeps data small initially, as we don't need extremely accurate prices with that little data
stattable["step"] = ceil(buyout / 100)
stattable["count"] = 0
end
priceindex = ceil(buyout / stattable["step"])
if stattable["count"] <= 20 then
stattable["count"] = stattable["count"] + 1
--get the refactoring out of the way first, because we're not capping the price yet
--failure to do this now can cause major trouble in the form of massive tables
if priceindex > 100 then
private.refactor(buyout, 100)
priceindex = 100
end
if not stattable["min"] then
stattable["min"] = priceindex
stattable["max"] = priceindex
stattable[priceindex] = 0
elseif stattable["min"] > priceindex then
for i = priceindex, (stattable["min"]-1) do
stattable[i] = 0
end
stattable["min"] = priceindex
end
if not stattable[priceindex] then stattable[priceindex] = 0 end
stattable[priceindex] = stattable[priceindex] + 1
data[faction][itemId][property] = private.PackStats()
elseif priceindex <= 300 then --we don't want prices too high: they'll bloat the data. If range needs to go higher, we'll refactor later
stattable["count"] = stattable["count"] + 1
if not stattable["min"] then --first time we've seen this
stattable["min"] = priceindex
stattable["max"] = priceindex
stattable[priceindex] = 0
elseif stattable["min"] > priceindex then
for i = priceindex, (stattable["min"]-1) do
stattable[i] = 0
end
stattable["min"] = priceindex
elseif stattable["max"] < priceindex then
for i = (stattable["max"]+1),priceindex do
stattable[i] = 0
end
stattable["max"] = priceindex
end
if not stattable[priceindex] then stattable[priceindex] = 0 end
stattable[priceindex] = stattable[priceindex] + 1
data[faction][itemId][property] = private.PackStats()
end
wipe(stattable)
end
function private.SetupConfigGui(gui)
local id = gui:AddTab(lib.libName, lib.libType.." Modules")
gui:AddHelp(id, "what histogram stats",
_TRANS('SHTG_Help_WhatHistogramStats') ,--What are Histogram stats?
_TRANS('SHTG_Help_WhatHistogramStatsAnswer') )--Histogram stats record a histogram of past prices.
gui:AddHelp(id, "what advantages",
_TRANS('SHTG_Help_WhatAdvantageHistogram') ,--What advantages does Histogram have?
_TRANS('SHTG_Help_WhatAdvantageHistogramAnswer') )--Histogram stats don't have a limitation to how many, or how long, it can keep data, so it can keep track of high-volume items well
gui:AddHelp(id, "what disadvantage",
_TRANS('SHTG_Help_WhatDisadvantagesHistogram') ,--What disadvantages does Histogram have?
_TRANS('SHTG_Help_WhatDisadvantagesHistogramAnswer') )--Histogram rounds prices slightly to help store them, so there is a slight precision loss. However, it is precise to 1/250th of market price. (an item with market price 250g will have prices stored to the nearest 1g)
frame = gui.tabs[id].content
private.frame = frame
frame.slot = frame:CreateTexture(nil, "BORDER")
frame.slot:SetDrawLayer("Artwork") -- or the border shades it
frame.slot:SetPoint("TOPLEFT", frame, "TOPLEFT", 30, -210)
frame.slot:SetWidth(45)
frame.slot:SetHeight(45)
frame.slot:SetTexCoord(0.17, 0.83, 0.17, 0.83)
frame.slot:SetTexture("Interface\\Buttons\\UI-EmptySlot")
function frame.IconClicked()
local objtype, _, link = GetCursorInfo()
ClearCursor()
if objtype == "item" then
lib.SetWorkingItem(link)
else
lib.SetWorkingItem()
end
end
function frame.ClickHook(link)
if not frame.slot:IsVisible() then return end
lib.SetWorkingItem(link)
end
hooksecurefunc("HandleModifiedItemClick", frame.ClickHook)
frame.icon = CreateFrame("Button", nil, frame)
frame.icon:SetPoint("TOPLEFT", frame.slot, "TOPLEFT", 2, -2)
frame.icon:SetPoint("BOTTOMRIGHT", frame.slot, "BOTTOMRIGHT", -2, 2)
frame.icon:SetHighlightTexture("Interface\\Buttons\\ButtonHilight-Square.blp")
frame.icon:SetScript("OnClick", frame.IconClicked)
frame.icon:SetScript("OnReceiveDrag", frame.IconClicked)
frame.icon:SetScript("OnEnter", function() --set mouseover tooltip
if not frame.link then return end
GameTooltip:SetOwner(frame.icon, "ANCHOR_BOTTOMRIGHT")
GameTooltip:SetHyperlink(frame.link)
end)
frame.icon:SetScript("OnLeave", function() GameTooltip:Hide() end)
frame.name = frame:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
frame.name:SetPoint("TOPLEFT", frame.slot, "TOPRIGHT", 5,-2)
frame.name:SetPoint("RIGHT", frame, "RIGHT", -15)
frame.name:SetHeight(20)
frame.name:SetJustifyH("LEFT")
frame.name:SetJustifyV("TOP")
frame.name:SetText("Insert or Alt-Click Item to start")
frame.name:SetTextColor(0.5, 0.5, 0.7)
frame.bargraph = CreateFrame("Frame", nil, frame)
frame.bargraph:SetPoint("TOPLEFT", frame, "TOPLEFT", 30, -260)
frame.bargraph:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -30, -260)
frame.bargraph:SetHeight(300)
frame.bargraph:SetBackdrop({
bgFile = "Interface/Tooltips/UI-Tooltip-Background",
edgeFile = "Interface/Tooltips/UI-Tooltip-Border",
tile = true, tileSize = 32, edgeSize = 16,
insets = { left = 5, right = 5, top = 5, bottom = 5 }
})
frame.bargraph:SetBackdropColor(0, 0, 0.0, 0.5)
frame.bargraph.bars = {}
frame.bargraph.pdf = {}
frame.bargraph.max = 100
local graphwidth = frame.bargraph:GetWidth()-10
local graphheight = frame.bargraph:GetHeight()-10
for i = 1, 300 do
frame.bargraph.bars[i] = frame.bargraph:CreateTexture(nil)
local bar = frame.bargraph.bars[i]
bar:SetPoint("BOTTOMLEFT", frame.bargraph, "BOTTOMLEFT", (graphwidth*(i-1)/300)+5, 5)
bar:SetWidth(graphwidth/300)
bar:SetTexture(0.2, 0.8, 0.2)
function bar:SetValue(value)
if value == 0 then value = 0.001 end
self:SetHeight((self:GetParent():GetHeight()-20)*value)
end
bar:SetValue(0)
end
for i = 1, 300 do
frame.bargraph.pdf[i] = frame.bargraph:CreateTexture(nil)
local pdf = frame.bargraph.pdf[i]
pdf.offset = (graphwidth*(i-1)/300)+5
pdf:SetPoint("BOTTOMLEFT", frame.bargraph, "BOTTOMLEFT", pdf.offset, 50)
pdf:SetWidth(graphwidth/300)
pdf:SetHeight(5)
pdf:SetTexture(.2, .2, 0.8, .6)
function pdf:SetValue(value)
local bottom = (self:GetParent():GetHeight()-20)*value
self:SetPoint("BOTTOMLEFT", frame.bargraph, "BOTTOMLEFT", self.offset, bottom)
end
pdf:SetValue(0)
end
frame.key = frame:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
frame.key:SetPoint("BOTTOMRIGHT", frame.bargraph, "TOPRIGHT", 0, 2)
frame.key:SetPoint("TOPLEFT", frame.bargraph, "TOPLEFT", 5, 22)
frame.key:SetJustifyH("RIGHT")
frame.key:SetJustifyV("TOP")
frame.key:SetText("|cff3fff3fRaw Data |cffff3f3fMedian |cff3f3fffPrice Probability")
frame.med = frame.bargraph:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
frame.med:SetPoint("TOP", frame.bargraph, "BOTTOM", -50, -10)
frame.med:SetPoint("BOTTOM", frame.bargraph, "BOTTOM", -50, -25)
frame.med:SetWidth(150)
frame.max = frame.bargraph:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
frame.max:SetPoint("TOPRIGHT", frame.bargraph, "BOTTOMRIGHT", 0, -10)
frame.max:SetPoint("BOTTOMLEFT", frame.bargraph, "BOTTOMRIGHT", -150, -25)
--all options in here will be duplicated in the tooltip frame
function private.addTooltipControls(id)
gui:AddControl(id, "Header", 0, _TRANS('SHTG_Interface_HistogramOptions') )--Histogram options
gui:AddControl(id, "Note", 0, 1, nil, nil, " ")
gui:AddControl(id, "Checkbox", 0, 1, "stat.histogram.enable", _TRANS('SHTG_Interface_EnableHistogram') )--Enable Histogram Stats
gui:AddTip(id, _TRANS('SHTG_HelpTooltip_EnableHistogram') )--Allow Histogram to gather and return price data
gui:AddControl(id, "Note", 0, 1, nil, nil, " ")
gui:AddControl(id, "Checkbox", 0, 4, "stat.histogram.tooltip", _TRANS('SHTG_Interface_ShowHistogramTooltips') )--Show Histogram stats in the tooltips?
gui:AddTip(id, _TRANS('SHTG_HelpTooltip_ShowHistogramTooltips') )--Toggle display of stats from the Histogram module on or off
gui:AddControl(id, "Checkbox", 0, 6, "stat.histogram.median", _TRANS('SHTG_Interface_DisplayMedian') )--Display Median
gui:AddTip(id, _TRANS('SHTG_HelpTooltip_DisplayMedian') )--Toggle display of \'Median\' calculation in tooltips on or off
gui:AddControl(id, "Checkbox", 0, 6, "stat.histogram.iqr", _TRANS('SHTG_Interface_DisplayIQR') )--Display IQR
gui:AddTip(id, _TRANS('SHTG_HelpTooltip_DisplayIQR') )--Toggle display of \'IQR\' calculation in tooltips on or off. See help for further explanation.
gui:AddControl(id, "Checkbox", 0, 6, "stat.histogram.precision", _TRANS('SHTG_Interface_DisplayPrecision') )--Display Precision
gui:AddTip(id, _TRANS('SHTG_HelpTooltip_DisplayPrecision') )--Toggle display of \'precision\' calculation in tooltips on or off
gui:AddControl(id, "Note", 0, 1, nil, nil, " ")
gui:AddControl(id, "Checkbox", 0, 4, "stat.histogram.quantmul", _TRANS('SHTG_Interface_MultiplyStack') )--Multiply by Stack Size
gui:AddTip(id, _TRANS('SHTG_HelpTooltip_MultiplyStack') )--Multiplies by current Stack Size if on
gui:AddHelp(id, "what median",
_TRANS('SHTG_Help_WhatMedian') ,--What is the median?
_TRANS('SHTG_Help_WhatMedianAnswer') )--The median value is the value where half of the prices seen are above, and half are below.
gui:AddHelp(id, "what IQR",
_TRANS('SHTG_Help_WhatIQR') ,--What is the IQR?
_TRANS('SHTG_Help_WhatIQRAnswer') )--The IQR is a measure of spread. The middle half of the prices seen is confined with the range of IQR. An item with median 100g, and IQR 10g, has very consistent data. If the IQR was 100g, the prices are all over the place.
gui:AddControl(id, "Note", 0, 1, nil, nil, " ")
end
--This is the Tooltip tab provided by aucadvnced so all tooltip configuration is in one place
local tooltipID = AucAdvanced.Settings.Gui.tooltipID
--now we create a duplicate of these in the tooltip frame
private.addTooltipControls(id)
if tooltipID then private.addTooltipControls(tooltipID) end
gui:MakeScrollable(id)
gui:AddControl(id, "Subhead", 0, _TRANS('SHTG_Interface_ItemDataViewer') )--Item Data Viewer
end
function lib.SetWorkingItem(link)
--clear the graph
frame.name:SetText(_TRANS('SHTG_Interface_InsertItemStart') )--Insert or Alt-Click Item to start
frame.icon:SetNormalTexture(nil) --set icon texture
frame.link = nil
for i = 1,300 do
frame.bargraph.bars[i]:SetValue(0)
frame.bargraph.bars[i]:SetTexture(0.2, 0.8, 0.2)
frame.bargraph.pdf[i]:SetValue(0)
end
local linkType,itemId,property,factor = AucAdvanced.DecodeLink(link)
if (linkType ~= "item") then return end
local name, _, _, _, _, _, _, _, _, texture = GetItemInfo(link)
if not name or not texture then return end
frame.name:SetText(link)
frame.link = link
frame.icon:SetNormalTexture(texture) --set icon texture
wipe(stattable)
if (factor and factor ~= 0) then property = property.."x"..factor end
local faction = GetFaction()
if (not data[faction]) or (not data[faction][itemId]) or (not data[faction][itemId][property]) then
debugPrint("SetWorkingItem: No data", libType.."-"..libName, "Info")
return
end
private.UnpackStats(data[faction][itemId][property])
local maxvalue = 0
local indexes = 0
local count = stattable["count"]
local recount = 0
local median = 0
for i = stattable["min"], stattable["max"] do
indexes = indexes +1
if stattable[i] > maxvalue then maxvalue = stattable[i] end
recount = recount + stattable[i]
if median == 0 then
if recount >= count/2 then
median = i
frame.bargraph.bars[i]:SetTexture(0.8, 0.2, 0.2)
end
end
end
for i = stattable["min"], stattable["max"] do
frame.bargraph.bars[i]:SetValue(stattable[i]/maxvalue)
end
--now show the PD curve
wipe(PDcurve)
PDcurve["step"] = stattable["step"]
PDcurve["min"] = stattable["min"]
PDcurve["max"] = stattable["max"]
local curcount = 0
count = stattable["count"]
maxvalue = 0
for i = stattable["min"], stattable["max"] do
curcount = curcount + stattable[i]
if count == stattable[i] then
PDcurve[i] = 1
else
PDcurve[i] = 1-(abs(2*curcount - count)/count)
end
if PDcurve[i] > maxvalue then
maxvalue = PDcurve[i]
end
end
for i = PDcurve["min"], PDcurve["max"] do
PDcurve[i]= (PDcurve[i] or 0)
end
for i = 1,300 do
if (i >= PDcurve["min"]) and (i <= PDcurve["max"]) then
frame.bargraph.pdf[i]:SetValue(PDcurve[i])
else
frame.bargraph.pdf[i]:SetValue(0)
end
end
frame.med:SetText("Median: "..AucAdvanced.Coins(median*stattable["step"], true))
frame.max:SetText("Max: "..AucAdvanced.Coins(300*stattable["step"], true))
end
function lib.ProcessTooltip(tooltip, name, hyperlink, quality, quantity, cost, ...)
-- In this function, you are afforded the opportunity to add data to the tooltip should you so
-- desire. You are passed a hyperlink, and it's up to you to determine whether or what you should
-- display in the tooltip.
if not get("stat.histogram.tooltip") then return end
local quantmul = get("stat.histogram.quantmul")
if (not quantmul) or (not quantity) or (quantity < 1) then quantity = 1 end
local median, Qone, Qthree, step, count, percent40, percent30 = lib.GetPrice(hyperlink)
if not count then
count = 0
end
if median then
if quantity == 1 then
tooltip:AddLine(_TRANS('SHTG_Tooltip_PricesSeenOnce'):format(count) )--Histogram prices: (seen {{%s}})
else
tooltip:AddLine(_TRANS('SHTG_Tooltip_Prices'):format(quantity, count) )--Histogram prices x {{%s}}) : (seen {{%s}})
end
local iqr = Qthree-Qone
if get("stat.histogram.median") then
tooltip:AddLine(" ".._TRANS('SHTG_Tooltip_Median'), median*quantity)--median:
if quantity > 1 then
tooltip:AddLine(" ".._TRANS('SHTG_Tooltip_Individually'), median)--(or individually):
end
end
if (iqr > 0) and (get("stat.histogram.iqr")) then
tooltip:AddLine(" ".._TRANS('SHTG_Tooltip_IQR') , iqr*quantity)-- IQR:
end
if get("stat.histogram.precision") then
tooltip:AddLine(" ".._TRANS('SHTG_Tooltip_Precision'), step*quantity)--precision:
end
end
end
function lib.OnLoad(addon)
private.makeData()
private.makeTotalData()
AucAdvanced.Settings.SetDefault("stat.histogram.tooltip", false)
AucAdvanced.Settings.SetDefault("stat.histogram.median", false)
AucAdvanced.Settings.SetDefault("stat.histogram.iqr", true)
AucAdvanced.Settings.SetDefault("stat.histogram.precision", false)
AucAdvanced.Settings.SetDefault("stat.histogram.quantmul", true)
AucAdvanced.Settings.SetDefault("stat.histogram.enable", true)
end
function lib.ClearItem(hyperlink, serverKey)
local linkType,itemId,property,factor = AucAdvanced.DecodeLink(hyperlink)
if (linkType ~= "item") then return end
if (factor and factor ~= 0) then property = property.."x"..factor end
if not serverKey then serverKey = GetFaction() end
if not data[serverKey] then return end
if not data[serverKey][itemId] then return end
if not data[serverKey][itemId][property] then return end
data[serverKey][itemId][property] = nil
local _,_,keyText = AucAdvanced.SplitServerKey(serverKey)
aucPrint(libType.._TRANS('SHTG_Interface_ClearingData'):format(hyperlink, keyText))--- Histogram: clearing data for %s for {{%s}}
end
function lib.ClearData(serverKey)
if not serverKey then serverKey = GetFaction() end
if AucAdvanced.API.IsKeyword(serverKey, "ALL") then
local version = data.version -- save this so it doesn't get lost in the wipe
wipe(data)
data.version = version
aucPrint(_TRANS('SHTG_Help_SlashHelp5').." {{".._TRANS("ADV_Interface_AllRealms").."}}") --Clearing Histogram stats for // All realms
elseif data[serverKey] then
data[serverKey] = nil
local _,_,keyText = AucAdvanced.SplitServerKey(serverKey)
aucPrint(_TRANS('SHTG_Help_SlashHelp5').." {{"..keyText.."}}") --Clearing Histogram stats for
end
end
--[[ Local functions ]]--
function private.DataLoaded()
-- This function gets called when the data is first loaded. You may do any required maintenence
-- here before the data gets used.
--Forces all data to be refactored if the database hasn't been updated yet
local VERSION = 2
if (data["version"]) and (data["version"] >= VERSION) then return end
local function findallprices()
aucPrint("Auc-Stat-Histogram: Updating database. Please be patient")
local i = 1
for faction, itemlist in pairs(data) do
if type(itemlist) == "table" then
for itemId, proplist in pairs(itemlist) do
for prop, datastring in pairs(proplist) do
i = i+1
wipe(stattable)
private.UnpackStats(datastring)
local _,_,_,_,_,refactored = private.GetPriceData()
if refactored then
data[faction][itemId][prop] = private.PackStats()
end
if i%5000 == 0 then
coroutine.yield()
end
end
end
end
end
aucPrint("Auc-Stat-Histogram: Database is updated. Thank You for your patience")
data["version"] = VERSION
end
local co = coroutine.create(findallprices)
coroutine.resume(co)
local function onupdate(self)
if coroutine.status(co) ~= "dead" then
coroutine.resume(co)
else
self:Hide() -- stops further onupdates
end
end
local onupdateframe = CreateFrame("Frame")
onupdateframe:SetScript("OnUpdate", onupdate)
end
function private.makeData()
if data then return end
if (not AucAdvancedStatHistogramData) then AucAdvancedStatHistogramData = {} end
data = AucAdvancedStatHistogramData
private.DataLoaded()
end
function private.makeTotalData()
if totaldata then return end
AucAdvancedStatHistogramTotalData = ""
totaldata = AucAdvancedStatHistogramTotalData
--private.DataLoaded()
end
function private.UnpackStats(dataItem)
wipe(stattable)
if dataItem then
local firstvalue, maxvalue, step, count, newdataItem = strsplit(";",dataItem)
if not newdataItem then
debugPrint("UnpackStats: dataItem only 4 long", libType.."-"..libName, "Critical")
return
end
stattable["min"] = tonumber(firstvalue)
stattable["max"] = tonumber(maxvalue)
stattable["step"] = tonumber(step)
stattable["count"] = tonumber(count)
local index = stattable["min"]
if not index then
aucPrint(dataItem)
end
for n in newdataItem:gmatch("[0-9]+") do
stattable[index] = tonumber(n)
index = index + 1
end
else
debugPrint("UnpackStats: No data passed", libType.."-"..libName, "Warning")
end
end
local meta0 = { __index = function(tbl,key) return 0 end }
function private.PackStats()
setmetatable(stattable, meta0) -- Instead of looping through and checking for nil->0. /Mikk
local values = concat(stattable, ",", stattable.min, stattable.max)
local ret = strjoin(";",stattable.min, stattable.max, stattable.step, stattable.count, values)
setmetatable(stattable, nil) -- I'm not even sure if this needs to be unset, but I don't want to change the behavior of code I don't fully understand, so unsetting it. /Mikk
return ret
end
--private.refactor(pmax, precision)
--pmax is the max for the distribution
--redistributes the price data so that pmax is at precision
--this does cause some loss of accuracy, but should only be necessary every once in a great while
--and increases future accuracy.
--If data points would end up having an index > 750, they get cut off. They're more than 3x market price, and should not be taken into account anyway
--called by the GetPrice function when price is detected as being too far off an index of 250
--Also called when adding new data early on that would push the max up.
function private.refactor(pmax, precision)
if type(stattable) ~= "table" or type(pmax)~="number" or pmax == 0 then
return
end
wipe(newstats)
newstats["step"] = ceil(pmax/precision)
local conversion = stattable["step"]/newstats["step"]
newstats["min"] = ceil(conversion*stattable["min"])
newstats["max"] = ceil(conversion*stattable["max"])
local count = 0
if newstats["max"] > 300 then
--we need to crop off the top end
newstats["max"] = 300
stattable["max"] = floor(300/conversion)
end
for i = newstats["min"], newstats["max"] do
newstats[i] = 0
end
for i = stattable["min"], stattable["max"] do
local j = ceil(conversion*i)
if not newstats[j] then newstats[j] = 0 end
newstats[j]= newstats[j] + stattable[i]
count = count + stattable[i]
end
wipe(stattable)
for i,j in pairs(newstats) do
stattable[i] = j
end
stattable["count"] = count
end
AucAdvanced.RegisterRevision("$URL: http://svn.norganna.org/auctioneer/branches/5.9/Auc-Stat-Histogram/StatHistogram.lua $", "$Rev: 4840 $")