Introduction to Axis Management
While basic plotting capabilities are often sufficient for simple data visualization, advanced charting requires precise control over the coordinate system. The axes serve as the fundamental reference frame for any plot. Without proper configuration of axes and their associated grid lines, interpreting complex data becomes difficult. This guide focuses on the architecture of axes within the QCustomPlot library, specifically examining how tick calculation and grid rendering are handled in modern versions.
Architecture Overview
In the evolution of QCustomPlot, particularly between legacy versions and the 2.x series, the core responsibility of axis management has remained consistent. The system separates the visual representation of the axis from the mathematical calculation of tick positions. This separation allows developers to customize how data is mapped to screen coordinates without altering the drawing logic.
The primary components involved are:
- QCPAxis: The main class responsible for rendering the axis line, tick marks, labels, and handling user interaction.
- QCPAxisTicker: A strategy class responsible for calculating the positions of major and minor ticks based on the current view range.
- QCPGrid: Responsible for drawing the grid lines that correspond to the tick positions of a specific axis.
Configuring QCPAxis
The QCPAxis class acts as the interface for users to modify axis properties. It supports four standard orientations (Left, Right, Top, Bottom) within an axis rect. Key configuration options include scale types (linear or logarithmic), range limits, and visual styles for ticks and labels.
Common adjustments involve setting the tick label formatting, rotation, and padding. For example, to modify the font and color of the tick labels, one would access the axis object directly:
QCPAxis *xAxis = customPlot->xAxis;
xAxis->setTickLabelFont(QFont("Arial", 10));
xAxis->setTickLabelColor(QColor(50, 50, 50));
xAxis->setTickLabelRotation(45);
The axis also manages the selection state, allowing specific parts like the axis line, labels, or the axis title to be selectable independently.
Tick Calculation Strategies
By default, QCPAxis uses an instance of QCPAxisTicker to determine where ticks should appear. This class calculates the step size between ticks to ensure a readable distribution based on the current zoom level. There are several specialized subclasses available for specific data types, such as:
QCPAxisTickerDateTime: For time-based data.QCPAxisTickerLog: For logarithmic scales.QCPAxisTickerFixed: For fixed step sizes.
The core method responsible for this logic is getTickStep. By inheriting from QCPAxisTicker and overriding this method, developers can implement custom spacing logic that differs from the default readability algorithms.
Grid Line Integration
Grid lines are managed by the QCPGrid class. In the QCustomPlot design, every axis owns a corresponding grid object. This means configuring the grid is typically done through the axis interface. For instance, to enable sub-grid lines or change the pen style of the zero-line, you access the grid via the axis:
customPlot->xAxis->grid()->setSubGridVisible(true);
customPlot->xAxis->grid()->setZeroLinePen(QPen(Qt::black, 2));
Since grid density is directly tied to tick positions, customizing the grid usually involves customizing the axis ticker rather than the grid class itself.
Custom Implementation: Fixed Pixel Spacing
A common requirement in technical charts is to maintain a constant visual distance between tick marks regardless of the zoom level. Standard tickers adjust the step value to keep the number of ticks constant, which changes the pixel distance. To keep the pixel distance constant, the step value must change dynamically based on the viewport size.
Below is a rewritten implementation of a custom ticker that enforces a fixed pixel spacing between major ticks.
Header Definition
#ifndef FIXEDSPACINGTICKER_H
#define FIXEDSPACINGTICKER_H
#include "qcustomplot.h"
class FixedSpacingTicker : public QCPAxisTicker
{
Q_OBJECT
public:
explicit FixedSpacingTicker(int pixelInterval = 100);
void setPixelInterval(int pixels);
int pixelInterval() const;
protected:
virtual double getTickStep(const QCPRange &range) override;
private:
int m_pixelInterval;
};
#endif // FIXEDSPACINGTICKER_H
Implementation Details
The logic calculates the current size of the axis in screen pixels. It then derives the coordinate step size required to match the desired pixel interval.
#include "FixedSpacingTicker.h"
#include <QCPAxis>
FixedSpacingTicker::FixedSpacingTicker(int pixelInterval)
: m_pixelInterval(pixelInterval)
{
}
void FixedSpacingTicker::setPixelInterval(int pixels)
{
m_pixelInterval = pixels;
}
int FixedSpacingTicker::pixelInterval() const
{
return m_pixelInterval;
}
double FixedSpacingTicker::getTickStep(const QCPRange &range)
{
// Access the parent axis to determine screen dimensions
QCPAxis *axis = mAxis.data();
if (!axis)
{
return QCPAxisTicker::getTickStep(range);
}
// Determine if the axis is vertical or horizontal to get correct dimension
int axisScreenSize = 0;
if (axis->orientation() == Qt::Vertical)
{
axisScreenSize = axis->axisRect()->height();
}
else
{
axisScreenSize = axis->axisRect()->width();
}
// Prevent division by zero
if (axisScreenSize <= 0 || m_pixelInterval <= 0)
{
return QCPAxisTicker::getTickStep(range);
}
// Calculate how many steps fit in the current view
double stepsCount = static_cast<double>(axisScreenSize) / m_pixelInterval;
// Calculate the coordinate value per step
double stepSize = range.size() / stepsCount;
return stepSize;
}
When this ticker is assigned to an axis, zooming in or out will adjust the numeric value of the tick steps sothat the physical distance between them on the screen remains approximately equal to the specified pixel interval. This is particularly useful for technical drawings where scale consistency is visually critical.