QCustomplot use the share (eight) charting - loaded cvs file

I. Overview

Done before a financial product, called fiscal Associated Press, interested can look at one fiscal Associated Press - Products , due to the need to draw complex graphs and some auxiliary k-line graph, a couple of my personal research drawing library, including: QWt, QCustomPlot, QtChart and directUI. Finally, the parties consider the decision to use QCustomPlot do our basic graphics library, there are several aspects to consider

  1. He is the first open source QCP
  2. Code Only two files can be more convenient to introduce our existing code
  3. Code readability strong, easy customization

When we selected graphics library, of course, it is to study our library, so I spent a few days to study our graphics library, and made a simple demo, interested can go to before writing the article, demo are in the CSDN placed, if there is no need to divide my hair can leave a message.

Before I explain the articles behind Related Articles section has been given, the idea of the students can directly see the first article before, it is easier to understand

Second, the effect of FIG.

As shown below, is a test renderings I do, the way including a simple line graph and cursor display mode line graph of a dozen effects, concrete can see QCustomplot use the share (a) capable of doing this article screenshot, here I will not be posted.

The renderings only shows part of a simple function, drawing control is actually my package is mainly used for loading cvs file, and then display the corresponding chart, of course, if you want to get yourself to add data to the chart also supported.

Finally, the drawing control also provides a number of interfaces, you can get the current mapping data, such as:

  1. For the cursor x value, y value, provides a maximum of two cursors
  2. Acquiring the x-value data segment between two cursors
  3. Get the value of y between two cursors data segments, and may specify line graph
  4. Color line chart provided
  5. Provided line graph type is provided with four axes title bar name
  6. Set cursor color

The following article I will analyze under the main interface and core functionality to achieve

FIG showing the effect of the test code is as follows, the code key to the node 2

  1. ESCvsDBOperater class structure, and load the file cvs
  2. Set interface setting data, and set the line graph type
ESCsvDBOperater * csvDBOperater = new ESCsvDBOperater(nullptr);
csvDBOperater->loadCSVFile(qApp->applicationDirPath() + "\\temp\\test31.csv");
QStringList names = csvDBOperater->getCSVNames();
auto callback = [this, names](const QString & name, const QVector<double> & data){
    int index = names.indexOf(name);
    if (index != -1)
    {
        if (index == 0)
        {
            ui->widget->SetGraphKey(data);
        }
        else
        {
            int l = name.indexOf("(");
            int r = name.indexOf(")");
            if (l != -1 && r != -1)
            {
                ui->widget->SetGraphValue(index - 1, name.left(l), /*name.mid(l + 1, r - l - 1)*/"", data);
                ui->widget->SetGraphScatterStyle(index - 1, 4);
            }
            else
            {
                ui->widget->SetGraphValue(index - 1, name, "", data);
            }
        }
    }

Of course, QCP only can display the line graph, he can also display a variety of renderings of interest to QCustomplot use the share (a) can do anything article Watch

Third, the source explained

1, the source structure

As shown, the header file is a screenshot of the project, more the number of files in the figure, but may be just outside a ESMPMultiPlot class we use, this class provides many interfaces, enough for us to use, of course, if there are special needs, then we can also provide custom






2, header files

The following is the header file interface, I just list the relevant Public interface, and these interfaces also happens we usually use more of the interfaces, see the interface name should know what the interface library is therefore not fine here Say

void SetGraphCount(int);
void SetGraphKey(const QVector<double> &);
double GetGraphKey(double);
void SetGraphValue(int, const QString &, const QString &, const QVector<double> &);
void SetGraphScatterStyle(int, int);
double GetGraphValue(int, bool);//获取折线图所在游标出y值 参数1:折线下标   参数2:左右游标标识
double GetGraphValue(int, double);//获取折线图所在游标出y值 参数1:折线下标   参数2:x
void SetGraphColor(int, const QColor &);
void SetGraphColor(const QString &, const QColor &);
void SetGraphUnit(int, const QString &);
void SetGraphTitle(int, const QString &);
void RefrushGraphID(int, const QString &);

int GetGraphIndex(const QString &) const;

void SetCursorColor(bool, const QColor &);
void ShowCursor(bool visible = true);
double GetCursorKey(bool);
bool CursorVisible();
double GetCursorValue(bool);

void ResizeKeyRange(bool);
void SetKeyRange(double, double);
void SetVauleRange(double, double);

void ConfigureGraph();//设置
std::shared_ptr<AxisRectConfigurations> GetAxisCache();

3, move the cursor

As shown in the following code, the cursor is moving core code

void ESMPPlot::mouseMoveEvent(QMouseEvent * event)
{
    if (m_bDragCursor && m_pDragCursor)
    {
        double pixelx = event->pos().x();
        QCPRange keyRange = axisRect()->axis(QCPAxis::atBottom)->range();
        double min = axisRect()->axis(QCPAxis::atBottom)->coordToPixel(keyRange.lower);
        double max = axisRect()->axis(QCPAxis::atBottom)->coordToPixel(keyRange.upper);
        double lcursor = m_mapLeftCursor.begin().key()->point1->key();
        double lcursorx = axisRect()->axis(QCPAxis::atBottom)->coordToPixel(lcursor);
        double rcursor = m_mapRightCursor.begin().key()->point1->key();
        double rcursorx = axisRect()->axis(QCPAxis::atBottom)->coordToPixel(rcursor);
        if (min > pixelx)
        {
            pixelx = min;
        }
        else if (max < pixelx)
        {
            pixelx = max;
        }
        if (m_bLeftCursor)
        {
            if (pixelx >= rcursorx - 4 && layer(r_cursorLayer)->visible())
            {
                pixelx = rcursorx - 4;
            }
            double key = axisRect()->axis(QCPAxis::atBottom)->pixelToCoord(pixelx);
            double value1 = m_pDragCursor->point1->value();
            double value2 = m_pDragCursor->point2->value();
            for each (QCPItemStraightLine * line in m_mapLeftCursor.keys())
            {
                line->point1->setCoords(key, value1);
                line->point2->setCoords(key, value2);
            }
            m_pLeftText->setText(QString::number(GetGraphKey(pixelx)));
            m_pLeftText->position->setPixelPosition(QPoint(pixelx, axisRect()->rect().bottom() + 25));
        }
        else
        {
            if (pixelx <= lcursorx + 4 && layer(l_cursorLayer)->visible())
            {
                pixelx = lcursorx + 4;
            }
            double key = axisRect()->axis(QCPAxis::atBottom)->pixelToCoord(pixelx);
            double value1 = m_pDragCursor->point1->value();
            double value2 = m_pDragCursor->point2->value();
            for each (QCPItemStraightLine * line in m_mapRightCursor.keys())
            {
                line->point1->setCoords(key, value1);
                line->point2->setCoords(key, value2);
            }
            m_pRightText->setText(QString::number(GetGraphKey(pixelx)));
            m_pRightText->position->setPixelPosition(QPoint(pixelx, axisRect()->rect().bottom() + 25));
        }
        event->accept();
        replot();
        emit CursorChanged(m_bLeftCursor);
        return;
    }

    __super::mouseMoveEvent(event);
}

In ESMPPlot class, m_mapLeftCursor and m_mapRightCursor are around the cursor, why here took a map of it? The answer is: when the time is designed to support multiple vertical placement of the cursor can be synchronized cursor, if stocks classmates might know, there may be a convenient value of k line between the line and targets, no matter what the plot area move, another chart where the line will move with

The students do not understand it does not matter, we have this control is a default table, so this map there will only keep a finger, so you can not be concerned about this issue

In ESMPMultiPlot class, we simulated ESMPPlot function, this time for it? Our only one axis of a rectangular, x-axis is the same, represents time, the y axis of the different curves we translated, in order to achieve different display positions

This side has a heavy skill that we had a y-axis data unit conversion, so that he can better display when shown in the development of our region might look like

/*
    y1p=(y1-Yzero1)/Ygrid1+Xaxis1;%核心转换公式,将原始坐标值y1转换为新坐标值y1p
    y1;%原始数值
    Yzero1;%零点幅值,决定曲线1零点位置的变量
    Ygrid1;%单格幅值,决定曲线1每个单元格大小的量
    Xaxis1;%显示位置,决定曲线1在画图板中显示位置的变量
*/

当然了,我们转换后的坐标只是为了显示方便而已,如果我们根据UI获取原始值,我们还需要使用一个逆向公式进行转换回去。

4、设置坐标轴矩形个数

QCP他自己的逻辑是这样的,每一个QCustomPlot类都包括多个坐标轴矩形,而一个坐标轴矩形里又可以包含多个图表,因此我们这个控件是这样的:

  1. 一个坐标轴矩形
  2. 多个QCPGraph

当我们设置的图表数量大于已有图表时,需要使用takeAt接口移除多余的图表;当我们设置的图表数据小于已有图表时,就需要添加新图表对象,添加时机是设置图表数据时

由于这个函数的代码量比较大,因此这里我删除了一些异常处理代码和设置属性代码

添加图表数据的流程可能像这面这样

  1. 首先处理数据异常
  2. 添加坐标轴
  3. 根据当前的折线图个数,计算当前折线图的位置和一些转换可能用的系数比率
  4. 添加图表所有两侧的标题栏名称,如name和unit
  5. 刷新图表
void ESMPMultiPlot::SetGraphCount(int count)
{
    QCPAxisTickerText * leftTick = new QCPAxisTickerText;
    axisRect()->axis(QCPAxis::atLeft)->setTicker(QSharedPointer<QCPAxisTickerText>(leftTick));

    QCPAxisTickerText * rightTick = new QCPAxisTickerText;
    axisRect()->axis(QCPAxis::atRight)->setTicker(QSharedPointer<QCPAxisTickerText>(rightTick));
    
    int tickCount = m_iCount * 4;//每个折线4个大刻度
    double tickDistance = (720 + 100)/ tickCount;
    QMap<double, QString> ticks;
    for (int i = 0; i <= tickCount; ++i)
    {
        ticks[tickDistance * i] = "";
    }
    leftTick->setTicks(ticks);
    leftTick->setSubTickCount(4);//每个大刻度包含4个小刻度

    double labelDistance = 720 / m_iCount;
    m_vecVerticalTick.resize(m_iCount);
    m_vecNames.resize(m_iCount);
    m_vecUnits.resize(m_iCount);
    double step = 1.0 / m_iCount;
    for (int i = 0; i < m_vecVerticalTick.size(); ++i)
    {
        m_vecVerticalTick[i] = labelDistance * i + labelDistance / 2;

        QCPItemText * name = new QCPItemText(this);
        name->position->setCoords(QPointF(0.01, 1 - (step * i + step / 2)));
        m_vecNames[m_vecVerticalTick.size() - i - 1] = name;

        QCPItemText * unit = new QCPItemText(this);
        unit->position->setCoords(QPointF(0.9, 1 - (step * i + step / 2)));
        m_vecUnits[m_vecVerticalTick.size() - i - 1] = unit;
    }

    RefrushItemPosition();

    m_graphConfigure->resize(count);
}

5、添加图表数据

毫无疑问,添加图表数据是我们这个控件的非常重要的一个借口
如下代码所示,看我们是怎么添加数据的

  1. 首先排除数据异常情况
  2. 更新图表的各个轴的名称
  3. 然后给图表添加数据
  4. 如果图表不存在则添加一个新的
  5. 设置图表数据
  6. 设置坐标轴信息
  7. 设置折线图对应的标题栏名称
void ESMPPlot::SetGraphValue(int index
    , const QString & xname, const QString & yname, const QVector<double> & values)
{
    if (index >= m_iCount
        || values.size() == 0)
    {
        return;
    }

    m_vecIndex[index] = xname;
    m_vecUnit[index] = yname;
    m_oldDatas[index] = values;

    QList<QCPGraph *> graphs = axisRect(index)->graphs();
    QCPGraph * graph = nullptr;
    if (graphs.size() == 0)
    {
        graph = addGraph(axisRect(index)->axis(QCPAxis::atBottom)
            , axisRect(index)->axis(QCPAxis::atLeft));
        graph->setLineStyle(QCPGraph::lsLine);
        graph->setPen(QColor(255, 0, 0, 200));
    }
    else
    {
        graph = graphs.at(0);
    }
    
    graph->setData(m_vecKeys, values, true);
    auto miniter = std::min_element(values.begin(), values.end());
    auto maxiter = std::max_element(values.begin(), values.end());
    double padding = (*maxiter - *miniter) * 0.2;
    axisRect(index)->axis(QCPAxis::atLeft)->ticker()->setTickOrigin(*miniter - padding);
    axisRect(index)->axis(QCPAxis::atLeft)->ticker()->setTickStepStrategy(
        QCPAxisTicker::tssReadability);
    axisRect(index)->axis(QCPAxis::atLeft)->ticker()->setTickCount(8);
    axisRect(index)->axis(QCPAxis::atLeft)->setRange(*miniter - padding, *maxiter + padding);

    axisRect(index)->axis(QCPAxis::atRight)->ticker()->setTickOrigin(*miniter - padding);
    axisRect(index)->axis(QCPAxis::atRight)->ticker()->setTickStepStrategy(
        QCPAxisTicker::tssReadability);
    axisRect(index)->axis(QCPAxis::atRight)->ticker()->setTickCount(8);
    axisRect(index)->axis(QCPAxis::atRight)->setRange(*miniter - padding, *maxiter + padding);
    
    int leftPadding = QFontMetrics(axisRect(index)->axis(QCPAxis::atLeft)->labelFont()).width(xname);
    axisRect(index)->axis(QCPAxis::atLeft)->setLabel(xname);
    
    int rightPadding = QFontMetrics(axisRect(index)->axis(QCPAxis::atBottom)->labelFont()).width(yname);
    axisRect(index)->axis(QCPAxis::atBottom)->setLabel(yname);
}

6、设置折线图类型

QCP自带的折线图类型很多,具体我们可以参看QCPScatterStyle::ScatterShape这个枚举类型有多少

void ESMPMultiPlot::SetGraphScatterStyle(int index, int style)
{
    QList<QCPGraph *> graphs = axisRect()->graphs();
    if (graphs.size() != 0 && index < graphs.size())
    {
        QCPGraph * graph = graphs.at(0);
        graph->setScatterStyle(QCPScatterStyle::ScatterShape(style));
    }
}

6、其他函数

还有一些其他的方法,比如保存图表、获取图表坐标、设置图表颜色等这里就不细讲了,文章篇幅所限,不能一一的都贴出来,有需要的伙伴可以联系我,提供功能定制

四、测试方式

1、测试工程

控件我们将的差不多了,这里把测试的代码放出来,大家参考下,首先测试工程截图如下所示,我们的测试代码,大多数都是写在了main函数中。






2、测试文件

这里简单说名下,我们的这个文件用途,第一列Time是代表了x轴的时间,而第二列开始的数据都是我们的折线图,一列数据代表一条折线图,并且列的名称就是我们折线图左侧的名称;列名称括号里的单位就是折线图右侧的单位。






3、测试代码

限于篇幅,这里我还是把无关的代码删减了很多,需要完整的源码的可以联系我。

void ESMultiPlot::LoadData()
{
    ESCsvDBOperater * csvDBOperater = new ESCsvDBOperater(nullptr);
    csvDBOperater->loadCSVFile(qApp->applicationDirPath() + "\\temp\\test31.csv");
    QStringList names = csvDBOperater->getCSVNames();

    auto callback = [this, names](const QString & name, const QVector<double> & data){
        添加图表数据
    };

    ui->widget->SetGraphCount(names.size() - 1);
    for (int i = 0; i < names.size(); ++i)
    {
        csvDBOperater->receiveData(names[i], callback);
    }

    double start = csvDBOperater->getStartTime();
    double end = csvDBOperater->getEndTime();

    csvDBOperater->receiveData(names[2], 10.201, 10.412, callback);
    QVector<double> tiems = csvDBOperater->getRangeTimeDatas(10.201, 10.412);

    ui->widget->SetGraphKeyRange(start, end);
}

五、相关文章

  1. QCustomplot使用分享(一) 能做什么事
  2. QCustomplot使用分享(二) 源码解读
  3. QCustomplot使用分享(三) 图
  4. QCustomplot使用分享(四) QCPAbstractItem
  5. QCustomplot使用分享(五) 布局
  6. QCustomplot使用分享(六) 坐标轴和网格线
  7. QCustomplot使用分享(七) 层(完结)

六、总结

QCustomPlot是一个非常强大的绘图类,并且效率很高,对效率要求较高的程序都可以使用。

本篇文章是继前7篇讲解QCP后的第一篇使用案例,后续还会陆续提供更多复杂的功能。

这个控件已经被我封装成一个dll,如果有需要的小伙伴可以加我咨询

七、关于美化

因为我这里的程序都是测试程序,因此都是使用的原生效果,如果有需要美化的同学,或者客户,我也可以提供定制美化功能,欢迎咨询。


有疑问可以留言,欢迎咨询




转载声明:本站文章无特别说明,皆为原创,版权所有,转载请注明:朝十晚八 or Twowords


Guess you like

Origin www.cnblogs.com/swarmbees/p/10962588.html