It is currently March 28th, 2024, 2:53 pm

Work-in-progress Steam-style histogram

Skins that don't neatly fit into other categories
Post Reply
FlyingHyrax
Posts: 232
Joined: July 1st, 2011, 1:32 am
Location: US

Work-in-progress Steam-style histogram

Post by FlyingHyrax »

I'm starting out using Lua, and as my first try (or at least the first try that is working...) I decided to try to create a Rainmeter histogram in the same style as the Steam desktop application. (This bit: http://img707.imageshack.us/img707/5504/asdask.jpg)

Current progress:
Image
This shows the skin with autoscaling turned on. The white number is the highest number in the current set, and all the other bar heights are scaled relative to that value.

Current script options
  • Define the width and height of the graph
  • Define the width of a single bar and the size of the gap between bars
  • Define the X and Y position of the first bar meter
  • Flip the histogram horizontally and/or vertically
  • Define which measure in the skin file will be used for the graph
  • Optional autoscaling (sort of like in Rainmeter's built-in histogram but not quite)
Current issues
I'm still using the script to write the image meters into the skin file. This has causes two separate problems:
  1. If you change the the width of the histogram or the width of the bars so that the number of bars is smaller, the excess image meters stay in the skin and still appear...
  2. On first run or after the number of bars changes (either smaller or larger) the script throw "nil reference" errors, like there is a gap between the meters being created and the meters being added to the tables (even though it's all done at the same time).
I thought I would be able to just have 1000-1500 image meters already written into the skin, and the script could just hide/show them as necessary. Unfortunately, I forgot that hidden meters still take up "space." 1000 invisible image meters to the right of the histogram blocked the skin from moving on the X axis. I'm still toying with this idea - I might try moving all of the unused, invisible meters into one location "out of the way." Not sure yet.

There is also an interesting point in the autoscaling code - the if/else blocks appear to be redundant:

Code: Select all

if Values[i] == maxV then
	h = height
else
	h  = math.ceil((Values[i]/maxV)*height)
end
If the current value (Value) is equal to the maximum value (maxV), then the result of the expression math.ceil((Values[i]/maxV)*height) should be equal to math.ceil(1*height), or 'height.' So I ought to be able to remove the if/else completely and just have on expression. But if I try, things go sideways. (Rainmeter doesn't crash, but the skin goes invisible, all my other loaded skins start to flicker, and Windows 7 changes my theme to Basic.)

Many thanks to Smurfier for cleaning up my Lua script! Much prettier now.

Current script code:

Code: Select all

function Initialize()
	-- Tables to hold the meter handles and the 'h' for each meter
	Meters = {}		
	Values = {}
	
	-- grabs the various appearance options
	gap = SELF:GetNumberOption('Gap',0)
	height = SELF:GetNumberOption('Height',10) - gap*2
	direction = SELF:GetNumberOption('FlipX',0) == 1
	orient = SELF:GetNumberOption('FlipY',0) == 1
	auto = SELF:GetNumberOption('Relative',0) == 1
	xStrt = SELF:GetOption('Xstart')
	yStrt = SELF:GetOption('Ystart')
	
	-- Gets the width of the histogram and divides that by the size of one bar 
	-- (including a gap) to get the number of bars in the histogram
	numBars = math.floor(SELF:GetNumberOption('Width',10) / (gap + SELF:GetNumberOption('BarWidth',1)))
	
	measure = SKIN:GetMeasure(SELF:GetOption('Msr'))
	-- for debug
	print(numBars .. ' | ' .. height .. ' | ' .. tostring(auto))
	
	for i=1,numBars do
		SKIN:Bang('!WriteKeyValue',i,'Meter','IMAGE')
		SKIN:Bang('!WriteKeyValue',i,'MeterStyle','sBar')
		Meters[i] = SKIN:GetMeter(i)
		Values[i] = 0
	end
	-- makes the X/Y of the first meter different (histogram 'start' position)
	SKIN:Bang('!WriteKeyValue',1,'X',xStrt)
	SKIN:Bang('!WriteKeyValue',1,'Y',yStrt)
end

function Update()
	if direction then
	-- if the chart should move from left to right...
		table.remove(Values)
		table.insert(Values, 1, measure:GetRelativeValue())
	else
	-- if the chart should move from right to left...
		table.remove(Values, 1)
		table.insert(Values, measure:GetRelativeValue())
	end
	
	-- find the largest value in the Values table, to use for autoscaling later
	maxV = 0
	for i,v in ipairs(Values) do
		maxV = math.max(maxV,v)
	end
	
	-- iterates through the meters and sets their 'H' values from the Values table
	for i=1, numBars do
		if auto then
			if Values[i] == maxV then
				h = height
			else
				h  = math.ceil((Values[i]/maxV)*height)
			end
		else
			h = math.ceil(Values[i]*height)
		end
		-- used b/c Meter:SetH() would not work
		SKIN:Bang('!SetOption ' .. Meters[i]:GetName() .. ' H ' .. h)
		-- note: Lua (X and Y or Z) same as C++/Java (X ? Y : Z)
		Meters[i]:SetY(orient and gap or (height - h + gap))
	end
	return math.floor(maxV*100)	-- could use this value in skin (not currently implemented)
end
Current skin code:

Code: Select all

[Rainmeter]
Update=1000

[Metadata]
Name=HistoBar (Test)
Author=FlyingHyrax
Information=
Version=beta
License=CC BY-NC-SA 3.0

[Variables]
SkinHeight=80
SkinWidth=500
Gap=1
BarWidth=4
BarColor=0,0,0,200

[mScript]
Measure=Script
ScriptFile=#@#histbarscript.lua
Gap=#Gap#
BarWidth=#BarWidth#
Height=#SkinHeight#
Width=#SkinWidth#
Msr=mCPU
FlipX=1
FlipY=0
Xstart=#*Gap*#
Ystart=0
Relative=1
firstRun=1

[mCPU]
Measure=CPU
Processor=0

[sBar]
SolidColor=#BarColor#
X=#Gap#R
Y=0
W=#BarWidth#
H=#SkinHeight#
Group=bars

[holder]
Meter=IMAGE
X=0
Y=0
W=(#SkinWidth#)+(#Gap#)
H=#SkinHeight#
SolidColor=0,0,0,50

[string]
Meter=STRING
MeasureName=mScript
X=#SkinWidth#
Y=#Gap#
StringAlign=RIGHTTOP
FontFace=Consolas
FontSize=18
FontColor=250,250,250,2500
AntiAlias=1
In the interest of getting better at this, any feedback on the Lua scripting is greatly appreciated. I know my code is not particularly elegant, so I'm definitely open to alternate solutions that would be more flexible.

Thanks!


Old:
Script code:

Code: Select all

function Initialize()
	-- Tables to hold the meter handles and the 'h' for each meter
	Meters = {}		
	Heights = {}
	
	-- grabs the various appearance options
	gap = SELF:GetNumberOption('Gap',0)
	height = SELF:GetNumberOption('Height',10) - gap
	direction = SELF:GetNumberOption('FlipX',0) == 1
	orient = SELF:GetNumberOption('FlipY',0) == 1
	auto = SELF:GetNumberOption('Relative',0) == 1
	xStrt = SELF:GetOption('Xstart')
	yStrt = SELF:GetOption('YStart')
	
	-- Gets the width of the histogram and divides that by the size of one bar 
	-- (including a gap) to get the number of bars in the histogram
	numBars = math.floor(SELF:GetNumberOption('Width',10) / (gap + SELF:GetNumberOption('BarWidth',1)))
	
	measure = SKIN:GetMeasure(SELF:GetOption('Msr'))
	-- for debug
	print(numBars .. ' | ' .. height .. ' | ' .. tostring(auto))
	
	-- writes image meters to the skin file, and adds handle to the Meters table
	-- also initializes Heights values
	for i=1,numBars do
		SKIN:Bang('!WriteKeyValue',i,'Meter','IMAGE')
		SKIN:Bang('!WriteKeyValue',i,'MeterStyle','sBar')
		Meters[i] = SKIN:GetMeter(i)
		Heights[i] = 0.01
	end
	-- makes the X/Y of the first meter different (histogram 'start' position)
	SKIN:Bang('!WriteKeyValue',1,'X',xStrt)
	SKIN:Bang('!WriteKeyValue',1,'Y',yStrt)
end

function Update()
	if direction then
	-- if the chart should move from left to right...
		table.remove(Heights)
		table.insert(Heights, 1, math.floor(measure:GetRelativeValue()*height))
	else
	-- if the chart should move from right to left...
		table.remove(Heights, 1)
		table.insert(Heights, math.floor(measure:GetRelativeValue()*height))
	end
	
	-- find the largest value in the Heights table, to use for autoscaling later
	max = 0
	for i,v in ipairs(Heights) do
		if max <= v then max = v end
	end
	print(max)	-- for debug
	
	-- iterates through the meters and sets their 'H' values from the Heights table
	for i=1, numBars do
		if auto then
			-- Add autoscaling bits here
		else
			-- used b/c Meter:SetH() would not work
			SKIN:Bang('!SetOption ' .. Meters[i]:GetName() .. ' H ' .. Heights[i])
			-- note: Lua (X and Y or Z) same as C++/Java (X ? Y : Z)
			Meters[i]:SetY(orient and gap or (height - Heights[i] - gap))
		end
		
	end
	return max	-- could use this value in skin (not currently implemented)
end
Skin Code:

Code: Select all

[Rainmeter]
Update=1000

[Metadata]
Name=HistoBar (Test)
Author=FlyingHyrax
Information=
Version=beta
License=CC BY-NC-SA 3.0

[Variables]
NumOfBars=100
SkinHeight=80
SkinWidth=500
Gap=1
BarWidth=3
BarColor=0,0,0,200

[mScript]
Measure=Script
ScriptFile=#@#histbarscript.lua
Gap=1
BarWidth=3
Height=#SkinHeight#
Width=#SkinWidth#
Msr=mCPU
FlipX=1
FlipY=0
Xstart=#*Gap*#r
Ystart=0
Relative=0

[mCPU]
Measure=CPU
Processor=0

[sBar]
SolidColor=#BarColor#
X=#Gap#R
Y=0
W=#BarWidth#
H=#SkinHeight#

[holder]
Meter=IMAGE
X=0
Y=0
W=(#SkinWidth#)+(#Gap#)
H=#SkinHeight#
SolidColor=0,0,0,10
Bekarfel
Posts: 217
Joined: May 16th, 2012, 5:38 am

Re: Work-in-progress Steam-style histogram

Post by Bekarfel »

will your script underestimate the amount of time for downloads to complete, and stop responding whenever downloads are occurring just like the steam client? :D
FlyingHyrax
Posts: 232
Joined: July 1st, 2011, 1:32 am
Location: US

Re: Work-in-progress Steam-style histogram

Post by FlyingHyrax »

Bekarfel wrote:will your script underestimate the amount of time for downloads to complete, and stop responding whenever downloads are occurring just like the steam client? :D
Haha! :lol: So true.
No, it won't, but it has it's own set of problems to make up for it... :(
Post Reply