Python
Prev | Chapter 7. Mathematical Expressions and Scripting | Next

## Python

This module provides bindings to the Python programming language. Basic usage in the context of QtiPlot will be discussed below, but for more in-depth information on the language itself, please refer to its excellent documentation.

### The Initialization File

This file allows you to customize the Python environment, import modules and define functions and classes that will be available in all of your projects. The default initialization file shipped with QtiPlot imports Python's standard math functions as well as special functions from SciPy (if available). Also, it creates some handy shortcuts, like `table("table1")` for `qti.app.table("table1")`.

When activating Python support, QtiPlot searches the following places, executing the first file it can find:

1. ~/.qtiplotrc.py[c]

2. /etc/qtiplotrc.py[c]

3. ./qtiplotrc.py[c]

Files ending in .pyc are compiled versions of the .py source files and therefore load a bit faster. The compiled version will be used if the source file is older or nonexistent. Otherwise, QtiPlot will try to compile the source file (if you've got write permissions for the output file).

### Python Basics

Mathematical expressions work largely as expected. However, there's one caveat, especially when switching from muParser (which has been used exclusively in previous versions of QtiPlot): `a^b` does not mean "raise a to the power of b" but rather "bitwise exclusive or of a and b"; Python's power operator is **. Thus:

    2^3 # read: 10 xor 11 = 01
    #> 1
    2**3
    #> 8

One thing you have to know when working with Python is that indentation is very important. It is used for grouping (most other languages use either braces or keywords like `do...end` for this). For example,

    x=23
    for i in (1,4,5):
    	x=i**2
    	print(x)

will do what you would expect: it prints out the numbers 1, 16 and 25; each on a line of its own. Deleting just a bit of space will change the functionality of your program:

    x=23
    for i in (1,4,5):
    	x=i**2
    print(x)

will print out only one number - no, not 23, but rather 25. This example was designed to also teach you something about variable scoping: There are no block-local variables in Python.

There are two different variable scopes to be aware of: local and global variables. Unless specified otherwise, variables are local to the context in which they were defined. Thus, the variable `x` can have three different values in, say, two different Note windows and a column formula. Global variables on the other hand can be accessed from everywhere within your project. A variable `x` is declared global by executing the statement `global x`. You have to do this before assigning a value to `x`, but you have to do it only once within the project (no need to "import" the variable before using it). Note that there is a slight twist to these rules when you define your own functions.

### Defining Functions and Control Flow

The basic syntax for defining a function (for use within one particular note, for example) is

    def answer():
    	return 42

If you want your function to be accessible from the rest of your project, you have to declare it global before the definition:

    global answer
    def answer():
    	return 42

You can add your own function to QtiPlot's function list. We'll also provide a documentation string that will show up, for example, in the "set column values" dialog:

    global answer
    def answer():
    	"Return the answer to the ultimate question about life, the universe and everything."
    	return 42
    qti.mathFunctions["answer"] = answer

If you want to remove a function from the list, do:

    del qti.mathFunctions["answer"]

Note that functions have their own local scope. That means that if you enter a function definition in a Note, you will not be able to access (neither reading nor writing) Note-local variables from within the function. However, you can access global variables as usual.

If-then-else decisions are entered as follows:

    if x>23:
    	print(x)
    else:
    	print("The value is too small.")

You can do loops, too:

    for i in range(1, 11):
    	print(i)

This will print out the numbers between 1 and 10 inclusively (the upper limit does not belong to the range, while the lower limit does).

### Mathematical Functions

Python comes with some basic mathematical functions that are automatically imported (if you use the initialization file shipped with QtiPlot). Along with them, the constants e (Euler's number) and pi (the one and only) are defined.

Table 7.4. Supported Mathematical Functions

Name | Description
--- | ---
acos(x) | inverse cosinus
asin(x) | inverse sinus
atan(x) | inverse tangent
atan2(y,x) | equivalent to atan(y/x), but more efficient
ceil(x) | ceiling; smallest integer greater or equal to x
cos(x) | cosinus of x
cosh(x) | hyperbolic cosinus of x
degrees(x) | convert angle from radians to degrees
exp(x) | Exponential function: e raised to the power of x.
fabs(x) | absolute value of x
floor(x) | largest integer smaller or equal to x
fmod(x,y) | remainder of integer division x/y
frexp(x) | Returns the tuple (mantissa,exponent) such that x=mantissa*(2**exponent) where exponent is an integer and 0.5 <=abs(m)<1.0
hypot(x,y) | equivalent to sqrt(x*x+y*y)
ldexp(x,y) | equivalent to x*(2**y)
log(x) | natural (base e) logarythm of x
log10(x) | decimal (base 10) logarythm of x
modf(x) | return fractional and integer part of x as a tuple
pow(x,y) | x to the power of y; equivalent to x**y
radians(x) | convert angle from degrees to radians
sin(x) | sinus of x
sinh(x) | hyperblic sinus of x
sqrt(x) | square root of x
tan(x) | tangent of x
tanh(x) | hyperbolic tangent of x

### Accessing QtiPlot's objects from Python

We will assume that you are using the initialization file shipped with QtiPlot. Accessing the objects in your project is straight-forward,

    t = table("Table1")
    m = matrix("Matrix1")
    g = graph("Graph1")
    n = note("Notes1")

as is creating new objects:

    # create an empty table named "tony" with 5 rows and 2 columns:
    t = newTable("tony", 5, 2)
    # use defaults
    t = newTable()
    # create an empty matrix named "gina" with 42 rows and 23 columns:
    m = newMatrix("gina", 42, 23)
    # use defaults
    m = newMatrix()
    # create an empty graph window
    g = newGraph()
    # create an empty note named "momo"
    n = note("momo")
    # use defaults
    n = note()

Every piece of code is executed in the context of an object which you can access via the `self` variable. For example, entering `self.cell("t",i)` as a column formula is equivalent to the convenience function `col("t")`.

Once you have established contact with a MDI window, you can modify some of it's properties, like the name, the window label, the geometry, etc.. For example, here's how to rename a window, change it's label and the way they are displayed in the window title bar, the so called caption policy:

    t = table("Table1")
    setWindowName(t, "toto")
    t.setWindowLabel("tutu")
    t.setCaptionPolicy(MDIWindow.Both)

The caption policy can have one of the following values:

1. Name

   the window caption is determined by the window name

2. Label

   the caption is detemined by the window label

3. Both

   caption = "name - label"

For a fast editing process, you can create template files from existing tables, matrices or plots. The templates can be used later on in order to create customized windows very easily:

    saveAsTemplate(graph("Graph1"), "my_plot.qpt")
    g = openTemplate("my_plot.qpt")

Also, you can easily clone a MDI window:

    g1 = clone(graph("Graph1"))

### Project Folders

Storing your data tables/matrices and your plots in folders can be very convenient and helpful when you're analysing loads of data files in the same project. New objects will always be added to the active folder. You can get a pointer to it via:

    f = activeFolder()

The functions table, matrix, graph and note will start searching in the active folder and, failing this, will continue with a depth-first recursive search of the project's root folder, given by:

    f = rootFolder()

In order to access subfolders and windows, there are the following functions:

    f2 = f.folders()[number]
    f2 = f.folder(name, caseSensitive=True, partialMatch=False)
    t = f.table(name, recursive=False)
    m = f.matrix(name, recursive=False)
    g = f.graph(name, recursive=False)
    n = f.note(name, recursive=False)

If you supply True for the recursive argument, a depth-first recursive search of all subfolders will be performed and the first match returned.

New folders can be created using:

    newFolder = addFolder("New Folder", parentFolder = 0)

If the `parentFolder` is not specified, the new folder will be added as a subfolder of the project's root folder. When you create a new folder via a Python script, it doesn't automatically become the active folder of the project. You have to set this programatically, using:

    changeFolder(newFolder, bool force=False)

Folders can be deleted using:

    deleteFolder(folder)

You can save a folder as a project file, and of course, you can also save the whole project:

    saveFolder(folder, "new_file.qti", compress=False)
    saveProjectAs("new_file_2.qti", compress=False)

If `compress` is set to True, the project file will be archived to the .gz format, using zlib.

Also, you can load a QtiPlot or an Origin project file into a new folder. The new folder will have the base name of the project file and will be added as a subfolder to the `parentFolder` or to the current folder if no parent folder is specified.

    newFolder = appendProject("projectName", parentFolder = 0)

If you don't want to be asked for confirmation when a table/matrix is renamed during this operation, or when deleting a folder via a Python script, you must change your preferences concerning prompting of warning messages, using the Preferences dialog ("Confirmations" tab).

### Working with Tables

We'll assume that you have assigned some table to the variable `t`. You can access its numeric cell values with

    t.cell(col, row)
    # and
    t.setCell(col, row, value)

Whenever you have to specify a column, you can use either the column name (as a string) or the consecutive column number (starting with 1). Row numbers also start with 1, just as they are displayed. If you want to work with arbitrary texts or the textual representations of numeric values, you can use:

    t.text(col, row)
    # and
    t.setText(col, row, string)

The number of columns and rows is accessed via:

    t.numRows()
    t.numCols()
    t.setNumRows(number)
    t.setNumCols(number)

You can delete a range of rows using:

    t.deleteRows(startRowNumber, endRowNumber)

Column names can be read and written with:

    t.colName(number)
    t.setColName(col, newName, enumerateRight=False)

If `enumerateRight` is set to True, all the table columns starting from index `col` will have their names modified to a combination of the `newName` and a numerical increasing index. If this parameter is not specified, by default it is set to False.

You can normalize a single column or all columns in a table:

    t.normalize(col)
    t.normalize()

Sort a single or all columns:

    t.sortColumn(col, order = 0);
    t.sort(type = 0, order = 0, leadingColumnName)

Import values from `file`, using `sep` as separator char, ignoring `ignore` lines at the head of the file and all lines starting with a `comment` string. The other flags should be self-explanatory.

    t.importASCII(file, sep="\t", ignore=0, renameCols=False, stripSpaces=True, simplifySpace=False, importComments = False, newTable=False, comment="#")

You can export values from a table to an ASCII `file`, using `sep` as separator chararacter. The `ColumnLabels` option allows you to export or ignore the column labels, `ColumnComments` does the same for the comments displayed in the table header and the `SelectionOnly` option makes possible to export only the selected cells of the table.

    t.exportASCII(file, sep="\t", ignore=0, ColumnLabels=False, ColumnComments=False, SelectionOnly=False)

Other settings that you can modify are the text displayed as a comment in the header of a column or the expression used to calculate the column values. Please beware that changing the command doesn't automatically update the values of the column!

    t.setComment(col, newComment)
    t.setCommand(col, newExpression)

After having changed some table values from a script, you will likely want to update dependent Graphs:

    t.notifyChanges()

As a simple example, let's set some column values without using the dialog.

    t = table("table1")
    for i in range(1, t.numRows()+1):
    	t.setCell(1, i, i**2)
    t.notifyChanges()

### Working with Matrices

We'll assume that you have assigned some matrix to the variable `m`. Accessing cell values is very similar to Table, but since Matrix doesn't use column logic, row arguments are specified before columns and obviously you can't use column name.

    m.cell(row, col)
    m.setCell(row, col, value)
    m.text(row, col)
    m.setText(row, col, string)

An alternative solution to assign values to a Matrix, would be to define a formula and to calculate the values using this formula, like in the following example:

    m.setFormula("x*y*sin(x*y)")
    m.calculate()

You can also specify a column/row range in the calculate() function, like this:

    m.calculate(startRow, endRow, startColumn, endColumn)

Before setting the values in a matrix you might want to define the numeric precision, that is the number of significant digits used for the computations:

    m.setNumericPrecision(prec)

Also, like with tables, you can access the number of rows/columns in a matrix:

    rows = m.numRows()
    columns = m.numCols()

Matrix objects allow you to define a system of x/y coordinates that will be used when plotting color/contour maps or 3D height maps. You can manipulate these coordinates using the following functions:

    xs = m.xStart()
    xe = m.xEnd()
    ys = m.yStart()
    ye = m.yEnd()
    m.setCoordinates(xs + 2.5, xe, ys - 1, ye + 1)

You can also transpose or invert a matrix and calculate its determinant, provided, of course, that the conditions on the matrix dimensions, required by these operations, are satisfied:

    m.transpose()
    m.invert()
    d = m.determinant()

If you need to get data from a table, in order to use it in a matrix (or vice-versa), you can avoid time consuming copy/paste operations and speed up the whole proces by simply converting the table into a matrix:

    m = tableToMatrix(table("Table1"))
    t = matrixToTable(m)

Also, is worth knowing that you can easily import image files to matrices, that can be used afterwards for plotting (see the next section for more details about 2D plots).

    m = importImage("C:/poze/adi/PIC00074.jpg")

The algorithm used to import the image returns a gray value between 0 and 255 from the (r, g, b) triplet corresponding to each pixel. The gray value is calculated using the formula: (r * 11 + g * 16 + b * 5)/32

### 2D Plots

As you have seen in the previous section, it is possible create 2D plots from matrices. Here's how you can do it in practice:

    m = importImage("C:/poze/adi/PIC00074.jpg")
    g1 = plot(m, Layer.ColorMap)
    g2 = plot(m, Layer.Contour)
    g3 = plot(m, Layer.GrayScale)

If you want to create a new Graph window for some data in table Table1, you can use the plot command:

    t = table("Table1")
    g = plot(t, column, type)

`type` specifies the desired plot type and can be one of the following numbers or the equivalent reserved word:

0

  Layer.Line

1

  Layer.Scatter

2

  Layer.LineSymbols

3

  Layer.VerticalBars

4

  Layer.Area

5

  Layer.Pie

6

  Layer.VerticalDropLines

7

  Layer.Spline

8

  Layer.HorizontalSteps

9

  Layer.Histogram

10

  Layer.HorizontalBars

13

  Layer.Box

15

  Layer.VerticalSteps

You can plot more than one column at once by giving a Python tuple (see the Python Tutorial) as an argument:

    g1 = plot(table("Table1"), (2,4,7), 2)
    g2 = plot(table("Table1"), ("Table1_2","Table1_3"), Layer.LineSymbols)

You can also create a vector plot by giving four columns in a Python tuple as an argument and the plot type as Layer.VectXYXY (11) or Layer.VectXYAM (14), depending on how you want to define the end point of your vectors: using (X, Y) coordinates or (Angle, Magnitude) coordinates.

    g = plot(table("Table1"), (2,3,4,5), Layer.VectXYXY)

If you want to add a curve to an existing Graph window, you have to choose the destination layer. Usually,

    l = g.activeLayer()

will do the trick, but you can also select a layer by its number:

    l = g.layer(num)

#### Working with curves

You can then add or remove curves to or from this layer:

    l.insertCurve(table, Xcolumn, Ycolumn, type=Layer.Scatter)
    l.addCurve(table, column, type=Layer.Line, lineWidth = 1, symbolSize = 3, startRow = 0, endRow = -1)
    l.addCurves(table, (2,4), type=Layer.Line, lineWidth = 1, symbolSize = 3, startRow = 0, endRow = -1)
    l.removeCurve(curveName)
    l.removeCurve(curveNumber)
    l.deleteFitCurves()

You can also add analytical function curves to a plot layer:

    l.addFunction("x*sin(x)", 0, 3*pi, points = 100);
    l.addParametricFunction("cos(m)", "sin(m)", 0, 2*pi, points = 100, variableName = "m")
    l.addPolarFunction("t", "t", 0, 2*pi, points = 100, variableName = "t")

In case you need the number of curves on a layer, you can get it with

    l.numCurves()

#### The plot title

    l.setTitle("My beautiful plot")
    l.setTitleFont(QFont("Arial", 12))
    l.setTitleColor(QtGui.QColor("red"))
    l.setTitleAlignment(QtCore.Qt.AlignLeft)

The alignment parameter can be any combination of the Qt alignment flags (see the PyQt documentation for more details).

If you want you can remove the plot title using:

    l.removeTitle()

#### Customizing the axes

Layer axes can be shown/hidden using the following function:

    l.enableAxis(int axis, on = True)

where `axis` can be any integer value between 0 and 3 or the equivalent reserved word:

0.

  Layer.Left

1.

  Layer.Right

2.

  Layer.Bottom

3.

  Layer.Top

If an axis is enabled, you can fully customize it via a Python script. For example you can set it's title:

    l.setAxisTitle(axis, "My axis title")
    l.setAxisTitleFont(axis, QtGui.QFont("Arial", 11))
    l.setAxisTitleColor(axis, QtGui.QColor("blue"))
    l.setAxisTitleAlignment(axis, alignFlags)

it's color and the font used for the tick labels:

    l.setAxisColor(axis, QtGui.QColor("green"))
    l.setAxisFont(axis, QtGui.QFont("Arial", 10))

The tick labels of an axis can be enabled or disabled, you can set their color and their rotation angle:

    l.enableAxisLabels(axis, on = True)
    l.setAxisLabelsColor(axis, QtGui.QColor("black"))
    l.setAxisLabelRotation(axis, angle)

`angle` can be any integer value between -90 and 90 degrees. A rotation angle can be set only for horizontal axes (Bottom and Top).

The numerical format of the labels can be set using:

    l.setAxisNumericFormat(axis, format, precision = 6, formula)

where `format` can have the following values:

0.

  Automatic: the most compact numeric representation is chosen

1.

  Decimal: numbers are displayed in floating point form

2.

  Scientific: numbers are displayed using the exponential notation

3.

  Superscripts: like Scientific, but the exponential part is displayed as a power of 10

`precision` is the number of significant digits and `formula` is a mathematical expression that can be used to link oposite scales. It's argument must be `x` for horizontal axes and `y` for vertical axes. For example, assuming that the bottom axis displays a range of wavelengths in nanometers and that the top axis represents the equivalent energies in eV, with the help of the code bellow all the wavelengths will be automatically converted to electron-volts and the result will be displayed in floating point form with two significant digits after the decimal dot sign:

    l.setAxisNumericFormat(Layer.Top, 1, 2, "1239.8419/x")

The axis ticks can be customized via the following functions:

    l.setTicksLength(minLength, majLength)
    l.setAxisTicksLength(axis, majTicksType, minTicksType, minLength, majLength)

where the `majTicksType` and `minTicksType` parameters specify the desired orientation for the major and minor ticks, respectively:

0.

  Layer.NoTicks

1.

  Layer.Out: outward orientation for ticks, with respect to the plot canvas

2.

  Layer.InOut: both inward and outward ticks

3.

  Layer.In: inward ticks

`minLength` specifies the length of the minor ticks, in pixels and `majLength` the length of the major ticks.

You can also customize the scales of the different axes using:

    l.setScale(int axis, double start, double end, double step=0.0, int majorTicks=5, int minorTicks=5, int type=0, bool inverted=False);

where `type` specifies the desired scale type:

0.

  Layer.Linear

1.

  Layer.Log10

and `step` defines the size of the interval between the major scale ticks. If not specified (default value is 0.0), the step size is calculated automatically. The other flags should be self-explanatory.

Defining a scale range for an axis doesn't automatically disable autoscaling. This means that if a curve is added or removed from the layer, the axes will still automatically adapt to the new data interval. This can be avoided by disabling the autoscaling mode, thus making sure that your scale settings will always be taken into account:

    l.enableAutoscaling(False)

Finally, you can specify the width of all axes and enable/disable the drawing of their backbone line, using:

    l.setAxesLinewidth(2)
    l.drawAxesBackbones(True)

#### The canvas

You can display a rectangular frame around the drawing area of the plot (the canvas) and fill it with a background color, using:

    l.setCanvasFrame(2, QtGui.QColor("red"))
    l.setCanvasColor(QtGui.QColor("lightGrey"))

Drawing the canvas frame and disabling the axes backbone lines is the only possible solution for the issue of axes not touching themselves at their ends.

#### The layer frame

You can display a rectangular frame around the whole layer and fill it with a background color, using:

    l.setFrame(2, QtGui.QColor("blue"))
    l.setBackgroundColor(QtGui.QColor("grey"))

The default spacing between the layer frame and the other layer elements (axes, title) can be changed via:

    l.setMargin(10)

#### Customizing the grid

You can display the grid associated to a layer axis or the whole grid using:

    l.showGrid(axis)
    l.showGrid()

This will display the grid with the default color, width and pen style settings. If you need to change these settings, as well as to enable/disable certain grid lines, you can use the following functions:

    grid = l.grid()
    grid.setMajPenX(QPen(QtGui.Color("red"), 1))
    grid.setMinPenX(QPen(QtGui.Color("yellow"), 1))
    grid.setMajPenY(QPen(QtGui.Color("green"), 1))
    grid.setMinPenY(QPen(QtGui.Color("blue"), 1))
    grid.enableXMax(True)
    grid.enableXMin()
    grid.enableYMax()
    grid.enableYMin(False)
    grid.enableZeroLineX(True)
    grid.enableZeroLineY(False)
    l.replot()

All the grid functions containing an `X` refer to the vertical grid lineas, whereas the `Y` leter indicates the horizontal ones. Also, the `Maj` word refers to the main grid lines and `Min` to the secondary grid.

#### The plot legend

You can add a new legend to a plot using:

    legend = l.newLegend()
    #or
    legend = l.newLegend("enter your text here")

Once you have created a legend, it's very easy to customize it. If you want to modify the text you can use:

    l.setLegend("enter your text here")
    #or
    legend.setText("Enter your text here")

All other properties of the legend: text color, background color, frame style, font and position of the top-left corner can be modified via the following functions:

    legend.setTextColor(QtGui.QColor("red"))
    legend.setBackgroundColor(QtGui.QColor("yellow"))
    legend.setFrameStyle(Legend.Shadow)
    legend.setFont(QtGui.QFont("Arial", 14, QtGui.QFont.Bold, True))
    legend.setOriginCoord(200.5, 600.32)
    l.replot()

Other frame styles available for legends are: `Legend.Line`, which draws a rectangle around the text and `Legend.None` (no frame at all). There are also functions allowing you to remove the default legend or to add an automatically built time stamp:

    l.removeLegend()
    l.addTimeStamp()

#### Adding arrows/lines to a plot layer

    arrow = ArrowMarker()
    arrow.setStart(10.5, 12.5)
    arrow.setEnd(200, 400.51)
    arrow.setStyle(QtCore.Qt.DashLine)
    arrow.setColor(QtGui.QColor("red"))
    arrow.setWidth(1)
    arrow.drawStartArrow(False)
    arrow.drawEndArrow(True)
    arrow.setHeadLength(7)
    arrow.setHeadAngle(35)
    arrow.fillArrowHead(True)

    l = newGraph().activeLayer()
    l.addArrow(arrow)

#### Adding images to a layer

    l = newGraph().activeLayer()
    image = l.addImage("C:/poze/adi/PIC00074.jpg")
    image.setCoordinates(200, 800, 800, 200)
    l.replot()

#### Antialiasing

Antialiasing can be enabled/disabled for the drawing of the curves and other layer objects, but it is a very resources consuming feature:

    l.setAntialiasing(True, bool update = True)

#### Resize events

The default behaviour of 2D plot layers, with respect to the resizing of the graph window is to adapt their size and the sizes of the fonts used for the various texts, to the new size of the plot window. You can override this behaviour and make the layers ignore any resize events coming from the parent window:

    l.setIgnoreResize(True)
    l.setAutoscaleFonts(False)

#### Exporting plots/layers to different image formats

Layers and whole Graphs can be printed and exported from within Python. The fastest way to export a plot/layer is the following:

    l.export(fileName)

This function uses some default parameters for the properties of the image. If you need more control over the exported images you can use one of the following specialized functions:

    l.exportVector(fileName, resolution = 96, color = True, keepAspect = True, pageSize = QtGui.QPrinter.Custom);
    l.exportImage(filename, quality=100, transparent=False)

The function `exportVector` can export the plot/layer to the following vector formats: .eps, .ps, .pdf. By setting the `keepAspect` variable to True (the default), the width/height ratio of the plot will be maintained in the exported image. This has the drawback that sometimes there are some visible white margins around the plot. In order to avoid the white borders you can set this option to False and in this case the plot will be scaled to fill the whole area of the `pageSize`, thus modifing its aspect. By default QtiPlot chooses a `pageSize` which fits best the real size of the plot, but you can specify a standard output page size, like: QtGui.QPrinter.A4, QtGui.QPrinter.A5, etc...

The function `exportImage` can be used if you need to export to one of the Qt supported bitmap image formats (.bmp, .png, .jpg, etc...). The `transparent` option can only be used in conjunction with the file formats supporting transprency: .png and .tif (.tiff). The `quality` parameter influences the size of the output file. The higher this value (maximum is 100), the higher the qualitity of the image, but the larger the size of the resulting files.

All the export functions rely on the file name suffix in order to choose the image format.

### Arranging Layers

When you are working with many layers in a 2D plot window, setting the layout of these layers manually can be a very tedious task. With the help of a simple Python script you can make this task very easy and automatically manage the layout of the plot window. For example, here's how you can create a two rows by two columns matrix of layers, each plot layer having a canvas size (the drawing area) of 400 pixels wide and 300 pixels in height:

    g=newGraph()
    g.setNumLayers(4)
    g.setLayerCanvasSize(400, 300)
    g.arrangeLayers(True, True)

The `arrangeLayers()` function takes two parameters. The first one specifies if the layers should be arranged automatically, using a best-layout algorithm, or if the numbers of rows and columns is fixed by the user. If the value of the second parameter is `True`, the size of the canvas is fixed by the user and the plot window will be enlarged or shrinked, according to the user settings. Otherwise the size of the plot window will be kept and the canvas area of each layer will be automatically adapted to fit this size. Here's how you can modify the graph created in the previous example, in order to display a row of three layers, while keeping the size of the plot window unchanged:

    g.setNumLayers(3)
    g.setRows(1)
    g.setCols(3)
    g.arrangeLayers(False, False)

By default, the space betwee two neighbouring layers as well as the distance between the layers and the borders of the plot window is set to five pixels. You can change the spacing between the layers and the margins using the following functions:

    g.setSpacing (x, y)
    g.setMargins (left, right, top, bottom)

Another aspect of the layout management is the alignement of the layers. There are three alignement flags that you can use for the horizontal alignement (HCenter, Left, Right) and another three for the vertical alignement (VCenter, Top, Bottom) of the layers. The following code line aligns the layers with the right edge of the window and centers them vertically in the available space:

    g.setAlignement(Graph.Right, Graph.VCenter)

All the examples above suppose that the layers are aranged on a grid, but of course you can add layers at any position in the plot window. In the examples bellow the x, y coordinates, in pixels, refer to the position of the top-left corner of the layer. The origin of the coordinates system coincides with the top-left corner of the plot window, the y coordinate increasing towards the bottom of the window. If the width and height of the layer are not specified they will be set to the default values:

    g = newGraph()
    l1 = g.addLayer()
    l2 = g.addLayer(10, 20, 200, 200)
    l3 = g.addLayer(215, 20)

As you have already seen, in a plot window the active layer is, by default, the last layer added to the plot, but you can change it programatically:

    l = g.layer(num)
    g.setActiveLayer(l)

In case you need to perform a repetitive task on all the layers in a plot window, you need to use a for loop and of course you need to know the number of layers existant on the plot. Here's a small example showing how to custom the titles of all the layers in the plot window:

    g = graph("Graph1")
    layers = g.numLayers()
    for i in range (1, layers+1):
    	l = g.layer(i)
    	l.setTitle("Layer"+QtCore.QString.number(i))
    	l.setTitleColor(QtGui.QColor("red"))
    	l.setTitleFont(QtGui.QFont("Arial", 14, QtGui.QFont.Bold, True))
    	l.setTitleAlignment(QtCore.Qt.AlignLeft)

Finally, sometimes it might be useful to be able to swap two layers. This can be done with the help of the following function:

    g.swapLayers(layerNum1, layerNum2)

### 3D Plots

#### Creating a 3D plot

You can plot 3D analytical surfaces, height maps using data from matrices and table columns:

    g = plot3D("sin(x*y)", -10.0, 10.0, -10.0, 10.0, -2.0, 2.0)
    g = plot3D(matrix("Matrix1"), style = 5)
    g = plot3D(table("Table1"), "3", style)

In the case of 3D plots created from matrix data sources the `style` parameter can take any integer value from 1 to 5, with the following signification:

1.

  Wireframe style

2.

  Hidden Line style

3.

  Color filled polygons without edges

4.

  Color filled polygons with separately colored edges

5.

  Scattered points (the default style)

For 3D plots created from tables the `style` parameter can take any integer value from 0 to 3 or the equivalent style values from the follwing list:

0.

  Graph3D.Scatter

1.

  Graph3D.Trajectory

2.

  Graph3D.Bars

3.

  Graph3D.Ribbon

An alternative method to create a 3D plot is to create an empty plot window and to assign a data source to it. As you have already seen a data source can be an analytical function, a matrix or a table. For large data sets you can increase the drawing speed by reducing the number of points taken into account. The lower the resolution parameter, the higher the number of points used: for an integer value of 1, all the data points are drawn.

    g = newPlot3D()
    g.setTitle("My 3D Plot", QtGui.QColor("blue"), QtGui.QFont("Arial",14))
    g.setResolution(2)
    g.setFunction("sin(x*y)", -10.0, 10.0, -10.0, 10.0, -2.0, 2.0)
    #or
    g.setData(table("Table1"), "3")
    #or
    g.setMatrix(matrix("Matrix1"))

Once a plot is created, you can modify the scales and set the data range to display, using, for example:

    g.setScales(-1.0, 1.0, -10.0, 11.0, -2.0, 3.0)

#### Customizing the view

When a new 3D plot is created, the scene view parameters are set to default values. Of course, QtiPlot provides functions to customize each aspect of the view. For example, you can set rotation angles, in degrees, around the X, Y and Z axes, respectively, using:

    g.setRotation(45, 15, 35)

The following function allows you to shift the plot along the world X, Y and Z axes, respectively:

    g.setShift(3.0, 7.0, -4.0)

You can also zoom in/out the entire plot as a whole, or you can zoom along a particular axis:

    g.setZoom(10)
    g.setScale(0.1, 0.05, 0.3)

Also, you can automatically detect the zoom values that fit best with the size of the plot window:

    g.findBestLayout()

You can enable/disable the perspective view mode or animate the view using:

    g.setOrthogonal(False)
    g.animate(True)

#### Plot Styles

The style of the 3D plot can be set using the following functions:

    g.setPolygonStyle()
    g.setFilledMeshStyle()
    g.showLegend(True)
    g.setHiddenLineStyle()
    g.setWireframeStyle()
    g.setAntialiasing(True)
    g.setMeshLineWidth(0.7)

For scatter plots using points you can specify the radius of the points and their shape: circles if `smooth` is True, rectangles otherwise.

    g.setDotOptions(10, smooth = True)
    g.setDotStyle()

Other symbols available for scatter plots are: bars

    g.setBarRadius(0.01)
    g.setBarStyle()

cones

    g.setConeOptions(radius, quality)
    g.setConeStyle()

and crosses (surrounded by a box frame, if `boxed` is set to True):

    g.setCrossOptions(radius, width, smooth, boxed)
    g.setCrossStyle()

#### The 2D Projection

By default the floor projection of the 3D surface plot is disabled. You can enable a full 2D projection or only display the isolines using the following functions:

    g.showFloorProjection()
    g.showFloorIsolines()
    g.setEmptyFloor()

#### Customizing the Coordinates System

The coordinates system around the surface plot can be customized to display all the twelve axes, only three of them or none, respectively, with the help of the following funcions:

    g.setBoxed()
    g.setFramed()
    g.setNoAxes()

If the axes are enabled, you can set their legends and the distance between the legend and the axes via:

    g.setXAxisLabel("X axis legend")
    g.setYAxisLabel("Y axis legend")
    g.setZAxisLabel("Z axis legend")
    g.setLabelsDistance(30)

Also, you can fix the length of the major and minor ticks of an axis:

    g.setXAxisTickLength(2.5, 1.5)
    g.setYAxisTickLength(2.5, 1.5)
    g.setZAxisTickLength(2.5, 1.5)

#### Grid

If the coordinate system is displayed, you can also display a grid around the surface plot. Each side of the grid can be shown/hidden:

    g.setLeftGrid(True)
    g.setRightGrid()
    g.setCeilGrid()
    g.setFloorGrid()
    g.setFrontGrid()
    g.setBackGrid(False)

#### Customizing the Plot Colors

The color map of the plot can be defined using two colors, one for the maximum data values and one for the minimum data values, or you can use more complex color maps defined in .map files. A .map file consists of a of 255 lines, each line defining a color coded as RGB values. A set of predefined color map files can be downloaded from QtiPlot web site, in the "Miscelanous" section.

    g.setDataColors(QtGui.QColor("red"), QtGui.QColor("orange"))
    #or
    g.setDataColorMap(fileName)
    g.update()

The colors of all the other plot elements can be customized as shown bellow. Don't forget to update the plot in order to display the new colors:

    g.setMeshColor(QtGui.QColor("blue"))
    g.setAxesColor(QtGui.QColor("green"))
    g.setNumbersColor(QtGui.QColor("black"))
    g.setLabelsColor(QtGui.QColor("darkRed"))
    g.setBackgroundColor(QtGui.QColor("lightYellow"))
    g.setGridColor(QtGui.QColor("grey"))
    g.setDataColors(QtGui.QColor("red"), QtGui.QColor("orange"))
    g.setOpacity(0.75)
    g.update()

#### Exporting

Finally here's how you can export the 3D plot to an image file:

    g.export(fileName)
    g.exportVector(fileName)
    g.exportImage(fileName, int quality = 100, bool transparent = False)

### Data Analysis

#### General Functions

As you will see in the following subsections, the data analysis operations available in QtiPlot are: convolution/deconvolution, correlation, differentiation, FFT, filtering, smoothing, fitting and numerical integration of data sets. Generally, you can declare/initialize an analysis operation using one of the following methods, depending on the data source, which can be a 2D plot curve or a table:

    op = FFTFilter(graph("Graph1").activeLayer(), "Table1_2", 1.5, 3.9)
    op = LinearFit(table("Table1", "colX", "colY", 10, 100)

In the first example the data source is a curve Table1_2, plotted in the active layer of the graph Graph1 and the abscissae range is chosen between 1.5 and 3.9. In the second example the data source is a table Table1. The abscissae of the data set are stored in the column called colX and the ordinates in the column colY. The data range is chosen between the 10th row and the row with the index 100. If you don't specify the row range, by default the whole table will be used. Not all operations support curves as data sources, like for example: convolution/deconvolution and correlation. For these operations only table columns can be used as data sources for the moment.

Once you have initialized an operation, you can still chage it's input data via the following functions:

    op.setDataFromCurve("Table1_energy", 10.5, 20.1, graph("Graph2").activeLayer())
    op.setDataFromTable(table("Table1", "colX", "colY", 10, 100)

You don't have to specify a plot layer in the setDataFromCurve() function, if the analysis operation has already been initialized by specifying a curve on an existing graph and you just want to treat another curve from the same plot layer.

Also, when performing analysis tasks via Python scripts, there are several utility functions that can be called for all operations. For example you can disable any graphical output from an operation or you can redirect the output to the plot layer of your choice:

    op.enableGraphicsDisplay(False)
    op.enableGraphicsDisplay(True, graph("Graph2").activeLayer())

Let's assume that you need to perform a specific operation `op`, which analyses your data and at the end, displays a result curve. For this kind of operations, you can customize the number of points in the resulting curve and its color:

    op.setOutputPoints(int)
    op.setColor(int)
    op.setColor("green")

Colors can be specified by their names or as integer values, from 0 to 23, each integer corresponding to a predefined color: 0 - "black", 1 - "red", 2 - "green", 3 - "blue", 4 - "cyan", 5 - "magenta", 6 - "yellow", 7 - "darkYellow", 8 - "navy", 9 - "purple", etc ...

Most of the time, a new table is also created as a result of a data analysis operation. This table stores the data displayed by the result curve and is hidden by default, but you can interact with it via the following function:

    t = op.resultTable()

After the initialization of an analysis operation, which consists of setting the data source, the data range and some other properties, like color, number of points, etc..., you can execute it via a call to its run() function:

    op.run()

For data fitting operations, there's an alias for the run() function which is: fit().

#### Correlation, Convolution/Deconvolution

Assuming you have a table named "Table1", here's how you can calculate the convolution of two of it's columns, "Table1_B" and "Table1_C":

    conv = Convolution(table("Table1"),  "B", "C")
    conv.setColor("green")
    conv.run()

The deconvolution and the correlation of two data sets can be done using a similar synthax:

    dec = Deconvolution(table("Table1"),  "B", "C")
    dec.run()

    cor = Correlation(table("Table1"),  "B", "C", 10, 200)
    cor.setColor("green")
    cor.run()

#### Differentiation

Assuming you have a Graph named "Graph1" with a curve entitled "Table1_2" (on its active layer), here's how you can differentiate this curve within a defined x interval, [2,10] in this case:

    diff = Differentiation(graph("Graph1").activeLayer(), "Table1_2", 2, 10)
    diff.run()

The result of these code sequence would be a new plot window displaying the derivative of the initial curve. The numerical derivative is calculated using a five terms formula.

#### FFT

Assuming you have a graph named "Graph1" with a curve entitled "Table1_2" (on its active layer), having a periodicity of 0.1 in the time domain, a FFT will allow you to extract it's characteristic frequencies. The results will be stored in a hidden table named "FFT1".

    fft = FFT(graph("Graph1").activeLayer(), "Table1_2")
    fft.normalizeAmplitudes(False)
    fft.shiftFrequencies(False)
    fft.setSampling(0.1)
    fft.run()

By default the calculated amplitudes are normalized and the corresponding frequencies are shifted in order to obtain a centered x-scale. If we want to recover the initial curve with the help of the inverse transformation, we mustn't modify the amplitudes and the frequencies. Also the sampling parameter must be set to the inverse of the time period, that is 10. Here's how we can perform the inverse FFT, using the "FFT1" table, in order to recover the original curve:

    ifft = FFT(table("FFT1"), "Real", "Imaginary")
    ifft.setInverseFFT()
    ifft.normalizeAmplitudes(False)
    ifft.shiftFrequencies(False)
    ifft.setSampling(10)
    ifft.run()

#### FFT Filters

In this section, it will be assumed that you have a signal (data set "Table1_2") displayed in a graph ("Graph1", on its active layer). This signal has a power spectrum with high and low frequencies. You can filter some of these frequencies according to your needs, using a FFTFilter. Here's how you can cut all the frequencies lower than 1 Hz:

    filter = FFTFilter(graph("Graph1").activeLayer(), "Table1_2", FFTFilter.HighPass)
    filter.setCutoff(1)
    filter.run()

Here's how you can cut all the frequencies lower than 1.5 Hz and higher than 3.5 Hz. In the following example the continuous component of the signal is also removed:

    filter.setFilterType(FFTFilter.BandPass)
    filter.enableOffset(False)
    filter.setBand(1.5, 3.5)
    filter.run()

Other types of FFT filters available in QtiPlot are: low pass (`FFTFilter.LowPass`) and band block (`FFTFilter.BandBlock`).

#### Fitting

Assuming you have a graph named "Graph1" with a curve entitled "Table1_2" (on its active layer), a minimal Fit example would be:

    f = GaussFit(graph("Graph1").activeLayer(), "Table1_2")
    f.guessInitialValues()
    f.fit()

This creates a new GaussFit object on the curve, lets it guess the start parameters and does the fit. The following fit types are supported:

- LinearFit(layer, curve)

- PolynomialFit(layer, curve, degree=2, legend=False)

- ExponentialFit(layer, curve, growth=False)

- TwoExpFit(layer, curve)

- ThreeExpFit(layer, curve)

- GaussFit(layer, curve)

- GaussAmpFit(layer, curve)

- LorentzFit(layer, curve)

- LogisticFit(layer, curve)

- SigmoidalFit(layer, curve)

- NonLinearFit(layer, curve)

      f = NonLinearFit(layer, curve)
      f.setParameters(name1, ...)
      f.setFormula(formula_string)

- PluginFit(layer, curve)

      f = PluginFit(layer, curve)
      f.load(pluginName)

For each of these, you can optionally restrict the X range that will be used for the fit, like in

    f = LinearFit(graph("Graph1").activeLayer(), "Table1_2", 2, 7)
    f.fit()

After creating the Fit object and before calling its fit() method, you can set a number of parameters that influence the fit:

    f.setDataFromTable(table("Table4"), "w", "energy", 10, 200) change data source
    f.setDataFromCurve(curve)			change data source
    f.setDataFromCurve(curve, graph)		change data source
    f.setDataFromCurve(curve, from, to)		change data source
    f.setDataFromCurve(curve, from, to, graph)	change data source
    f.setInterval(from, to)				change data range
    f.setInitialValue(number, value)
    f.setInitialValues(value1, ...)
    f.guessInitialValues()
    f.setAlgorithm(algo) # algo = Fit.ScaledLevenbergMarquardt, Fit.UnscaledLevenbergMarquardt, Fit.NelderMeadSimplex
    f.setWeightingData(method, colname) # method = Fit.NoWeighting, Fit.Instrumental, Fit.Statistical, Fit.Dataset
    f.setTolerance(tolerance)
    f.setOutputPrecision(precision)
    f.setMaximumIterations(number)
    f.scaleErrors(yes = True)
    f.setColor("green")			change the color of the result fit curve to green (default color is red)

After you've called fit(), you have a number of possibilities for extracting the results:

    f.results()
    f.errors()
    f.chiSquare()
    f.rSquare()
    f.dataSize()
    f.numParameters()
    f.parametersTable("params")
    f.covarianceMatrix("cov")

It is important to know that QtiPlot can generate an analytical formula for the resulting fit curve or a normal plot curve with data stored in a hidden table. You can choose either of these two output options, before calling the fit() instruction, using:

    f.generateFunction(True, points=100)

If the first parameter of the above function is set to True, QtiPlot will generate an analytical function curve. If the `points` parameter is not specified, by default the function will be estimated over 100 points. You can get the analytical formula of the fit curve via a call to resultFormula():

    formula = f.resultFormula()
    print(formula)

If the first parameter of generateFunction() is set to False, QtiPlot will create a hidden data table contining the same number of points as the data set/curve to be fitted (same abscissae). You can interact with this table and extract the data points of the result fit curve using:

    t = f.resultTable()

#### Integration

With the same assumptions as above, here's how you can integrate a curve within a given interval:

    integral = Integration(graph("Graph1").activeLayer(), "Table1_2", 2, 10)
    integral.setMethodOrder(4)
    integral.setTolerance(1e-4)
    integral.setMaximumIterations(100)
    integral.run()
    result = integral.area()

The method order parameter can be any integer value between 1 (Trapezoidal rule, the default value) and 5. The code integrates the curve using an iterative algorithm. The tolerance determines the termination criteria for the solver. Because, sometimes we ask for too much accuracy, setting a maximum number of iterations makes sure that the solver will not enter an infinite loop, which could freeze the application.

As you can see from the above example, the numerical value of the integral can be obtained via the `area()` function.

#### Interpolation

The interpolation is used to generate a new data curve with a high number of points from an existing data set. Here's an example:

    interpolation = Interpolation(graph("Graph1").activeLayer(), "Table1_2", 2, 10, Interpolation.Linear)
    interpolation.setOutputPoints(10000)
    interpolation.setColor("green")
    interpolation.run()

The simplest interpolation method is the linear method. There are two other methods available: `Interpolation.Akima` and `Interpolation.Cubic`. You can choose the interpolation method using:

    interpolation.setMethod(Interpolation.Akima)

#### Smoothing

Assuming you have a graph named "Graph1" with an irregular curve entitled "Table1_2" (on its active layer). You can smooth this curve using a SmoothFilter:

    smooth = SmoothFilter(graph("Graph1").activeLayer(), "Table1_2", SmoothFilter.Average)
    smooth.setSmoothPoints(10)
    smooth.run()

The default smoothing method is the mowing window average. Other smoothing methods are the `SmoothFilter.FFT` and `SmoothFilter.SavitzkyGolay`. Here's an example of how to use this last method:

    smooth.setSmoothPoints(5,5)
    smooth.setMethod(SmoothFilter.SavitzkyGolay)
    smooth.setPolynomOrder(9)
    smooth.run()

### Using Qt's dialogs and classes

Let's assume that you have a lot of ASCII data files to analyze. Furthermore, let's suppose that these files were created during several series of measurements, each measurement generating a set of files identified by a certain string in the file name, like for example: "disper1". In order to analyze these files, you need first of all to import them into tables. The following code snippet shows how to automize this task using Qt dialogs and convenience classes:

    # Pop-up a file dialog allowing to chose the working folder:
    dirPath = QtGui.QFileDialog.getExistingDirectory(qti.app, "Choose Working Folder", "/test/")

    # Create a folder object using Qt's QDir class:
    folder = QtCore.QDir(dirPath)

    # Pop-up a text input dialog allowing to chose the file naming pattern:
    namePattern = QtGui.QInputDialog.getText(qti.app, "Enter Pattern", "Text: ", QtGui.QLineEdit.Normal, "disper1")

    # Get the list of file names in the working directory containing the above pattern:
    fileNames = folder.entryList (QtCore.QStringList ("*_" + namePattern[0] + "*.dat"))

    # Import each file into a new project table:
    for i in range (0, lst.count()):
        t = newTable()
        t.importASCII(dirPath + fileNames[i], " ", 0, False, True, True)

For a detailed description of all the dialogs and utility classes provided by Qt/PyQt please take a look at the PyQt documentation.

Prev | Up | Next
Chapter 7. Mathematical Expressions and Scripting | Home | Chapter 8. Credits and License