402 lines
11 KiB
C++
402 lines
11 KiB
C++
#include "CustomQwtPlot.h"
|
||
#include <QwtPlotCurve>
|
||
#include <QwtLegend>
|
||
#include <QwtPlotCanvas>
|
||
#include <QwtText>
|
||
#include <QwtPlotMarker>
|
||
#include <QwtPlotZoneItem>
|
||
#include <QwtScaleMap>
|
||
#include <QwtScaleDiv>
|
||
#include <QPen>
|
||
#include <QMouseEvent>
|
||
#include <QwtScaleWidget>
|
||
#include <QwtPlotPicker>
|
||
#include <QwtPlotShapeItem>
|
||
#include <QwtPickerDragRectMachine>
|
||
#include <QDebug>
|
||
|
||
CustomQwtPlot::CustomQwtPlot(QWidget *parent)
|
||
: QwtPlot(parent)
|
||
{
|
||
}
|
||
|
||
CustomQwtPlot::~CustomQwtPlot()
|
||
{
|
||
qDebug() << "~CustomQwtPlot";
|
||
}
|
||
|
||
void CustomQwtPlot::SetEventFilterFunc(std::function<bool (QObject *, QEvent *)> event_filter_func)
|
||
{
|
||
this->_event_filter_func = event_filter_func;
|
||
}
|
||
|
||
void CustomQwtPlot::SetXaxisDragScale(bool enable)
|
||
{
|
||
QwtScaleWidget *x_scale = axisWidget(QwtPlot::xBottom);
|
||
x_scale->setMouseTracking(enable);
|
||
x_scale->installEventFilter(enable ? this : nullptr);
|
||
}
|
||
|
||
void CustomQwtPlot::ResetPlot()
|
||
{
|
||
this->setAxisScale(QwtPlot::xBottom, _init_x_min, _init_x_max);
|
||
this->setAxisScale(QwtPlot::yLeft, _init_y_min, _init_y_max);
|
||
this->replot();
|
||
}
|
||
|
||
QwtPlotCurve *CustomQwtPlot::GetCurve(const QString &curve_name)
|
||
{
|
||
return _curves.value(curve_name, nullptr);
|
||
}
|
||
|
||
QList<QwtPlotCurve *> CustomQwtPlot::GetCurveList() const
|
||
{
|
||
return _curves.values();
|
||
}
|
||
|
||
void CustomQwtPlot::AddCurve(QwtPlotCurve *curve)
|
||
{
|
||
if (curve) {
|
||
curve->setPen(QPen(getDistinctColorForManyCurves(_curves.count()), 1));
|
||
curve->setZ(0);
|
||
curve->attach(this);
|
||
_curves[curve->title().text()] = curve;
|
||
}
|
||
}
|
||
|
||
QwtPlotMarker *CustomQwtPlot::GetMarker(const QString &marker_name, const QString &postion)
|
||
{
|
||
return _markers.value(marker_name).value(postion);
|
||
}
|
||
|
||
QList<QwtPlotMarker *> CustomQwtPlot::GetMarkerList() const
|
||
{
|
||
QList<QwtPlotMarker *> markers;
|
||
for (auto tmp_markers : this->_markers) {
|
||
markers.append(tmp_markers.values());
|
||
}
|
||
return markers;
|
||
}
|
||
|
||
void CustomQwtPlot::AddMarker(QwtPlotMarker *marker, const QString &marker_name, const QString &postion)
|
||
{
|
||
if (marker) {
|
||
QwtPlotCurve* curve = GetCurve(marker_name);
|
||
if (curve) {
|
||
QPen pen = curve->pen();
|
||
pen.setWidth(2);
|
||
marker->setLinePen(pen);
|
||
marker->setZ(20);
|
||
marker->attach(this);
|
||
_markers[marker_name][postion] = marker;
|
||
}
|
||
}
|
||
}
|
||
|
||
void CustomQwtPlot::RemoveMarker(const QString &marker_name, const QString &postion)
|
||
{
|
||
QwtPlotMarker* marker = GetMarker(marker_name, postion);
|
||
if (marker) {
|
||
marker->detach();
|
||
delete marker;
|
||
}
|
||
_markers[marker_name].remove(postion);
|
||
}
|
||
|
||
void CustomQwtPlot::CleanMarkers()
|
||
{
|
||
QList<QwtPlotMarker *> markers = GetMarkerList();
|
||
for (auto marker : markers) {
|
||
if (marker) {
|
||
marker->detach();
|
||
delete marker;
|
||
}
|
||
}
|
||
this->_markers.clear();
|
||
}
|
||
|
||
QwtPlotZoneItem* CustomQwtPlot::GetZoneItem(const QString& zone_item_name)
|
||
{
|
||
return _zone_items.value(zone_item_name);
|
||
}
|
||
|
||
QList<QwtPlotZoneItem *> CustomQwtPlot::GetZoneItemList() const
|
||
{
|
||
return this->_zone_items.values();
|
||
}
|
||
|
||
void CustomQwtPlot::AddZoneItem(QwtPlotZoneItem *zone_item, const QString &zone_item_name)
|
||
{
|
||
if (zone_item) {
|
||
zone_item->setPen(Qt::transparent); // 无边框
|
||
zone_item->setBrush(QColor(0, 120, 215, 95)); // 半透明蓝色(RGBA:A=80 透明度)
|
||
zone_item->setZ(10);
|
||
zone_item->attach(this);
|
||
_zone_items[zone_item_name] = zone_item;
|
||
}
|
||
}
|
||
|
||
void CustomQwtPlot::RemoveZoneItem(const QString& zone_item_name)
|
||
{
|
||
QwtPlotZoneItem* zone_item = GetZoneItem(zone_item_name);
|
||
if (zone_item) {
|
||
zone_item->detach();
|
||
delete zone_item;
|
||
}
|
||
_zone_items.remove(zone_item_name);
|
||
}
|
||
|
||
void CustomQwtPlot::CleanZoneItems()
|
||
{
|
||
QList<QwtPlotZoneItem *> zone_items = GetZoneItemList();
|
||
for (auto zone_item : zone_items) {
|
||
if (zone_item) {
|
||
zone_item->detach();
|
||
delete zone_item;
|
||
}
|
||
}
|
||
this->_zone_items.clear();
|
||
}
|
||
|
||
bool CustomQwtPlot::eventFilter(QObject *obj, QEvent *event)
|
||
{
|
||
if (this->_event_filter_func) {
|
||
bool filted = this->_event_filter_func(obj, event);
|
||
if (filted) {
|
||
return filted;
|
||
}
|
||
}
|
||
if (dynamic_cast<QwtScaleWidget*>(obj) != this->axisWidget(QwtPlot::xBottom))
|
||
return false;
|
||
QMouseEvent *me = static_cast<QMouseEvent*>(event);
|
||
QwtScaleMap map = this->canvasMap(QwtPlot::xBottom);
|
||
// ===================== 1. 鼠标拖动 X 轴 =====================
|
||
if (event->type() == QEvent::MouseButtonPress) {
|
||
this->_is_dragging = true;
|
||
this->_last_pos = me->pos();
|
||
return true;
|
||
} else if (event->type() == QEvent::MouseMove && this->_is_dragging) {
|
||
// 计算坐标偏移
|
||
double old_x = map.invTransform(_last_pos.x());
|
||
double new_x = map.invTransform(me->pos().x());
|
||
double dx = old_x - new_x;
|
||
// 移动 X 轴
|
||
double x_min = axisScaleDiv(QwtPlot::xBottom).lowerBound() + dx;
|
||
double x_max = axisScaleDiv(QwtPlot::xBottom).upperBound() + dx;
|
||
this->setAxisScale(QwtPlot::xBottom, x_min, x_max);
|
||
this->replot();
|
||
this->_last_pos = me->pos();
|
||
return true;
|
||
} else if (event->type() == QEvent::MouseButtonRelease) {
|
||
this->_is_dragging = false;
|
||
return true;
|
||
}
|
||
// ===================== 2. 滚轮缩放 X 轴 =====================
|
||
if (event->type() == QEvent::Wheel) {
|
||
QWheelEvent *we = static_cast<QWheelEvent*>(event);
|
||
double scale = we->angleDelta().y() > 0 ? 0.8 : 1.25; // 放大/缩小
|
||
// 以鼠标所在位置为中心缩放
|
||
double mouse_x = map.invTransform(we->pos().x());
|
||
double x1 = this->axisScaleDiv(QwtPlot::xBottom).lowerBound();
|
||
double x2 = this->axisScaleDiv(QwtPlot::xBottom).upperBound();
|
||
double new1 = mouse_x - (mouse_x - x1) * scale;
|
||
double new2 = mouse_x + (x2 - mouse_x) * scale;
|
||
this->setAxisScale(QwtPlot::xBottom, new1, new2);
|
||
this->replot();
|
||
return true;
|
||
}
|
||
return QwtPlot::eventFilter(obj, event);
|
||
}
|
||
|
||
void CustomQwtPlot::showEvent(QShowEvent *event)
|
||
{
|
||
Q_UNUSED(event);
|
||
this->updateAxes();
|
||
double x_min = this->axisScaleDiv(QwtPlot::xBottom).lowerBound();
|
||
double x_max = this->axisScaleDiv(QwtPlot::xBottom).upperBound();
|
||
double y_min = this->axisScaleDiv(QwtPlot::yLeft).lowerBound();
|
||
double y_max = this->axisScaleDiv(QwtPlot::yLeft).upperBound();
|
||
this->_init_x_min = x_min;
|
||
this->_init_x_max = x_max;
|
||
this->_init_y_min = y_min;
|
||
this->_init_y_max = y_max;
|
||
}
|
||
|
||
CustomQwtPlotXaxisPanner::CustomQwtPlotXaxisPanner(QWidget *canvas)
|
||
: QwtPlotPanner(canvas)
|
||
{
|
||
}
|
||
|
||
CustomQwtPlotXaxisPanner::~CustomQwtPlotXaxisPanner()
|
||
{
|
||
qDebug() << "~CustomQwtPlotXaxisPanner";
|
||
}
|
||
|
||
void CustomQwtPlotXaxisPanner::moveCanvas(int dx, int dy)
|
||
{
|
||
QwtPlotPanner::moveCanvas(dx, 0);
|
||
}
|
||
|
||
CustomQwtPlotXaxisMagnifier::CustomQwtPlotXaxisMagnifier(QWidget *canvas)
|
||
: QwtPlotMagnifier(canvas)
|
||
{
|
||
}
|
||
|
||
CustomQwtPlotXaxisMagnifier::~CustomQwtPlotXaxisMagnifier()
|
||
{
|
||
qDebug() << "~CustomQwtPlotXaxisMagnifier";
|
||
}
|
||
|
||
void CustomQwtPlotXaxisMagnifier::rescale(double factor)
|
||
{
|
||
factor = qBound(0.1, factor, 10.0);
|
||
|
||
QwtScaleMap x_map = plot()->canvasMap(QwtPlot::xBottom);
|
||
double center = x_map.invTransform(plot()->canvas()->width() / 2);
|
||
plot()->setAxisScale(
|
||
QwtPlot::xBottom,
|
||
center - (center - x_map.s1()) * factor,
|
||
center + (x_map.s2() - center) * factor
|
||
);
|
||
plot()->replot();
|
||
}
|
||
|
||
CustomQwtPlotXaxisSelector::CustomQwtPlotXaxisSelector(QWidget *canvas)
|
||
: QwtPlotPicker(QwtPlot::xBottom, QwtPlot::yLeft,
|
||
QwtPicker::NoRubberBand,
|
||
QwtPicker::AlwaysOn,
|
||
canvas)
|
||
, _min_x(0.0)
|
||
, _max_x(0.0)
|
||
{
|
||
this->setStateMachine(new QwtPickerDragRectMachine());
|
||
this->setRubberBandPen(QPen(Qt::NoPen));
|
||
|
||
_overlay = new QwtPlotShapeItem;
|
||
_overlay->setRenderHint(QwtPlotItem::RenderAntialiased);
|
||
_overlay->setPen(QPen(Qt::red, 2));
|
||
_overlay->setBrush(QBrush(QColor(0, 120, 210, 50)));
|
||
_overlay->setZ(100);
|
||
_overlay->attach(plot());
|
||
_overlay->setVisible(false);
|
||
}
|
||
|
||
CustomQwtPlotXaxisSelector::~CustomQwtPlotXaxisSelector()
|
||
{
|
||
qDebug() << "~CustomQwtPlotXaxisSelector";
|
||
}
|
||
|
||
|
||
double CustomQwtPlotXaxisSelector::selectedMinX() const
|
||
{
|
||
return _min_x;
|
||
}
|
||
|
||
double CustomQwtPlotXaxisSelector::selectedMaxX() const
|
||
{
|
||
return _max_x;
|
||
}
|
||
|
||
void CustomQwtPlotXaxisSelector::clearSelection()
|
||
{
|
||
_min_x = 0.0;
|
||
_max_x = 0.0;
|
||
if (_overlay)
|
||
_overlay->setVisible(false);
|
||
}
|
||
|
||
void CustomQwtPlotXaxisSelector::move(const QPoint & pos)
|
||
{
|
||
QPolygon points = pickedPoints();
|
||
if (points.size() < 2) {
|
||
clearSelection();
|
||
return QwtPlotPicker::move(pos);
|
||
}
|
||
|
||
QPointF p1 = invTransform(points.first());
|
||
QPointF p2 = invTransform(points.last());
|
||
|
||
// 计算X范围
|
||
_min_x = qMin(p1.x(), p2.x());
|
||
_max_x = qMax(p1.x(), p2.x());
|
||
|
||
// Y轴铺满
|
||
double y_min = plot()->axisScaleDiv(QwtPlot::yLeft).lowerBound();
|
||
double y_max = plot()->axisScaleDiv(QwtPlot::yLeft).upperBound();
|
||
QRectF area(_min_x, y_min, _max_x - _min_x, y_max - y_min);
|
||
|
||
_overlay->setRect(area);
|
||
_overlay->setVisible(true);
|
||
plot()->replot();
|
||
|
||
QwtPlotPicker::move(pos);
|
||
}
|
||
|
||
bool CustomQwtPlotXaxisSelector::end(bool ok)
|
||
{
|
||
if (!ok) {
|
||
clearSelection();
|
||
return QwtPlotPicker::end(ok);
|
||
}
|
||
if (_overlay->isVisible()) {
|
||
_overlay->setVisible(false);
|
||
plot()->replot();
|
||
}
|
||
if ( (_max_x - _min_x) > 3 ) {
|
||
emit selectionFinished(_min_x, _max_x);
|
||
}
|
||
return QwtPlotPicker::end(ok);
|
||
}
|
||
|
||
|
||
QColor getDistinctColorForManyCurves(int curve_index)
|
||
{
|
||
// 1. 定义基础色相(覆盖不同主色系,0-360度)
|
||
const QList<int> base_hues = {
|
||
0, // 红色
|
||
30, // 橙色
|
||
60, // 黄色
|
||
90, // 黄绿色
|
||
120, // 绿色
|
||
150, // 青绿色
|
||
180, // 青色
|
||
210, // 天蓝色
|
||
240, // 蓝色
|
||
270, // 紫色
|
||
300, // 洋红色
|
||
330 // 玫红色
|
||
};
|
||
|
||
// 2. 定义不同的饱和度/明度组合(避免颜色太暗/太灰)
|
||
const QList<QPair<int, int>> sv_combinations = {
|
||
{85, 90}, // 高饱和、高明度
|
||
{70, 85}, // 中高饱和、中高明度
|
||
{85, 75}, // 高饱和、中明度
|
||
{60, 80}, // 中饱和、中高明度
|
||
{75, 70}, // 中高饱和、中明度
|
||
{90, 80} // 极高饱和、中高明度
|
||
};
|
||
|
||
// 3. 计算当前曲线对应的色相和饱和度/明度
|
||
int hue_index = curve_index % base_hues.size(); // 循环使用基础色相
|
||
int sv_index = (curve_index / base_hues.size()) % sv_combinations.size(); // 循环使用饱和度/明度组合
|
||
|
||
// 4. 获取HSV参数(色相0-360,饱和度0-255,明度0-255)
|
||
int hue = base_hues[hue_index];
|
||
int saturation = sv_combinations[sv_index].first * 255 / 100; // 转换为0-255范围
|
||
int value = sv_combinations[sv_index].second * 255 / 100; // 转换为0-255范围
|
||
|
||
// 5. 生成并返回颜色(HSV转RGB)
|
||
QColor color;
|
||
color.setHsv(hue, saturation, value);
|
||
|
||
// 额外优化:避免极浅/极暗的颜色(保证曲线可见)
|
||
if (value < 50 * 255 / 100) {
|
||
value = 50 * 255 / 100;
|
||
color.setHsv(hue, saturation, value);
|
||
}
|
||
|
||
return color;
|
||
}
|
||
|