Basics of plotting with QCustomPlot

The tutorials use customPlot as a pointer to the QCustomPlot instance. If you have promoted a widget in QtCreator, you'll likely access the respective widget via ui->customPlot (or whatever name you gave the widget) instead.

You can create a new graph in the plot via customPlot->addGraph(). Then you assign the graph some data points, e.g. via customPlot->graph(0)->setData(..), for example in the form of two QVector<double> for x and y (key and value). The reason why QCustomPlot uses the terminology key and value instead of x and y is to allow more flexibility in assigning which axis has what role. So if you define the left axis as "key axis" and the bottom as "value axis", you can plot a graph that's standing upright on the left of the plot. By default a QCustomPlot widget has four axes: customPlot->xAxisyAxisxAxis2, and yAxis2 of type QCPAxis, corresponding to the bottom, left, top and right axis. Their range defines which portion of the plot is currently visible: customPlot->xAxis->setRange(-1, 1).

To make any kind of changes to the plot appear on screen, call customPlot->replot(). Note that a replot will automatically happen when the widget is resized and when the built-in user interactions are triggered. Such user interactions are for example dragging the axis ranges with the mouse and zooming with the mouse wheel.

// generate some data:
QVector<double> x(101), y(101); // initialize with entries 0..100
for (int i=0; i<101; ++i)
  x[i] = i/50.0 - 1; // x goes from -1 to 1
  y[i] = x[i]*x[i]; // let's plot a quadratic function
// create graph and assign data to it:
customPlot->graph(0)->setData(x, y);
// give the axes some labels:
// set axes ranges, so we see all data:
customPlot->xAxis->setRange(-1, 1);
customPlot->yAxis->setRange(0, 1);

The output should look something like shown below.

The tick step and labeling is chosen automatically, by the axis ticker that is currently used by the axis. This is an instance of type QCPAxisTicker, and is accessible e.g. via xAxis->ticker(). You can adjust the approximate number of ticks that the ticker tries to create via xAxis->ticker()->setTickCount(6). The default axis ticker is well suited for simple numerical displays, however there are specialized classes e.g. for time spans, calendar dates, categories, pi (or other symbolic units) and logarithmic axes. See the QCPAxisTicker documentation for details.

The tick labels (the numbers) of the axes never reach outside the widget border, even when they get wider. This is due to the automatic margin calculation, which is turned on by default. It makes the axis rect shrink if the tick labels and axis labels need more space. If you don't wish that the margin is determined automatically, disable the behaviour by calling customPlot->axisRect()->setAutoMargins(QCP::msNone).  Then you can adjust the margin manually via customPlot->axisRect()->setMargins(..).

Changing the look...

...of the graph

The look of the graph is characterized by many factors, all of which can be modified. Here are the most important ones:

  • Line style: Call graph->setLineStyle(..). For all possible line styles, see the LineStyle documentation or the line style demo screenshot on the introduction page.
  • Line pen: All pens the QPainter-framework provides are available, e.g. solid, dashed, dotted, different widths, colors, transparency, etc. Set the configured pen via graph->setPen(..).
  • Scatter symbol: Call graph->setScatterStyle(..) to change the look of the scatter point symbols. For all possible scatter styles, see the QCPScatterStyle documentation or the scatter style demo screenshot shown on the introduction page. If you don't want any scatter symbols to show at the data points, set the graph's scatter style to QCPScatterStyle::ssNone.
  • Fills under graph or between two graphs: All brushes the QPainter-framework provides can be used in graph fills: solid, various patterns, textures, gradients, colors, transparency, etc. Set the configured brush via graph->setBrush(..).

...of the axis

The appearance of the axis can be modified by changing the pens they are painted with and the fonts their labels use. A look at the documentation of QCPAxis should be self-explanatory. Here's a quick summary of the most important properties: setBasePen, setTickPen, setTickLength, setSubTickLength, setSubTickPen, setTickLabelFont, setLabelFont, setTickLabelPadding, setLabelPadding. You can reverse an axis (e.g. make the values decrease instead of increase from left to right) with setRangeReversed. If you want decorations (e.g. arrows) at the axis ends, use setLowerEnding or setUpperEnding.

...of the grid lines

The grid is modified by accessing the respective QCPGrid instance of an axis. For example, changing the look of the horizontal grid lines, which are tied to the left axis, is done by accessing customPlot->yAxis->grid(). The look of the grid lines is basically the pen they are drawn with, which can be set via yAxis->grid()->setPen(). The grid line at tick 0 can be drawn with a different pen, it can be configured with setZeroLinePen. If you do not wish to draw the zero line with a special pen, just set it to Qt::NoPen, and the grid line at tick 0 will be drawn with the normal grid pen.
Sub-grid lines are set to be invisible by default. They can be activated with grid()->setSubGridVisible(true).


Simple plot of two graphs

Here's an example which creates the image of the decaying cosine function with its exponential envelope, as shown in the Demo Screenshots.

  // add two new graphs and set their look:
  customPlot->graph(0)->setPen(QPen(Qt::blue)); // line color blue for first graph
  customPlot->graph(0)->setBrush(QBrush(QColor(0, 0, 255, 20))); // first graph will be filled with translucent blue
  customPlot->graph(1)->setPen(QPen(Qt::red)); // line color red for second graph
  // generate some points of data (y0 for first, y1 for second graph):
  QVector<double> x(251), y0(251), y1(251);
  for (int i=0; i<251; ++i)
    x[i] = i;
    y0[i] = qExp(-i/150.0)*qCos(i/10.0); // exponentially decaying cosine
    y1[i] = qExp(-i/150.0);              // exponential envelope
  // configure right and top axis to show ticks but no labels:
  // (see QCPAxisRect::setupFullAxesBox for a quicker method to do this)
  // make left and bottom axes always transfer their ranges to right and top axes:
  connect(customPlot->xAxis, SIGNAL(rangeChanged(QCPRange)), customPlot->xAxis2, SLOT(setRange(QCPRange)));
  connect(customPlot->yAxis, SIGNAL(rangeChanged(QCPRange)), customPlot->yAxis2, SLOT(setRange(QCPRange)));
  // pass data points to graphs:
  customPlot->graph(0)->setData(x, y0);
  customPlot->graph(1)->setData(x, y1);
  // let the ranges scale themselves so graph 0 fits perfectly in the visible area:
  // same thing for graph 1, but only enlarge ranges (in case graph 1 is smaller than graph 0):
  // Note: we could have also just called customPlot->rescaleAxes(); instead
  // Allow user to drag axis ranges with mouse, zoom with mouse wheel and select graphs by clicking:
  customPlot->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom | QCP::iSelectPlottables);

As you can see, applying a fill to a graph is as easy as setting a brush that is not Qt::NoBrush. The fill will go from the graph (here graph 0) to the zero-value-line parallel to the key (here x) axis. If we wanted a channel fill between this and another graph, we would additionally call graph->setChannelFillGraph(otherGraph). To remove the channel fill, just pass 0 as other graph, and the fill will reach all the way to the zero-value-line as before. To remove the fill completely, call graph->setBrush(Qt::NoBrush).

Plotting with multiple axes and more advanced styling

Now, let's look at a more complex example for creating the demo screenshot which contains the five graphs on four axes, textured filling, vertical error bars, a legend, dots as decimal separators etc.

  customPlot->setLocale(QLocale(QLocale::English, QLocale::UnitedKingdom)); // period as decimal separator and comma as thousand separator
  QFont legendFont = font();  // start out with MainWindow's font..
  legendFont.setPointSize(9); // and make a bit smaller for legend
  // by default, the legend is in the inset layout of the main axis rect. So this is how we access it to change legend placement:
  customPlot->axisRect()->insetLayout()->setInsetAlignment(0, Qt::AlignBottom|Qt::AlignRight);
  // setup for graph 0: key axis left, value axis bottom
  // will contain left maxwell-like function
  customPlot->addGraph(customPlot->yAxis, customPlot->xAxis);
  customPlot->graph(0)->setPen(QPen(QColor(255, 100, 0)));
  customPlot->graph(0)->setBrush(QBrush(QPixmap("./balboa.jpg"))); // fill with texture of specified image
  customPlot->graph(0)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssDisc, 5));
  customPlot->graph(0)->setName("Left maxwell function");
  // setup for graph 1: key axis bottom, value axis left (those are the default axes)
  // will contain bottom maxwell-like function with error bars
  customPlot->graph(1)->setBrush(QBrush(QPixmap("./balboa.jpg"))); // same fill as we used for graph 0
  customPlot->graph(1)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssCircle, Qt::red, Qt::white, 7));
  customPlot->graph(1)->setName("Bottom maxwell function");
  QCPErrorBars *errorBars = new QCPErrorBars(customPlot->xAxis, customPlot->yAxis);
  // setup for graph 2: key axis top, value axis right
  // will contain high frequency sine with low frequency beating:
  customPlot->addGraph(customPlot->xAxis2, customPlot->yAxis2);
  customPlot->graph(2)->setName("High frequency sine");
  // setup for graph 3: same axes as graph 2
  // will contain low frequency beating envelope of graph 2
  customPlot->addGraph(customPlot->xAxis2, customPlot->yAxis2);
  QPen blueDotPen;
  blueDotPen.setColor(QColor(30, 40, 255, 150));
  customPlot->graph(3)->setName("Sine envelope");
  // setup for graph 4: key axis right, value axis top
  // will contain parabolically distributed data points with some random perturbance
  customPlot->addGraph(customPlot->yAxis2, customPlot->xAxis2);
  customPlot->graph(4)->setPen(QColor(50, 50, 50, 255));
  customPlot->graph(4)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssCircle, 4));
  customPlot->graph(4)->setName("Some random data around\na quadratic function");
  // generate data, just playing with numbers, not much to learn here:
  QVector<double> x0(25), y0(25);
  QVector<double> x1(15), y1(15), y1err(15);
  QVector<double> x2(250), y2(250);
  QVector<double> x3(250), y3(250);
  QVector<double> x4(250), y4(250);
  for (int i=0; i<25; ++i) // data for graph 0
    x0[i] = 3*i/25.0;
    y0[i] = qExp(-x0[i]*x0[i]*0.8)*(x0[i]*x0[i]+x0[i]);
  for (int i=0; i<15; ++i) // data for graph 1
    x1[i] = 3*i/15.0;;
    y1[i] = qExp(-x1[i]*x1[i])*(x1[i]*x1[i])*2.6;
    y1err[i] = y1[i]*0.25;
  for (int i=0; i<250; ++i) // data for graphs 2, 3 and 4
    x2[i] = i/250.0*3*M_PI;
    x3[i] = x2[i];
    x4[i] = i/250.0*100-50;
    y2[i] = qSin(x2[i]*12)*qCos(x2[i])*10;
    y3[i] = qCos(x3[i])*10;
    y4[i] = 0.01*x4[i]*x4[i] + 1.5*(rand()/(double)RAND_MAX-0.5) + 1.5*M_PI;
  // pass data points to graphs:
  customPlot->graph(0)->setData(x0, y0);
  customPlot->graph(1)->setData(x1, y1);
  customPlot->graph(2)->setData(x2, y2);
  customPlot->graph(3)->setData(x3, y3);
  customPlot->graph(4)->setData(x4, y4);
  // activate top and right axes, which are invisible by default:
  // set ranges appropriate to show data:
  customPlot->xAxis->setRange(0, 2.7);
  customPlot->yAxis->setRange(0, 2.6);
  customPlot->xAxis2->setRange(0, 3.0*M_PI);
  customPlot->yAxis2->setRange(-70, 35);
  // set pi ticks on top axis:
  customPlot->xAxis2->setTicker(QSharedPointer<QCPAxisTickerPi>(new QCPAxisTickerPi));
  // add title layout element:
  customPlot->plotLayout()->addElement(0, 0, new QCPTextElement(customPlot, "Way too many graphs in one plot", QFont("sans", 12, QFont::Bold)));
  // set labels:
  customPlot->xAxis->setLabel("Bottom axis with outward ticks");
  customPlot->yAxis->setLabel("Left axis label");
  customPlot->xAxis2->setLabel("Top axis label");
  customPlot->yAxis2->setLabel("Right axis label");
  // make ticks on bottom axis go outward:
  customPlot->xAxis->setTickLength(0, 5);
  customPlot->xAxis->setSubTickLength(0, 3);
  // make ticks on right axis go inward and outward:
  customPlot->yAxis2->setTickLength(3, 3);
  customPlot->yAxis2->setSubTickLength(1, 1);

As you can see, you can define freely which axis should play which role for a graph. Graph with index 0 for example uses the left axis (yAxis) as its key and the bottom axis (xAxis) as its value. Consequently the graph is standing upward against the left axis:

In order to display error bars for graph 1, we've created a QCPErrorBars instance which can attach to other plottables (like QCPGraph) and give them error bars. For further explanation of the used methods, have a look at the respective documentation.

Plotting date and time data

Next, we'll look at how to plot date and/or time related data. It basically comes down to installing a different axis ticker of type QCPAxisTickerDateTime on the respective axis.

  // set locale to english, so we get english month names:
  customPlot->setLocale(QLocale(QLocale::English, QLocale::UnitedKingdom));
  // seconds of current time, we'll use it as starting point in time for data:
  double now = QDateTime::currentDateTime().toTime_t();
  srand(8); // set the random seed, so we always get the same random data
  // create multiple graphs:
  for (int gi=0; gi<5; ++gi)
    QColor color(20+200/4.0*gi,70*(1.6-gi/4.0), 150, 150);
    // generate random walk data:
    QVector<QCPGraphData> timeData(250);
    for (int i=0; i<250; ++i)
      timeData[i].key = now + 24*3600*i;
      if (i == 0)
        timeData[i].value = (i/50.0+1)*(rand()/(double)RAND_MAX-0.5);
        timeData[i].value = qFabs(timeData[i-1].value)*(1+0.02/4.0*(4-gi)) + (i/50.0+1)*(rand()/(double)RAND_MAX-0.5);
  // configure bottom axis to show date instead of number:
  QSharedPointer<QCPAxisTickerDateTime> dateTicker(new QCPAxisTickerDateTime);
  dateTicker->setDateTimeFormat("d. MMMM\nyyyy");
  // configure left axis text labels:
  QSharedPointer<QCPAxisTickerText> textTicker(new QCPAxisTickerText);
  textTicker->addTick(10, "a bit\nlow");
  textTicker->addTick(50, "quite\nhigh");
  // set a more compact font size for bottom and left axis tick labels:
  customPlot->xAxis->setTickLabelFont(QFont(QFont().family(), 8));
  customPlot->yAxis->setTickLabelFont(QFont(QFont().family(), 8));
  // set axis labels:
  customPlot->yAxis->setLabel("Random wobbly lines value");
  // make top and right axes visible but without ticks and labels:
  // set axis ranges to show all data:
  customPlot->xAxis->setRange(now, now+24*3600*249);
  customPlot->yAxis->setRange(0, 60);
  // show legend with slightly transparent background brush:
  customPlot->legend->setBrush(QColor(255, 255, 255, 150));

The string you pass to dateTicker->setDateTimeFormat() has the same date formatting options as the string passed to QDateTime::toString, see Qt docs. All date/time coordinates in QCustomPlot are handled as seconds since midnight 1. January 1970, UTC (known as Unix/Epoch Time). This is also the unit you use, when calling QDateTime::toTime_t or setTime_t on the Qt date/time classes.

For sub-second accuracy, the axis ticker works with floating point numbers. So values smaller than 1.0 represent the according fraction of a second. You can use QCPAxisTickerDateTime::dateTimeToKey and keyToDateTime to convert between floating point Unix Time and QDateTime, independent of Qt version (Qt's QDateTime::toMSecsSinceEpoch was introduced only in Qt 4.7).

Beyond Graphs: Curves, Bar Charts, Statistical Box Plot,...

Up to now we've only looked at graphs. Since they are such a dominant use case, QCustomPlot offers a specialized interface for them. We've been using it all the time: QCustomPlot::addGraph, QCustomPlot::graph, etc. But that's not the the whole story. QCustomPlot has a more general interface for classes that draw data inside the plot, called Plottables. This interface is built around the abstract base class QCPAbstractPlottable. All Plottables derive from this class, also the familiar QCPGraph class. QCustomPlot offers many other plottable classes:

  • QCPGraph: That's the plottable class we've been using. Displays a series of data points as a graph with different line styles, filling and scatters.
  • QCPCurve: Similar to QCPGraph with the difference that it's made for displaying parametric curves. Unlike function graphs, they may have loops.
  • QCPBars: A Bar Chart. Takes a series of data points and represents them with bars. If there are multiple QCPBars plottables in the plot, they can be stacked on top of each other, as shown in the screenshot on the introduction page.
  • QCPStatisticalBox: A Statistical Box Plot. Takes a five-number-summary (minimum, lower quartile, median, upper quartile, maximum) and represents it as a statistical box. Outliers can also be displayed.
  • QCPColorMap: A 2D map which visualizes a third data dimension by using a color gradient. The class QCPColorScale accompanies this plottable to visualize the data scale in the plot.
  • QCPFinancial: A plottable which can be used to visualize for example stock price open, high, low, close information by either using Candlesticks or OHLC bars.
  • QCPErrorBars: This is a special plottable in that it attaches to a second plottable, to allow displaying error bars on the data points of the other plottables.

Unlike graphs, other plottables need to be created with new outside of QCustomPlot. This means that there is no addCurve or addBars function in the way there is an addGraph function.  The QCustomPlot instance which the plottable shall belong to is inferred from the passed axes in the plottable's constructor. QCustomPlot then takes ownership of the plottable. Existing plottables can be accessed with QCustomPlot::plottable(int index) and the total number of plottables in the plot (including graphs) can be retrieved with QCustomPlot::plottableCount

Here's a quick example that creates a bar chart with three bars:

QCPBars *myBars = new QCPBars(customPlot->xAxis, customPlot->yAxis);
// now we can modify properties of myBars:
myBars->setName("Bars Series 1");
QVector<double> keyData;
QVector<double> valueData;
keyData << 1 << 2 << 3;
valueData << 2 << 4 << 8;
myBars->setData(keyData, valueData);

More details about the other plottables can be found in the example project and the other tutorials. Further, each plottable type has a detailed description on the documentation page of the respective class.

Of course, it's absolutely possible to write your own plottable to make any data look exactly the way you need it. You should look at the QCPAbstractPlottable documentation for a guide how to start subclassing it. You can also look at the existing plottables to see how they work. For that purpose, It is recommended to have a look at QCPBars or QCPCurve for a start. QCPGraph is quite feature rich and thus might not be perfectly suited as a starting point.