这篇文章介绍的自定义插件比较简单,主要都是通过绘图完成的。 特点如下: 1. 精度是可以放缩 2. 时间轴时间连续 3. 可以通过鼠标左右滑动改变时间轴显示时间。
思路逻辑
基本需求
- 时间轴时间连续
- 时间轴精度可以放缩
- 时间轴可以左右滑动
简单的逻辑
- 考虑到时间轴精度放缩的时候是以时间轴中间的时间为基本(中间的时间位置不变,一直在视图中间),所以所有的刻度生成都是基于时间轴中间的时间来计算
- 假设提供一个中间时间,每次通过绘制当前中间时间在一定的时间精度等级下的图,当左右鼠标拖动时,改变中间时间值,再次重新绘制,这样基本就可以满足基本需求了
绘图
由于全是绘图事件。首先需要知道需要绘制哪些内容
- 最简单的背景
- 时间刻度
- 中间的游标
- 需要显示的内容
计算刻度数值
计算具体的时间刻度数值, 这一部分稍微麻烦一点。在已知中间的时间,通过像素和时间之间的比例,求出时间轴最左侧的起点时间,根据不同的精度,绘制完整的时间轴
代码逻辑
整体逻辑
根据上面的逻辑, 在 paintEvent(QPaintEvent *)
函数中对每一部分需要做的做一个简单说明
void DrawTimeline::paintEvent(QPaintEvent *)
{
// 绘制背景
QPainter painter(this);
painter.setBrush(QColor{50,50,50});
painter.drawRect(rect());
// 绘制时间刻度
drawTimescale(painter);
// 显示中间位置时间
m_midDisplayLabel->setText(m_currentDateTime.toString("yyyy-MM-dd hh:mm:ss"));
m_midDisplayLabel->move(this->size().width()/2-m_midDisplayLabel->width()/2-5,10);
// 绘制具体的时间数据
drawTimeRangeInfo(painter);
// 绘制中间的游标
drawTimeCursor(painter);
}
接下来主要先把简单的内容先说明以下
中间位置时间
m_currentDateTime
记录当前的时间
m_midDisplayLabel
是一个 QLabel
, 主要就是显示 m_currentDateTime
时间, 位置在视图的中央
绘制中间游标
中间的游标分为2部分
第一部分是一个红色的竖线,位置在视图中间位置,只需要确定y方向的高度
第二部分是一个类似标签一样的5边形,可以通过提供5个点的位置, 使用 drawPolygon()
函数直接绘制得到
void DrawTimeline::drawTimeCursor(QPainter &painter)
{
painter.setPen( QPen{QColor{255,0,0,200},2});
painter.drawLine(this->width()/2+1, CURSORHEIGTH, this->width()/2+1, 105);
QPolygonF cursorPolygon;
cursorPolygon << QPointF(this->size().width()/2, CURSORHEIGTH)
<< QPointF(this->size().width()/2+4, CURSORHEIGTH-4)
<< QPointF(this->size().width()/2+4, CURSORHEIGTH-12)
<< QPointF(this->size().width()/2-4, CURSORHEIGTH-12)
<< QPointF(this->size().width()/2-4, CURSORHEIGTH-4) ;
painter.setBrush(QColor{200,200,200});
painter.setPen(QColor{200,200,200});
painter.drawPolygon(cursorPolygon);
}
绘制时间刻度
在绘制时间刻度之前,首先需要总结大概需要的时间和距离的关系。
我的代码中,目前分为8个精度
12个小时,6个小时,2个小时, 1个小时, 30分钟, 10分钟, 5分钟, 1分钟
同时每个小的时间段(时间步长)对应的像素(像素步长),我设置的比较随意, 50像素开始到120像素,每1级加10像素,这样在调整精确度的时候,有放缩的感觉。
定义结构体 TimeForDistance
, 存储时间步长和像素步长的关系
QVector<TimeForDistance> m_vecStep
存储我所有的精度信息
int m_sizeLevel
存储当前绘图使用的精度等级(0-7),对应 m_vecStep
中的信息
struct TimeForDistance
{
int timestep;
float pixelstep;
};
QVector<TimeForDistance> m_vecStep; 这个变量存储所有的像素和时间的关系
int m_sizeLevel;
所有的准备工作都做好了,接下来就是绘制时间刻度
这边需要对刻度做一下简单的说明, 我以一小时的时间步长为例,时间刻度显示应该是完整的小时, 比如00:00, 01:00, 02:00,等等
总结以下 如果已知中间时间,如何绘制完整的时间轴
- 已知中间时间,可以根据对应精度中每个像素代表多少时间,求出时间轴上起点的时间
- 因为起点时间并不能作为时间轴上刻度,我们需要算出起点时间右侧的第一个刻度
- 我将起点时间全部换成秒为单位的数值,该数值除以步长的整数部分+1之后乘以步长就能得到右侧第一个刻度S的数值
- 实际上我们只关心起点的位置信息, 计算起点时间和右侧第一个刻度的时间差值,在换成位置信息即可
- 在算出第一个刻度位置后,之后的时间刻度累加像素步长,可以得到完整的时间轴
接下来简单看一下代码,代码就是按照这个逻辑实现的
void DrawTimeline::drawTimescale(QPainter &painter)
{
// 绘制容纳时间的矩形
painter.setPen( QColor{150,150,150});
painter.drawRect(-1,RECTY,this->width()+40,RECTHEIGH);
float pixelstep = m_vecStep.at(m_sizeLevel).pixelstep;
int timestep = m_vecStep.at(m_sizeLevel).timestep;
float secsper = timestep/pixelstep;
QDateTime timeline_starttime = m_currentDateTime.addSecs(-( this->width()/2)*secsper);
int timeH = timeline_starttime.time().hour();
int timeM = timeline_starttime.time().minute();
int timeS = timeline_starttime.time().second();
int second = timeH*3600+timeM*60+timeS;
int drawstarttime = (second/timestep+1)*timestep - second;
int startPosition = drawstarttime / secsper;
QDateTime show_starttime = timeline_starttime.addSecs(drawstarttime);
QFont font{"\"Microsoft YaHei\"",8,57};
int count = this->size().width() / pixelstep;
for (int i = 0; i <= count; ++i) {
float timeX = pixelstep*i+startPosition -10;
painter.setFont(font);
painter.drawText(timeX, RECTY-5, show_starttime.toString("hh:mm"));
painter.drawLine(pixelstep*i+startPosition, RECTY-1, pixelstep*i+startPosition, RECTY+25);
show_starttime = show_starttime.addSecs(timestep);
}
}
绘制具体的时间数据
QVector<TimeRange> m_vecTimeRangeInfo
存储所有的时间信息
结构体 TimeRange
中只记录每个时间信息的开始时间和结束时间
struct TimeRange
{
QDateTime starttime;
QDateTime endtime;
};
在得到开始时间和结束时间之后, 通过计算开始时间的位置,以及时间的长度,在时间轴中绘制一个矩形
void DrawTimeline::drawTimeRangeInfo(QPainter &paint)
{
for (int i=0; i<m_vecTimeRangeInfo.count(); ++i) {
QDateTime stime = m_vecTimeRangeInfo.at(i).starttime;
QDateTime etime = m_vecTimeRangeInfo.at(i).endtime;
float pixelstep = m_vecStep.at(m_sizeLevel).pixelstep;
int timestep = m_vecStep.at(m_sizeLevel).timestep;
float secsper = timestep/pixelstep;
float sToCurrent = m_currentDateTime.secsTo(stime);
float eToCurrent = m_currentDateTime.secsTo(etime);
float length = eToCurrent - sToCurrent;
int timeX = sToCurrent/secsper + this->width()/2;
int timeLen = length/secsper;
paint.setPen(QPen(Qt::darkYellow,1,Qt::NoPen));
paint.setBrush(QBrush(Qt::cyan,Qt::SolidPattern));
paint.drawRect(timeX,RECTY+1,timeLen,RECTHEIGH-1);
}
}
左右滑动
这一部分比较简单主要重新实现以下几个函数
virtual void mousePressEvent(QMouseEvent *event);
virtual void mouseReleaseEvent(QMouseEvent *event);
virtual void mouseMoveEvent(QMouseEvent *event);
在鼠标点击的时候记录一个位置信息,和点击时的 m_currentDateTime
时间
在鼠标移动过程中计算移动的距离,通过当前的精度, 换算出新的 m_currentDateTime
值, 之后调用 update()
刷新绘图即可
精度的切换
之前通过 m_sizeLevel
来记录当前的精度等级,在放大和缩小精度的时候,对应改变它的值,之后调用 update()
刷新绘图即可
扩展
- 放大缩小可以引入鼠标滚轮事件
void wheelEvent(QWheelEvent *event)
代码地址
github 地址 :https://github.com/catcheroftime/DrawTimelinePlugin