#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(_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(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(std::distance(s_xy->begin_data(), it), (int)s_xy->size() - 1); int idx1 = std::max(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(sender())); } void chart::get_axis_series(QList &series) { for(auto s: _series) { if(s->axis_y() == sender()) series.push_back(static_cast(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(_axis_x)->set_theme(is_dack); static_cast(_axis_y)->set_theme(is_dack); foreach(::chart::series::series *s, _series) static_cast(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(_series[i]->axis_y())->save(axis_element); } } else { QDomElement axis_element = root_element.ownerDocument().createElement("axis"); axes_element.appendChild(axis_element); static_cast(_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(_series[i]->axis_y())->load(axis_element); axis_element = axis_element.nextSiblingElement("axis"); } } else { static_cast(_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(device), >l::device::recieved_data, this, &chart::device_recieved_data); _devices[device].push_back(series); connect(ad, >l::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(series)); remove(series); set_axes_y(); set_bounds_x(); } void chart::remove_series() { while(!_series.empty()) { gtl::gui::chart_series* series = static_cast(_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(_series[i])->ad() == ad) { gtl::data_model_node* device = ad->root(); _devices[device].remove(static_cast(_series[i])); if(_devices[device].empty()) { disconnect(static_cast(device), >l::device::recieved_data, this, &chart::device_recieved_data); _devices.erase(device); } chart_series *series = static_cast(_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(*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(min, s->front_x()); max = std::max(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((*series_iterator))->left(), static_cast((*series_iterator))->right()); } } } else { fit_axis(*iter_axis); } } } } } void chart::mouseMoveEvent(QMouseEvent *event) { if(!_tool_zooming->isVisible()) { for(auto it : _series) static_cast(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; } } }