@ -1,5 +1,6 @@
# include "tools/cabana/chartswidget.h"
# include <QActionGroup>
# include <QApplication>
# include <QCompleter>
# include <QDialogButtonBox>
@ -183,7 +184,7 @@ void ChartsWidget::settingChanged() {
range_slider - > setRange ( 1 , settings . max_cached_minutes * 60 ) ;
for ( auto c : charts ) {
c - > setFixedHeight ( settings . chart_height ) ;
c - > setSeriesType ( settings . chart_series_type = = 0 ? QAbstractSeries : : SeriesTypeLine : QAbstractSeries : : SeriesTypeScatter ) ;
c - > setSeriesType ( ( SeriesType ) settings . chart_series_type ) ;
}
}
@ -309,7 +310,7 @@ bool ChartsWidget::eventFilter(QObject *obj, QEvent *event) {
// ChartView
ChartView : : ChartView ( QWidget * parent ) : QChartView ( nullptr , parent ) {
series_type = settings . chart_series_type = = 0 ? QAbstractSeries : : SeriesTypeLine : QAbstractSeries : : SeriesTypeScatter ;
series_type = ( SeriesType ) settings . chart_series_type ;
QChart * chart = new QChart ( ) ;
chart - > setBackgroundVisible ( false ) ;
axis_x = new QValueAxis ( this ) ;
@ -325,40 +326,54 @@ ChartView::ChartView(QWidget *parent) : QChartView(nullptr, parent) {
background - > setPen ( Qt : : NoPen ) ;
background - > setZValue ( chart - > zValue ( ) - 1 ) ;
move_icon = new QGraphicsPixmapItem ( utils : : icon ( " grip-horizontal " ) , chart ) ;
setChart ( chart ) ;
createToolButtons ( ) ;
setRenderHint ( QPainter : : Antialiasing ) ;
// TODO: enable zoomIn/seekTo in live streaming mode.
setRubberBand ( can - > liveStreaming ( ) ? QChartView : : NoRubberBand : QChartView : : HorizontalRubberBand ) ;
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 ) ;
}
void ChartView : : createToolButtons ( ) {
move_icon = new QGraphicsPixmapItem ( utils : : icon ( " grip-horizontal " ) , chart ( ) ) ;
move_icon - > setToolTip ( tr ( " Drag and drop to combine charts " ) ) ;
QToolButton * remove_btn = toolButton ( " x " , tr ( " Remove Chart " ) ) ;
close_btn_proxy = new QGraphicsProxyWidget ( chart ) ;
close_btn_proxy = new QGraphicsProxyWidget ( chart ( ) ) ;
close_btn_proxy - > setWidget ( remove_btn ) ;
close_btn_proxy - > setZValue ( chart - > zValue ( ) + 11 ) ;
close_btn_proxy - > setZValue ( chart ( ) - > zValue ( ) + 11 ) ;
QToolButton * manage_btn = toolButton ( " list " , " " ) ;
// series types
QMenu * menu = new QMenu ( this ) ;
line_series_action = menu - > addAction ( tr ( " Line " ) , [ this ] ( ) { setSeriesType ( QAbstractSeries : : SeriesTypeLine ) ; } ) ;
line_series_action - > setCheckable ( true ) ;
line_series_action - > setChecked ( series_type = = QAbstractSeries : : SeriesTypeLine ) ;
scatter_series_action = menu - > addAction ( tr ( " Scatter " ) , [ this ] ( ) { setSeriesType ( QAbstractSeries : : SeriesTypeScatter ) ; } ) ;
scatter_series_action - > setCheckable ( true ) ;
scatter_series_action - > setChecked ( series_type = = QAbstractSeries : : SeriesTypeScatter ) ;
auto change_series_group = new QActionGroup ( menu ) ;
change_series_group - > setExclusive ( true ) ;
QStringList types { tr ( " line " ) , tr ( " Step Line " ) , tr ( " Scatter " ) } ;
for ( int i = 0 ; i < types . size ( ) ; + + i ) {
QAction * act = new QAction ( types [ i ] , change_series_group ) ;
act - > setData ( i ) ;
act - > setCheckable ( true ) ;
act - > setChecked ( i = = ( int ) series_type ) ;
menu - > addAction ( act ) ;
}
menu - > addSeparator ( ) ;
menu - > addAction ( tr ( " Manage series " ) , this , & ChartView : : manageSeries ) ;
QToolButton * manage_btn = toolButton ( " list " , " " ) ;
manage_btn - > setMenu ( menu ) ;
manage_btn - > setPopupMode ( QToolButton : : InstantPopup ) ;
manage_btn_proxy = new QGraphicsProxyWidget ( chart ) ;
manage_btn_proxy = new QGraphicsProxyWidget ( chart ( ) ) ;
manage_btn_proxy - > setWidget ( manage_btn ) ;
manage_btn_proxy - > setZValue ( chart - > zValue ( ) + 11 ) ;
manage_btn_proxy - > setZValue ( chart ( ) - > zValue ( ) + 11 ) ;
setChart ( chart ) ;
setRenderHint ( QPainter : : Antialiasing ) ;
// TODO: enable zoomIn/seekTo in live streaming mode.
setRubberBand ( can - > liveStreaming ( ) ? QChartView : : NoRubberBand : QChartView : : HorizontalRubberBand ) ;
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 ( remove_btn , & QToolButton : : clicked , this , & ChartView : : remove ) ;
QObject : : connect ( change_series_group , & QActionGroup : : triggered , [ this ] ( QAction * action ) {
setSeriesType ( ( SeriesType ) action - > data ( ) . toInt ( ) ) ;
} ) ;
}
void ChartView : : addSeries ( const MessageId & msg_id , const Signal * sig ) {
@ -476,7 +491,7 @@ void ChartView::updateSeriesPoints() {
int num_points = std : : max < int > ( end - begin , 1 ) ;
int pixels_per_point = width ( ) / num_points ;
if ( series_type = = QAbstract Series: : SeriesType Scatter) {
if ( series_type = = SeriesType : : Scatter ) {
( ( QScatterSeries * ) s . series ) - > setMarkerSize ( std : : clamp ( pixels_per_point / 3 , 2 , 8 ) ) ;
} else {
s . series - > setPointsVisible ( pixels_per_point > 20 ) ;
@ -490,7 +505,9 @@ void ChartView::updateSeries(const Signal *sig, const std::vector<Event *> *even
if ( ! sig | | s . sig = = sig ) {
if ( clear ) {
s . vals . clear ( ) ;
s . step_vals . clear ( ) ;
s . vals . reserve ( settings . max_cached_minutes * 60 * 100 ) ; // [n]seconds * 100hz
s . step_vals . reserve ( settings . max_cached_minutes * 60 * 100 * 2 ) ;
s . last_value_mono_time = 0 ;
}
s . series - > setColor ( getColor ( s . sig ) ) ;
@ -498,6 +515,7 @@ void ChartView::updateSeries(const Signal *sig, const std::vector<Event *> *even
struct Chunk {
std : : vector < Event * > : : const_iterator first , second ;
QVector < QPointF > vals ;
QVector < QPointF > step_vals ;
} ;
// split into one minitue chunks
QVector < Chunk > chunks ;
@ -510,6 +528,7 @@ void ChartView::updateSeries(const Signal *sig, const std::vector<Event *> *even
QtConcurrent : : blockingMap ( chunks , [ & ] ( Chunk & chunk ) {
chunk . vals . reserve ( 60 * 100 ) ; // 100 hz
chunk . step_vals . reserve ( 60 * 100 * 2 ) ; // 100 hz
double route_start_time = can - > routeStartTime ( ) ;
for ( auto it = chunk . first ; it ! = chunk . second ; + + it ) {
if ( ( * it ) - > which = = cereal : : Event : : Which : : CAN ) {
@ -519,6 +538,10 @@ void ChartView::updateSeries(const Signal *sig, const std::vector<Event *> *even
double value = get_raw_value ( ( uint8_t * ) dat . begin ( ) , dat . size ( ) , * s . sig ) ;
double ts = ( ( * it ) - > mono_time / ( double ) 1e9 ) - route_start_time ; // seconds
chunk . vals . push_back ( { ts , value } ) ;
if ( ! chunk . step_vals . empty ( ) ) {
chunk . step_vals . push_back ( { ts , chunk . step_vals . back ( ) . y ( ) } ) ;
}
chunk . step_vals . push_back ( { ts , value } ) ;
}
}
}
@ -526,11 +549,12 @@ void ChartView::updateSeries(const Signal *sig, const std::vector<Event *> *even
} ) ;
for ( auto & c : chunks ) {
s . vals . append ( c . vals ) ;
s . step_vals . append ( c . step_vals ) ;
}
if ( events - > size ( ) ) {
s . last_value_mono_time = events - > back ( ) - > mono_time ;
}
s . series - > replace ( s . vals ) ;
s . series - > replace ( series_type = = SeriesType : : StepLine ? s . step_vals : s . vals ) ;
}
}
updateAxisY ( ) ;
@ -607,7 +631,7 @@ qreal ChartView::niceNumber(qreal x, bool ceiling) {
}
void ChartView : : leaveEvent ( QEvent * event ) {
track_pts . clear ( ) ;
clearTrackPoints ( ) ;
scene ( ) - > update ( ) ;
QChartView : : leaveEvent ( event ) ;
}
@ -663,26 +687,31 @@ void ChartView::mouseMoveEvent(QMouseEvent *ev) {
auto rubber = findChild < QRubberBand * > ( ) ;
bool is_zooming = rubber & & rubber - > isVisible ( ) ;
const auto plot_area = chart ( ) - > plotArea ( ) ;
track_pts . clear ( ) ;
clearTrackPoints ( ) ;
if ( ! is_zooming & & plot_area . contains ( ev - > pos ( ) ) ) {
track_pts . resize ( sigs . size ( ) ) ;
QStringList text_list ;
const double sec = chart ( ) - > mapToValue ( ev - > pos ( ) ) . x ( ) ;
for ( int i = 0 ; i < sigs . size ( ) ; + + i ) {
QString value = " -- " ;
qreal x = - 1 ;
for ( auto & s : sigs ) {
if ( ! s . series - > isVisible ( ) ) continue ;
// use reverse iterator to find last item <= sec.
auto it = std : : lower_bound ( sigs [ i ] . vals . rbegin ( ) , sigs [ i ] . vals . rend ( ) , sec , [ ] ( auto & p , double x ) { return p . x ( ) > x ; } ) ;
if ( it ! = sigs [ i ] . vals . rend ( ) & & it - > x ( ) > = axis_x - > min ( ) ) {
value = QString : : number ( it - > y ( ) ) ;
track_pts [ i ] = chart ( ) - > mapToPosition ( * it ) ;
auto it = std : : lower_bound ( s . vals . rbegin ( ) , s . vals . rend ( ) , sec , [ ] ( auto & p , double x ) { return p . x ( ) > x ; } ) ;
if ( it ! = s . vals . rend ( ) & & it - > x ( ) > = axis_x - > min ( ) ) {
s . track_pt = chart ( ) - > mapToPosition ( * it ) ;
x = std : : max ( x , s . track_pt . x ( ) ) ;
}
text_list . push_back ( QString ( " <span style= \" color:%1; \" >■ </span>%2: <b>%3</b> " ) . arg ( sigs [ i ] . series - > color ( ) . name ( ) , sigs [ i ] . sig - > name , value ) ) ;
text_list . push_back ( QString ( " <span style= \" color:%1; \" >■ </span>%2: <b>%3</b> " )
. arg ( s . series - > color ( ) . name ( ) , s . sig - > name ,
s . track_pt . isNull ( ) ? " -- " : QString : : number ( s . track_pt . y ( ) ) ) ) ;
}
if ( x < 0 ) {
x = ev - > pos ( ) . x ( ) ;
}
auto max = std : : max_element ( track_pts . begin ( ) , track_pts . end ( ) , [ ] ( auto & a , auto & b ) { return a . x ( ) < b . x ( ) ; } ) ;
auto pt = ( max = = track_pts . end ( ) ) ? ev - > pos ( ) : * max ;
text_list . push_front ( QString : : number ( chart ( ) - > mapToValue ( pt ) . x ( ) , ' f ' , 3 ) ) ;
QPointF tooltip_pt ( pt . x ( ) + 12 , plot_area . top ( ) - 20 ) ;
QToolTip : : showText ( mapToGlobal ( tooltip_pt . toPoint ( ) ) , pt . isNull ( ) ? " " : text_list . join ( " <br /> " ) , this , plot_area . toRect ( ) ) ;
text_list . push_front ( QString : : number ( chart ( ) - > mapToValue ( { x , 0 } ) . x ( ) , ' f ' , 3 ) ) ;
QPointF tooltip_pt ( x + 12 , plot_area . top ( ) - 20 ) ;
QToolTip : : showText ( mapToGlobal ( tooltip_pt . toPoint ( ) ) , text_list . join ( " <br /> " ) , this , plot_area . toRect ( ) ) ;
scene ( ) - > invalidate ( { } , QGraphicsScene : : ForegroundLayer ) ;
} else {
QToolTip : : hideText ( ) ;
@ -727,6 +756,7 @@ void ChartView::dropEvent(QDropEvent *event) {
}
void ChartView : : drawForeground ( QPainter * painter , const QRectF & rect ) {
// draw time line
qreal x = chart ( ) - > mapToPosition ( QPointF { cur_sec , 0 } ) . x ( ) ;
x = std : : clamp ( x , chart ( ) - > plotArea ( ) . left ( ) , chart ( ) - > plotArea ( ) . right ( ) ) ;
qreal y1 = chart ( ) - > plotArea ( ) . top ( ) - 2 ;
@ -734,18 +764,20 @@ void ChartView::drawForeground(QPainter *painter, const QRectF &rect) {
painter - > setPen ( QPen ( chart ( ) - > titleBrush ( ) . color ( ) , 2 ) ) ;
painter - > drawLine ( QPointF { x , y1 } , QPointF { x , y2 } ) ;
auto max = std : : max_element ( track_pts . begin ( ) , track_pts . end ( ) , [ ] ( auto & a , auto & b ) { return a . x ( ) < b . x ( ) ; } ) ;
if ( max ! = track_pts . end ( ) & & ! max - > isNull ( ) ) {
painter - > setPen ( QPen ( Qt : : darkGray , 1 , Qt : : DashLine ) ) ;
painter - > drawLine ( QPointF { max - > x ( ) , y1 } , QPointF { max - > x ( ) , y2 } ) ;
painter - > setPen ( Qt : : NoPen ) ;
for ( int i = 0 ; i < track_pts . size ( ) ; + + i ) {
if ( ! track_pts [ i ] . isNull ( ) & & i < sigs . size ( ) ) {
painter - > setBrush ( sigs [ i ] . series - > color ( ) . darker ( 125 ) ) ;
painter - > drawEllipse ( track_pts [ i ] , 5.5 , 5.5 ) ;
}
// draw track points
painter - > setPen ( Qt : : NoPen ) ;
qreal track_line_x = - 1 ;
for ( auto & s : sigs ) {
if ( ! s . track_pt . isNull ( ) & & s . series - > isVisible ( ) ) {
painter - > setBrush ( s . series - > color ( ) . darker ( 125 ) ) ;
painter - > drawEllipse ( s . track_pt , 5.5 , 5.5 ) ;
track_line_x = std : : max ( track_line_x , s . track_pt . x ( ) ) ;
}
}
if ( track_line_x > 0 ) {
painter - > setPen ( QPen ( Qt : : darkGray , 1 , Qt : : DashLine ) ) ;
painter - > drawLine ( QPointF { track_line_x , y1 } , QPointF { track_line_x , y2 } ) ;
}
// paint points. OpenGL mode lacks certain features (such as showing points)
painter - > setPen ( Qt : : NoPen ) ;
@ -761,11 +793,14 @@ void ChartView::drawForeground(QPainter *painter, const QRectF &rect) {
}
}
QXYSeries * ChartView : : createSeries ( QAbstractSeries : : SeriesType type , QColor color ) {
QXYSeries * ChartView : : createSeries ( SeriesType type , QColor color ) {
QXYSeries * series = nullptr ;
if ( type = = QAbstract Series: : SeriesType Line) {
if ( type = = SeriesType : : Line ) {
series = new QLineSeries ( this ) ;
chart ( ) - > legend ( ) - > setMarkerShape ( QLegend : : MarkerShapeRectangle ) ;
} else if ( type = = SeriesType : : StepLine ) {
series = new QLineSeries ( this ) ;
chart ( ) - > legend ( ) - > setMarkerShape ( QLegend : : MarkerShapeFromSeries ) ;
} else {
series = new QScatterSeries ( this ) ;
chart ( ) - > legend ( ) - > setMarkerShape ( QLegend : : MarkerShapeCircle ) ;
@ -786,9 +821,7 @@ QXYSeries *ChartView::createSeries(QAbstractSeries::SeriesType type, QColor colo
return series ;
}
void ChartView : : setSeriesType ( QAbstractSeries : : SeriesType type ) {
line_series_action - > setChecked ( type = = QAbstractSeries : : SeriesTypeLine ) ;
scatter_series_action - > setChecked ( type = = QAbstractSeries : : SeriesTypeScatter ) ;
void ChartView : : setSeriesType ( SeriesType type ) {
if ( type ! = series_type ) {
series_type = type ;
for ( auto & s : sigs ) {
@ -797,7 +830,7 @@ void ChartView::setSeriesType(QAbstractSeries::SeriesType type) {
}
for ( auto & s : sigs ) {
auto series = createSeries ( series_type , getColor ( s . sig ) ) ;
series - > replace ( s . vals ) ;
series - > replace ( series_type = = SeriesType : : StepLine ? s . step_vals : s . vals ) ;
s . series = series ;
}
updateSeriesPoints ( ) ;