@ -1,6 +1,7 @@
# include "tools/cabana/chartswidget.h"
# include <QGraphicsLayout>
# include <QGridLayout>
# include <QRubberBand>
# include <QTimer>
# include <QtCharts/QLineSeries>
@ -57,47 +58,85 @@ ChartsWidget::ChartsWidget(QWidget *parent) : QWidget(parent) {
QObject : : connect ( dbc ( ) , & DBCManager : : DBCFileChanged , [ this ] ( ) { removeAll ( nullptr ) ; } ) ;
QObject : : connect ( dbc ( ) , & DBCManager : : signalRemoved , this , & ChartsWidget : : removeAll ) ;
QObject : : connect ( dbc ( ) , & DBCManager : : signalUpdated , [ this ] ( const Signal * sig ) {
for ( auto chart : charts ) {
if ( chart - > signal = = sig ) {
chart - > chart_view - > updateSeries ( ) ;
}
}
} ) ;
QObject : : connect ( dbc ( ) , & DBCManager : : msgUpdated , [ this ] ( const QString & id ) {
for ( auto chart : charts ) {
if ( chart - > id = = id )
chart - > updateTitle ( ) ;
QObject : : connect ( dbc ( ) , & DBCManager : : signalUpdated , this , & ChartsWidget : : signalUpdated ) ;
QObject : : connect ( dbc ( ) , & DBCManager : : msgUpdated , [ this ] ( const QString & msg_id ) {
for ( auto c : charts ) {
if ( c - > id = = msg_id ) c - > updateTitle ( ) ;
}
} ) ;
QObject : : connect ( can , & CANMessages : : rangeChanged , [ this ] ( ) { updateTitleBar ( ) ; } ) ;
QObject : : connect ( reset_zoom_btn , & QPushButton : : clicked , can , & CANMessages : : resetRange ) ;
QObject : : connect ( can , & CANMessages : : eventsMerged , this , & ChartsWidget : : eventsMerged ) ;
QObject : : connect ( can , & CANMessages : : updated , this , & ChartsWidget : : updateState ) ;
QObject : : connect ( remove_all_btn , & QPushButton : : clicked , [ this ] ( ) { removeAll ( ) ; } ) ;
QObject : : connect ( reset_zoom_btn , & QPushButton : : clicked , this , & ChartsWidget : : zoomReset ) ;
QObject : : connect ( dock_btn , & QPushButton : : clicked , [ this ] ( ) {
emit dock ( ! docking ) ;
docking = ! docking ;
updateTitleBar ( ) ;
} ) ;
QObject : : connect ( & settings , & Settings : : changed , [ this ] ( ) {
for ( auto chart : charts ) {
chart - > setHeight ( settings . chart_height ) ;
}
void ChartsWidget : : eventsMerged ( ) {
if ( auto events = can - > events ( ) ; events & & ! events - > empty ( ) ) {
auto it = std : : find_if ( events - > begin ( ) , events - > end ( ) , [ = ] ( const Event * e ) { return e - > which = = cereal : : Event : : Which : : CAN ; } ) ;
event_range . first = it = = events - > end ( ) ? 0 : ( * it ) - > mono_time / ( double ) 1e9 - can - > routeStartTime ( ) ;
event_range . second = it = = events - > end ( ) ? 0 : events - > back ( ) - > mono_time / ( double ) 1e9 - can - > routeStartTime ( ) ;
if ( display_range . first = = 0 & & event_range . second = = 0 ) {
display_range . first = event_range . first ;
display_range . second = std : : min ( event_range . first + settings . max_chart_x_range , event_range . second ) ;
}
}
}
void ChartsWidget : : zoomIn ( double min , double max ) {
zoomed_range = { min , max } ;
is_zoomed = zoomed_range ! = display_range ;
updateTitleBar ( ) ;
emit rangeChanged ( min , max , is_zoomed ) ;
updateState ( ) ;
}
void ChartsWidget : : zoomReset ( ) {
zoomIn ( display_range . first , display_range . second ) ;
}
void ChartsWidget : : updateState ( ) {
if ( charts . isEmpty ( ) ) return ;
const double current_sec = can - > currentSec ( ) ;
if ( is_zoomed ) {
if ( current_sec < zoomed_range . first | | current_sec > = zoomed_range . second ) {
can - > seekTo ( zoomed_range . first ) ;
}
} else {
auto prev_range = display_range ;
if ( current_sec < display_range . first | | current_sec > = ( display_range . second - 5 ) ) {
// line marker reached the end, or seeked to a timestamp out of range.
display_range . first = current_sec - 5 ;
}
display_range . first = std : : max ( display_range . first , event_range . first ) ;
display_range . second = std : : min ( display_range . first + settings . max_chart_x_range , event_range . second ) ;
if ( prev_range ! = display_range ) {
for ( auto c : charts )
c - > chart_view - > updateSeries ( display_range ) ;
}
}
const auto & range = is_zoomed ? zoomed_range : display_range ;
for ( auto c : charts ) {
c - > chart_view - > setRange ( range . first , range . second ) ;
c - > chart_view - > updateLineMarker ( current_sec ) ;
}
} ) ;
}
void ChartsWidget : : updateTitleBar ( ) {
title_bar - > setVisible ( ! charts . isEmpty ( ) ) ;
if ( charts . isEmpty ( ) ) return ;
// show select range
range_label - > setVisible ( can - > isZoomed ( ) ) ;
reset_zoom_btn - > setEnabled ( can - > isZoomed ( ) ) ;
if ( can - > isZoomed ( ) ) {
auto [ min , max ] = can - > range ( ) ;
range_label - > setText ( tr ( " %1 - %2 " ) . arg ( min , 0 , ' f ' , 2 ) . arg ( max , 0 , ' f ' , 2 ) ) ;
range_label - > setVisible ( is_zoomed ) ;
reset_zoom_btn - > setEnabled ( is_zoomed ) ;
if ( is_zoomed ) {
range_label - > setText ( tr ( " %1 - %2 " ) . arg ( zoomed_range . first , 0 , ' f ' , 2 ) . arg ( zoomed_range . second , 0 , ' f ' , 2 ) ) ;
}
title_label - > setText ( tr ( " Charts (%1) " ) . arg ( charts . size ( ) ) ) ;
dock_btn - > setText ( docking ? " ⬈ " : " ⬋ " ) ;
dock_btn - > setToolTip ( docking ? tr ( " Undock charts " ) : tr ( " Dock charts " ) ) ;
@ -107,23 +146,19 @@ void ChartsWidget::addChart(const QString &id, const Signal *sig) {
auto it = std : : find_if ( charts . begin ( ) , charts . end ( ) , [ = ] ( auto c ) { return c - > id = = id & & c - > signal = = sig ; } ) ;
if ( it = = charts . end ( ) ) {
auto chart = new ChartWidget ( id , sig , this ) ;
QObject : : connect ( chart , & ChartWidget : : remove , this , & ChartsWidget : : removeChart ) ;
chart - > chart_view - > updateSeries ( display_range ) ;
QObject : : connect ( chart , & ChartWidget : : remove , [ = ] ( ) { removeChart ( chart ) ; } ) ; ;
QObject : : connect ( chart - > chart_view , & ChartView : : zoomIn , this , & ChartsWidget : : zoomIn ) ;
QObject : : connect ( chart - > chart_view , & ChartView : : zoomReset , this , & ChartsWidget : : zoomReset ) ;
charts_layout - > insertWidget ( 0 , chart ) ;
charts . push_back ( chart ) ;
}
updateTitleBar ( ) ;
}
void ChartsWidget : : removeChart ( const QString & msg_id , const Signal * sig ) {
QMutableListIterator < ChartWidget * > it ( charts ) ;
while ( it . hasNext ( ) ) {
auto c = it . next ( ) ;
if ( c - > id = = msg_id & & c - > signal = = sig ) {
c - > deleteLater ( ) ;
it . remove ( ) ;
break ;
}
}
void ChartsWidget : : removeChart ( ChartWidget * chart ) {
charts . removeOne ( chart ) ;
chart - > deleteLater ( ) ;
updateTitleBar ( ) ;
}
@ -139,6 +174,17 @@ void ChartsWidget::removeAll(const Signal *sig) {
updateTitleBar ( ) ;
}
void ChartsWidget : : signalUpdated ( const Signal * sig ) {
for ( auto c : charts ) {
if ( c - > signal = = sig ) {
c - > updateTitle ( ) ;
c - > chart_view - > updateSeries ( display_range ) ;
c - > chart_view - > setRange ( display_range . first , display_range . second , true ) ;
}
}
}
bool ChartsWidget : : eventFilter ( QObject * obj , QEvent * event ) {
if ( obj ! = this & & event - > type ( ) = = QEvent : : Close ) {
emit dock_btn - > clicked ( ) ;
@ -156,17 +202,19 @@ ChartWidget::ChartWidget(const QString &id, const Signal *sig, QWidget *parent)
QWidget * header = new QWidget ( this ) ;
header - > setStyleSheet ( " background-color:white " ) ;
QHBox Layout * header_layout = new QHBox Layout ( header ) ;
QGrid Layout * header_layout = new QGrid Layout ( header ) ;
header_layout - > setContentsMargins ( 11 , 11 , 11 , 0 ) ;
title = new QLabel ( tr ( " %1 %2 " ) . arg ( dbc ( ) - > msg ( id ) - > name . c_str ( ) ) . arg ( id ) ) ;
header_layout - > addWidget ( title ) ;
header_layout - > addStretch ( ) ;
msg_name_label = new QLabel ( this ) ;
msg_name_label - > setTextFormat ( Qt : : RichText ) ;
header_layout - > addWidget ( msg_name_label , 0 , 0 , Qt : : AlignLeft ) ;
sig_name_label = new QLabel ( this ) ;
sig_name_label - > setStyleSheet ( " font-weight:bold " ) ;
header_layout - > addWidget ( sig_name_label , 0 , 1 , Qt : : AlignCenter ) ; //, 0, Qt::AlignCenter);
QPushButton * remove_btn = new QPushButton ( " ✖ " , this ) ;
remove_btn - > setFixedSize ( 30 , 30 ) ;
remove_btn - > setFixedSize ( 20 , 2 0) ;
remove_btn - > setToolTip ( tr ( " Remove chart " ) ) ;
QObject : : connect ( remove_btn , & QPushButton : : clicked , [ = ] ( ) { emit remove ( id , sig ) ; } ) ;
header_layout - > addWidget ( remove_btn ) ;
header_layout - > addWidget ( remove_btn , 0 , 2 , Qt : : AlignRight ) ;
main_layout - > addWidget ( header ) ;
chart_view = new ChartView ( id , sig , this ) ;
@ -175,10 +223,15 @@ ChartWidget::ChartWidget(const QString &id, const Signal *sig, QWidget *parent)
main_layout - > addStretch ( ) ;
setSizePolicy ( QSizePolicy : : Expanding , QSizePolicy : : Fixed ) ;
updateTitle ( ) ;
QObject : : connect ( remove_btn , & QPushButton : : clicked , [ = ] ( ) { emit remove ( id , sig ) ; } ) ;
QObject : : connect ( & settings , & Settings : : changed , [ this ] ( ) { chart_view - > setFixedHeight ( settings . chart_height ) ; } ) ;
}
void ChartWidget : : updateTitle ( ) {
title - > setText ( tr ( " %1 %2 " ) . arg ( dbc ( ) - > msg ( id ) - > name . c_str ( ) ) . arg ( id ) ) ;
msg_name_label - > setText ( tr ( " %1 <font color= \" gray \" >%2</font> " ) . arg ( dbc ( ) - > msg ( id ) - > name . c_str ( ) ) . arg ( id ) ) ;
sig_name_label - > setText ( signal - > name . c_str ( ) ) ;
}
// ChartView
@ -186,15 +239,10 @@ void ChartWidget::updateTitle() {
ChartView : : ChartView ( const QString & id , const Signal * sig , QWidget * parent )
: id ( id ) , signal ( sig ) , QChartView ( nullptr , parent ) {
QLineSeries * series = new QLineSeries ( ) ;
series - > setUseOpenGL ( true ) ;
QChart * chart = new QChart ( ) ;
chart - > setTitle ( sig - > name . c_str ( ) ) ;
chart - > addSeries ( series ) ;
chart - > createDefaultAxes ( ) ;
chart - > legend ( ) - > hide ( ) ;
QFont font ;
font . setBold ( true ) ;
chart - > setTitleFont ( font ) ;
chart - > setMargins ( { 0 , 0 , 0 , 0 } ) ;
chart - > layout ( ) - > setContentsMargins ( 0 , 0 , 0 , 0 ) ;
@ -220,16 +268,18 @@ ChartView::ChartView(const QString &id, const Signal *sig, QWidget *parent)
timer - > setSingleShot ( true ) ;
timer - > callOnTimeout ( this , & ChartView : : adjustChartMargins ) ;
QObject : : connect ( can , & CANMessages : : updated , this , & ChartView : : updateState ) ;
QObject : : connect ( can , & CANMessages : : rangeChanged , this , & ChartView : : rangeChanged ) ;
QObject : : connect ( can , & CANMessages : : eventsMerged , this , & ChartView : : updateSeries ) ;
QObject : : connect ( dynamic_cast < QValueAxis * > ( chart - > axisX ( ) ) , & QValueAxis : : rangeChanged , can , & CANMessages : : setRange ) ;
QObject : : connect ( chart , & QChart : : plotAreaChanged , [ = ] ( const QRectF & plotArea ) {
// use a singleshot timer to avoid recursion call.
timer - > start ( ) ;
} ) ;
}
updateSeries ( ) ;
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 ( ) ) ) {
axis_x - > setRange ( min , max ) ;
updateAxisY ( ) ;
}
}
void ChartView : : adjustChartMargins ( ) {
@ -241,18 +291,17 @@ void ChartView::adjustChartMargins() {
}
}
void ChartWidget : : setHeight ( int height ) {
chart_view - > setFixedHeight ( height ) ;
}
void ChartView : : updateState ( ) {
void ChartView : : updateLineMarker ( double current_sec ) {
auto axis_x = dynamic_cast < QValueAxis * > ( chart ( ) - > axisX ( ) ) ;
int x = chart ( ) - > plotArea ( ) . left ( ) + chart ( ) - > plotArea ( ) . width ( ) * ( can - > currentSec ( ) - axis_x - > min ( ) ) / ( axis_x - > max ( ) - axis_x - > min ( ) ) ;
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 ) {
line_marker - > setLine ( x , 0 , x , height ( ) ) ;
chart ( ) - > update ( ) ;
}
}
void ChartView : : updateSeries ( ) {
chart ( ) - > setTitle ( signal - > name . c_str ( ) ) ;
void ChartView : : updateSeries ( const std : : pair < double , double > & range ) {
auto events = can - > events ( ) ;
if ( ! events ) return ;
@ -261,15 +310,18 @@ void ChartView::updateSeries() {
uint32_t address = l [ 1 ] . toUInt ( nullptr , 16 ) ;
vals . clear ( ) ;
vals . reserve ( 3 * 60 * 100 ) ;
uint64_t route_start_time = can - > routeStartTime ( ) ;
for ( auto & evt : * events ) {
if ( evt - > which = = cereal : : Event : : Which : : CAN ) {
for ( auto c : evt - > event . getCan ( ) ) {
vals . reserve ( ( range . second - range . first ) * 100 ) ; // [n]minutes * 100hz
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 ( 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 = ( evt - > mono_time / ( double ) 1e9 ) - route_start_time ; // seconds
double ts = ( ( * it ) - > mono_time / ( double ) 1e9 ) - route_start_time ; // seconds
vals . push_back ( { ts , value } ) ;
}
}
@ -277,40 +329,20 @@ void ChartView::updateSeries() {
}
QLineSeries * series = ( QLineSeries * ) chart ( ) - > series ( ) [ 0 ] ;
series - > replace ( vals ) ;
series - > setPointLabelsColor ( Qt : : black ) ;
auto [ begin , end ] = can - > range ( ) ;
chart ( ) - > axisX ( ) - > setRange ( begin , end ) ;
updateAxisY ( ) ;
}
void ChartView : : rangeChanged ( qreal min , qreal max ) {
auto axis_x = dynamic_cast < QValueAxis * > ( chart ( ) - > axisX ( ) ) ;
if ( axis_x - > min ( ) ! = min | | axis_x - > max ( ) ! = max ) {
axis_x - > setRange ( min , max ) ;
}
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 ( ) ) ;
// vals is a sorted list
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 ( min - > y ( ) = = max - > y ( ) ) {
if ( max - > y ( ) < 0 ) {
axis_y - > setRange ( max - > y ( ) , 0 ) ;
} else {
axis_y - > setRange ( 0 , max - > y ( ) = = 0 ? 1 : max - > y ( ) ) ;
}
} else {
axis_y - > setRange ( min - > y ( ) , max - > y ( ) ) ;
}
( min - > y ( ) = = max - > y ( ) ) ? axis_y - > setRange ( min - > y ( ) - 1 , max - > y ( ) + 1 )
: axis_y - > setRange ( min - > y ( ) , max - > y ( ) ) ;
}
void ChartView : : enterEvent ( QEvent * event ) {
@ -328,39 +360,35 @@ void ChartView::leaveEvent(QEvent *event) {
void ChartView : : mouseReleaseEvent ( QMouseEvent * event ) {
auto rubber = findChild < QRubberBand * > ( ) ;
if ( event - > button ( ) = = Qt : : LeftButton & & rubber & & rubber - > isVisible ( ) ) {
auto [ begin , end ] = can - > range ( ) ;
if ( rubber - > width ( ) < = 0 ) {
double seek_to = begin + ( ( event - > pos ( ) . x ( ) - chart ( ) - > plotArea ( ) . x ( ) ) / chart ( ) - > plotArea ( ) . width ( ) ) * ( end - begin ) ;
can - > seekTo ( seek_to ) ;
} else if ( ( ( double ) rubber - > width ( ) / chart ( ) - > plotArea ( ) . width ( ) ) * ( end - begin ) < 0.5 ) {
// don't zoom if selected range is less than 0.5s
rubber - > hide ( ) ;
event - > accept ( ) ;
return ;
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 ) {
// no rubber dragged, seek to mouse position
can - > seekTo ( min ) ;
} else if ( ( max - min ) > = 0.5 ) {
// zoom in if selected range is greater than 0.5s
emit zoomIn ( min , max ) ;
}
} else if ( event - > button ( ) = = Qt : : RightButton ) {
// reset zoom
if ( can - > isZoomed ( ) ) {
can - > resetRange ( ) ;
event - > accept ( ) ;
return ;
}
emit zoomReset ( ) ;
}
QChartView : : mouseReleaseEvent ( event ) ;
line_marker - > setVisible ( true ) ;
event - > accept ( ) ;
}
void ChartView : : mouseMoveEvent ( QMouseEvent * ev ) {
auto rubber = findChild < QRubberBand * > ( ) ;
bool show = ! ( rubber & & rubber - > isVisible ( ) ) ;
if ( show ) {
bool dragging = rubber & & rubber - > isVisible ( ) ;
if ( ! dragging ) {
const auto plot_area = chart ( ) - > plotArea ( ) ;
float x = std : : clamp ( ( float ) ev - > pos ( ) . x ( ) , ( float ) plot_area . left ( ) , ( float ) plot_area . right ( ) ) ;
track_line - > setLine ( x , plot_area . top ( ) , x , plot_area . bottom ( ) ) ;
auto [ begin , end ] = can - > range ( ) ;
double sec = begin + ( ( x - plot_area . x ( ) ) / plot_area . width ( ) ) * ( end - begin ) ;
auto axis_x = dynamic_cast < QValueAxis * > ( chart ( ) - > axisX ( ) ) ;
double sec = axis_x - > min ( ) + ( ( x - plot_area . x ( ) ) / plot_area . width ( ) ) * ( axis_x - > max ( ) - axis_x - > min ( ) ) ;
auto value = std : : lower_bound ( vals . begin ( ) , vals . end ( ) , sec , [ ] ( auto & p , double x ) { return p . x ( ) < x ; } ) ;
value_text - > setPos ( x + 6 , plot_area . bottom ( ) - 25 ) ;
if ( value ! = vals . end ( ) ) {
@ -369,9 +397,5 @@ void ChartView::mouseMoveEvent(QMouseEvent *ev) {
value_text - > setText ( " (--, --) " ) ;
}
}
value_text - > setVisible ( show ) ;
track_line - > setVisible ( show ) ;
line_marker - > setVisible ( show ) ;
QChartView : : mouseMoveEvent ( ev ) ;
}