-
-
Notifications
You must be signed in to change notification settings - Fork 622
Using Lua scripts (Part 11): For loops and cpu chart
for loops are extremely useful when you need to do the same thing a number of times
there are many ways you can use the FOR operator, but the way that I use the most is like so: for example
for i=1,10 do
print (i)
end
The i=1,10
is telling the script to perform the code within the loop (everything from the do to the end) for a certain number of times
when just written as i=1,10
, the code will be repeated 10 times for every whole number between 1 and 10, starting at 1 and ending at 10 (you can tell the code to count in different increments or even in reverse but i usually find counting by whole numbers to work)
change the numbers in the line to get different numbers of passes of the loop, different starting points and different ending points
BUT not only is the loop repeated for the designated number of times, the useful part is that for each pass of the loop the value of "i" changes when it is used inside the loop.
so in our loop above:
- the loop starts, "i" is set to a value of 1 inside the loop,
- the code within the loop is executed and we print 1 to the terminal **because i=1 and we are printing i ** (i is a just like other strings we have set values to)
the loop repeats, "i" is set to a value of 2 inside the loop, the code is executed, we print 2 to the terminal
loop repeats, i=3, print 3 to terminal
loop repeats, i=4, print 4 to terminal
and so on until i=10 and then the loop is done
so in the terminal we see
1
2
3
4
5
6
7
8
9
10
what makes this useful is that we can then use the changing values for i in calculations.
Say you wanted a horizontal row of 10 filled in white circles with each circle center 20 pixels away from the next with the first circle starting at coordinates 10,10
we would set our colors and set up the cairo_arc
function (x,y,radius,start_angle, end_angle) then draw the circle like this
cairo_set_source_rgba (cr,1,1,1,1)
cairo_arc (cr,10,10,10,0,2*math.pi)
cairo_fill (cr)
then we could copy and paste the code a further 9 times and edit the x coordinates for every subsequent circle
or we could use a for loop and have it do the repeats for us we can set our color outside of the loop as we want all our circles to be white
cairo_set_source_rgba (cr,1,1,1,1)
for i=1,10 do
x=10+((i-1)*20)
cairo_arc (cr,x,10,10,0,2*math.pi)
cairo_fill (cr)
end
In the above code I've separated the calculation for x out of the arc function and set a string called "x" to the value of the equation x=10+((i-1)*20)
this equation takes the value of i and subtracts 1 from it (so our first circle is drawn at x = 10) it then multiplies i-1 by 20 which is the gap we want between our circles then adds 10 which is the staring x for our first circle
so the for loop is activated and "i" is set to 1 so our equation works out so that x=10 and our first circle is drawn at coordinates 10,10 as we wanted
the loop repeats and "i" is set to 2. The equation works out so that x=30 and our next circle is drawn at coordinates 30,10 (ie 20 pixels further on from our first circle)
the loop continues until "i" = 10 and we end up with 10 circles positioned as we wanted
using equations you can do a lot with a simple progression of numbers as you get in this version of the for loop!
Another good use of the for loop is to put data into tables or read the data that is in a table.
Here is another lua display project to work on that uses these techniques:
conky has the ability to display a chart showing your cpu usage history with just a short command. It takes a little more to do the same thing in lua, but when you are done you have much more flexibility in how to use it
the first thing we need to be able to do it record out cpu usage over a period of time so that we have something to display. To do this we are going to use a table and we are going to get the data into the table using a for loop.
The type of table we will use is an indexed table, and in this case our table will only contain numbers
Indexed tables
these kinds of tables usually look like this when you write them out (i'll only put numbers into the table for now, but it can hold other things too)
somedata={13,44,25,26,10}
so unlike the "dictionary" takes we used before, this table only has one part to each bit of information. commas are always used in tables to separate values
in an indexed table, we are concerned about the order in which the data is in the table. in the table above
position 1 in the table holds the number 13, position 2 holds the number 44 and so on
to get the data out of the table we have to use a slightly different method tablename[tableposition]
you have to use the square brackets for this purpose
so i called the table "somedata", so to get the first entry in the table we put: somedata[1]
NOTE we can also use the square brackets to get data out of dictionary tables
dosettings={--set table and open
red=1,
green=0,
blue=0,
alpha=1,--alpha of 0 = full transparent, 1=full opaque
font="mono",
fontsize=12,
}--closes table
print (dosettings["font"]) -->prints "mono" in the terminal
getting data into a table Tables can be constructed by putting data into them via code rather than having to write the consent into the table directly
We can use the square brackets to get information into tables (just thinking about indexed tables for now)
we can set up a blank table like this: datatable={}
then we can use this code to put the number 16 into the first position of the table datatable[1]=16
and we can continue adding values using the same method
AS LONG AS we don't skip over any positions
datatable[2]=18
datatable[3]=20
datatable[4]=22
if we were able to look at the contents of "datatable" directly it would look like this: datatable={16,18,20,22}
in fact is is quite easy to take a look at what is in a table using a for loop
reading a table with a for loop the first thing we need to know is how many repeats we need the loop to do in order to see all the values in the table. In the above case we know that we have put 4 values into the table, but sometimes you dont know ahead of time how many things there are in a table.
an easy way to find out is to put # in front of the table name like so
--set blank table
datatable={}
--put data into table
datatable[1]=16
datatable[2]=18
datatable[3]=20
datatable[4]=22
--count entries
entries=#datatable
print (entries) --> 4 is printed to the terminal
then we can do this
for i=1,entries do
print ( datatable[i] )
end
so what happening in the loop?
loop starts, "i"=1 inside the loop, the code in the loop is executed: print ( datatable[i] )
and since i=1, we print the number in the first position of the table "datatable" and we see 16
printed in the terminal.
The loop repeats, "i"=2 18 is printed in the terminal
The loop repeats, "i"=3 20 is printed to the terminal
The loop repeats, "i"=4 (the upper limit of the loop since entries=4) 22 is printed to the terminal
end of loop.
NOTE the loop is activated and repeats until done before any subsequent code in the script is executed
So lets think about what is required to get our cpu history chart
The first thing to consider is that we want to information in our cpu table to be persistent from one execution of the lua script to the next and that leads us to think a bit more about how the whole lua script within conky works!
EXECUTING THE LUA SCRIPT every conky cycle the entire lua script is executed BUT we can write code in the lua script with conditions, using if statements for example, to control the operation of different bits of code. We already saw this in the setup lines required by the conky main function.
we used the line
if updates>5 then
--all the code stuff
end--if updates>5
the point of this condition is so that you don't get a segmentation fault if you try and access cpu values via the lua script
conky updates are quite useful for controlling other things too.
now if we had a setup like this:
function conky_main()
--main conky function setup lines
if updates>5 then
--##############################
--set blank table
cputable={}
--put data into table
cputable[1]=conky_parse("${cpu}")
--##############################
end-- if updates>5
--main function close out lines
end-- end main function
Then every cycle of conky, as long as the update number is above 5 the script is executed
beginning "updates=6" (since updates>5) the table called "cputable" is set blank and then subsequently the cpu usage value is put into it
the next conky cycle "updates=7" (since updates is still > than 5 and will be for every additional cycle) the lua script is executed from the beginning and "cputable" is set blank again and again the current cpu% is put into it
clearly this isnt going to get us anywhere because we are blanking our table each cycle we want the script to "remember" the values for cpu from previous conky cycles
SO we have to come up with a situation where the table "cputable" is set up blank only once and not blanked each time the lua script is executed
something like this will do it
function conky_main()
--main conky function setup lines
--setup cpu table
if updates==4 then
cputable={}
end
if updates>5 then
--##############################
cputable[1]=conky_parse("${cpu}")
--##############################
end-- if updates>5
--main function close out lines
end-- end main function
there will be only one time when "updates=4" so only one occasion for the table to be created every subsequent conky cycle "updates" will not equal 4 so the table isnt blanked and will be persistent from
but the next problem is that we are writing (and then overwriting) the current cpu% to position1 in the table each cycle!
so we have a persistent table in which we can store our cpu values so that we will have them available to the script so we can display them.
Now we have to get the information into the table in a useful way. Say we wanted to record 10 readings of cpu data, the value now plus the previous 9 values...
it may have been:
5 -- 9 seconds ago
6
8
6
12
10
8
7
4
3 -- now
the table we want would look like this: cputable={5,6,8,6,12,10,8,7,4,3}
we want to limit the number of values we are storing.
You could just keep recording values into the table and have the table grow bigger and bigger, but leave conky on for a few hours and you will have thousands of entries in the table. Big tables like that are inefficient and would almost certainly increase processor drain as the script has to read through those entries every conky cycle.
We want to specify the number of entires to store and have the script update the table, overwriting the older values with the new ones
so a second later (and our current cpu% is 13) our table would still contain 10 entries, but they would have shifted in order: cputable={,6,8,6,12,10,8,7,4,3,13}
and so on and so on until our 3 has moved all the way to the left and is eventually lost being replaced with more recent values
So we need to specify how many entries we want and set a string: table_length=10
As ever, there are many ways you could go about getting this result...
when i first wrote a script to give moving bars I wasnt aware of the use of for loops, wlourf then came up with the following code:
for i = 1, tonumber(table_length) do
if cpu_table[i+1]==nil then cpu_table[i+1]=0 end
cpu_table[i]=cpu_table[i+1]
if i==table_length then
cpu_table[table_length]=tonumber(conky_parse('${cpu}'))
end
end--of for loop
this cutting the number of code lines by many hundreds :D
NOTE - code indenting. Code indenting can help you keep track of whats going on in your code (when you have compound if statements and or loops). Since it is important that whenever you open an if or a loop you end it with end
but whether I do it or not is hit or miss.
so lets look at what the code is doing bit by bit
for i = 1, tonumber(table_length) do
here we are opening the for loop,
- setting the number of repeats for the loop
- setting where the loop starts and ends
im using tonumber(
table_length)
to make sure that there is no error given
more often than not i use a "wait and see" approach with tonumber :)
ie I tend not to put them in unless it turns out that i need them (or unless i can anticipate a potential problem for example when getting a value using conky_parse
its usually a good idea to use tonumber)
this loop will run 10 times, and "i" will take the value of every whole number between 1 and 10 within the loop in order.
if cpu_table[i+1]==nil then cpu_table[i+1]=0 end
this is one of those nil catcher lines because in the next line we will be trying to read the value that our table cpu_table
has at position [i+1]
we need this because if you try and read a table position that doesn't exist you are going to get a nil value, and then when you try and do something with a string that is nil you get an error and the script wont work until it is fixed
cpu_table[i]=cpu_table[i+1]
this is how we are going to get our number cycling.
This line will give us nil values because initially the table is empty
so if we try and read from table positioncpu_table[i+1]
,
when we start our loop, i=1
,
so essentially we are trying to read position 2 in the table which is empty and therefore nil
the line above has anticipated this so when we read position 2 in cpu_table
it is no longer nil, it has a bvalue of 0 instead
so our loop starts, i=1
we read position 2 of cpu_table
and check to see if it is nil
(if cpu_table[i+1]==nil
)
if it is nil (which it will be at the start) then we set it to a value of 0 (then cpu_table[i+1]=0 end
)
we set position 1 of cpu_table(cpu_table[ i ])
to the same value found at position 2 (=cpu_table[i+1]
)
position 1 in the table=0
our loop continues, i=2
we read position 3 of cpu_table
and check to see if it is nil
(if cpu_table[i+1]==nil
)
it will be nil to start so we set it to a value of 0 (then cpu_table[i+1]=0 end
)
we set position 2 of cpu_table
(cpu_table[ i ]
) to the same value found at position 3 (=cpu_table[i+1]
)
position 2 in the table=0
the loop continues in the same fashion, setting up our table on the first run of the script, so that position 1 to 9 are 0. BUT when we get to the last repeat of the loop we activate the next part of the script:
if i==table_length then
cpu_table[table_length]=tonumber(conky_parse('${cpu}'))
end
when i=table_length (which we set to 10) then position 10 in the table cpu_table
is set to the current cpu% value as read through the conky_parse
command
SO the very first cycle of the lua script, conky reads a cpu% value of 8 for example...
so once our for loop is all done what would cpu_table
look like?
cpu_table={0,0,0,0,0,0,0,0,0,8,0}
we will in fact have 11 entries in our table because when i=10 we still executed the first lines within the for loop, creating an entry [i+1] with a value of 0
next we end our for loop end--of for loop
the next conky cycle the script is run again and our for loop runs
this line cpu_table[i]=cpu_table[i+1]
shifts all the values in our table 1 position to the left and these lines
if i==table_length then
cpu_table[table_length]=tonumber(conky_parse('${cpu}'))
end
capture the new reading into the table at position 10
if the next cpu value is 5 our table will now look like this cpu_table={0,0,0,0,0,0,0,0,8,5,0}
next cycle cpu%=6 cpu_table={0,0,0,0,0,0,0,8,5,6,0}
and so on: shift existing values one place to the left, put latest value into position 10
We now have our record of CPU, now we have to work to display the values!
here is what we have so far, with a couple of additions and ive added a comment here and there
--this script draws history graphs
require 'cairo'
function conky_main()
if conky_window == nil then return end
local cs = cairo_xlib_surface_create(conky_window.display, conky_window.drawable, conky_window.visual, conky_window.width, conky_window.height)
cr = cairo_create(cs)
local updates=tonumber(conky_parse('${updates}'))
--setup tables, need one table for each graph to be drawn
if updates==4 then
cpu_table={}
end
--end of table setup
if updates>5 then
--#########################################################################################################
--you will need to copy and paste this section for each different graph you want to draw
--set table length, ie how many values you want recorded
table_length=10
for i = 1, tonumber(table_length) do
if cpu_table[i+1]==nil then cpu_table[i+1]=0 end
cpu_table[i]=cpu_table[i+1]
if i==table_length then
cpu_table[table_length]=tonumber(conky_parse('${cpu}'))
end
end--of for loop
--call graph drawing function
draw_graph(cpu_table,table_length)
--end of graph setup
--#########################################################################################################
end-- if updates>5
cairo_destroy(cr)
cairo_surface_destroy(cs)
cr=nil
end-- end main function
function draw_graph(data,table_length)
for i=1,table_length do
print (data[i])
end
end--of function draw_graph
i have started to think about writing a separate function to do the drawing part of the script...
so far i have the function call in the main function: draw_graph(cpu_table,table_length)
and i have the function itself started below the main function
function draw_graph(data,table_length)
for i=1,table_length do
print (data[i])
end
end--of function draw_graph
right now all i am doing is sending the contents of cpu_table
and table_length
from the main function to the drawing function
in the drawing function, the contents of cpu_table
are received and put into a table called "data".
I expect to be sending this function more than one table, for example once i have my cpu graph set up, i can use the same method for a memory usage graph, or a graph for each one of my cpu cores...or whatever else i want to see displayed
so im using a more generic term for my table, and "data" seemed appropriate :)
at this stage i just have a for loop in the drawing function that will print out in the terminal the contents of "data", using the value of table_length
as the upper limit of our loop repeats and "i" value
so im going to run the script and make sure that my data is being collected correctly and being sent to my drawing function correctly. I'll have cpu values showing in conky via ${cpu} so i can watch conky and the terminal and makes sure the numbers are correct.
and everything seems to be working...so we can move on and do something with that data so we have to think about what we want to see at the end... and what we want to make configurable
settings i want to be able to position my graph, and in this case everything will be relative to the bottom left corner
i want each cpu value to be represented by a vertical bar i want the color, width and maximum height of the bar to be configurable 0% will be at the bottom of the bar, 100% at the top
i want my graph to move from right to left so that current cpu value is on the right, getting older to the left
that should do for now...
the first thing i would do is to write in a settings section into the main function, and add those settings to the function call as well as editing the drawing function to receive the settings. For now I'll just be sending strings
im also going to make a string to hold the value to be displayed instead of using conky_parse
directly in the code
and im going to make a setting to specify max_value in case we want to use something other than % outputs in the main function
--you will need to copy and paste this section for each different graph you want to draw
--SETTINGS ##############################################
--set coordinates of bottom left corner of graph
blx,bly=100,200
--set value to display
gvalue=conky_parse('${cpu}')
--set max value for the above value
max_value=100
--set color, red green blue alpha , values 0 to 1
red,green,blue,alpha=1,1,1,1--fully opaque white
--set bar width
width=2
--set height of chart
cheight=150
--set table length, ie how many values you want recorded
table_length=10
--END OF SETTINGS #########################################
for i = 1, tonumber(table_length) do
if cpu_table[i+1]==nil then cpu_table[i+1]=0 end
cpu_table[i]=cpu_table[i+1]
if i==table_length then
cpu_table[table_length]=tonumber(gvalue)
end
end--of for loop
--call graph drawing function
draw_graph(cpu_table,max_value,table_length,blx,bly,red,green,blue,alpha,width,cheight)
--end of graph setup
and in the drawing function
function draw_graph(data,max_value,table_length,blx,bly,red,green,blue,alpha,width,cheight)
for i=1,#data do
print (data[i])
end
end--of function draw_graph
remember - when sending strings you have to make sure the numbers of strings you send in the main function is the same number you set (AND IN THE RIGHT ORDER) in the function that is receiving the strings
NOTE in the above case i have asked for bar width...so the total width of the graph will be determined by the number of bars (which is 10 in our example multiplied by width) I could have decided to set total graph width in which case i would have to calculate the width of the individual bars... as always there are plenty of different ways to go!
THE BARS there are a few ways we could draw our bars, we could use rectangles in which case our setting for width would be the width of the rectangle or we could draw line in which case width would affect line width
also depending on which way you go, you will have to think about positioning. with the rectangle you set a corner and draw the rectangle relative to that corner with the line you would set coordinates of the midpoint, in this case the midpoint at the bottom because of the way the setting of line width is applied
im going to go with drawing lines for this graph.
we can go ahead and set up out colors and line width commands we have our loop set up, and we can start to think about how we will get the lines drawn...
function draw_graph(data,max_value,table_length,blx,bly,red,green,blue,alpha,width,cheight)
cairo_set_source_rgba (cr,red,green,blue,alpha)
cairo_set_line_width (cr,width)
for i=1,table_length do
print (data[i])
end
end--of function draw_graph
when writing the code within the for loop, its a good idea to just think about one instance of code, for example, our loop starts and the first value of "i" will be 1... so what do we want to happen when "i"=1...
well... if we were to write print (data[1])
we would see the first entry in the "data" which is (in the code so far) the first value in cpu_table
it is the oldest cpu value that our table contains
we want our oldest value on the left, and we want the graph relative to the bottom left corner coordinates, so this all works out nicely :)
BUT we do have the hiccup of line width to think about
say we did this to draw a vertical line 100 pixels long:
cairo_set_line_width (cr,10)
cairo_move_to (cr,100,200)
cairo_line_to (cr,100,100)
cairo_stroke (cr)
our "move to" coordinates would be the bottom MIDDLE of the line. The line would extend for 1/2 line width to the left and1/2 line width to the right.
So the actual coordinates of the bottom left corner of the line would be 95,200
to put that as a calculation
--set value of blx
blx=100
--calculate actual coordinate to use with line so that bottom left x is actually at 100
blx=blx+(width/2)
NOTE we are adding (width/2) to blx because we want to shift the line to the right a little (increasing x means moving right)
the second instance of blx= uses the original blx in the calculation but then overwrites the contents of the blx string with the new value
bly will stay the same for each line we draw so we can construct an initial cairo_move_to
line
replacing out print(data[ i ])
test line like so:
cairo_move_to (cr,blx+(width/2),bly)
this would be the start position for the leftmost bar...
now we have to think about how we can apply the changing values of "i" inside the for loop to set the starting positions for all the bars
from the bottom left corner of the first line to the bottom left corner of the next line will be equal to our line width and this will be true for each subsequent bar
so we will be multiplying "width" by the value of "i"
BUT cairo_move_to (cr,blx+(width/2),bly)
is what we want for the first bar
and since we are using i=1,table_length
... the first number "i" will be is 1
so to keep out first start point as it is we would do something like this: cairo_move_to (cr,blx+(width/2)+((i-1)*width),bly)
why not just use "for i=0,9" ? you ask
so that we can just put cairo_move_to (cr,blx+(width/2)+(i*width),bly)
no particular reason... but then you would have to go and make our other for loops "for i=0,9" because right now if we were to try and read "data[ i ]" and i=0, we would get a nil value
i almost always start my for loops at 1 and calculate relative to that :) of course there are times when you want to start at 0 or a different number
so in our code so far we can put
function draw_graph(data,max_value,table_length,blx,bly,red,green,blue,alpha,width,cheight)
cairo_set_source_rgba (cr,red,green,blue,alpha)
cairo_set_line_width (cr,width)
for i=1,table_length do
cairo_move_to (cr,blx+(width/2)+((i-1)*width),bly)
end
end--of function draw_graph
the next thing to do, just in the cpu indicator bar example, we need to calculate bar height based on the value of cpu and the setting for total graph height (which i called "cheight") and max value
in this case,however, the values are in the table "data" so we need to get then out using the square brackets like this inside the for loop bar_height=(cheight/max_value)*data[i]
- we have a start position for each bar
- we know how tall to draw each bar
we just need to actually draw the lines, and in this case im going to use cairo_rel_line_to
cairo_rel_line_to
draws a line relative to the coordinates we set in cairo_move_to
in this case we want the x value of each line to be the same (as we are drawing vertical lines)
and we want the y value to change and represent our cpu value
- for the rel_line, to draw UP we have to specify NEGATIVE values
so put all that together our drawing function looks like this
function draw_graph(data,max_value,table_length,blx,bly,red,green,blue,alpha,width,cheight)
cairo_set_source_rgba (cr,red,green,blue,alpha)
cairo_set_line_width (cr,width)
for i=1,table_length do
--calculate bar height
bar_height=(cheight/max_value)*data[i]
--set start position for each bar, and modify with the value of "i"
cairo_move_to (cr,blx+(width/2)+((i-1)*width),bly)
--draw relative line, y becomes equal to bar height and must be negative to draw up
cairo_rel_line_to (cr,0,bar_height*-1)
--draw the line
cairo_stroke (cr)
end
end--of function draw_graph
and watch your cpu chart in action!
- Home
- Installation
- Configurations
- Window Configuration
- Configs
- FAQ
- Lua
- Variables
- Compatibility
- Using Lua scripts
- How does a Lua script work
- Displaying stuff in conky
- Drawing lines
- Drawing rectangles, circles and arcs
- Making a bar meter and a circle meter
- Borders and if statements
- Alarm colors and complex if statements
- Functions
- Function parameters
- Table parameters
- For loops and cpu chart
- Clock and circular things
- Useful functions and code
- CLI commands timers and line editing
- Mouse events
- Contributing
- Issue Reporting