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 thedataTransform
of the graph area the axis is for (there may be multiple graphareas)direction
- this must be set to one ofLeft
,Right
,Top
orBottom
, depending on which side of the graph area the axis isticks
- 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.