@ -4,11 +4,9 @@
# include <QGraphicsLayout>
# include <QRubberBand>
# include <QTimer>
# include <QToolBar>
# include <QToolButton>
# include <QtCharts/QLineSeries>
# include <QtCharts/QValueAxis>
# include <QtConcurrent>
# include <QToolBar>
// ChartsWidget
@ -19,7 +17,7 @@ ChartsWidget::ChartsWidget(QWidget *parent) : QWidget(parent) {
// toolbar
QToolBar * toolbar = new QToolBar ( tr ( " Charts " ) , this ) ;
title_label = new QLabel ( ) ;
title_label - > setSizePolicy ( QSizePolicy : : Expanding , QSizePolicy : : Preferred ) ;
title_label - > setSizePolicy ( QSizePolicy : : Expanding , QSizePolicy : : Preferred ) ;
toolbar - > addWidget ( title_label ) ;
toolbar - > addWidget ( range_label = new QLabel ( ) ) ;
reset_zoom_btn = toolbar - > addAction ( " ⟲ " ) ;
@ -42,21 +40,10 @@ ChartsWidget::ChartsWidget(QWidget *parent) : QWidget(parent) {
main_layout - > addWidget ( charts_scroll ) ;
QObject : : connect ( dbc ( ) , & DBCManager : : DBCFileChanged , [ this ] ( ) { removeAll ( nullptr ) ; } ) ;
QObject : : connect ( dbc ( ) , & DBCManager : : signalRemoved , this , & ChartsWidget : : removeAll ) ;
QObject : : connect ( dbc ( ) , & DBCManager : : signalUpdated , this , & ChartsWidget : : signalUpdated ) ;
QObject : : connect ( dbc ( ) , & DBCManager : : msgRemoved , [ this ] ( uint32_t address ) {
for ( auto c : charts . toVector ( ) )
if ( DBCManager : : parseId ( c - > id ) . second = = address ) removeChart ( c ) ;
} ) ;
QObject : : connect ( dbc ( ) , & DBCManager : : msgUpdated , [ this ] ( uint32_t address ) {
for ( auto c : charts ) {
if ( DBCManager : : parseId ( c - > id ) . second = = address ) c - > updateTitle ( ) ;
}
} ) ;
QObject : : connect ( dbc ( ) , & DBCManager : : DBCFileChanged , this , & ChartsWidget : : removeAll ) ;
QObject : : connect ( can , & CANMessages : : eventsMerged , this , & ChartsWidget : : eventsMerged ) ;
QObject : : connect ( can , & CANMessages : : updated , this , & ChartsWidget : : updateState ) ;
QObject : : connect ( remove_all_btn , & QAction : : triggered , [ this ] ( ) { removeAll ( ) ; } ) ;
QObject : : connect ( remove_all_btn , & QAction : : triggered , this , & ChartsWidget : : removeAll ) ;
QObject : : connect ( reset_zoom_btn , & QAction : : triggered , this , & ChartsWidget : : zoomReset ) ;
QObject : : connect ( dock_btn , & QAction : : triggered , [ this ] ( ) {
emit dock ( ! docking ) ;
@ -81,8 +68,8 @@ void ChartsWidget::zoomIn(double min, double max) {
zoomed_range = { min , max } ;
is_zoomed = zoomed_range ! = display_range ;
updateToolBar ( ) ;
emit rangeChanged ( min , max , is_zoomed ) ;
updateState ( ) ;
emit rangeChanged ( min , max , is_zoomed ) ;
}
void ChartsWidget : : zoomReset ( ) {
@ -108,13 +95,13 @@ void ChartsWidget::updateState() {
if ( prev_range ! = display_range ) {
QFutureSynchronizer < void > future_synchronizer ;
for ( auto c : charts )
future_synchronizer . addFuture ( QtConcurrent : : run ( c , & ChartView : : updateSeries , display_range ) ) ;
future_synchronizer . addFuture ( QtConcurrent : : run ( c , & ChartView : : setEventsRange , display_range ) ) ;
}
}
const auto & range = is_zoomed ? zoomed_range : display_range ;
for ( auto c : charts ) {
c - > setRange ( range . first , range . second ) ;
c - > setDisplay Range ( range . first , range . second ) ;
c - > updateLineMarker ( current_sec ) ;
}
}
@ -128,49 +115,45 @@ void ChartsWidget::updateToolBar() {
dock_btn - > setToolTip ( docking ? tr ( " Undock charts " ) : tr ( " Dock charts " ) ) ;
}
void ChartsWidget : : showChart ( const QString & id , const Signal * sig , bool show ) {
auto it = std : : find_if ( charts . begin ( ) , charts . end ( ) , [ = ] ( auto c ) { return c - > id = = id & & c - > signal = = sig ; } ) ;
if ( it ! = charts . end ( ) ) {
if ( ! show ) removeChart ( ( * it ) ) ;
} else if ( show ) {
auto chart = new ChartView ( id , sig , this ) ;
chart - > updateSeries ( display_range ) ;
QObject : : connect ( chart , & ChartView : : remove , [ = ] ( ) { removeChart ( chart ) ; } ) ;
QObject : : connect ( chart , & ChartView : : zoomIn , this , & ChartsWidget : : zoomIn ) ;
QObject : : connect ( chart , & ChartView : : zoomReset , this , & ChartsWidget : : zoomReset ) ;
charts_layout - > insertWidget ( 0 , chart ) ;
charts . push_back ( chart ) ;
emit chartOpened ( chart - > id , chart - > signal ) ;
ChartView * ChartsWidget : : findChart ( const QString & id , const Signal * sig ) {
for ( auto c : charts )
if ( c - > hasSeries ( id , sig ) ) return c ;
return nullptr ;
}
void ChartsWidget : : showChart ( const QString & id , const Signal * sig , bool show , bool merge ) {
if ( ! show ) {
if ( ChartView * chart = findChart ( id , sig ) ) {
chart - > removeSeries ( id , sig ) ;
}
} else {
ChartView * chart = merge & & charts . size ( ) > 0 ? charts . back ( ) : nullptr ;
if ( ! chart ) {
chart = new ChartView ( this ) ;
chart - > setEventsRange ( display_range ) ;
QObject : : connect ( chart , & ChartView : : remove , [ = ] ( ) { removeChart ( chart ) ; } ) ;
QObject : : connect ( chart , & ChartView : : zoomIn , this , & ChartsWidget : : zoomIn ) ;
QObject : : connect ( chart , & ChartView : : zoomReset , this , & ChartsWidget : : zoomReset ) ;
QObject : : connect ( chart , & ChartView : : seriesRemoved , this , & ChartsWidget : : chartClosed ) ;
charts_layout - > insertWidget ( 0 , chart ) ;
charts . push_back ( chart ) ;
}
chart - > addSeries ( id , sig ) ;
emit chartOpened ( id , sig ) ;
updateState ( ) ;
}
updateToolBar ( ) ;
}
bool ChartsWidget : : isChartOpened ( const QString & id , const Signal * sig ) {
auto it = std : : find_if ( charts . begin ( ) , charts . end ( ) , [ = ] ( auto c ) { return c - > id = = id & & c - > signal = = sig ; } ) ;
return it ! = charts . end ( ) ;
}
void ChartsWidget : : removeChart ( ChartView * chart ) {
charts . removeOne ( chart ) ;
chart - > deleteLater ( ) ;
updateToolBar ( ) ;
emit chartClosed ( chart - > id , chart - > signal ) ;
}
void ChartsWidget : : removeAll ( const Signal * sig ) {
void ChartsWidget : : removeAll ( ) {
for ( auto c : charts . toVector ( ) )
if ( ! sig | | c - > signal = = sig ) removeChart ( c ) ;
}
void ChartsWidget : : signalUpdated ( const Signal * sig ) {
for ( auto c : charts ) {
if ( c - > signal = = sig ) {
c - > updateTitle ( ) ;
c - > updateSeries ( display_range ) ;
c - > setRange ( display_range . first , display_range . second , true ) ;
}
}
removeChart ( c ) ;
}
bool ChartsWidget : : eventFilter ( QObject * obj , QEvent * event ) {
@ -183,14 +166,14 @@ bool ChartsWidget::eventFilter(QObject *obj, QEvent *event) {
// ChartView
ChartView : : ChartView ( const QString & id , const Signal * sig , QWidget * parent )
: id ( id ) , signal ( sig ) , QChartView ( nullptr , parent ) {
QLineSeries * series = new QLineSeries ( ) ;
ChartView : : ChartView ( QWidget * parent ) : QChartView ( nullptr , parent ) {
QChart * chart = new QChart ( ) ;
chart - > setBackgroundRoundness ( 0 ) ;
chart - > addSeries ( series ) ;
chart - > createDefaultAxes ( ) ;
chart - > legend ( ) - > hide ( ) ;
axis_x = new QValueAxis ( this ) ;
axis_y = new QValueAxis ( this ) ;
chart - > addAxis ( axis_x , Qt : : AlignBottom ) ;
chart - > addAxis ( axis_y , Qt : : AlignLeft ) ;
chart - > legend ( ) - > setShowToolTips ( true ) ;
chart - > layout ( ) - > setContentsMargins ( 0 , 0 , 0 , 0 ) ;
// top margin for title
chart - > setMargins ( { 0 , 11 , 0 , 0 } ) ;
@ -213,33 +196,108 @@ ChartView::ChartView(const QString &id, const Signal *sig, QWidget *parent)
remove_btn - > setToolTip ( tr ( " Remove Chart " ) ) ;
close_btn_proxy = new QGraphicsProxyWidget ( chart ) ;
close_btn_proxy - > setWidget ( remove_btn ) ;
close_btn_proxy - > setZValue ( chart - > zValue ( ) + 11 ) ;
setChart ( chart ) ;
setRenderHint ( QPainter : : Antialiasing ) ;
setRubberBand ( QChartView : : HorizontalRubberBand ) ;
updateFromSettings ( ) ;
updateTitle ( ) ;
QTimer * timer = new QTimer ( this ) ;
timer - > setInterval ( 100 ) ;
timer - > setSingleShot ( true ) ;
timer - > callOnTimeout ( this , & ChartView : : adjustChartMargins ) ;
QObject : : connect ( dbc ( ) , & DBCManager : : signalRemoved , this , & ChartView : : signalRemoved ) ;
QObject : : connect ( dbc ( ) , & DBCManager : : signalUpdated , this , & ChartView : : signalUpdated ) ;
QObject : : connect ( dbc ( ) , & DBCManager : : msgRemoved , this , & ChartView : : msgRemoved ) ;
QObject : : connect ( dbc ( ) , & DBCManager : : msgUpdated , this , & ChartView : : msgUpdated ) ;
QObject : : connect ( & settings , & Settings : : changed , this , & ChartView : : updateFromSettings ) ;
QObject : : connect ( remove_btn , & QToolButton : : clicked , [ = ] ( ) { emit remove ( id , sig ) ; } ) ;
QObject : : connect ( remove_btn , & QToolButton : : clicked , this , & ChartView : : remove ) ;
QObject : : connect ( chart , & QChart : : plotAreaChanged , [ = ] ( const QRectF & plotArea ) {
// use a singleshot timer to avoid recursion call.
timer - > start ( ) ;
} ) ;
}
ChartView : : ~ ChartView ( ) {
for ( auto & s : sigs )
emit seriesRemoved ( s . msg_id , s . sig ) ;
}
void ChartView : : addSeries ( const QString & msg_id , const Signal * sig ) {
QLineSeries * series = new QLineSeries ( this ) ;
chart ( ) - > addSeries ( series ) ;
series - > attachAxis ( axis_x ) ;
series - > attachAxis ( axis_y ) ;
auto [ source , address ] = DBCManager : : parseId ( msg_id ) ;
sigs . push_back ( { . msg_id = msg_id , . address = address , . source = source , . sig = sig , . series = series } ) ;
updateTitle ( ) ;
updateSeries ( sig ) ;
}
void ChartView : : removeSeries ( const QString & msg_id , const Signal * sig ) {
auto it = std : : find_if ( sigs . begin ( ) , sigs . end ( ) , [ & ] ( auto & s ) { return s . msg_id = = msg_id & & s . sig = = sig ; } ) ;
if ( it ! = sigs . end ( ) ) {
it = removeSeries ( it ) ;
}
}
bool ChartView : : hasSeries ( const QString & msg_id , const Signal * sig ) const {
auto it = std : : find_if ( sigs . begin ( ) , sigs . end ( ) , [ & ] ( auto & s ) { return s . msg_id = = msg_id & & s . sig = = sig ; } ) ;
return it ! = sigs . end ( ) ;
}
QList < ChartView : : SigItem > : : iterator ChartView : : removeSeries ( const QList < ChartView : : SigItem > : : iterator & it ) {
chart ( ) - > removeSeries ( it - > series ) ;
it - > series - > deleteLater ( ) ;
emit seriesRemoved ( it - > msg_id , it - > sig ) ;
auto ret = sigs . erase ( it ) ;
if ( ! sigs . isEmpty ( ) ) {
updateAxisY ( ) ;
} else {
emit remove ( ) ;
}
return ret ;
}
void ChartView : : signalUpdated ( const Signal * sig ) {
auto it = std : : find_if ( sigs . begin ( ) , sigs . end ( ) , [ = ] ( auto & s ) { return s . sig = = sig ; } ) ;
if ( it ! = sigs . end ( ) ) {
updateTitle ( ) ;
// TODO: don't update series if only name changed.
updateSeries ( sig ) ;
}
}
void ChartView : : signalRemoved ( const Signal * sig ) {
for ( auto it = sigs . begin ( ) ; it ! = sigs . end ( ) ; /**/ ) {
it = ( it - > sig = = sig ) ? removeSeries ( it ) : + + it ;
}
}
void ChartView : : msgUpdated ( uint32_t address ) {
auto it = std : : find_if ( sigs . begin ( ) , sigs . end ( ) , [ = ] ( auto & s ) { return s . address = = address ; } ) ;
if ( it ! = sigs . end ( ) )
updateTitle ( ) ;
}
void ChartView : : msgRemoved ( uint32_t address ) {
for ( auto it = sigs . begin ( ) ; it ! = sigs . end ( ) ; /**/ ) {
it = ( it - > address = = address ) ? removeSeries ( it ) : + + it ;
}
}
void ChartView : : resizeEvent ( QResizeEvent * event ) {
QChartView : : resizeEvent ( event ) ;
close_btn_proxy - > setPos ( event - > size ( ) . width ( ) - close_btn_proxy - > size ( ) . width ( ) - 11 , 8 ) ;
}
void ChartView : : updateTitle ( ) {
chart ( ) - > setTitle ( tr ( " <font color= \" gray \" text-align:left>%1 %2</font> <b>%3</b> " ) . arg ( dbc ( ) - > msg ( id ) - > name ) . arg ( id ) . arg ( signal - > name . c_str ( ) ) ) ;
for ( auto & s : sigs ) {
s . series - > setName ( QString ( " <b>%1</b> <font color= \" gray \" >%2 %3</font> " ) . arg ( s . sig - > name . c_str ( ) ) . arg ( msgName ( s . msg_id ) ) . arg ( s . msg_id ) ) ;
}
}
void ChartView : : updateFromSettings ( ) {
@ -249,9 +307,15 @@ void ChartView::updateFromSettings() {
line_marker - > setPen ( QPen ( color , 2 ) ) ;
}
void ChartView : : setRange ( double min , double max , bool force_update ) {
auto axis_x = dynamic_cast < QValueAxis * > ( chart ( ) - > axisX ( ) ) ;
if ( force_update | | ( min ! = axis_x - > min ( ) | | max ! = axis_x - > max ( ) ) ) {
void ChartView : : setEventsRange ( const std : : pair < double , double > & range ) {
if ( range ! = events_range ) {
events_range = range ;
updateSeries ( ) ;
}
}
void ChartView : : setDisplayRange ( double min , double max ) {
if ( min ! = axis_x - > min ( ) | | max ! = axis_x - > max ( ) ) {
axis_x - > setRange ( min , max ) ;
updateAxisY ( ) ;
}
@ -268,7 +332,6 @@ void ChartView::adjustChartMargins() {
}
void ChartView : : updateLineMarker ( double current_sec ) {
auto axis_x = dynamic_cast < QValueAxis * > ( chart ( ) - > axisX ( ) ) ;
int x = chart ( ) - > plotArea ( ) . left ( ) +
chart ( ) - > plotArea ( ) . width ( ) * ( current_sec - axis_x - > min ( ) ) / ( axis_x - > max ( ) - axis_x - > min ( ) ) ;
if ( int ( line_marker - > line ( ) . x1 ( ) ) ! = x ) {
@ -276,48 +339,72 @@ void ChartView::updateLineMarker(double current_sec) {
}
}
void ChartView : : updateSeries ( const std : : pair < double , double > range ) {
void ChartView : : updateSeries ( const Signal * sig ) {
auto events = can - > events ( ) ;
if ( ! events ) return ;
vals . clear ( ) ;
vals . reserve ( ( range . second - range . first ) * 1000 ) ; // [n]seconds * 1000hz
auto [ bus , address ] = DBCManager : : parseId ( id ) ;
double route_start_time = can - > routeStartTime ( ) ;
Event begin_event ( cereal : : Event : : Which : : INIT_DATA , ( route_start_time + range . first ) * 1e9 ) ;
auto begin = std : : lower_bound ( events - > begin ( ) , events - > end ( ) , & begin_event , Event : : lessThan ( ) ) ;
double end_ns = ( route_start_time + range . second ) * 1e9 ;
for ( auto it = begin ; it ! = events - > end ( ) & & ( * it ) - > mono_time < = end_ns ; + + it ) {
if ( ( * it ) - > which = = cereal : : Event : : Which : : CAN ) {
for ( const auto & c : ( * it ) - > event . getCan ( ) ) {
if ( bus = = c . getSrc ( ) & & address = = c . getAddress ( ) ) {
auto dat = c . getDat ( ) ;
double value = get_raw_value ( ( uint8_t * ) dat . begin ( ) , dat . size ( ) , * signal ) ;
double ts = ( ( * it ) - > mono_time / ( double ) 1e9 ) - route_start_time ; // seconds
vals . push_back ( { ts , value } ) ;
for ( int i = 0 ; i < sigs . size ( ) ; + + i ) {
if ( auto & s = sigs [ i ] ; ! sig | | s . sig = = sig ) {
s . vals . clear ( ) ;
s . vals . reserve ( ( events_range . second - events_range . first ) * 1000 ) ; // [n]seconds * 1000hz
s . min_y = std : : numeric_limits < double > : : max ( ) ;
s . max_y = std : : numeric_limits < double > : : lowest ( ) ;
double route_start_time = can - > routeStartTime ( ) ;
Event begin_event ( cereal : : Event : : Which : : INIT_DATA , ( route_start_time + events_range . first ) * 1e9 ) ;
auto begin = std : : lower_bound ( events - > begin ( ) , events - > end ( ) , & begin_event , Event : : lessThan ( ) ) ;
double end_ns = ( route_start_time + events_range . second ) * 1e9 ;
for ( auto it = begin ; it ! = events - > end ( ) & & ( * it ) - > mono_time < = end_ns ; + + it ) {
if ( ( * it ) - > which = = cereal : : Event : : Which : : CAN ) {
for ( const auto & c : ( * it ) - > event . getCan ( ) ) {
if ( s . source = = c . getSrc ( ) & & s . address = = c . getAddress ( ) ) {
auto dat = c . getDat ( ) ;
double value = get_raw_value ( ( uint8_t * ) dat . begin ( ) , dat . size ( ) , * s . sig ) ;
double ts = ( ( * it ) - > mono_time / ( double ) 1e9 ) - route_start_time ; // seconds
s . vals . push_back ( { ts , value } ) ;
if ( value < s . min_y ) s . min_y = value ;
if ( value > s . max_y ) s . max_y = value ;
}
}
}
}
QLineSeries * series = ( QLineSeries * ) chart ( ) - > series ( ) [ i ] ;
series - > replace ( s . vals ) ;
}
}
QLineSeries * series = ( QLineSeries * ) chart ( ) - > series ( ) [ 0 ] ;
series - > replace ( vals ) ;
updateAxisY ( ) ;
}
// auto zoom on yaxis
void ChartView : : updateAxisY ( ) {
const auto axis_x = dynamic_cast < QValueAxis * > ( chart ( ) - > axisX ( ) ) ;
const auto axis_y = dynamic_cast < QValueAxis * > ( chart ( ) - > axisY ( ) ) ;
auto begin = std : : lower_bound ( vals . begin ( ) , vals . end ( ) , axis_x - > min ( ) , [ ] ( auto & p , double x ) { return p . x ( ) < x ; } ) ;
if ( begin = = vals . end ( ) )
return ;
auto end = std : : upper_bound ( vals . begin ( ) , vals . end ( ) , axis_x - > max ( ) , [ ] ( double x , auto & p ) { return x < p . x ( ) ; } ) ;
const auto [ min , max ] = std : : minmax_element ( begin , end , [ ] ( auto & p1 , auto & p2 ) { return p1 . y ( ) < p2 . y ( ) ; } ) ;
if ( max - > y ( ) = = min - > y ( ) ) {
axis_y - > setRange ( min - > y ( ) - 1 , max - > y ( ) + 1 ) ;
double min_y = std : : numeric_limits < double > : : max ( ) ;
double max_y = std : : numeric_limits < double > : : lowest ( ) ;
if ( events_range = = std : : pair { axis_x - > min ( ) , axis_x - > max ( ) } ) {
for ( auto & s : sigs ) {
if ( s . min_y < min_y ) min_y = s . min_y ;
if ( s . max_y > max_y ) max_y = s . max_y ;
}
} else {
double range = max - > y ( ) - min - > y ( ) ;
axis_y - > setRange ( min - > y ( ) - range * 0.05 , max - > y ( ) + range * 0.05 ) ;
for ( auto & s : sigs ) {
auto begin = std : : lower_bound ( s . vals . begin ( ) , s . vals . end ( ) , axis_x - > min ( ) , [ ] ( auto & p , double x ) { return p . x ( ) < x ; } ) ;
if ( begin = = s . vals . end ( ) )
return ;
auto end = std : : upper_bound ( s . vals . begin ( ) , s . vals . end ( ) , axis_x - > max ( ) , [ ] ( double x , auto & p ) { return x < p . x ( ) ; } ) ;
const auto [ min , max ] = std : : minmax_element ( begin , end , [ ] ( auto & p1 , auto & p2 ) { return p1 . y ( ) < p2 . y ( ) ; } ) ;
if ( min - > y ( ) < min_y ) min_y = min - > y ( ) ;
if ( max - > y ( ) > max_y ) max_y = max - > y ( ) ;
}
}
if ( max_y = = min_y ) {
axis_y - > setRange ( min_y - 1 , max_y + 1 ) ;
} else {
double range = max_y - min_y ;
axis_y - > setRange ( min_y - range * 0.05 , max_y + range * 0.05 ) ;
axis_y - > applyNiceNumbers ( ) ;
}
}
@ -333,7 +420,6 @@ void ChartView::mouseReleaseEvent(QMouseEvent *event) {
rubber - > hide ( ) ;
QRectF rect = rubber - > geometry ( ) . normalized ( ) ;
rect . translate ( - chart ( ) - > plotArea ( ) . topLeft ( ) ) ;
const auto axis_x = dynamic_cast < QValueAxis * > ( chart ( ) - > axisX ( ) ) ;
double min = axis_x - > min ( ) + ( rect . left ( ) / chart ( ) - > plotArea ( ) . width ( ) ) * ( axis_x - > max ( ) - axis_x - > min ( ) ) ;
double max = axis_x - > min ( ) + ( rect . right ( ) / chart ( ) - > plotArea ( ) . width ( ) ) * ( axis_x - > max ( ) - axis_x - > min ( ) ) ;
if ( rubber - > width ( ) < = 0 ) {
@ -357,26 +443,40 @@ void ChartView::mouseReleaseEvent(QMouseEvent *event) {
void ChartView : : mouseMoveEvent ( QMouseEvent * ev ) {
auto rubber = findChild < QRubberBand * > ( ) ;
bool is_zooming = rubber & & rubber - > isVisible ( ) ;
if ( ! is_zooming ) {
const auto plot_area = chart ( ) - > plotArea ( ) ;
auto axis_x = dynamic_cast < QValueAxis * > ( chart ( ) - > axisX ( ) ) ;
const auto plot_area = chart ( ) - > plotArea ( ) ;
if ( ! is_zooming & & plot_area . contains ( ev - > pos ( ) ) ) {
double x = std : : clamp ( ( double ) ev - > pos ( ) . x ( ) , plot_area . left ( ) , plot_area . right ( ) - 1 ) ;
double sec = axis_x - > min ( ) + ( axis_x - > max ( ) - axis_x - > min ( ) ) * ( x - plot_area . left ( ) ) / plot_area . width ( ) ;
auto value = std : : upper_bound ( vals . begin ( ) , vals . end ( ) , sec , [ ] ( double x , auto & p ) { return x < p . x ( ) ; } ) ;
if ( value ! = vals . end ( ) ) {
QPointF pos = chart ( ) - > mapToPosition ( ( * value ) ) ;
QStringList text_list ;
QPointF pos = plot_area . bottomRight ( ) ;
double tm = 0.0 ;
for ( auto & s : sigs ) {
auto value = std : : upper_bound ( s . vals . begin ( ) , s . vals . end ( ) , sec , [ ] ( double x , auto & p ) { return x < p . x ( ) ; } ) ;
if ( value ! = s . vals . end ( ) ) {
text_list . push_back ( QString ( " %1 : %2 " ) . arg ( sigs . size ( ) > 1 ? s . sig - > name . c_str ( ) : " Value " ) . arg ( value - > y ( ) ) ) ;
tm = value - > x ( ) ;
auto y_pos = chart ( ) - > mapToPosition ( * value ) ;
if ( y_pos . y ( ) < pos . y ( ) ) pos = y_pos ;
}
}
if ( ! text_list . isEmpty ( ) ) {
value_text - > setHtml ( " <div style= \" background-color: darkGray;color: white; \" > Time: " +
QString : : number ( tm , ' f ' , 3 ) + " <br /> " + text_list . join ( " <br /> " ) + " </div> " ) ;
track_line - > setLine ( pos . x ( ) , plot_area . top ( ) , pos . x ( ) , plot_area . bottom ( ) ) ;
track_ellipse - > setRect ( pos . x ( ) - 5 , pos . y ( ) - 5 , 10 , 10 ) ;
value_text - > setHtml ( tr ( " <div style='background-color:darkGray'><font color='white'>%1, %2)</font></div> " )
. arg ( value - > x ( ) , 0 , ' f ' , 3 ) . arg ( value - > y ( ) ) ) ;
int text_x = pos . x ( ) + 8 ;
if ( ( text_x + value_text - > boundingRect ( ) . width ( ) ) > plot_area . right ( ) ) {
text_x = pos . x ( ) - value_text - > boundingRect ( ) . width ( ) - 8 ;
QRectF text_rect = value_text - > boundingRect ( ) ;
if ( ( text_x + text_rect . width ( ) ) > plot_area . right ( ) ) {
text_x = pos . x ( ) - text_rect . width ( ) - 8 ;
}
value_text - > setPos ( text_x , pos . y ( ) - 10 ) ;
value_text - > setPos ( text_x , pos . y ( ) - text_rect . height ( ) / 2 ) ;
track_ellipse - > setRect ( pos . x ( ) - 5 , pos . y ( ) - 5 , 10 , 10 ) ;
}
item_group - > setVisible ( value ! = vals . end ( ) ) ;
item_group - > setVisible ( ! text_list . isEmpty ( ) ) ;
} else {
item_group - > setVisible ( false ) ;
setViewportUpdateMode ( QGraphicsView : : FullViewportUpdate ) ;
}
QChartView : : mouseMoveEvent ( ev ) ;