Qt uses PaintEvent to draw real-time waveforms

The data source is still the real-time data imported by the hardware, as follows:

[0, 3, 5, 8, 10, 13, 15, 18, 20, 23, 25, 28, 26, 23, 20, 16, 13, 11, 9, 6, 4, 3, 0]

In fact, some people will say when they see this, isn’t it enough to draw the data directly?

In our actual application process, the data uploaded by these hardware is not transmitted at one time, but depends on the frequency of your operating hardware and events. Therefore, if you want to draw a whole piece of data at once, it is already too late.

drawing ideas

1: Receive data from hardware

SetRealTimeDepthData(stDepthData stData) is used here; meaning: set the real-time depth data value.

The parameter is inserted into a structure. Before using this function, I have done a simple processing of the data, including the setting of the depth direction.

For example: when the depth gradually increases, the div in the depth direction is a positive number, and when the depth gradually decreases, the div in the depth direction is a negative number.

Below, I show my actual processed data values

For these real data, I set a structure to store data time, depth direction and specific depth value

struct DrawingEffectivePress
{
    int nDiv; //深度方向
    int nDepth; //深度值
    DWORD dwTime; //记录当前真实数据的时间
    DrawingEffectivePress():nDiv(0),nDepth(0),dwTime(0){}
}

The specific implementation of SetRealTimeDepthData is as follows:

void QDrawingWaveform::SetRealTimeDepthData(stDepthData stData)
{
    std::lock_guard<std::mutex> lck(m_dataMutex); //加锁进行数据操作
    DrawingEffectivePress stDepth;
    stDepth.nDiv = stData.nDiv>=0?1:-1;
    stDepth.nDepth = stData.nDepth;
    stDepth.dwTime = GetTickCont();
    m_vetDepth.push_back(stDepth);
}

Code explanation:

According to the real data of the above pictures, the depth orientation is gradually increasing, so in the program we use 1 and -1 to represent, the positive direction is represented by 1, and the negative direction is represented by -1 To represent.

Every time there is a piece of real data, it is necessary to record the specific time of the current real data to draw the real-time dynamic trend.

2: The timer dynamically refreshes the page

Set a timer to refresh the page every 40 milliseconds:

#define TimeInterval 40 //定时器时间间隔

Timer start m_nTimerId = startTimer(TimeInterval);

3: Real data processing

This is an important point of our drawing, and it is also a more troublesome part.

Friends who have dealt with hardware know that the instability of hardware data, sometimes the direction of data is downward, because of manual operation, occasionally there will be some floating data, these data need to be screened out, I have done processing before passing in the data, this problem does not exist in this article.

The actual depth data value is stored using m_vetDepth.

std::vector<DrawingEffectivePress> m_vetDepth;

Step 1: Every time a data update is performed, the timeout display data needs to be removed.

What is the timeout display data?

According to the animation at the beginning of the article, it can be seen that the waveform graph moves to the left while drawing.

During the moving process, it will definitely move out of the left boundary, which means that the current graphics do not need to be displayed. In this regard, we need to judge which data has exceeded the display range every time the data is updated, and need to be eliminated.

So, here is another problem. Is the data we culled stored in m_vetDepth?

The answer is yes, but for the sake of logical operation, we need to redefine a structure. The current structure is mainly used to record the points that have been drawn into graphics.

std::vector<DrawingEffectivePress> m_vetEffectiveDepth;

The data stored in the current container must be specifically identified, that is, the inflection point data of the waveform graph, the lowest point and the highest point of a complete waveform.

When we do the actual drawing, we also take the data in m_vetEffectiveDepth, which ensures the simplicity of the data logic.

Now, let's take a look at the actual code for removing timeout data, as follows:

void QDrawingWaveform::DeletingTimeoutData()
{
    DWORD dwCurrentTime = GetTickCount(); //当前时间
    std::vector<DrawingEffectivePress>::iterator itvet = m_vetEffectivePress.begin();
    for (itvet; itvet != m_vetEffectivePress.end();)
    {
	DrawingEffectivePress stPoint = *itvet;
	if ((dwCurrentTime - stPoint.dwPressTime) > m_nSingShowTime)
	{
            //超过界面展示范围,剔除数据
            itvet = m_vetEffectivePress.erase(itvet++);
	}
	else
            itvet++;
    }
}

Code analysis : Get the latest time in real time, and compare the stored hardware operation time with the set maximum value (m_nSingShowTime) every time.

When the set time is exceeded, it means that the graph has disappeared on the interface, and the data needs to be removed.

Step 2: Screen valid data and record

The previous step is to remove the overtime data, so how do we store these effective data in the m_vetEffectivePress container?

Ideas:

1: Store the first two pieces of data in the default container.

2: When the follow-up data comes, it needs to be distinguished from the previous data.

2.1: If the direction is the same, it means that the depth has been increasing or decreasing, just replace the value of the last valid data directly.

2.2: If the directions are inconsistent, it means that the depth value has changed, maybe from downward to upward; or from upward to downward. Instead of doing data replacement operations, a new piece of data is inserted.

3: After operating a piece of data, delete the data.

Turn the above idea into code, as follows:

std::vector<DrawingEffectivePress>::iterator itvet = m_vetPress.begin()
for (itvet; itvet != m_vetPress.end(); )
{
	DrawingEffectivePress stPoint = *itvet;
	DrawingEffectivePress stPress;
	stPress.dwTime = stPoint.dwTime;
	stPress.nDiv = stPoint.nDiv;
	stPress.nDepth = stPoint.nDepth;
	int nsize = m_vetEffectivePress.size();
	if (nsize < 2)
	{
		m_vetEffectivePress.push_back(stPress);
	}
	else 
	{
            //如果当前数据stPoint与m_vetEffectivePress的最后一位的拐点一致,
            //剔除掉m_vetEffectivePress的最后一位,存储成最新数据
            if (m_vetEffectivePress[nsize - 1].nDiv == stPoint.nDiv)
            {
                    //更新m_vetEffectivePress的最后一位
                    m_vetEffectivePress[nsize - 1] = stPress;
            }
            else
            {
                    //存储的最后一位的拐点与当前数值不一致时,直接存储
                    m_vetEffectivePress.push_back(stPress); 
                        
            }
	}

	itvet = m_vetPress.erase(itvet++);
}

4: Graphic drawing

After the above data processing, we can directly draw the m_vetEffectivePress graphic in the panitEvent.

The actual code effect is as follows:

QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing); //抗锯齿
QPolygon polygon;
for (int i = 0; i < m_vetEffectivePress.size(); i++)
{
	DrawingEffectivePress stPoint = m_vetEffectivePress[i];
	int nX = this->CalcRealPointX(stPoint.dwPressTime);
	int nY = this->CalcRealPointY(stPoint.nDepth);
	polygon << QPoint(nX, nY);
}
painter.drawPolyline(polygon);

Code analysis : According to the operation time of the actual data and the specific depth value, the x-axis and y-axis of the waveform graph can be determined.

According to the change of time, it can make the graphics look like a moving effect.

CalcRealPointX actually handles

//TODO:计算,实际的x轴坐标
DWORD dwCurrent = GetTickCount();
int nRectRight = rect().right();
int nMoveOriginal = dwDepthTime - dwCurrent;
double dMoveLen = nMoveOriginal / 8;
int nX = nRectRight + dMoveLen;
return nX;

Code analysis : Get the current drawing time in real time, subtract the depth time stored in the structure, and set the moving length (dwMoveOriginal).

Because it needs to be shifted to the left, it is enough to subtract it from the right area of ​​the window each time.

Note: I am using "+" here, because the offset length I calculated must be a negative value. The real time must be greater than the actual depth time, so the result must be a negative value.

At this point, our real-time graphics drawing is 80% complete, and it can be realized if we want to draw continuous real-time depth value graphics.

Why is it 80%?

Because there is another problem we need to consider. When the data is not obtained continuously, when using QPolygon to draw graphics, the following effects will appear:

When we do not draw the depth value continuously, after a certain interval of time, when we draw again, the strange phenomenon of the above-mentioned red area box will appear.

According to the actual application, the drawing effect should be the same as the effect at the beginning of the article. After a certain period of time, draw again, and it should still be a complete waveform data.

The QPolygon drawing class is obviously not supported by using the above code. Even if we change to the DrawLine method, this problem still needs to be solved.

At this point, we need to do a special process. When we don’t have the actual depth value data, we need to know where the current drawing point is in real time. That is to say, before there is no real data, we need to refresh the drawing data every interval. time, it is necessary to draw a straight line instead of drawing the waveform directly from the previous drawing point.

The benefits of this article, free to receive Qt development learning materials package, technical video, including (C++ language foundation, C++ design pattern, introduction to Qt programming, QT signal and slot mechanism, QT interface development-image drawing, QT network, QT database programming, QT project actual combat, QSS, OpenCV, Quick module, interview questions, etc.) ↓↓↓↓↓↓See below↓↓Click on the bottom of the article to receive the fee↓↓

Guess you like

Origin blog.csdn.net/m0_73443478/article/details/130605870