Graphics Question

C

Thread Starter

curt wuollet

Hi All

Not that I need another project, but...
A long, long, time ago I wrote a program in Turbo Pascal on DOS that read four thermistors attached to a game card and plotted their temperature in strip chart recorder fashion on screen with the chart grads, etc. added as the chart scrolled. It also output the chart to a dot matrix printer attached for a permanent record.

I recently had to resurrect that program, blow the dust out of an old Zenith XT compatible computer and an epson FX286 printer and set the thing up.

I now have someone that wants that capability on a modern laptop. I explained that I don't do Windows and that was fine, so I set out to write the chart recorder part in C. This program was fairly easy to write in ancient TP with a Graphic library they had as an add on. The screen was basically just a big array of dots and you just plotted away. Amazingly enough, I haven't been able to find a simple way to do it under Linux.

There are console libs used for games, but to be really modern, it should be in a window. Since this is very much like a trending graph and there are some HMI folks here, I thought I could perhaps save some grief and solicit suggestions on what to use to do this in a reasonable amount of time. I would prefer C but I'm open to Python or other Linux solutions. I just need to define a canvas and be able to write one bit column at a time and scroll.

Regards
cww
 
I suggest you check out FreePascal which is very compatible with TurboPascal and also works great on Linux. FreePascal is mature and being used for a lot of *real* projects. Let me know if I can be of help.

See http://www.TurboControl.com for more information about FreePascal and for some example projects.
 
W

William Sturm

Have you looked at SVGALib? It creates non X-Windows graphics in Linux "IF" you have a compatible video card. It is similar in capabilities to the old Borland BGI graphics. DirectFB may also be an option.

Bill Sturm
 
C

curt wuollet

I did download FreePascal and have pored through the docs a bit. I haven't yet rigged a computer with ethernet and a 5.25" floppy drive or USB or some other way to get the program to a modern box.

I should, I suppose, try to just compile it and see what happens. I would like to do a rewrite as Pascal isn't as popular as it one was and I could do more with a C version, but the F route _might_ be faster. Some of the blessed simplicity of TP seems to have been lost in the move to portability. I do vaguely recall doing a rewrite to MIX C on DOS at one time but it would crash and I never did find out why even with their nifty visual debugger. I suspect that would be less portable than the TP version.

Regards
cww
 
Here's a simple example in Python that just draws a line graph using random numbers for "y". This uses the Tkinter graphics toolkit, although Gtk, wXwidgets or Qt would be fairly similar.

<pre>
#!/usr/bin/python
# A simple example that draws a line graph.
import Tkinter
import random

# This creates the window.
root = Tkinter.Tk()
root.title('A simple graph.')

# This creates the "canvas" object to draw on.
canvas = Tkinter.Canvas(root, width=500, height=250, bg = 'white')

canvas.pack()

# This makes a "quit" button.
Tkinter.Button(root, text='Quit', command=root.quit).pack()


# This loop draws a line using sequential values for "x"
# and random values for "y".
# Note: In case the web page eats the formatting, the next 3 lines after the
# "for" are supposed to be indented.
lasty = random.uniform(50, 200)
for x in xrange(0, 500, 50):
nexty = random.uniform(50, 200)
canvas.create_line(x, lasty, x + 50, nexty)
lasty = nexty

# This is the main event loop.
root.mainloop()
</pre>

This is cross platform and will work just about anywhere that Python will run. Tkinter comes in a standard Python install, so that is the easiest to install. If you want to use Gtk, wXwidgets, or Qt on MS Windows, you will have to install those packages separately, along with the corresponding Python bindings.

You can do this in 'C' but it takes a lot more lines of code to define things and set up call backs.

What I haven't shown is continuous updating. That would be done by using a timer to do a call back on a regular basis. There are timers built into the GUI event handler that are designed for this, so it's actually pretty easy.

A typical program of the type you are talking about involves having the GUI event loop (which happens inside "mainloop()" in this example) handling a timer and calling a designated function. That function does all the updating (reading the I/O, redrawing the screen, etc.) and then exiting, which hands things back to the event loop. Mouse clicks and keyboard presses all have their own handlers, but they are all dispatched by the same event handler.

If you want a more detailed example, let me know. The sort of thing you are talking about is fairly straightforward. I haven't got an example for printing, but that is similar to drawing on the screen except it's handled by a different API.
 
C

curt wuollet

Thanks Michael

Just the sort of example I was looking for. I'd messed with Tcl before but not low level graphics. That gives me something to check out. Plotting seems straightforward. I'll check out the Tkinter stuff. If they don't have a dot plot I can use a line length of 1. Next trick would be to scroll the canvas left when you get to the end to keep going. But this looks doable. I've wanted a vehicle to learn Python anyway. It is "new and hot" enough to justify the effort.

Regards
cww
 
In order to output graphics on modern computers you must use a library you can't write to the graphics card directly.

On Windows the low level interface is GDI (graphics device interface).
On Linux/Unix it is the XLIB.

On top of these libs you have higher level libraries. I would suggest to use Qt as the higher level library because it works on practically all modern OS.
http://qt.nokia.com/
(Qt is the basis for KDE for example but also for Google Earth ...)

Since you do not want to start completely from scratch you should use some sort of framework.

We provide a framework for HMI/SCADA that is based on Qt and also works on all modern OS.
See:
http://pvbrowser.org

This framework is easy to use and does not necessitate too much programming skills.
Graphics like you use can be done very easy.
Once you have it on screen you can print everything or insert it into office programs for example.

Please also have a look on:
http://pvbrowser.org/pvbrowser/doc/pvb.en.pdf
 
C

curt wuollet

Yeah, the only thing is that SVGALib is deprecated to be replaced by? But it is much like the old direct screen memory stuff. I've been pondering picking a graphics method for a while. It's just trying to pick the right one so as not to add to my immense store of non-reusable knowledge:^).

That's an occupational hazard for old programmers. Right now, nobody around here cares how many languages you can program in unless they belong to the evil empire. That's why I do maintenance work.

Regards
cww
 
Here's a scrolling graph. There's not much more to it than the original

<pre>
#!/usr/bin/python
# A simple example that draws a scrolling line graph.
import Tkinter
import random


# We create a class to make it easier to keep static data between calls.
class Graph:

def __init__(self):
# Initialise the first Y point.
self._YPoints = [125]
# The X points are all pre-defined.
self._XPoints = range(0, 500, 50)


def UpdateGraph(self):
# Create a new Y point.
self._YPoints.append(random.uniform(50, 200))
# This bit of magic combines the X and Y points in one list.
GraphPoints = list(sum(zip(self._XPoints, self._YPoints), ()))
# Delete everything which is already in the canvas.
canvas.delete(Tkinter.ALL)
# Draw a new line.
canvas.create_line(GraphPoints, fill='red', width=5)
# Shift the Y buffer left one if it is full.
if len(self._YPoints) > 10:
self._YPoints.pop(0)
# Call the update function again later.
root.after(250, self.UpdateGraph)

# This creates the graph object.
SimpleGraph = Graph()

# This creates the window.
root = Tkinter.Tk()
root.title('A simple graph.')

# This creates the "canvas" object to draw on.
canvas = Tkinter.Canvas(root, width=500, height=250, bg = 'white')

canvas.pack()

# This makes a "quit" button.
Tkinter.Button(root, text='Quit', command=root.quit).pack()

# Start the graph running.
SimpleGraph.UpdateGraph()


# This is the main event loop.
root.mainloop()
</pre>

The main differences to note here are that to scroll it we delete the previous line and then draw a new one. The other thing to note is the call using "after" at the end of "UpdateGraph" This sets up a call back to "UpdateGraph" after the specified interval (250 milliseconds in this example). For your application you would probably use one call back which would drive everything, including the reading the I/O and updating the graph with the data. In that case, you would put the call to "after" somewhere else more convenient.

If you don't understand the GraphPoints = list(sum(zip(self._XPoints, self._YPoints), ())) bit, don't worry about it. It's just a short cut that I took to keep the example program short. It just combines the X and Y lists into a single list with the X and Y interleaved with each other. You can do the exact same thing with a for loop if you are more comfortable with that. Python has a lot of features which allow you to work on an entire list (array) at once using a single instruction instead of using an explicit for loop. I could have used a deque (FIFO buffer) to automatically manage the length of YPoints (instead of testing it with an if statement), but I didn't want to have to explain that bit.

I also just took a short cut to delete just the whole graph every time. If I had axes and grids drawn on there, I might want to be a bit more selective about what I delete.

As for a dot plot, there is a parameter called "stipple" which uses a bit map graphic as a fill instead of using a colour. 'gray25' seems to be a example that gets shipped with it, but you are supposed to create your own. I've never used it however, so I don't know much about it. Using different colours for the lines has always been satisfactory for me.

For this example, CPU usage on a fairly slow computer is less than 1%. You don't have to worry to much about efficiency just to do something like that.

If you can, try to get your hands on a copy of "Python and Tkinter Programming" by John E. Grayson. It is the best GUI programming book that I have seen for any tool kit or language. Even if you decide to use a different tool kit later (e.g. wXWidgets), a lot of the principles are the same, so learning Tkinter would not be a waste of effort.
 
C
Yes, that is supposed to be the replacement for SVGALib. When I last looked at it, it was in a dormant state and there was really nothing I could see that actually suggested how to use it. That was quite a while ago. That was quite a while ago, I'll have to take another look. I've been away from C programming for a while, messing with some goofy relay based stuff:^) I'm not sure which path my future is in. Probably the one that offers to pay me first.

Regards,
cww
 
> In order to output graphics on modern computers you must use a library you can't write to the graphics card directly.

On Windows the low level interface is GDI (graphics device interface).
> On Linux/Unix it is the XLIB. <

The XLIB is really the lowest level of X but it is not easy to use and not very fast. The best choice for Linux is the framebuffer:
http://www.tldp.org/HOWTO/Framebuffer-HOWTO.html

The libsdl is able to use the framebuffer (http://www.libsdl.org ... for 2D apps use
http://www.ferzkopp.net/Software/SDL_gfx-2.0

For 3D apps is available a subset of OpenGL at:
http://bellard.org/TinyGL and SDL based:
http://www.kyb.tuebingen.mpg.de/bu/people/gf/software

> On top of these libs you have higher level libraries. I would suggest to use Qt as the higher level library because it works on practically all modern OS.
http://qt.nokia.com/
(Qt is the basis for KDE for example but also for Google Earth ...)

Since you do not want to start completely from scratch you should use some sort of framework. <

If you want a complete solution ... go with the ROOT system:
http://root.cern.ch
Some screenshots:
http://root.cern.ch/drupal/category/image-galleries/data-analysis-visualization

Regards,
Armin Steinhoff
http://www.steinhoff-automation.com
 
C
Thanks Michael

I'll pore through it, last night I was looking at pygames which offers scrolling and blitting. Hardly needed since this will be seconds per frame rather than fps, but it seems there are many ways to accomplish this. I could have a graph paper image, copy it to two surfaces and once the trace reaches the end of the first one, start scrolling it off and the other one on, plotting the traces as I go then flipping to the screen. I just need to pay my dues. I'm not very OO so, it'll take some getting used to. From what I've seen, Python is real and around to stay and worth learning. I'm afraid at the moment, I have to rely on the community for info and tutorials, adding to my library of aging programming books is for better times. The really great thing about OSS is that I can keep on learning. I'd like to be studying Contrologix and the new gen PACs but there's no way to do that on pocket change. I doubt the automation world will mind if I wander back into general computing.

Regards,
cww
 
> I'll pore through it, last night I was looking at pygames which offers scrolling and blitting. <

BTW ... pygames is SDL powered.

> Hardly needed since this will be seconds per frame rather than fps <

Yes, that's necessary for animated graphics.

Regards
--Armin


 
Here it is again with 50 points, and with grid and axis labels. This could obviously be made into a library which automatically calculates the axes, grid positions, and labels, but I have hard coded all the parameters here to keep the example short and simple.

You will notice that I keep track of the line using the self._PrevLine variable so I can delete just the line and leave the other graphics alone. I also made a few other minor changes (e.g. used a deque) to keep the example short.

CPU usage for this on a slow computer is about 1% for the program, and overall the CPU is 97% idle. Forget about framebuffer graphics or anything like that for this sort of application. You don't need it. This is not high performance graphics. For this sort of project you just want to get the program finished as quickly as possible with the minimum effort and number of lines of code. I'm not aware of anything else that even comes close to Python for those types of projects.

This will also be cross-platform. I have written Python programs on Linux that run as is on MS Windows without any making any special effort other than to use the normal Python libraries (e.g. let the libraries handle path separators and line endings). That includes communicating with IO through Ethernet and serial connections.

The only differences that I've found are platform limitations (things that MS Windows simply doesn't have, or poorer performance due to OS design limitations). You need to test for those, but generally it is a lot easier to develop Python programs on Linux and then copy them to MS Windows if that is what the user wants to use.

How are you going to interface with the hardware? If that involves a C library, Python has lots of options there as well.

If you have any more questions, let me know and I will be happy to answer them.

<pre>
#!/usr/bin/python
# A simple example that draws a scrolling line graph.
import Tkinter
import random
import collections


# We create a class to make it easier to keep static data between calls.
class Graph:

def __init__(self):
# Initialise the first Y point. This is a FIFO buffer that
# automatically manages the buffer length for us.
self._YPoints = collections.deque([125], maxlen=45)
# The X points are all pre-defined.
self._XPoints = range(40, 490, 10)
# Draw the axes.
canvas.create_line(40, 225, 490, 225, fill='blue', width=2)
canvas.create_line(40, 10, 40, 225, fill='blue', width=2)
# Label the axes.
for x in range(40, 490, 50):
canvas.create_text(x, 235, text=(x - 40))
for y in range(10, 225, 25):
canvas.create_text(15, y, text=(225 - y))

# Draw the grid.
for x in range(90, 490, 50):
canvas.create_line(x, 10, x, 225, fill='grey', width=1)
for y in range(10, 225, 25):
canvas.create_line(40, y, 490, y, fill='grey', width=1)

# We draw a small line to initialise the line delete operation
# so we don't have to check each time if a line is already there.
self._PrevLine = canvas.create_line(0,0,0,0)

def UpdateGraph(self):
# Create a new Y point.
self._YPoints.append(random.uniform(50, 200))
# This bit of magic combines the X and Y points in one list.
GraphPoints = list(sum(zip(self._XPoints, self._YPoints), ()))
# Delete the previous line.
canvas.delete(self._PrevLine)
# Draw a new line.
self._PrevLine = canvas.create_line(GraphPoints, fill='red', width=5)
# Shift the Y buffer left one it is full.
# Call the update function again later.
root.after(250, self.UpdateGraph)


# This creates the window.
root = Tkinter.Tk()
root.title('A simple graph.')

# This creates the "canvas" object to draw on.
canvas = Tkinter.Canvas(root, width=550, height=250, bg = 'white')
canvas.pack()

# This makes a "quit" button.
Tkinter.Button(root, text='Quit', command=root.quit).pack()

# This creates the graph object.
SimpleGraph = Graph()

# Start the graph running.
SimpleGraph.UpdateGraph()


# This is the main event loop.
root.mainloop()</pre>
 
C
I am embarrassed by your kindness and expenditure of time just to help out and most impressed with your command of the language, which to me at the moment looks rather overwhelming and large.

We must have a version difference, the first ran but the second bailed with:
<pre>
File "grapher.py", line 13, in __init__
self._YPoints = collections.deque([125],maxlen=45)
TypeError: deque() does not take keyword arguments
</pre>
My debugging ability is not good at the moment, but perhaps the deque has been extended recently?

You're right though, it may take a book to get the big picture and background.

Regards,
cww

Python -V gives:
Python 2.5.2
 
> Here it is again with 50 points, and with grid and axis labels. This could obviously be made into a library which automatically calculates the axes, grid positions, and labels, but I have hard coded all the parameters here to keep the example short and simple.

> You will notice that I keep track of the line using the self._PrevLine variable so I can delete just the line and leave the other graphics alone. I also made a few other minor changes (e.g. used a deque) to keep the example short.

> CPU usage for this on a slow computer is about 1% for the program, and overall the CPU is 97% idle. Forget about framebuffer graphics or anything like that for this sort of application. You don't need it. <

You need it if you want to develop efficient animated graphics. SDL and e.g. SDL-gfx are offering low level graphic widgets... you can also build your own low level widgets which allows you to optimize the blitting actions down to single pixels.

> This is not high performance graphics. For this sort of project you just want to get the program finished as quickly as possible with the minimum effort and number of lines of code. I'm not aware of anything else that even comes close to Python for those types of projects. <

Yes, I like Python very much. PyQt, PyGtk are also an interesting options for standard GUIs. But for object oriented graphical frameworks go with ROOT and CINT. BTW ... the integration of a C/C++ library into CINT (C/C++ interpreter, http://root.cern.ch) is a matter of few text lines!

(http://root.cern.ch/phpBB3/viewtopic.php?f=5&t=10314 scroll down for links)

> This will also be cross-platform. I have written Python programs on Linux that run as is on MS Windows without any making any special effort other than to use the normal Python libraries (e.g. let the libraries handle path separators and line endings). That includes communicating with IO through Ethernet and serial connections. <

Ethernet Powerlink, EtherCAT, Modbus-TCP and EthernetIP are available by open source projects ... and they are compiling on Linux "out of the box"

> The only differences that I've found are platform limitations (things that MS Windows simply doesn't have, or poorer performance due to OS design limitations). You need to test for those, but generally it is a lot easier to develop Python programs on Linux and then copy them to MS Windows if that is what the user wants to use.

> How are you going to interface with the hardware? If that involves a C library, Python has lots of options there as well. <

> If you have any more questions, let me know and I will be happy to answer them.

> [ clip]

> # This is the main event loop.
> root.mainloop()

The main loop is the weak point. It should allow conditional blitting...

Regards
--Armin
 
I don't mind making the explanation and I imagine that other people are interested as well. Go ahead and ask questions, this is a subject that I like to talk about.

The maxlen parameter in deque is a 2.6 feature. If you're using 2.5 you will get an error. Version 2.6 has been out for a while (2.7 is about to come out and the 3.x series is actually the newest), but I'm guessing that you are using something like Debian Stable or Centos (enterprise server distros tend to be a bit behind the times). This however is an issue that I have to deal with regularly with my MBLogic project. New features like this make things more convenient for me, but I get complaints from users if the software won't run on Debian Stable (which is using 2.5 until the new Debian release comes out). I get more downloads for the MS Windows version, but a lot of the people who contact me directly about actual applications they are working on seem to deploy on Debian.

The deque was something that I added in the last version to save a couple of lines. You just have to go back to the method shown in the previous ones to fix the problem:

1) Remove the line with "import collections"

2) Replace the following:
# Initialise the first Y point. This is a FIFO buffer that
# automatically manages the buffer length for us.
self._YPoints = collections.deque([125], maxlen=45)

with:

# Initialise the first Y point.
self._YPoints = [125]

3) Restore the following back into "UpdateGraph":

<pre>
# Shift the Y buffer left one it is full.
if len(self._YPoints) > 45:
self._YPoints.pop(0)
</pre>

This check should probably go right after the "append" operation, rather than at the end of the function like I originally had it.

Make sure you use proper indentation. In Python, indentation is syntactically significant, as in a messy program is also an incorrect program. Use consistent indentation (either spaces or tabs) all they way through. Don't mix indents with tabs or you'll have headaches. On the other hand, you'll never have to worry about forgetting a curly brace (which is the number one error I have in Javascript).

Most editors understand Python syntax and will highlight and auto-indent (or whatever they do) for it, but you may have to enable that feature if it's turned off.

Here it is with the changes. I will include a line by line explanation just after the listing.

<pre>
#!/usr/bin/python
# A simple example that draws a scrolling line graph.
import Tkinter
import random


# We create a class to make it easier to keep static data between calls.
class Graph:

def __init__(self):
# Initialise the first Y point.
self._YPoints = [125]
# The X points are all pre-defined.
self._XPoints = range(40, 490, 10)

# Draw the axes.
canvas.create_line(40, 225, 490, 225, fill='blue', width=2)
canvas.create_line(40, 10, 40, 225, fill='blue', width=2)
# Label the axes.
for x in range(40, 490, 50):
canvas.create_text(x, 235, text=(x - 40))
for y in range(10, 225, 25):
canvas.create_text(15, y, text=(225 - y))

# Draw the grid.
for x in range(90, 490, 50):
canvas.create_line(x, 10, x, 225, fill='grey', width=1)
for y in range(10, 225, 25):
canvas.create_line(40, y, 490, y, fill='grey', width=1)

# We draw a small line to initialise the line delete operation
# so we don't have to check each time if a line is already there.
self._PrevLine = canvas.create_line(0,0,0,0)

def UpdateGraph(self):
# Create a new Y point.
self._YPoints.append(random.uniform(50, 200))
# Shift the Y buffer left one it is full.
if len(self._YPoints) > 45:
self._YPoints.pop(0)

# This bit of magic combines the X and Y points in one list.
GraphPoints = list(sum(zip(self._XPoints, self._YPoints), ()))

# Delete the previous line.
canvas.delete(self._PrevLine)
# Draw a new line.
self._PrevLine = canvas.create_line(GraphPoints, fill='red', width=5)

# Call the update function again later.
root.after(250, self.UpdateGraph)


# This creates the window.
root = Tkinter.Tk()
root.title('A simple graph.')

# This creates the "canvas" object to draw on.
canvas = Tkinter.Canvas(root, width=550, height=250, bg = 'white')
canvas.pack()

# This makes a "quit" button.
Tkinter.Button(root, text='Quit', command=root.quit).pack()

# This creates the graph object.
SimpleGraph = Graph()

# Start the graph running.
SimpleGraph.UpdateGraph()


# This is the main event loop.
root.mainloop()
</pre>

Here's a line by line explanation of what I did and why:

import Tkinter
import random

This imports the libraries we need. Tkinter is the graphics library, and random is just to generate the random numbers I used to fill the graph.

class Graph:

This creates a class (the prototype for an object). You don't have to use object oriented programming with Python, but it makes accessing the associated data a lot easier than trying to use global variables.

def __init__(self):

This is the declaration for the initialisation function (constructor). That exact name __init__ belongs to the language and is automatically called when the object is created. The "self" is a reserved name which allows the object to reference itself. You have to include it as the first parameter in declarations, but you don't have to include it explicitly in the actual function calls. "Self" exists because there are ways of doing special things without it, but I've never tried those.

self._YPoints = [125]
self._XPoints = range(40, 490, 10)

Notice the use of "self" again when referencing things inside the class. This is how Python knows you are using the class variables, rather than locals (local to the function). The underscore in these names is not significant. It is just a convention to show these are intended to be private variables.

The "self._YPoints = [125]" creates a list one element long with the first element being the number 125. Just [] would have created an empty list. A Python list is sort of a cross between an array and a linked list. It's one of the basic types, and it gets used a lot.

self._XPoints will form the X coordinates for out graph. We only need to create this once (since they never change), and just use it over and over again.

"range(40, 490, 10)" outputs a list starting at 40, ending at 490, and incrementing in steps of 10. The first and last parameters are option, so for example "range(10)" would just output the list of numbers from 0 to 9.

Range is used a lot in for loops where instead of saying "for (in i=0; i < 10; i++)" you would just say "for i in range(10):"

canvas.create_line(40, 225, 490, 225, fill='blue', width=2)

"canvas" was created in the "main" level using the statement "canvas = Tkinter.Canvas(root, width=550, height=250, bg = 'white')". I really should have explicitly imported it into the example as a parameter in the __init__ (or better still created it inside the Graph class), but I was trying to keep the example simple. create_line accepts a series of points as the X and Y coordinates for lines. The more pairs of points you provide, the more line segments you get. fill='blue' illustrates calling optional parameters by name. There are a lot more optional parameters which can be used to control the characteristics of the line.

<pre>
for x in range(40, 490, 50):
canvas.create_text(x, 235, text=(x - 40))
</pre>
This is just labelling one of the graph axes. create_text obviously creates text. The first two parameters give the coordinates (0,0 is the upper left of the canvas). text=(x - 40) is the text we want to print (text is an optional parameter). In this case we are printing the x coordinate (minus 40).

<pre>
for x in range(90, 490, 50):
canvas.create_line(x, 10, x, 225, fill='grey', width=1)
</pre>
This is drawing grid lines. I won't explain it as it should be obvious at this point.

self._PrevLine = canvas.create_line(0,0,0,0)

I drew a line which gets thrown away as soon as we start drawing. This way I don't have to check if a line exists before I delete it.


def UpdateGraph(self):

This is the function declaration. A function declaration can include parameters, but we don't have any in this case (except for "self").


self._YPoints.append(random.uniform(50, 200))

self._YPoints is a list, a list is an object, and among the properties it has is "append". Here we are appending a random number (between 50 and 200) to the end of the list. Normally you would be appending your readings.

<pre>
# Shift the Y buffer left one it is full.
if len(self._YPoints) > 45:
self._YPoints.pop(0)
</pre>

Here we're checking the length of self._YPoints to see if we need to start trimming off old points. If so, we "pop" a point from position 0 in the list.


GraphPoints = list(sum(zip(self._XPoints, self._YPoints), ()))

I won't explain the details of this, other than to say that "zip" combines two lists (our X and Y points) into a list of pairs of numbers, sum "flattens" the pairs, and "list" turns the result back into a list. The use of "sum" this way is a bit of a strange trick, since technically it is supposed to be used to add numbers together (sum a list). However, it also seems to be able to flatten lists as well. I could have done all this with a "for" loop, but this wasn't the point of the example, and I wanted to keep things short.


canvas.delete(self._PrevLine)
self._PrevLine = canvas.create_line(GraphPoints, fill='red', width=5)

Delete the old line, and draw a new one. We keep a reference to the new line so we can delete it later. This is why we created the "dummy" line in the initialisation - we could delete the line without having to check if we have created one yet.

root.after(250, self.UpdateGraph)

We have a window named "root" (it could be named anything). Windows have a method called "after" which schedules a call to a function after a set period of time. Pretty much every GUI toolkit that I am familiar with has something similar. In this case we are calling UpdateGraph again (note the use of "self"). In your own program you may not call it here, but you would have a call to "after" somewhere in your program to schedule the next cycle. That would wake up your program and have it do everything - read the data, log it (if necessary), update the graph, etc.

The call to "after" schedules the next wake-up and then exits. The program then returns to the main loop (we'll get to that later).


root = Tkinter.Tk()
root.title('A simple graph.')

At this point we're back in the outermost level of the program (the equivalent to a 'main' in C). Here, we've created a window and called it "root". We then add a title to the title bar of the window. Tkinter is the name of the module we imported, and somewhere inside it has a class called "Tk". Calling Tkinter.Tk() creates an object. Note the use of "()". That causes the "__init__" function in the class to execute. Without that we're just getting a reference (effectively a pointer) to the class, not creating an initialised object.


canvas = Tkinter.Canvas(root, width=550, height=250, bg = 'white')
canvas.pack()

Here we're creating a canvas object (which we just called "canvas"). A canvas is a GUI widget that you can draw on using drawing properties. All the toolkits that I am familiar with have something similar. The "pack" tells the canvas to fit itself into the window. You're going to want to read up on this but it is a common GUI concept. If you've used something like Glade however, the concepts will become familiar even if you didn't realise what was going on at the time. You have different options for packing widgets into containers, and packing containers into other containers, so that the GUI will lay itself out automatically into the best fit. You can position things using absolute coordinates, but that is not the normal way to do things. This isn't special to Tkinter. Gtk+, wxWidgets, or just about anything else will work the same way.

If anyone else who is reading this is trying to relate these concepts to how you do things in something like VB, the concepts may not translate directly. The native MS Windows GUI system is rather primative compared to what is used just about anywhere else. Here you deal with the GUI at an abstract level and use containers and packing options to say what you want to achieve and let the computer figure out how to actually position things for you (e.g. I want to arrange these buttons horizontally across the bottom of the window).


Tkinter.Button(root, text='Quit', command=root.quit).pack()

This adds the "quit" button. A "quit" method is built into Tkinter (to let us exit the program), but we could have used a different function to do something else (e.g. command=SimpleGraph.PrintGraph if we had a "PrintGraph" function in our graph object.)


SimpleGraph = Graph()

We create our graph object by calling our Graph function (note the use of the "()" - this is important). If this was a general purpose graph library we could have passed parameters into it to specify the size, colour, axis lables, etc. For example we could write a library class that looked like this: MoreComplicatedGraph = Graph2(5, 10, 'Time', 'Temperature'). In our example above however, we didn't use any parameters.


SimpleGraph.UpdateGraph()

We make a first call to UpdateGraph to start the cycle running. After this, the call to "after" in the function will schedule subsequent calls.


root.mainloop()

This has to be at the *end* of our start up. It doesn't exit "mainloop" until our program exits. Instead, mainloop dispatches calls to whatever functions we've scheduled to handle mouse clicks, keyboard input, and timed delays (including "after"). Again, this is a common GUI concept (the event dispatcher) and other toolkits will have something similar.


 
In reply to Armin Steinhoff: Displaying a scrolling strip chart doesn't require efficient animated graphics. Your points are quite valid, but my own point was that a scrolling strip chart isn't very demanding. I do the same thing by the way in a web browser using SVG and Javascript, and CPU usage is also very low.

You said that "Ethernet Powerlink, EtherCAT, Modbus-TCP and EthernetIP are available by open source projects"

Modbus-TCP is, without question open. With EtherCAT the specs were initially open, but then the EtherCAT people changed their minds when and sent their lawyers after a university in either Belgium or The Netherlands (I forget which) and force them to close down their open source EtherCAT project. The EtherCAT people were demanding registration, licensing and certification (which the university wasn't willing to to pay). If that has changed back once again, then I would appreciate hearing about any information you might have on it.

For Ethernet Powerlink, I wouldn't mind any up to date information.

With EthernetIP so far as I know they're still proprietary. You can get source, but it's still under a proprietary license. They were calling it a "modified BSD license", but the "modification" turned out to be to toss out all the BSD clauses and substitute their own.

They actually had two different licenses in the package. The first was BSD-like. The second (which you had to unpack everything to find) was their traditional proprietary license (the usual purchase a proprietary license, registration, certification, and approval).

The hosting service would see the first license, which lets the project qualify for free hosting (Free Software projects get free hosting because that helps promote the hosting company's commercial project hosting business). Under the second (hidden) license however, they should be paying for hosting under normal commercial terms (the hosting company isn't running a charity for big businesses). I'm not sure of the motivations behind having a hidden license, but it looks a bit unusual to me.

If you know of a version of EthernetIP that is available under genuinely open terms, I would be very interested in hearing about it.
 
Top