843 lines
32 KiB
Lua
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 $")
|