QCustomplot uses sharing-drawing charts-loading cvs files

I. Overview

I have made a financial product before, the name is Cailianshe. If you are interested, you can take a look at Cailianshe-Product Display. Since I need to draw complex k-line diagrams and some auxiliary charts, I personally researched several drawing libraries. Including: QWt, QCustomPlot, QtChart and directUI. Finally, all parties considered and decided to use QCustomPlot as our basic drawing library. Here are several considerations

  1. First of all, QCP is open source

  1. There are only 2 files in the code, it is more convenient to introduce our existing code

  1. The code is more readable and easy to customize

When our drawing library is selected, it is a matter of course to study our library, so I also spent a few days studying our drawing library and made a simple demo. If you are interested, you can go to see it. The demo s of the articles I wrote before are all on CSDN. If there is no point, I can leave a message.

I have given the articles I explained before in the relevant article sections below. Students who have ideas can also read the previous articles directly, which is easier to understand.

2. Rendering

As shown in the figure below, it is a test effect diagram I made, including a simple line chart and a cursor on the way. The display mode of the line chart has more than a dozen effects. For details, please refer to QCustomplot use sharing (1) what can be done. I will not post the screenshots in the article here.

This rendering only shows some simple functions. The drawing control I encapsulated is actually mainly used to load the cvs file and then display the corresponding chart. Of course, if you want to get the data yourself and add it to the chart, it is also supported.

Finally, the drawing control also provides many interfaces to obtain the current drawing data, such as:

  1. For the x value and y value of the cursor, at most 2 cursors are provided

  1. Get the x-value data segment between two cursors

  1. Get the y-value data segment between two cursors, and you can specify a line chart

  1. Set line chart color

  1. Set the line chart type and set the title bar names of the 4 axes

  1. set cursor color

In the following article, I will analyze the main interface and core function implementation

The display effect test code in the figure is as follows, and there are only two key nodes in the code

  1. Construct the ESCvsDBOperator class and load the cvs file

  1. Set the data through the Set interface and set the line chart 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 can not only display line graphs, but also display a variety of renderings. If you are interested, go to QCustomplot to use and share (1) what you can do in the article

3. Source code explanation

1. Source code structure

As shown in the picture, it is a screenshot of the header file of the project. There are many files in the picture, but we may only use an ESMPMultiPlot class externally. This class provides many interfaces, which are enough for us to use. Of course, if there are special needs. , we can also provide customized

2. Header file

The following are the interfaces in the header file. I just listed the relevant Public interfaces, and these interfaces happen to be the interfaces we usually use more often. You should know what the interface library does by looking at the interface name, so I won’t go into details 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);//Get the y value of the cursor where the line chart is located. Parameter 1: Subscript of the line Parameter 2: Marks of left and right cursors
double GetGraphValue(int, double);//Get the y value of the cursor where the line chart is located. Parameter 1: Line subscript Parameter 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();//set up
std::shared_ptr<AxisRectConfigurations> GetAxisCache();

3. Move the cursor

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

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 the ESMPPlot class, m_mapLeftCursor and m_mapRightCursor are left and right cursors respectively. Why is a map taken here? The answer is: At the time of design, it was designed to support multiple cursors placed vertically to synchronize the cursors. If you are a stock trader, you may know that there may be a numerically convenient line between the k-line and the indicator, no matter which drawing area you are in Move it, and the line in the other chart will move with it Students who don’t understand this don’t matter, our control is a table by default, so there is only one pointer stored in this map, so you don’t need to care about this problem

In the ESMPMultiPlot class, we simulate the function of ESMPPlot, what about this time? We have only one coordinate axis rectangle, and the x-axis is the same, representing time. We have shifted the y-axis of different curves to achieve different display positions

There is a very important skill here, that is, we have performed a unit conversion on the y-axis data, so that it can be better displayed in the area we set when it is displayed, which may be as follows

/*
	y1p=(y1-Yzero1)/Ygrid1+Xaxis1;%The core conversion formula converts the original coordinate value y1 to the new coordinate value y1p
	y1;%raw value
	Yzero1;%Zero amplitude, the variable that determines the zero position of curve 1
	Ygrid1;%Single cell amplitude, determines the amount of each cell size of curve 1
	Xaxis1;%Display position, the variable that determines the display position of curve 1 in the drawing board
*/

Of course, our converted coordinates are only for display convenience. If we get the original value according to the UI, we also need to use a reverse formula to convert it back.

4. Set the number of coordinate axis rectangles

QCP's own logic is like this, each QCustomPlot class includes multiple coordinate axis rectangles, and one coordinate axis rectangle can contain multiple charts, so our control is like this:

  1. an axis rectangle

  1. Multiple QCPGraph s

When the number of charts we set is larger than the existing charts, we need to use the takeAt interface to remove redundant charts; when the chart data we set is smaller than the existing charts, we need to add a new chart object, and the time to add it is when setting the chart data

Since the code volume of this function is relatively large, I deleted some exception handling code and attribute setting code here

The process of adding chart data may look like this

  1. Handle data exceptions first

  1. add axis

  1. According to the number of current line charts, calculate the position of the current line chart and the coefficient ratios that may be used for some conversions

  1. Add title bar names on all sides of the chart, such as name and unit

  1. refresh chart

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 major ticks per polyline
	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);//Each major scale contains 4 minor scales

	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. Add chart data

There is no doubt that adding chart data is a very important excuse for our control

As shown in the following code, see how we add data

  1. First rule out data anomalies

  1. Update the names of the individual axes of the chart

  1. Then add data to the chart

  1. Adds a new graph if it does not exist

  1. Set chart data

  1. Set axis information

  1. Set the title bar name corresponding to the line chart

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. Set the line chart type

There are many types of line charts that come with QCP. For details, we can refer to the enumeration type of 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. Other functions

There are also some other methods, such as saving the chart, obtaining the coordinates of the chart, setting the color of the chart, etc. I will not go into details here. Due to the limited length of the article, I cannot post them all. Partners in need can contact me to provide function customization .

4. Test method

1. Test engineering

We are almost done with the controls. Here we release the test code for your reference. First, the screenshot of the test project is shown below. Most of our test codes are written in the main function.

2. Test file

Here is a brief description of the purpose of our file. The first column Time represents the time on the x-axis, and the data starting from the second column is our line chart. One column of data represents a line chart, and the name of the column is The name on the left side of our line chart; the unit in the brackets of the column name is the unit on the right side of the line chart.

3. Test code

Due to space limitations, I still cut out a lot of irrelevant code here. If you need complete source code, you can contact me.

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){
	    Add chart 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);
}

Original Author: Ten to Eight or Twowords

Reprinted from the original text: https://www.cnblogs.com/swarmbees/p/10962588.html

Tags: Qt

Posted by bqallover on Fri, 06 Jan 2023 15:07:50 +0300