test_sdk/gui/gtl_gui_chart.cpp

638 lines
21 KiB
C++
Raw Normal View History

#include "gtl_gui_chart.h"
#include "core/gtl_device.h"
namespace gtl
{
namespace gui
{
chart::chart(QWidget* parent, chart_axis_x *axis_x, chart_axis_y *axis_y)
: ::chart::widget(parent, axis_x == nullptr ? new chart_axis_x() : axis_x, axis_y == nullptr ? new chart_axis_y() : axis_y)
, _is_multi_y(false)
, _is_updating(true)
, _is_cyclic(true)
{
setBackgroundBrush(QBrush(Qt::white));
connect(static_cast<chart_axis_y*>(_axis_y), &chart_axis_y::get_series, this, &chart::get_axis_series);
setContextMenuPolicy(Qt::DefaultContextMenu);
_marker_series = new ::chart::series::xy::line(_axis_x, _axis_y);
scene()->addItem(_marker_series);
_menu = new QMenu(this);
_markers_menu = _menu->addMenu(tr("Markers"));
_marker_action_add = _markers_menu->addAction(tr("Add marker"));
connect(_marker_action_add, &QAction::triggered, this, &chart::add_marker);
_marker_action_remove = _markers_menu->addAction(tr("Remove marker"));
connect(_marker_action_remove, &QAction::triggered, this, &chart::delete_marker);
_marker_action_clear = _markers_menu->addAction(tr("Clear marker"));
connect(_marker_action_clear, &QAction::triggered, this, &chart::clear_markers);
_menu->addSeparator();
_set_theme_action = _menu->addAction("Set dark theme");
_set_theme_action->setCheckable(true);
connect(_set_theme_action, &QAction::toggled, this, &chart::set_theme);
_markers = new chart_markers(this);
_single_markers = new chart_single_markers(this);
}
chart::~chart()
{
delete _marker_series;
}
void chart::set_axis_y_mode(bool is_multi_y)
{
if(is_multi_y != _is_multi_y)
{
_is_multi_y = is_multi_y;
set_axes_y();
emit axis_y_mode_changed(_is_multi_y);
}
}
void chart::add_marker()
{
chart_marker* marker = new chart_marker(_marker_series, _set_theme_action->isChecked());
add(marker);
connect(marker, &chart_marker::get_nearest_x, this, &chart::get_neares_series_x);
connect(marker, &chart_marker::get_series_data, this, &chart::get_series_data);
connect(marker, &chart_marker::get_series_values, this, &chart::get_series_values);
connect(marker, &chart_marker::deleting, this, &chart::remove_marker);
marker->set_pos(_mouse_pos_release);
_markers->add_marker(marker);
_single_markers->add(marker);
}
void chart::remove_marker()
{
chart_marker* marker = static_cast<chart_marker*>(sender());
_markers->remove_marker(marker);
// _single_markers->remove(marker);
remove(marker);
}
void chart::clear_markers()
{
while(_markers->count_markers() != 0)
{
chart_marker* marker = _markers->marker(0);
_markers->remove_marker(marker);
// _single_markers->remove(marker);
remove(marker);
delete marker;
}
}
void chart::get_neares_series_x(qreal &x, chart_line::pos_behaviors behavior)
{
if(behavior == chart_line::magnetic_to_sample)
{
qreal neares_x = x;
qreal delta_min = qInf();
foreach(::chart::series::series *s, _series)
{
::chart::series::xy::xy *s_xy = static_cast<::chart::series::xy::xy*>(s);
if(s_xy->size() < 2)
continue;
auto it = std::lower_bound(s_xy->begin_data(), s_xy->end_data(), QPointF(x, 0), [](
const QPointF &p0, const QPointF &p1)
{
return p0.x() < p1.x();
}
);
int idx0 = std::min<int>(std::distance(s_xy->begin_data(), it), (int)s_xy->size() - 1);
int idx1 = std::max<int>(idx0 - 1, 0);
qreal delta = qAbs(x - s_xy->at_x(idx0));
if(delta < delta_min)
{
delta_min = delta;
neares_x = s_xy->at_x(idx0);
}
delta = qAbs(x - s_xy->at_x(idx1));
if(delta < delta_min)
{
delta_min = delta;
neares_x = s_xy->at_x(idx1);
}
}
x = neares_x;
}
else{
qreal min = qInf(), max = -qInf();
for(auto s : _series)
{
::chart::series::xy::xy *s_xy = static_cast<::chart::series::xy::xy*>(s);
if(s_xy->empty())
continue;
if(s_xy->front_x() < min)
min = s_xy->front_x();
if(s_xy->back_x() > max)
max = s_xy->back_x();
}
if(min == qInf() || max == -qInf())
{
min = _axis_x->min_boundary();
max = _axis_x->max_boundary();
}
if(x < min)
x = min;
else if(x > max)
x = max;
}
}
void chart::get_series_data(qreal x, bool is_widget_pos, QVariantList &data)
{
foreach(::chart::series::series *s, _series)
{
::chart::series::xy::xy *s_xy = static_cast<::chart::series::xy::xy*>(s);
if(s_xy->size() < 2)
{
data.push_back(QVariant(qQNaN()));
data.push_back(QVariant(QColor(Qt::black)));
}
auto it = std::lower_bound(s_xy->begin_data(), s_xy->end_data(), QPointF(x, 0), [](
const QPointF &p0, const QPointF &p1)
{
return p0.x() < p1.x();
}
);
if(it == s_xy->end_data())
{
data.push_back(QVariant(qQNaN()));
data.push_back(QVariant(QColor(Qt::black)));
}
else if(it->x() == x)
{
if(is_widget_pos)
data.push_back(QVariant(s_xy->axis_y()->map_to_widget(it->y())));
else
data.push_back(QVariant(it->y()));
data.push_back(QVariant(s_xy->color()));
}
else
{
auto it_prev = std::prev(it);
qreal k = (it_prev->y() - it->y())/(it_prev->x() - it->x());
qreal y = k*x + it_prev->y() - k*it_prev->x();
if(is_widget_pos)
data.push_back(QVariant(s_xy->axis_y()->map_to_widget(y)));
else
data.push_back(QVariant(y));
data.push_back(QVariant(s_xy->color()));
/*
data.push_back(QVariant(qQNaN()));
data.push_back(QVariant(QColor(Qt::black)));
*/
}
}
}
void chart::get_series_values(qreal x_min, qreal x_max, int series_idx, QVariantList &data)
{
::chart::series::xy::xy *s_xy = static_cast<::chart::series::xy::xy*>(_series[series_idx]);
auto it_min = std::lower_bound(s_xy->begin_data(), s_xy->end_data(), QPointF(x_min, 0), [](
const QPointF &p0, const QPointF &p1)
{
return p0.x() < p1.x();
}
);
auto it_max = std::upper_bound(s_xy->begin_data(), s_xy->end_data(), QPointF(x_max, 0), [](
const QPointF &p0, const QPointF &p1)
{
return p0.x() < p1.x();
}
);
for(auto it = it_min; it != it_max; it++)
data.push_back(it->y());
}
void chart::delete_marker()
{
delete _marker_popup;
}
void chart::deleting_ad()
{
remove_ad(static_cast<gtl::analog_data*>(sender()));
}
void chart::get_axis_series(QList<chart_series*> &series)
{
for(auto s: _series)
{
if(s->axis_y() == sender())
series.push_back(static_cast<chart_series*>(s));
}
}
void chart::set_theme(bool is_dack)
{
if(is_dack)
{
setBackgroundBrush(QBrush(Qt::black));
_markers->set_bgnd_color(Qt::black);
_markers->set_color(Qt::white);
}
else
{
setBackgroundBrush(QBrush(Qt::white));
_markers->set_bgnd_color(Qt::white);
_markers->set_color(Qt::black);
}
static_cast<chart_axis_x*>(_axis_x)->set_theme(is_dack);
static_cast<chart_axis_y*>(_axis_y)->set_theme(is_dack);
foreach(::chart::series::series *s, _series)
static_cast<chart_axis_y*>(s->axis_y())->set_theme(is_dack);
}
void chart::device_recieved_data()
{
}
bool chart::is_axis_y_multi() const
{
return _is_multi_y;
}
bool chart::is_updating() const
{
return _is_updating;
}
bool chart::is_cyclic() const
{
return _is_cyclic;
}
void chart::set_cyclic(bool value)
{
if(_is_cyclic != value)
{
qDebug() << value;
_is_cyclic = value;
emit cyclic_changed(value);
}
}
chart_markers *chart::chart_markers_model() const
{
return _markers;
}
void chart::save(QDomElement &root_element)
{
root_element.setAttribute("is_updating", _is_updating);
root_element.setAttribute("is_cyclic", _is_cyclic);
root_element.setAttribute("is_multi_y", _is_multi_y);
QDomElement axes_element = root_element.ownerDocument().createElement("axes");
root_element.appendChild(axes_element);
if(_is_multi_y)
{
for(int i = 0; i < (int)_series.size(); i++)
{
QDomElement axis_element = root_element.ownerDocument().createElement("axis");
axes_element.appendChild(axis_element);
static_cast<gtl::gui::chart_axis_y*>(_series[i]->axis_y())->save(axis_element);
}
}
else
{
QDomElement axis_element = root_element.ownerDocument().createElement("axis");
axes_element.appendChild(axis_element);
static_cast<gtl::gui::chart_axis_y*>(_axis_y)->save(axis_element);
}
}
void chart::load(const QDomElement &root_element)
{
set_updating(root_element.attribute("is_updating", "1").toInt() == 1);
set_cyclic(root_element.attribute("is_cyclic", "1").toInt() == 1);
set_axis_y_mode(root_element.attribute("is_multi_y", "0").toInt() == 1);
QDomElement axes_element = root_element.firstChildElement("axes");
QDomElement axis_element = axes_element.firstChildElement("axis");
if(_is_multi_y)
{
for(int i = 0; i < (int)_series.size(); i++)
{
static_cast<gtl::gui::chart_axis_y*>(_series[i]->axis_y())->load(axis_element);
axis_element = axis_element.nextSiblingElement("axis");
}
}
else
{
static_cast<gtl::gui::chart_axis_y*>(_axis_y)->load(axis_element);
}
}
bool chart::is_multi_y() const
{
return _is_multi_y;
}
void chart::set_background(QColor color)
{
setBackgroundBrush(QBrush(color));
_markers->set_bgnd_color(color);
}
chart_single_markers *chart::single_markers() const
{
return _single_markers;
}
void chart::dragEnterEvent(QDragEnterEvent *event)
{
event->ignore();
}
void gtl::gui::chart::add_series(chart_series *series)
{
add(series);
_markers->add_series(series);
set_axes_y();
set_bounds_x();
if(_series.size() == 1)
_axis_x->set_range();
}
void chart::add_ad(analog_data *ad)
{
chart_series *series = create_series(ad);
gtl::data_model_node* device = ad->root();
if(_devices.find(device) == _devices.end())
connect(static_cast<gtl::device*>(device), &gtl::device::recieved_data, this, &chart::device_recieved_data);
_devices[device].push_back(series);
connect(ad, &gtl::data_model_node::deleting, this, &chart::deleting_ad);
add_series(series);
}
void gtl::gui::chart::remove_series(chart_series *series)
{
if(_is_multi_y)
{
::chart::axis* axis = series->axis_y();
series->set_axis_y(_axis_y);
remove(axis);
delete axis;
}
_markers->remove_series(static_cast<chart_series*>(series));
remove(series);
set_axes_y();
set_bounds_x();
}
void chart::remove_series()
{
while(!_series.empty())
{
gtl::gui::chart_series* series = static_cast<gtl::gui::chart_series*>(_series.front());
remove_series(series);
delete series;
}
_series.clear();
}
void chart::remove_ad(analog_data *ad)
{
for(int i = 0; i < (int)_series.size(); i++)
{
if(static_cast<chart_series*>(_series[i])->ad() == ad)
{
gtl::data_model_node* device = ad->root();
_devices[device].remove(static_cast<chart_series*>(_series[i]));
if(_devices[device].empty())
{
disconnect(static_cast<gtl::device*>(device), &gtl::device::recieved_data, this, &chart::device_recieved_data);
_devices.erase(device);
}
chart_series *series = static_cast<chart_series*>(_series[i]);
remove_series(series);
delete series;
break;
}
}
}
void chart::set_updating(bool value)
{
if(_is_updating != value)
{
for(std::vector<::chart::series::series*>::iterator iter_series = _series.begin(); iter_series != _series.end(); iter_series++)
static_cast<chart_series*>(*iter_series)->set_updating(value);
_is_updating = value;
emit updating_changed(value);
}
}
void chart::set_bounds_x()
{
qreal min = qInf(), max = -qInf();
for(int i = 0; i < _series.size(); i++)
{
::chart::series::xy::xy* s = static_cast<::chart::series::xy::xy*>(_series[i]);
if(s->empty())
continue;
min = std::min<qreal>(min, s->front_x());
max = std::max<qreal>(max, s->back_x());
}
if((min == qInf() && max == -qInf()) || (min == max))
_axis_x->set_boundaries(0, 100);
else
_axis_x->set_boundaries(min, max);
}
void chart::contextMenuEvent(QContextMenuEvent *event)
{
if(_mouse_pos_press != _mouse_pos_release)
return;
_marker_popup = _markers->marker_at(event->pos());
_marker_action_remove->setVisible(_marker_popup != NULL);
_marker_action_clear->setVisible(_markers->count_markers() != 0);
_set_theme_action->setText(_set_theme_action->isChecked() ? tr("Set light theme") : tr("Set dark theme"));
_set_theme_action->setIcon(_set_theme_action->isChecked() ? QIcon(":chart/light_theme") : QIcon(":chart/dark_theme"));
_menu->popup(event->globalPos());
}
void chart::set_axes_y()
{
if(!_is_multi_y)
{
for(int i = 0; i < _series.size(); i++)
{
::chart::axis* axis = _series[i]->axis_y();
if(axis != _axis_y)
{
_series[i]->set_axis_y(_axis_y);
remove(axis);
delete axis;
}
}
_axis_y->setVisible(true);
fit_axis(_axis_y);
}
else
{
for(int i = 0; i < _series.size(); i++)
{
::chart::axis* axis = _series[i]->axis_y();
if(axis == _axis_y)
{
chart_axis_y* axis = create_axis_y();
connect(axis, &chart_axis_y::get_series, this, &chart::get_axis_series);
_series[i]->set_axis_y(axis);
add(axis);
fit_axis(axis);
}
_series[i]->axis_y()->set_pos(i / (qreal)_series.size(), (i + 1) / (qreal)_series.size());
}
_axis_y->setVisible(false);
}
}
void chart::mousePressEvent(QMouseEvent *event)
{
_mouse_pos_press = event->pos();
chart::widget::mousePressEvent(event);
}
void chart::mouseReleaseEvent(QMouseEvent *event)
{
_mouse_pos_release = event->pos();
chart::widget::mouseReleaseEvent(event);
}
void chart::mouseDoubleClickEvent(QMouseEvent *event)
{
if(event->button() == Qt::LeftButton)
{
QPointF pos = mapToScene(event->pos());
for(std::vector<::chart::axis*>::iterator iter_axis = _axes.begin(); iter_axis != _axes.end(); iter_axis++)
{
QRectF rect_axis = (*iter_axis)->boundingRect();
if(rect_axis.contains(pos))
{
if(_is_multi_y)
{
if((*iter_axis)->orient() == ::chart::axis::vert)
{
fit_axis(*iter_axis);
auto series_iterator = std::find_if(_series.begin(), _series.end(), [=](::chart::series::series* series){return series->axis_y() == *iter_axis;});
if(series_iterator != _series.end())
{
(*series_iterator)->axis_x()->set_range(static_cast<chart_series*>((*series_iterator))->left(), static_cast<chart_series*>((*series_iterator))->right());
}
}
}
else
{
fit_axis(*iter_axis);
}
}
}
}
}
void chart::mouseMoveEvent(QMouseEvent *event)
{
if(!_tool_zooming->isVisible())
{
for(auto it : _series)
static_cast<chart_series*>(it)->set_tool_tip(event->pos());
}
chart::widget::mouseMoveEvent(event);
}
chart_axis_y *chart::create_axis_y() const
{
chart_axis_y* axis = new chart_axis_y();
axis->set_theme(_set_theme_action->isChecked());
return axis;
}
}
}