# fplot module

This provides a rudimentary means of displaying two dimensional Cartesian (xy)
and polar graphs on `framebuf` based displays. It is an optional extension to
the MicroPython [nano-gui](https://github.com/peterhinch/micropython-nano-gui)
library: this should be installed, configured and tested before use.

This was ported from the
[lcd160cr-gui library](https://github.com/peterhinch/micropython-lcd160cr-gui).
Like `nanogui.py` it uses synchronous code.

Please excuse the photography: these displays look **much** better than in my
pictures.

![Image](images/cartesian.png) Classic Cartesian plot.  

![Image](images/polar.png) Classic polar plot.  

![Image](images/discont.png) Plot of discontinuous data.  

![Image](images/lissajous.png) A Lissajous figure.  

![Image](images/sine.png) Still from a simulation of plotting realtime data
using the `TSequence` class. 

![Image](images/rtpolar.png) Still from a simulation of realtime acquisition of
two varying vectors.  

![Image](images/bernoulli.png) The lemniscate of Bernoulli.  

# Contents

 1. [Python files](./FPLOT.md#1-python-files)  
 2. [Concepts](./FPLOT.md#2-concepts)  
  2.1 [Graph classes](./FPLOT.md#21-graph-classes)  
  2.2 [Curve clsses](./FPLOT.md#22-curve-classes)  
  2.3 [Coordinates](./FPLOT.md#23-coordinates) 
 3. [Graph classes](./FPLOT.md#3-graph-classes)  
  3.1 [Class CartesianGraph](./FPLOT.md#31-class-cartesiangraph)  
  3.2 [Class PolarGraph](./FPLOT.md#32-class-polargraph)  
 4. [Curve classes](./FPLOT.md#4-curve-classes)  
  4.1 [class Curve](./FPLOT.md#41-class-curve)  
   4.1.1 [Scaling](./FPLOT.md#411-scaling) Optional scaling of data values.  
  4.2 [class PolarCurve](./FPLOT.md#42-class-polarcurve)  
   4.2.1 [Scaling](./FPLOT.md#421-scaling) Required scaling of complex points.  
  4.3 [class TSequence](./FPLOT.md#43-class-tsequence) Plot Y values on time axis.  

###### [Main README](./README.md)

# 1. Python files

These are located in the `plot` directory.

 1. `fplot.py` The plot library
 2. `fpt.py` Test program. Usage examples.

Before attempting to use this library please install and test the `nanogui`
module on your hardware.

# 2. Concepts

Data for Cartesian graphs constitutes a sequence of x, y pairs, for polar
graphs it is a sequence of complex `z` values. The module supports three
common cases:  
 1. The dataset to be plotted is complete at the outset.
 2. Arbitrary data arrives gradually and needs to be plotted as it arrives.
 3. One or more `y` values arrive gradually. The `X` axis represents time. This
 is a simplifying case of 2.

## 2.1 Graph classes

A user program first instantiates a graph object (`PolarGraph` or
`CartesianGraph`). This creates an empty graph image upon which one or more
curves may be plotted.

## 2.2 Curve classes

The user program then instantiates one or more curves (`Curve` or
`PolarCurve`) as appropriate to the graph. Curves may be assigned colors to
distinguish them.

A curve is plotted by means of a user defined `populate` generator. This
assigns points to the curve in the order in which they are to be plotted. The
curve will be displayed on the graph as a sequence of straight line segments
between successive points.

Where it is required to plot realtime data as it arrives, this is achieved
via calls to the curve's `point` method.

## 2.3 Coordinates

Graph objects are sized and positioned in terms of TFT screen pixel
coordinates, with (0, 0) being the top left corner of the display, with x
increasing to the right and y increasing downwards. By `nanogui` convention
a border, if specified, extends two pixels beyond the graph in each direction.
If the graph is placed at [row, col] the top left hand corner of the border is
at [row-2, col-2]. The coordinate system within a graph conforms to normal
mathematical conventions.

Scaling is provided on Cartesian curves enabling user defined ranges for x and
y values. Points lying outside of the defined range will produce lines which
are clipped at the graph boundary.

Points on polar curves are defined as Python `complex` types and should lie
within the unit circle. Points which are out of range may be plotted beyond the
unit circle but will be clipped to the rectangular graph boundary.

###### [Contents](./FPLOT.md#contents)

# 3. Graph classes

## 3.1 Class CartesianGraph

Constructor.  
Mandatory positional arguments:  
 1. `writer` A `CWriter` instance.
 2. `row` Position of the graph in screen coordinates.
 3. `col`

Keyword only arguments (all optional):  
 * `height=90` Dimension of the bounding box.
 * `width=110` Dimension of the bounding box.
 * `fgcolor=None` Color of the axis lines. Defaults to Writer forgeround color.
 * `bgcolor=None` Background color of graph. Defaults to Writer background.
 * `bdcolor=None` Border color. If `False` no border is displayed. If `None` a
 border is shown in the `Writer` forgeround color. If a color is passed, it is
 used.
 * `gridcolor=None` Color of grid. Default: Writer forgeround color.
 * `xdivs=10` Number of divisions (grid lines) on x axis.
 * `ydivs=10` Number of divisions on y axis.
 * `xorigin=5` Location of origin in terms of grid divisions.
 * `yorigin=5` As `xorigin`. The default of 5, 5 with 10 grid lines on each
 axis puts the origin at the centre of the graph. Settings of 0, 0 would be
 used to plot positive values only.

Methods:  
 1. `clear` No args. Clears all curves from the graph.
 2. `show` No args. Redraws the graph. For future/subclass use.

## 3.2 Class PolarGraph

Constructor.  
Mandatory positional arguments:  
 1. `writer` A `CWriter` instance.
 2. `row` Position of the graph in screen coordinates.
 3. `col`

Keyword only arguments (all optional):  
 * `height=90` Dimension of the square bounding box.
 * `fgcolor=None` Color of the axis lines. Defaults to Writer forgeround color.
 * `bgcolor=None` Background color of graph. Defaults to Writer background.
 * `bdcolor=None` Border color. If `False` no border is displayed. If `None` a
 border is shown in the `Writer` forgeround color. If a color is passed, it is
 used.
 * `gridcolor=None` Color of grid. Default: Writer forgeround color.
 * `adivs=3` Number of angle divisions per quadrant.
 * `rdivs=4` Number radius divisions.

Methods:  
 1. `clear` No args. Clears all curves from the graph.
 2. `show` No args. Redraws the graph. For future/subclass use.

###### [Contents](./FPLOT.md#contents)

# 4. Curve classes

## 4.1 class Curve

The Cartesian curve constructor takes the following positional arguments:

Mandatory arguments:
 1. `graph` The `CartesianGraph` instance.
 2. `color` If `None` is passed, the `graph` foreground color is used.

Optional arguments:  
 3. `populate=None` A generator to populate the curve. See below.   
 4. `origin=(0,0)` 2-tuple containing x and y values for the origin. Provides
 for an optional shift of the data's origin.  
 5. `excursion=(1,1)` 2-tuple containing scaling values for x and y.  

Methods:
 * `point` Arguments x, y. Defaults `None`. Adds a point to the curve. If a
 prior point exists a line will be drawn between it and the current point. If a
 point is out of range or if either arg is `None` no line will be drawn.
 Passing no args enables discontinuous curves to be plotted. This method is
 normally used for real time plotting.

The `populate` generator may take zero or more positional arguments. It should
repeatedly yield `x, y` values before returning. Where a curve is discontinuous
`None, None` may be yielded: this causes the line to stop. It is resumed when
the next valid `x, y` pair is yielded.

If `populate` is not provided the curve may be plotted by successive calls to
the `point` method. This may be of use where data points are acquired in real
time, and realtime plotting is required. See function `rt_rect` in `fpt.py`.

### 4.1.1 Scaling

By default, with symmetrical axes, x and y values are assumed to lie between -1
and +1.

To plot x values from 1000 to 4000 we would set the `origin` x value to 1000
and the `excursion` x value to 3000. The `excursion` values scale the plotted
values to fit the corresponding axis.

## 4.2 class PolarCurve

The constructor takes the following positional arguments:

Mandatory arguments:
 1. `graph` The `PolarGraph` instance.
 2. `color`

Optional arguments:  
 3. `populate=None` A generator to populate the curve. See below.   

Methods:
 * `point` Argument `z=None`. Normally a `complex`. Adds a point
 to the curve. If a prior point exists a line will be drawn between it and the
 current point. If the arg is `None` no line  will be drawn. Passing no args
 enables discontinuous curves to be plotted. Lines are clipped at the square
 region bounded by (-1, -1) to (+1, +1).

The `populate` generator may take zero or more positional arguments. It should
yield a complex `z` value for each point before returning. Where a curve is
discontinuous a value of `None` may be yielded: this causes plotting to stop.
It is resumed when the next valid `z` point is yielded.

If `populate` is not provided the curve may be plotted by successive calls to
the `point` method. This may be of use where data points are acquired in real
time, and realtime plotting is required.

### 4.2.1 Scaling

Complex points should lie within the unit circle to be drawn within the grid.

###### [Contents](./FPLOT.md#contents)

## 4.3 class TSequence

A common task is the acquisition and plotting of real time data against time,
such as hourly temperature and air pressure readings. This class facilitates
this. Time is on the x-axis with the most recent data on the right. Older
points are plotted to the left until they reach the left hand edge when they
are discarded. This is akin to old fashioned pen plotters where the pen was at
the rightmost edge (corresponding to time now) with old values scrolling to the
left with the time axis in the conventional direction.

The user instantiates a graph with the X origin at the right hand side and then
instantiates one or more `TSequence` objects. As each set of data arrives it is
appended to its `TSequence` using the `add` method. See the example below.

The constructor takes the following args:

Mandatory arguments:
 1. `graph` The `CartesianGraph` instance.
 2. `color`
 3. `size` Integer. The number of time samples to be plotted. See below.

Optional arguments:  
 4. `yorigin=0` These args provide scaling of Y axis values as per the `Curve`
 class.
 5 `yexc=1`

Method:
 1. `add` Arg `v` the value to be plotted. This should lie between -1 and +1
 unless scaling is applied.

Note that there is little point in setting the `size` argument to a value
greater than the number of X-axis pixels on the graph. It will work but RAM
and execution time will be wasted: the constructor instantiates an array of
floats of this size.

Each time a data set arrives the graph should be cleared, a data value should
be added to each `TSequence` instance, and the display instance should be
refreshed. The following example assumes that `ssd` is the display device and
`wri` is a `Writer` or `CWriter` instance.

```python
def foo():
    refresh(ssd, True)  # Clear any prior image
    g = CartesianGraph(wri, 2, 2, xorigin = 10, fgcolor=WHITE, gridcolor=LIGHTGREEN)
    tsy = TSequence(g, YELLOW, 50)
    tsr = TSequence(g, RED, 50)
    for t in range(100):
        g.clear()
        tsy.add(0.9*math.sin(t/10))
        tsr.add(0.4*math.cos(t/10))
        refresh(ssd)
        utime.sleep_ms(100)
```

###### [Contents](./FPLOT.md#contents)
