Graph structure

Top-level graph structure

Basic graphs usually consist of an X axis, a Y axis and a graphing area where the data is displayed. In QuickGraphLib, this structure is expressed in QML using a 2x2 GridLayout:

In QML, this would be expressed as:

 import QtQuick.Layouts as QQL
 import QuickGraphLib as QuickGraphLib

 QQL.GridLayout {
     columns: 2

     QuickGraphLib.Axis { ... }
     QuickGraphLib.GraphArea { ... }
     Item {} // A filler item for the empty cell
     QuickGraphLib.Axis { ... }
 }

QuickGraphLib provides two important types in this example:

  • Axis, which draws an axis (a left and bottom axis in this example)
  • GraphArea, which provides an area to draw data, like line graphs

This grid structure is very flexible: for example, if you wanted to place an axis on the top and right as well, the GridLayout can be expanded to a 3x3 grid. Or if you could add more columns or rows to add subgraphs.

Graphing area contents

Once we have a GraphArea, we can add graph items to it. The example above has a grid, a line graph and a legend:

 import QuickGraphLib.GraphItems as QGLGraphItems

 QuickGraphLib.GraphArea {
     QGLGraphItems.Grid { ... }
     QGLGraphItems.Line { ... }
     QGLGraphItems.BasicLegend { ... }
 }

Each graph item is a QML component, with a set of properties to control it's position or data and how it is drawn.

Understanding the data transform

One of the most important properties of a GraphArea is dataTransform. It must be passed to all of the graph items used, as well as any axes used:

 QQL.GridLayout {
     QuickGraphLib.GraphArea {
         id: grapharea

         viewRect: Qt.rect(-20, -1.1, 760, 2.2)

         QGLGraphItems.Line {
             dataTransform: grapharea.dataTransform
         }
     }
     QuickGraphLib.Axis {
         dataTransform: grapharea.dataTransform
     }
 }

The dataTransform is a matrix4x4 which encodes the transformation from data coordinates (which can be any unit, like degrees) to pixel coordinates relative to the GraphArea. To be precise, dataTransform is a affine transformation consisting of a scaling and translation. To convert any x/y data coordinate to pixels, just do:

 grapharea.dataTransform.map(Qt.point(dataX, dataY))

You can also transform from pixels back to data coordinates by inverting the transform:

 grapharea.dataTransform.inverted().map(Qt.point(mouseX, mouseY))

The dataTransform matrix is used by graph items and axes to convert any position/data you give them into pixel positions for drawing. You can also make your own graph items using the dataTransform.

Configuring an axis (and grid)

An Axis has a few properties you must set:

  • dataTransform - this must be set to the dataTransform of the graph area the axis is for (there may be multiple graphareas)
  • direction - this must be set to one of Left, Right, Top or Bottom, depending on which side of the graph area the axis is
  • ticks - this must be set to a list of tick positions (which may be empty)

There are a few options for generating tick positions. The most automatic option is to use tickLocator:

 ticks: QuickGraphLib.Helpers.tickLocator(-20, 740, 11)

This will find up to 11 nice positions between -20 and 740. You can specify the spacing etc. manually using linspace:

 ticks: QuickGraphLib.Helpers.linspace(0, 720, 9)

This function works the same way as numpy's function of the same name. It will generate 9 positions between 0 and 720 inclusive. And finally, since this is just a list, you can specify ticks using any JavaScript expression you want:

 ticks: [0, 100, 700]

Tick positions are also important for grids, normally you can just feed in the ticks from the axes into the grid:

 QGLGraphItems.Grid {
     dataTransform: grapharea.dataTransform
     xTicks: xAxis.ticks
     yTicks: yAxis.ticks
 }

Drawing a line

To draw a line between two points, use a Line:

 QGLGraphItems.Line {
     dataTransform: grapharea.dataTransform
     path: [Qt.point(0, 0), Qt.point(10, 10)]
 }

This code snippet will draw a line between the point (0, 0) and (10, 10). The Line item will convert these data coordinates into pixel coordinates for you. To change the color of the line, set the strokeColor property:

 QGLGraphItems.Line {
     // ...
     strokeColor: "red"
 }

Line inherits from ShapePath, so any of the properties of ShapePath can be used. This includes line thickness, dashing, line end styles, etc.

The recommended way to generate data for a Line or other graph items is to generate it in C++ or Python, since it is much more efficient. However, JavaScript arrays also work, so data can be constructed in the QML code using helper functions like linspace and map:

 QGLGraphItems.Line {
     dataTransform: grapharea.dataTransform
     path: QuickGraphLib.Helpers.linspace(0, 720, 100).map(x => Qt.point(x, Math.sin(x / 180 * Math.PI)))
     strokeColor: "red"
     strokeWidth: 2
 }

A simple legend

QuickGraphLib provides a basic legend type, which can be used to label the different data sets in use in a graph. Since legends come in many styles, it is quite basic. If you have specific needs, the best option is to implement your own using standard QML types, as you will have maximum flexibility. However, if you need something simple, you can use BasicLegend and BasicLegendItem:

 QGLGraphItems.BasicLegend {
     anchors.margins: 10
     anchors.right: parent.right
     anchors.top: parent.top

     QGLGraphItems.BasicLegendItem {
         strokeColor: "red"
         text: "Sin(θ)"
     }
 }

Anchors are used to position the legend (since this is purely a UI component, we don't need to care about data coordinates - the legend is not representing any data). BasicLegendItem can be used to add rows to the legend, and it's properties can be used to set the name and indicator color.

Full example

The full code for this example can be seen in the Basic sin graph (without prefabs) example.

Prefabs

QuickGraphLib comes with a set of prefab components, so make common tasks like simple X/Y axes simpler. XYAxes sets up a graph area with two axes, one on the left and one on the bottom. When using XYAxes, you only need to add the graph items, the axes, grid and graph area are already created:

 QGLPreFabs.XYAxes {
     id: axes

     viewRect: Qt.rect(-20, -1.1, 760, 2.2)
     xLabel: "Angle (°)"
     yLabel: "Value"

     QGLGraphItems.Line {
         id: sinLine

         dataTransform: axes.dataTransform
         path: QuickGraphLib.Helpers.linspace(0, 720, 100).map(x => Qt.point(x, Math.sin(x / 180 * Math.PI)))
         strokeColor: "red"
         strokeWidth: 2
     }
     QGLGraphItems.BasicLegend {
         anchors.margins: 10
         anchors.right: parent.right
         anchors.top: parent.top

         QGLGraphItems.BasicLegendItem {
             strokeColor: sinLine.strokeColor
             text: "Sin(θ)"
         }
     }
 }

XYAxes can be customized to some extent (see Dark theme sin graph for a theming example), the components of the graph are exposed though properties. However, if a more flexibly layout is needed, then prefabs should not be used.