Working with Annotations and Decorative Items in QCustomPlot
What are Abstract Items?
When visualizing data, we often focus on the primary graph types like bar charts, pie charts, or splines. However, a chart's effectiveness relies heavily on auxiliary elements that provide context, such as axes, grid lines, legends, and annotations. In QCustomPlot, these supportive graphical elements are managed through the QCPAbstractItem class. This section explores how to utilize these items to add meaningful decorations and annotations to your plots.
Visual Example
Below is an example rendering showing a spline graph accompanied by axes, grids, and legends. The focus heree is on the decorative annotations—such as text labels, tracers, and arrows—that enhance the visulaization.
### Technical Breakdown
1. Positioning Logic: QCPItemPosition
Before adding items, it is crucial to undesrtand QCPItemPosition, the class responsible for defining where an item sits on the plot. The power of this class lies in its PositionType enum, which determines how coordinates are interpreted:
- ptAbsolute: Position is defined in absolute pixels relative to the top-left corner of the widget/viewport. This is the default behavior.
- ptViewportRatio: Position is defined as a fraction of the viewport size. For instance,
setCoords(0.5, 0.5)places the item in the center of the visible widget. - ptAxisRectRatio: Similar to viewport ratio, but relative to the specific axis rectangle.
setCoords(1, 1)places the item at the bottom-right corner of the plot area, allowing items to sit outside the plot bounds if values exceed 1.0. - ptPlotCoords: Dynamic positioning based on the actual data coordinates of the X and Y axes.
2. Available Item Types
QCustomPlot provides a rich set of item classes derived from QCPAbstractItem. Here are the most common ones used in the example above:
- QCPItemStraightLine: Defines an infinite line defined by two points.
- QCPItemLine: A finite line segment between two points.
- QCPItemCurve: A parametric curve often used with arrowheads (like the one pointing to the data point in the image).
- QCPItemRect / QCPItemEllipse: Shapes for highlighting areas.
- QCPItemText: Floating text labels. Note that the vertical line appearing next to the text in the example is actually a decoration of the arrow tail, not the text box itself.
- QCPItemTracer: Small shapes (like circles) that "snap" to data points on a graph (the red and green dots).
- QCPItemPixmap: For rendering images onto the plot.
- QCPItemBracket: Used to draw brackets, often for annotating ranges (like the brace next to "Wavepacket").
3. Implementation Examples
The following demonstrates how to implement a text label and a curved arrow pointing to a data tracer.
Adding a Text Label
// Create a text annotation object
QCPItemText *infoLabel = new QCPItemText(customPlot);
customPlot->addItem(infoLabel);
// Set positioning strategy to relative axis coordinates
infoLabel->position->setType(QCPItemPosition::ptAxisRectRatio);
infoLabel->setPositionAlignment(Qt::AlignRight | Qt::AlignBottom);
// Place it near the bottom-right of the plot area
infoLabel->position->setCoords(0.98, 0.90);
// Configure text properties
infoLabel->setText("Fixed phase points\ndefine velocity vp");
infoLabel->setTextAlignment(Qt::AlignLeft);
infoLabel->setFont(QFont("Helvetica", 9));
infoLabel->setPadding(QMargins(5, 0, 0, 0));
Drawing a Curved Arrow
// Create a curve item for the arrow
QCPItemCurve *connectorArrow = new QCPItemCurve(customPlot);
customPlot->addItem(connectorArrow);
// Anchor the start of the curve to the text label's left side
connectorArrow->start->setParentAnchor(infoLabel->left);
// Define the starting direction (control point) offset
connectorArrow->startDir->setParentAnchor(connectorArrow->start);
connectorArrow->startDir->setCoords(-50, 0);
// Anchor the end of the curve to the tracer's position
connectorArrow->end->setParentAnchor(dataTracer->position);
// Define the ending direction offset
connectorArrow->endDir->setParentAnchor(connectorArrow->end);
connectorArrow->endDir->setCoords(20, 20);
// Style the arrow
connectorArrow->setHead(QCPLineEnding::esSpikeArrow);
// Create a vertical bar at the tail matching the text height
double textHeight = infoLabel->bottom->pixelPoint().y() - infoLabel->top->pixelPoint().y();
connectorArrow->setTail(QCPLineEnding(QCPLineEnding::esBar, textHeight * 0.8));