Implementación de Gráficos de Área en Qt usando QCharts y QAreaSeries

Conceptos Fundamentales de los Gráficos de Área

En el marco de trabajo Qt, los gráficos de área se construyen utilizando la clase QAreaSeries (o su equivalente AreaSeries en QML). Este tipo de visualización es ideal para resaltar la magnitud de un cambio a lo largo del tiempo, rellenando el espacio entre dos límites. Por defecto, el eje X actúa como límite inferior, mientras que una serie de líneas (QLineSeries) define el límite superior. Sin embargo, es posible definir ambos límites utilizando dos instancias de QLineSeries para crear áreas flotantes o apiladas.

Uso de QAreaSeries

La clase QAreaSeries se encarga de renderizar los datos cuantitativos rellenando la región comprendida entre las series de líneas superior e inferior. Dado que hereda conceptualmente de las series lineales, su configuración requiere al menos una serie de líneas para el borde superior. Si no se especifica un límite inferior, el área se extenderá hasta la base del gráfico.

Nota: Los términos "superior" e "inferior" son relativos. Si los valores de la serie inferior superan a los de la superior, el área se seguirá rellenando correctamente entre ambas trayectorias.

Inserción Eficiente de Datos

Para optimizar el rendimiento, especialmente con grandes volúmenes de datos, se recomienda evitar la inserción punto por punto. En su lugar, es preferible construir una lista de QPointF y agregarla de una sola vez. Además, en versiones modernas de Qt, se debe utilizar QRandomGenerator en lugar de la función obsoleta qrand().

#include <QLineSeries>
#include <QAreaSeries>
#include <QRandomGenerator>
#include <QVector>
#include <QPointF>

// Creación de las series de límites
QLineSeries *upperBoundary = new QLineSeries();
QLineSeries *lowerBoundary = new QLineSeries();

// Generación de datos en lote para el límite superior
QVector<QPointF> upperPoints;
for (int i = 0; i < 15; ++i) {
    upperPoints.append(QPointF(i, QRandomGenerator::global()->bounded(20, 50)));
}
upperBoundary->append(upperPoints);
upperBoundary->setName("Límite Superior");

// Generación de datos en lote para el límite inferior
QVector<QPointF> lowerPoints;
for (int i = 0; i < 15; ++i) {
    lowerPoints.append(QPointF(i, QRandomGenerator::global()->bounded(0, 20)));
}
lowerBoundary->append(lowerPoints);
lowerBoundary->setName("Límite Inferior");

// Configuración del área utilizando ambos límites
QAreaSeries *customArea = new QAreaSeries(upperBoundary, lowerBoundary);
customArea->setName("Zona de Operación");

// Añadir al gráfico (asumiendo que 'chart' es un puntero a QChart válido)
chart->addSeries(customArea);
chart->createDefaultAxes();

Arquitectura del Controlador de Gráficos

A continuación, se detalla la implementación de una clase controladora que gestiona la inicialización, configuración visual y actualización dinámica de un gráfico de área.

Configuración Inicial y Ejes

AreaPlotManager::AreaPlotManager(QWidget *parent) 
    : QWidget(parent), 
      chartView(new QChartView(this)), 
      mainChart(new QChart()),
      primaryArea(nullptr),
      secondaryArea(nullptr) 
{
    setupEnvironment();
}

void AreaPlotManager::setupEnvironment() {
    // Inicialización de series
    QLineSeries *topLine = new QLineSeries();
    QLineSeries *midLine = new QLineSeries();
    QLineSeries *bottomLine = new QLineSeries();

    QVector<QPointF> topData, midData, bottomData;
    for (int i = 0; i < 12; ++i) {
        topData << QPointF(i, QRandomGenerator::global()->bounded(70, 100));
        midData << QPointF(i, QRandomGenerator::global()->bounded(40, 70));
        bottomData << QPointF(i, QRandomGenerator::global()->bounded(10, 40));
    }

    topLine->append(topData);
    midLine->append(midData);
    bottomLine->append(bottomData);

    // Creación de áreas
    primaryArea = new QAreaSeries(topLine, midLine);
    primaryArea->setName("Rango Alto");
    
    secondaryArea = new QAreaSeries(midLine, bottomLine);
    secondaryArea->setName("Rango Bajo");

    mainChart->addSeries(primaryArea);
    mainChart->addSeries(secondaryArea);
    mainChart->createDefaultAxes();

    // Personalización del eje Y
    QValueAxis *axisY = qobject_cast<QValueAxis *>(mainChart->axisY());
    if (axisY) {
        axisY->setRange(0, 110);
        axisY->setTickCount(6);
        axisY->setMinorTickCount(2);
        axisY->setLabelFormat("%d");
        axisY->setGridLineVisible(true);
        axisY->setMinorGridLineVisible(true);
    }

    // Personalización del eje X
    QValueAxis *axisX = qobject_cast<QValueAxis *>(mainChart->axisX());
    if (axisX) {
        axisX->setRange(0, 11);
        axisX->setLabelFormat("%d s");
        axisX->setGridLineVisible(true);
    }

    // Configuración de la vista
    chartView->setChart(mainChart);
    chartView->setRenderHint(QPainter::Antialiasing, true);
    chartView->setRubberBand(QChartView::NoRubberBand);
    
    // Leyenda y efectos
    mainChart->legend()->setAlignment(Qt::AlignBottom);
    mainChart->setDropShadowEnabled(true);
}

Gestión de Visibilidad y Estilos

El controlador permite modificar dinámicamente las propiedades visuales del gráfico, como la visibilidad de las series, los puntos de datos y las etiquetas.

void AreaPlotManager::toggleSeriesVisibility(int seriesIndex, bool isVisible) {
    if (seriesIndex == 0 && primaryArea) {
        primaryArea->setVisible(isVisible);
    } else if (seriesIndex == 1 && secondaryArea) {
        secondaryArea->setVisible(isVisible);
    }
}

void AreaPlotManager::applyChartTheme(QChart::ChartTheme selectedTheme) {
    mainChart->setTheme(selectedTheme);
}

void AreaPlotManager::configureAnimations(QChart::AnimationOptions options) {
    mainChart->setAnimationOptions(options);
}

void AreaPlotManager::updateLegendAlignment(Qt::Alignment alignment) {
    mainChart->legend()->setAlignment(alignment);
}

void AreaPlotManager::toggleDataPoints(bool showPoints) {
    if (primaryArea) primaryArea->setPointsVisible(showPoints);
    if (secondaryArea) secondaryArea->setPointsVisible(showPoints);
}

void AreaPlotManager::togglePointLabels(bool showLabels) {
    QString format = showLabels ? "(@xPoint, @yPoint)" : "";
    if (primaryArea) {
        primaryArea->setPointLabelsFormat(format);
        primaryArea->setPointLabelsVisible(showLabels);
    }
    if (secondaryArea) {
        secondaryArea->setPointLabelsFormat(format);
        secondaryArea->setPointLabelsVisible(showLabels);
    }
}

void AreaPlotManager::enableAntialiasing(bool enable) {
    chartView->setRenderHint(QPainter::Antialiasing, enable);
}

Regeneración de Datos

Para actualizar el gráfico con nuevos valores aleatorios, es necesario limpiar las series existentes y reconstruir los límites.

void AreaPlotManager::regenerateChartData() {
    mainChart->removeAllSeries();

    QLineSeries *newTop = new QLineSeries();
    QLineSeries *newMid = new QLineSeries();
    QLineSeries *newBottom = new QLineSeries();

    auto generateRandomPoints = [](int count, int minVal, int maxVal) {
        QVector<QPointF> points;
        for (int i = 0; i < count; ++i) {
            points << QPointF(i, QRandomGenerator::global()->bounded(minVal, maxVal));
        }
        return points;
    };

    newTop->append(generateRandomPoints(12, 70, 100));
    newMid->append(generateRandomPoints(12, 40, 70));
    newBottom->append(generateRandomPoints(12, 10, 40));

    // Asignación de colores aleatorios para diferenciar las líneas
    auto randomColor = []() {
        return QColor(QRandomGenerator::global()->bounded(256),
                      QRandomGenerator::global()->bounded(256),
                      QRandomGenerator::global()->bounded(256));
    };

    newTop->setColor(randomColor());
    newMid->setColor(randomColor());
    newBottom->setColor(randomColor());

    primaryArea = new QAreaSeries(newTop, newMid);
    primaryArea->setName("Rango Alto");
    
    secondaryArea = new QAreaSeries(newMid, newBottom);
    secondaryArea->setName("Rango Bajo");

    mainChart->addSeries(primaryArea);
    mainChart->addSeries(secondaryArea);
    
    // Recrear ejes para ajustar los nuevos rangos si es necesario
    mainChart->createDefaultAxes();
}

Etiquetas: Qt QCharts QAreaSeries QLineSeries C++

Publicado el 7-3 23:22