KiCad PCB EDA Suite
Loading...
Searching...
No Matches
sim_plot_tab.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2016-2023 CERN
5 * Copyright (C) 2021-2023 KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * @author Tomasz Wlostowski <[email protected]>
8 * @author Maciej Suminski <[email protected]>
9 *
10 * This program is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU General Public License
12 * as published by the Free Software Foundation; either version 3
13 * of the License, or (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, you may find one here:
22 * https://www.gnu.org/licenses/gpl-3.0.html
23 * or you may search the http://www.gnu.org website for the version 3 license,
24 * or you may write to the Free Software Foundation, Inc.,
25 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
26 */
27
28#include "sim_plot_colors.h"
29#include "sim_plot_tab.h"
30#include "simulator_frame.h"
31#include "core/kicad_algo.h"
32
33#include <algorithm>
34#include <limits>
35
36
37static wxString formatFloat( double x, int nDigits )
38{
39 wxString rv, fmt;
40
41 if( nDigits )
42 fmt.Printf( "%%.0%df", nDigits );
43 else
44 fmt = wxT( "%.0f" );
45
46 rv.Printf( fmt, x );
47
48 return rv;
49}
50
51
52static void getSISuffix( double x, const wxString& unit, int& power, wxString& suffix )
53{
54 const int n_powers = 11;
55
56 const struct
57 {
58 int exponent;
59 char suffix;
60 } powers[] =
61 {
62 { -18, 'a' },
63 { -15, 'f' },
64 { -12, 'p' },
65 { -9, 'n' },
66 { -6, 'u' },
67 { -3, 'm' },
68 { 0, 0 },
69 { 3, 'k' },
70 { 6, 'M' },
71 { 9, 'G' },
72 { 12, 'T' },
73 { 14, 'P' }
74 };
75
76 power = 0;
77 suffix = unit;
78
79 if( x == 0.0 )
80 return;
81
82 for( int i = 0; i < n_powers - 1; i++ )
83 {
84 double r_cur = pow( 10, powers[i].exponent );
85
86 if( fabs( x ) >= r_cur && fabs( x ) < r_cur * 1000.0 )
87 {
88 power = powers[i].exponent;
89
90 if( powers[i].suffix )
91 suffix = wxString( powers[i].suffix ) + unit;
92 else
93 suffix = unit;
94
95 return;
96 }
97 }
98}
99
100
101static int countDecimalDigits( double x, int maxDigits )
102{
103 if( std::isnan( x ) )
104 return 0;
105
106 auto countSignificantDigits =
107 [&]( int64_t k )
108 {
109 while( k && ( k % 10LL ) == 0LL )
110 k /= 10LL;
111
112 int n = 0;
113
114 while( k != 0LL )
115 {
116 n++;
117 k /= 10LL;
118 }
119
120 return n;
121 };
122
123 int64_t k = (int)( ( x - floor( x ) ) * pow( 10.0, (double) maxDigits ) );
124 int n = countSignificantDigits( k );
125
126 // check for trailing 9's
127 n = std::min( n, countSignificantDigits( k + 1 ) );
128
129 return n;
130}
131
132
133template <typename T_PARENT>
134class LIN_SCALE : public T_PARENT
135{
136public:
137 LIN_SCALE( const wxString& name, const wxString& unit, int flags ) :
138 T_PARENT( name, flags, false ),
139 m_unit( unit )
140 {};
141
142 wxString GetUnits() const { return m_unit; }
143
144private:
145 void formatLabels() override
146 {
147 double maxVis = T_PARENT::AbsVisibleMaxValue();
148
149 wxString suffix;
150 int power = 0;
151 int digits = 0;
152 int constexpr MAX_DIGITS = 3;
153 int constexpr MAX_DISAMBIGUATION_DIGITS = 6;
154 bool duplicateLabels = false;
155
156 getSISuffix( maxVis, m_unit, power, suffix );
157
158 double sf = pow( 10.0, power );
159
160 for( mpScaleBase::TICK_LABEL& l : T_PARENT::m_tickLabels )
161 digits = std::max( digits, countDecimalDigits( l.pos / sf, MAX_DIGITS ) );
162
163 do
164 {
165 for( size_t ii = 0; ii < T_PARENT::m_tickLabels.size(); ++ii )
166 {
167 mpScaleBase::TICK_LABEL& l = T_PARENT::m_tickLabels[ii];
168
169 l.label = formatFloat( l.pos / sf, digits );
170 l.visible = true;
171
172 if( ii > 0 && l.label == T_PARENT::m_tickLabels[ii-1].label )
173 duplicateLabels = true;
174 }
175 }
176 while( duplicateLabels && ++digits <= MAX_DISAMBIGUATION_DIGITS );
177
178 if( m_base_axis_label.IsEmpty() )
179 m_base_axis_label = T_PARENT::GetName();
180
181 T_PARENT::SetName( wxString::Format( "%s (%s)", m_base_axis_label, suffix ) );
182 }
183
184private:
185 const wxString m_unit;
187};
188
189
190class TIME_SCALE : public LIN_SCALE<mpScaleX>
191{
192public:
193 TIME_SCALE( const wxString& name, const wxString& unit, int flags ) :
194 LIN_SCALE( name, unit, flags ),
195 m_startTime( 0.0 ),
196 m_endTime( 1.0 )
197 {};
198
199 void ExtendDataRange( double minV, double maxV ) override
200 {
201 LIN_SCALE::ExtendDataRange( minV, maxV );
202
203 // Time is never longer than the simulation itself
204 if( m_minV < m_startTime )
206
207 if( m_maxV > m_endTime )
209 };
210
211 void SetStartAndEnd( double aStartTime, double aEndTime )
212 {
213 m_startTime = aStartTime;
214 m_endTime = aEndTime;
216 }
217
218 void ResetDataRange() override
219 {
222 m_rangeSet = true;
223 }
224
225protected:
227 double m_endTime;
228};
229
230
231template <typename T_PARENT>
232class LOG_SCALE : public T_PARENT
233{
234public:
235 LOG_SCALE( const wxString& name, const wxString& unit, int flags ) :
236 T_PARENT( name, flags, false ),
237 m_unit( unit )
238 {};
239
240 wxString GetUnits() const { return m_unit; }
241
242private:
243 void formatLabels() override
244 {
245 wxString suffix;
246 int power;
247 int constexpr MAX_DIGITS = 3;
248
249 for( mpScaleBase::TICK_LABEL& l : T_PARENT::m_tickLabels )
250 {
251 getSISuffix( l.pos, m_unit, power, suffix );
252 double sf = pow( 10.0, power );
253 int k = countDecimalDigits( l.pos / sf, MAX_DIGITS );
254
255 l.label = formatFloat( l.pos / sf, k ) + suffix;
256 l.visible = true;
257 }
258 }
259
260private:
261 const wxString m_unit;
262};
263
264
265void CURSOR::SetCoordX( double aValue )
266{
267 wxRealPoint oldCoords = m_coords;
268
269 doSetCoordX( aValue );
270 m_updateRequired = false;
271 m_updateRef = true;
272
273 if( m_window )
274 {
275 wxRealPoint delta = m_coords - oldCoords;
276 mpInfoLayer::Move( wxPoint( m_window->x2p( m_trace->x2s( delta.x ) ),
277 m_window->y2p( m_trace->y2s( delta.y ) ) ) );
278
279 m_window->Refresh();
280 }
281}
282
283
284void CURSOR::doSetCoordX( double aValue )
285{
286 m_coords.x = aValue;
287
288 const std::vector<double>& dataX = m_trace->GetDataX();
289 const std::vector<double>& dataY = m_trace->GetDataY();
290
291 if( dataX.size() <= 1 )
292 return;
293
294 // Find the closest point coordinates
295 auto maxXIt = std::upper_bound( dataX.begin(), dataX.end(), m_coords.x );
296 int maxIdx = maxXIt - dataX.begin();
297 int minIdx = maxIdx - 1;
298
299 // Out of bounds checks
300 if( minIdx < 0 || maxIdx >= (int) dataX.size() )
301 {
302 // Simulation may not be complete yet, or we may have a cursor off the beginning or end
303 // of the data. Either way, that's where the user put it. Don't second guess them; just
304 // leave its y value undefined.
305 m_coords.y = NAN;
306 return;
307 }
308
309 const double leftX = dataX[minIdx];
310 const double rightX = dataX[maxIdx];
311 const double leftY = dataY[minIdx];
312 const double rightY = dataY[maxIdx];
313
314 // Linear interpolation
315 m_coords.y = leftY + ( rightY - leftY ) / ( rightX - leftX ) * ( m_coords.x - leftX );
316}
317
318
320{
321 for( const auto& [ id, cursor ] : m_trace->GetCursors() )
322 {
323 if( cursor == this )
324 return wxString::Format( _( "%d" ), id );
325 }
326
327 return wxEmptyString;
328}
329
330
331void CURSOR::Plot( wxDC& aDC, mpWindow& aWindow )
332{
333 if( !m_window )
334 m_window = &aWindow;
335
336 if( !m_visible || m_trace->GetDataX().size() <= 1 )
337 return;
338
339 if( m_updateRequired )
340 {
341 doSetCoordX( m_trace->s2x( aWindow.p2x( m_dim.x ) ) );
342 m_updateRequired = false;
343
344 // Notify the parent window about the changes
345 wxQueueEvent( aWindow.GetParent(), new wxCommandEvent( EVT_SIM_CURSOR_UPDATE ) );
346 }
347 else
348 {
349 m_updateRef = true;
350 }
351
352 if( m_updateRef )
353 {
355 m_updateRef = false;
356 }
357
358 // Line length in horizontal and vertical dimensions
359 const wxPoint cursorPos( aWindow.x2p( m_trace->x2s( m_coords.x ) ),
360 aWindow.y2p( m_trace->y2s( m_coords.y ) ) );
361
362 wxCoord leftPx = aWindow.GetMarginLeft();
363 wxCoord rightPx = aWindow.GetScrX() - aWindow.GetMarginRight();
364 wxCoord topPx = aWindow.GetMarginTop();
365 wxCoord bottomPx = aWindow.GetScrY() - aWindow.GetMarginBottom();
366
367 wxPen pen = GetPen();
368 wxColour fg = GetPen().GetColour();
369
370 pen.SetColour( COLOR4D( m_trace->GetTraceColour() ).Mix( fg, 0.6 ).ToColour() );
371 pen.SetStyle( m_continuous ? wxPENSTYLE_SOLID : wxPENSTYLE_LONG_DASH );
372 aDC.SetPen( pen );
373
374 if( topPx < cursorPos.y && cursorPos.y < bottomPx )
375 aDC.DrawLine( leftPx, cursorPos.y, rightPx, cursorPos.y );
376
377 if( leftPx < cursorPos.x && cursorPos.x < rightPx )
378 {
379 aDC.DrawLine( cursorPos.x, topPx, cursorPos.x, bottomPx );
380
381 wxString id = getID();
382 wxSize size = aDC.GetTextExtent( wxS( "M" ) );
383 wxRect textRect( wxPoint( cursorPos.x + 1 - size.x / 2, topPx - 4 - size.y ), size );
384 wxBrush brush;
385 wxPoint poly[3];
386
387 // Because a "1" looks off-center if it's actually centred.
388 if( id == "1" )
389 textRect.x -= 1;
390
391 // We want an equalateral triangle, so use size.y for both axes.
392 size.y += 3;
393 // Make sure it's an even number so the slopes of the sides will be identical.
394 size.y = ( size.y / 2 ) * 2;
395 poly[0] = { cursorPos.x - 1 - size.y / 2, topPx - size.y };
396 poly[1] = { cursorPos.x + 1 + size.y / 2, topPx - size.y };
397 poly[2] = { cursorPos.x, topPx };
398
399 brush.SetStyle( wxBRUSHSTYLE_SOLID );
400 brush.SetColour( m_trace->GetTraceColour() );
401 aDC.SetBrush( brush );
402 aDC.DrawPolygon( 3, poly );
403
404 aDC.SetTextForeground( fg );
405 aDC.DrawLabel( id, textRect, wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL );
406 }
407}
408
409
410bool CURSOR::Inside( const wxPoint& aPoint ) const
411{
412 if( !m_window || !m_trace )
413 return false;
414
415 return ( std::abs( (double) aPoint.x -
416 m_window->x2p( m_trace->x2s( m_coords.x ) ) ) <= DRAG_MARGIN )
417 || ( std::abs( (double) aPoint.y -
418 m_window->y2p( m_trace->y2s( m_coords.y ) ) ) <= DRAG_MARGIN );
419}
420
421
423{
424 if( !m_window )
425 return;
426
429}
430
431
432SIM_PLOT_TAB::SIM_PLOT_TAB( const wxString& aSimCommand, wxWindow* parent ) :
433 SIM_TAB( aSimCommand, parent ),
434 m_axis_x( nullptr ),
435 m_axis_y1( nullptr ),
436 m_axis_y2( nullptr ),
437 m_axis_y3( nullptr ),
438 m_dotted_cp( false )
439{
440 m_sizer = new wxBoxSizer( wxVERTICAL );
441 m_plotWin = new mpWindow( this, wxID_ANY );
442
443 m_plotWin->LimitView( true );
444 m_plotWin->SetMargins( 30, 70, 45, 70 );
446
447 updateAxes();
448
449 // a mpInfoLegend displays le name of traces on the left top panel corner:
450 m_legend = new mpInfoLegend( wxRect( 0, 0, 200, 40 ), wxTRANSPARENT_BRUSH );
451 m_legend->SetVisible( false );
454
457
458 m_sizer->Add( m_plotWin, 1, wxALL | wxEXPAND, 1 );
459 SetSizer( m_sizer );
460}
461
462
464{
465 // ~mpWindow destroys all the added layers, so there is no need to destroy m_traces contents
466}
467
468
469void SIM_PLOT_TAB::SetY1Scale( bool aLock, double aMin, double aMax )
470{
471 wxCHECK( m_axis_y1, /* void */ );
472 m_axis_y1->SetAxisMinMax( aLock, aMin, aMax );
473}
474
475
476void SIM_PLOT_TAB::SetY2Scale( bool aLock, double aMin, double aMax )
477{
478 wxCHECK( m_axis_y2, /* void */ );
479 m_axis_y2->SetAxisMinMax( aLock, aMin, aMax );
480}
481
482
483void SIM_PLOT_TAB::SetY3Scale( bool aLock, double aMin, double aMax )
484{
485 wxCHECK( m_axis_y3, /* void */ );
486 m_axis_y3->SetAxisMinMax( aLock, aMin, aMax );
487}
488
489
491{
492 LOG_SCALE<mpScaleXLog>* logScale = dynamic_cast<LOG_SCALE<mpScaleXLog>*>( m_axis_x );
493 LIN_SCALE<mpScaleX>* linScale = dynamic_cast<LIN_SCALE<mpScaleX>*>( m_axis_x );
494
495 if( logScale )
496 return logScale->GetUnits();
497 else if( linScale )
498 return linScale->GetUnits();
499 else
500 return wxEmptyString;
501}
502
503
505{
506 LIN_SCALE<mpScaleY>* linScale = dynamic_cast<LIN_SCALE<mpScaleY>*>( m_axis_y1 );
507
508 if( linScale )
509 return linScale->GetUnits();
510 else
511 return wxEmptyString;
512}
513
514
516{
517 LIN_SCALE<mpScaleY>* linScale = dynamic_cast<LIN_SCALE<mpScaleY>*>( m_axis_y2 );
518
519 if( linScale )
520 return linScale->GetUnits();
521 else
522 return wxEmptyString;
523}
524
525
527{
528 LIN_SCALE<mpScaleY>* linScale = dynamic_cast<LIN_SCALE<mpScaleY>*>( m_axis_y3 );
529
530 if( linScale )
531 return linScale->GetUnits();
532 else
533 return wxEmptyString;
534}
535
536
537void SIM_PLOT_TAB::updateAxes( int aNewTraceType )
538{
539 switch( GetSimType() )
540 {
541 case ST_AC:
542 if( !m_axis_x )
543 {
544 m_axis_x = new LOG_SCALE<mpScaleXLog>( wxEmptyString, wxT( "Hz" ), mpALIGN_BOTTOM );
547
548 m_axis_y1 = new LIN_SCALE<mpScaleY>( wxEmptyString, wxT( "dBV" ), mpALIGN_LEFT );
551
552 m_axis_y2 = new LIN_SCALE<mpScaleY>( wxEmptyString, wxT( "°" ), mpALIGN_RIGHT );
556 }
557
558 m_axis_x->SetName( _( "Frequency" ) );
559 m_axis_y1->SetName( _( "Gain" ) );
560 m_axis_y2->SetName( _( "Phase" ) );
561 break;
562
563 case ST_SP:
564 if( !m_axis_x )
565 {
566 m_axis_x = new LOG_SCALE<mpScaleXLog>( wxEmptyString, wxT( "Hz" ), mpALIGN_BOTTOM );
569
570 m_axis_y1 = new LIN_SCALE<mpScaleY>( wxEmptyString, wxT( "" ), mpALIGN_LEFT );
573
574 m_axis_y2 = new LIN_SCALE<mpScaleY>( wxEmptyString, wxT( "°" ), mpALIGN_RIGHT );
578 }
579
580 m_axis_x->SetName( _( "Frequency" ) );
581 m_axis_y1->SetName( _( "Amplitude" ) );
582 m_axis_y2->SetName( _( "Phase" ) );
583 break;
584
585 case ST_DC:
586 prepareDCAxes( aNewTraceType );
587 break;
588
589 case ST_NOISE:
590 if( !m_axis_x )
591 {
592 m_axis_x = new LOG_SCALE<mpScaleXLog>( wxEmptyString, wxT( "Hz" ), mpALIGN_BOTTOM );
595
596 if( ( aNewTraceType & SPT_CURRENT ) == 0 )
597 {
598 m_axis_y1 = new LIN_SCALE<mpScaleY>( wxEmptyString, wxT( "" ), mpALIGN_LEFT );
601 }
602 else
603 {
604 m_axis_y2 = new LIN_SCALE<mpScaleY>( wxEmptyString, wxT( "" ), mpALIGN_RIGHT );
607 }
608 }
609
610 m_axis_x->SetName( _( "Frequency" ) );
611
612 if( m_axis_y1 )
613 m_axis_y1->SetName( _( "Noise (V/√Hz)" ) );
614
615 if( m_axis_y2 )
616 m_axis_y2->SetName( _( "Noise (A/√Hz)" ) );
617
618 break;
619
620 case ST_FFT:
621 if( !m_axis_x )
622 {
623 m_axis_x = new LOG_SCALE<mpScaleXLog>( wxEmptyString, wxT( "Hz" ), mpALIGN_BOTTOM );
626
627 m_axis_y1 = new LIN_SCALE<mpScaleY>( wxEmptyString, wxT( "dB" ), mpALIGN_LEFT );
630 }
631
632 m_axis_x->SetName( _( "Frequency" ) );
633 m_axis_y1->SetName( _( "Intensity" ) );
634 break;
635
636 case ST_TRAN:
637 if( !m_axis_x )
638 {
639 m_axis_x = new TIME_SCALE( wxEmptyString, wxT( "s" ), mpALIGN_BOTTOM );
642
643 m_axis_y1 = new LIN_SCALE<mpScaleY>(wxEmptyString, wxT( "V" ), mpALIGN_LEFT );
646
647 m_axis_y2 = new LIN_SCALE<mpScaleY>( wxEmptyString, wxT( "A" ), mpALIGN_RIGHT );
651 }
652
653 m_axis_x->SetName( _( "Time" ) );
654 m_axis_y1->SetName( _( "Voltage" ) );
655 m_axis_y2->SetName( _( "Current" ) );
656
657 if( ( aNewTraceType & SPT_POWER ) && !m_axis_y3 )
658 {
659 m_plotWin->SetMargins( 30, 140, 45, 70 );
660
661 m_axis_y3 = new LIN_SCALE<mpScaleY>( wxEmptyString, wxT( "W" ), mpALIGN_FAR_RIGHT );
665 }
666
667 if( m_axis_y3 )
668 m_axis_y3->SetName( _( "Power" ) );
669
670 break;
671
672 default:
673 // suppress warnings
674 break;
675 }
676
677 if( m_axis_x )
679
680 if( m_axis_y1 )
682
683 if( m_axis_y2 )
685
686 if( m_axis_y3 )
688}
689
690
691void SIM_PLOT_TAB::prepareDCAxes( int aNewTraceType )
692{
693 wxString sim_cmd = GetSimCommand().Lower();
694 wxString rem;
695
696 if( sim_cmd.StartsWith( ".dc", &rem ) )
697 {
698 wxChar ch = 0;
699
700 rem.Trim( false );
701
702 try
703 {
704 ch = rem.GetChar( 0 );
705 }
706 catch( ... )
707 {
708 // Best efforts
709 }
710
711 switch( ch )
712 {
713 // Make sure that we have a reliable default (even if incorrectly labeled)
714 default:
715 case 'v':
716 if( !m_axis_x )
717 {
718 m_axis_x = new LIN_SCALE<mpScaleX>( wxEmptyString, wxT( "V" ), mpALIGN_BOTTOM );
721 }
722
723 m_axis_x->SetName( _( "Voltage (swept)" ) );
724 break;
725
726 case 'i':
727 if( !m_axis_x )
728 {
729 m_axis_x = new LIN_SCALE<mpScaleX>( wxEmptyString, wxT( "A" ), mpALIGN_BOTTOM );
732 }
733
734 m_axis_x->SetName( _( "Current (swept)" ) );
735 break;
736
737 case 'r':
738 if( !m_axis_x )
739 {
740 m_axis_x = new LIN_SCALE<mpScaleX>( wxEmptyString, wxT( "Ω" ), mpALIGN_BOTTOM );
743 }
744
745 m_axis_x->SetName( _( "Resistance (swept)" ) );
746 break;
747
748 case 't':
749 if( !m_axis_x )
750 {
751 m_axis_x = new LIN_SCALE<mpScaleX>( wxEmptyString, wxT( "°C" ), mpALIGN_BOTTOM );
754 }
755
756 m_axis_x->SetName( _( "Temperature (swept)" ) );
757 break;
758 }
759
760 if( !m_axis_y1 )
761 {
762 m_axis_y1 = new LIN_SCALE<mpScaleY>( wxEmptyString, wxT( "V" ), mpALIGN_LEFT );
765 }
766
767 if( !m_axis_y2 )
768 {
769 m_axis_y2 = new LIN_SCALE<mpScaleY>( wxEmptyString, wxT( "A" ), mpALIGN_RIGHT );
772 }
773
774 m_axis_y1->SetName( _( "Voltage (measured)" ) );
775 m_axis_y2->SetName( _( "Current" ) );
776
777 if( ( aNewTraceType & SPT_POWER ) )
779
780 if( m_axis_y3 )
781 m_axis_y3->SetName( _( "Power" ) );
782 }
783}
784
785
787{
788 if( !m_axis_y3 )
789 {
790 m_plotWin->SetMargins( 30, 140, 45, 70 );
791 m_axis_y3 = new LIN_SCALE<mpScaleY>( wxEmptyString, wxT( "W" ), mpALIGN_FAR_RIGHT );
795 }
796}
797
798
800{
801 // Update bg and fg colors:
805
807}
808
809
811{
812 updateAxes();
814}
815
816
818{
819 int type = trace->GetType();
820 wxPenStyle penStyle = ( ( ( type & SPT_AC_PHASE ) || ( type & SPT_CURRENT ) ) && m_dotted_cp )
821 ? wxPENSTYLE_DOT
822 : wxPENSTYLE_SOLID;
823 trace->SetPen( wxPen( trace->GetTraceColour(), 2, penStyle ) );
824
825 m_sessionTraceColors[ trace->GetName() ] = trace->GetTraceColour();
826}
827
828
829TRACE* SIM_PLOT_TAB::GetOrAddTrace( const wxString& aVectorName, int aType )
830{
831 TRACE* trace = GetTrace( aVectorName, aType );
832
833 if( !trace )
834 {
835 updateAxes( aType );
836
837 if( GetSimType() == ST_TRAN || GetSimType() == ST_DC )
838 {
839 bool hasVoltageTraces = false;
840
841 for( const auto& [ id, candidate ] : m_traces )
842 {
843 if( candidate->GetType() & SPT_VOLTAGE )
844 {
845 hasVoltageTraces = true;
846 break;
847 }
848 }
849
850 if( !hasVoltageTraces )
851 {
852 if( m_axis_y2 )
853 m_axis_y2->SetMasterScale( nullptr );
854
855 if( m_axis_y3 )
856 m_axis_y3->SetMasterScale( nullptr );
857 }
858 }
859
860 trace = new TRACE( aVectorName, (SIM_TRACE_TYPE) aType );
861
862 if( m_sessionTraceColors.count( aVectorName ) )
863 trace->SetTraceColour( m_sessionTraceColors[ aVectorName ] );
864 else
866
867 UpdateTraceStyle( trace );
868 m_traces[ getTraceId( aVectorName, aType ) ] = trace;
869
870 m_plotWin->AddLayer( (mpLayer*) trace );
871 }
872
873 return trace;
874}
875
876
877void SIM_PLOT_TAB::SetTraceData( TRACE* trace, std::vector<double>& aX, std::vector<double>& aY )
878{
879 if( dynamic_cast<LOG_SCALE<mpScaleXLog>*>( m_axis_x ) )
880 {
881 // log( 0 ) is not valid.
882 if( aX.size() > 0 && aX[0] == 0 )
883 {
884 aX.erase( aX.begin() );
885 aY.erase( aY.begin() );
886 }
887 }
888
889 if( GetSimType() == ST_AC || GetSimType() == ST_FFT )
890 {
891 if( trace->GetType() & SPT_AC_PHASE )
892 {
893 for( double& pt : aY )
894 pt = pt * 180.0 / M_PI; // convert to degrees
895 }
896 else
897 {
898 for( double& pt : aY )
899 {
900 // log( 0 ) is not valid.
901 if( pt != 0 )
902 pt = 20 * log( pt ) / log( 10.0 ); // convert to dB
903 }
904 }
905 }
906
907 trace->SetData( aX, aY );
908
909 if( ( trace->GetType() & SPT_AC_PHASE ) || ( trace->GetType() & SPT_CURRENT ) )
910 trace->SetScale( m_axis_x, m_axis_y2 );
911 else if( trace->GetType() & SPT_POWER )
912 trace->SetScale( m_axis_x, m_axis_y3 );
913 else
914 trace->SetScale( m_axis_x, m_axis_y1 );
915
916 for( auto& [ cursorId, cursor ] : trace->GetCursors() )
917 {
918 if( cursor )
919 cursor->SetCoordX( cursor->GetCoords().x );
920 }
921}
922
923
925{
926 for( const auto& [ name, trace ] : m_traces )
927 {
928 if( trace == aTrace )
929 {
930 m_traces.erase( name );
931 break;
932 }
933 }
934
935 for( const auto& [ id, cursor ] : aTrace->GetCursors() )
936 {
937 if( cursor )
938 m_plotWin->DelLayer( cursor, true );
939 }
940
941 m_plotWin->DelLayer( aTrace, true, true );
942 ResetScales( false );
943}
944
945
946bool SIM_PLOT_TAB::DeleteTrace( const wxString& aVectorName, int aTraceType )
947{
948 if( TRACE* trace = GetTrace( aVectorName, aTraceType ) )
949 {
950 DeleteTrace( trace );
951 return true;
952 }
953
954 return false;
955}
956
957
958void SIM_PLOT_TAB::EnableCursor( const wxString& aVectorName, int aType, int aCursorId,
959 bool aEnable, const wxString& aSignalName )
960{
961 TRACE* t = GetTrace( aVectorName, aType );
962
963 if( t == nullptr || t->HasCursor( aCursorId ) == aEnable )
964 return;
965
966 if( aEnable )
967 {
968 CURSOR* cursor = new CURSOR( t, this );
969 mpWindow* win = GetPlotWin();
970 int width = win->GetXScreen() - win->GetMarginLeft() - win->GetMarginRight();
971 int center = win->GetMarginLeft() + KiROUND( width * ( aCursorId == 1 ? 0.4 : 0.6 ) );
972
973 cursor->SetName( aSignalName );
974 cursor->SetX( center );
975
976 t->SetCursor( aCursorId, cursor );
978 }
979 else
980 {
981 CURSOR* cursor = t->GetCursor( aCursorId );
982 t->SetCursor( aCursorId, nullptr );
983 m_plotWin->DelLayer( cursor, true );
984 }
985
986 // Notify the parent window about the changes
987 wxQueueEvent( GetParent(), new wxCommandEvent( EVT_SIM_CURSOR_UPDATE ) );
988}
989
990
991void SIM_PLOT_TAB::ResetScales( bool aIncludeX )
992{
993 if( m_axis_x && aIncludeX )
994 {
996
997 if( GetSimType() == ST_TRAN )
998 {
999 wxStringTokenizer tokenizer( GetSimCommand(), wxS( " \t\n\r" ), wxTOKEN_STRTOK );
1000 wxString cmd = tokenizer.GetNextToken().Lower();
1001
1002 wxASSERT( cmd == wxS( ".tran" ) );
1003
1004 SPICE_VALUE step;
1005 SPICE_VALUE end( 1.0 );
1006 SPICE_VALUE start( 0.0 );
1007
1008 if( tokenizer.HasMoreTokens() )
1009 step = SPICE_VALUE( tokenizer.GetNextToken() );
1010
1011 if( tokenizer.HasMoreTokens() )
1012 end = SPICE_VALUE( tokenizer.GetNextToken() );
1013
1014 if( tokenizer.HasMoreTokens() )
1015 start = SPICE_VALUE( tokenizer.GetNextToken() );
1016
1017 static_cast<TIME_SCALE*>( m_axis_x )->SetStartAndEnd( start.ToDouble(), end.ToDouble() );
1018 }
1019 }
1020
1021 if( m_axis_y1 )
1023
1024 if( m_axis_y2 )
1026
1027 if( m_axis_y3 )
1029
1030 for( auto& [ name, trace ] : m_traces )
1031 trace->UpdateScales();
1032}
1033
1034
1035wxDEFINE_EVENT( EVT_SIM_CURSOR_UPDATE, wxCommandEvent );
const char * name
Definition: DXF_plotter.cpp:57
The SIMULATOR_FRAME holds the main user-interface for running simulations.
Definition: sim_plot_tab.h:64
mpWindow * m_window
Definition: sim_plot_tab.h:117
wxString getID()
wxRealPoint m_coords
Definition: sim_plot_tab.h:116
bool m_updateRef
Definition: sim_plot_tab.h:115
static constexpr int DRAG_MARGIN
Definition: sim_plot_tab.h:119
bool Inside(const wxPoint &aPoint) const override
Checks whether a point is inside the info box rectangle.
void doSetCoordX(double aValue)
void SetCoordX(double aValue)
void UpdateReference() override
Updates the rectangle reference point.
bool m_updateRequired
Definition: sim_plot_tab.h:114
TRACE * m_trace
Definition: sim_plot_tab.h:113
void Plot(wxDC &aDC, mpWindow &aWindow) override
Plot method.
A color representation with 4 components: red, green, blue, alpha.
Definition: color4d.h:104
wxColour ToColour() const
Definition: color4d.cpp:220
COLOR4D Mix(const COLOR4D &aColor, double aFactor) const
Return a color that is mixed with the input by a factor.
Definition: color4d.h:295
void formatLabels() override
wxString GetUnits() const
const wxString m_unit
LIN_SCALE(const wxString &name, const wxString &unit, int flags)
wxString m_base_axis_label
wxString GetUnits() const
const wxString m_unit
LOG_SCALE(const wxString &name, const wxString &unit, int flags)
void formatLabels() override
wxColour GetPlotColor(enum COLOR_SET aColorId)
wxColour GenerateColor(std::map< wxString, wxColour > aTraceColors)
bool DeleteTrace(const wxString &aVectorName, int aTraceType)
void EnableCursor(const wxString &aVectorName, int aType, int aCursorId, bool aEnable, const wxString &aSignalName)
Reset scale ranges to fit the current traces.
mpScaleXBase * m_axis_x
Definition: sim_plot_tab.h:405
mpWindow * GetPlotWin() const
Definition: sim_plot_tab.h:350
void prepareDCAxes(int aNewTraceType)
Create/Ensure axes are available for plotting.
wxString GetUnitsY2() const
void SetTraceData(TRACE *aTrace, std::vector< double > &aX, std::vector< double > &aY)
void SetY2Scale(bool aLock, double aMin, double aMax)
TRACE * GetTrace(const wxString &aVecName, int aType) const
Definition: sim_plot_tab.h:261
virtual ~SIM_PLOT_TAB()
void SetY1Scale(bool aLock, double aMin, double aMax)
mpInfoLegend * m_legend
Definition: sim_plot_tab.h:409
void SetY3Scale(bool aLock, double aMin, double aMax)
wxBoxSizer * m_sizer
Definition: sim_plot_tab.h:400
std::map< wxString, TRACE * > m_traces
Definition: sim_plot_tab.h:403
SIM_PLOT_COLORS m_colors
Definition: sim_plot_tab.h:395
void UpdateTraceStyle(TRACE *trace)
Update plot colors.
void ResetScales(bool aIncludeX)
Update trace line style.
void UpdatePlotColors()
mpScaleY * m_axis_y2
Definition: sim_plot_tab.h:407
SIM_PLOT_TAB(const wxString &aSimCommand, wxWindow *parent)
wxString GetUnitsX() const
void OnLanguageChanged() override
Getter for math plot window.
void EnsureThirdYAxisExists()
wxString getTraceId(const wxString &aVectorName, int aType) const
Construct the plot axes for DC simulation plot.
Definition: sim_plot_tab.h:383
TRACE * GetOrAddTrace(const wxString &aVectorName, int aType)
mpScaleY * m_axis_y1
Definition: sim_plot_tab.h:406
mpScaleY * m_axis_y3
Definition: sim_plot_tab.h:408
wxPoint m_LastLegendPosition
Definition: sim_plot_tab.h:364
wxString GetUnitsY1() const
std::map< wxString, wxColour > m_sessionTraceColors
Definition: sim_plot_tab.h:396
mpWindow * m_plotWin
Definition: sim_plot_tab.h:399
void updateAxes(int aNewTraceType=SIM_TRACE_TYPE::SPT_UNKNOWN)
wxString GetUnitsY3() const
SIM_TYPE GetSimType() const
Definition: sim_tab.cpp:75
const wxString & GetSimCommand() const
Definition: sim_tab.h:52
Helper class to recognize Spice formatted values.
Definition: spice_value.h:56
double ToDouble() const
void ResetDataRange() override
void ExtendDataRange(double minV, double maxV) override
void SetStartAndEnd(double aStartTime, double aEndTime)
double m_startTime
TIME_SCALE(const wxString &name, const wxString &unit, int flags)
double m_endTime
void SetTraceColour(const wxColour &aColour)
Definition: sim_plot_tab.h:181
std::map< int, CURSOR * > & GetCursors()
Definition: sim_plot_tab.h:177
SIM_TRACE_TYPE GetType() const
Definition: sim_plot_tab.h:179
bool HasCursor(int aCursorId)
Definition: sim_plot_tab.h:173
void SetData(const std::vector< double > &aX, const std::vector< double > &aY) override
Assigns new data set for the trace.
Definition: sim_plot_tab.h:159
void SetCursor(int aCursorId, CURSOR *aCursor)
Definition: sim_plot_tab.h:175
const std::vector< double > & GetDataY() const
Definition: sim_plot_tab.h:171
wxColour GetTraceColour() const
Definition: sim_plot_tab.h:182
const std::vector< double > & GetDataX() const
Definition: sim_plot_tab.h:170
CURSOR * GetCursor(int aCursorId)
Definition: sim_plot_tab.h:176
virtual void SetScale(mpScaleBase *scaleX, mpScaleBase *scaleY)
Definition: mathplot.cpp:2670
double y2s(double y) const
Definition: mathplot.cpp:2707
double x2s(double x) const
Definition: mathplot.cpp:2701
double s2x(double plotCoordX) const
Definition: mathplot.cpp:2689
wxPoint m_reference
Definition: mathplot.h:381
wxRect m_dim
Definition: mathplot.h:379
wxPoint GetPosition() const
Returns the position of the upper left corner of the box (in pixels)
Definition: mathplot.cpp:164
virtual void Move(wxPoint delta)
Moves the layer rectangle of given pixel deltas.
Definition: mathplot.cpp:112
Implements the legend to be added to the plot This layer allows you to add a legend to describe the p...
Definition: mathplot.h:393
void SetFont(const wxFont &font)
Set layer font.
Definition: mathplot.h:278
const wxString & GetName() const
Get layer name.
Definition: mathplot.h:239
bool m_continuous
Definition: mathplot.h:312
bool m_visible
Definition: mathplot.h:315
virtual void SetName(const wxString &name)
Set layer name.
Definition: mathplot.h:273
const wxPen & GetPen() const
Get pen set for this layer.
Definition: mathplot.h:254
void SetVisible(bool show)
Sets layer visibility.
Definition: mathplot.h:295
void SetPen(const wxPen &pen)
Set layer pen.
Definition: mathplot.h:283
void SetAxisMinMax(bool lock, double minV, double maxV)
Definition: mathplot.h:681
void SetNameAlign(int align)
Definition: mathplot.h:627
bool m_rangeSet
Definition: mathplot.h:741
virtual void ResetDataRange()
Definition: mathplot.h:666
double m_maxV
Definition: mathplot.h:740
double m_minV
Definition: mathplot.h:740
void SetMasterScale(mpScaleY *masterScale)
Definition: mathplot.h:851
Canvas for plotting mpLayer implementations.
Definition: mathplot.h:906
void SetColourTheme(const wxColour &bgColour, const wxColour &drawColour, const wxColour &axesColour)
Set Color theme.
Definition: mathplot.cpp:2447
int GetMarginLeft() const
Definition: mathplot.h:1217
void SetMargins(int top, int right, int bottom, int left)
Set window margins, creating a blank area where some kinds of layers cannot draw.
Definition: mathplot.cpp:2383
int GetScrX() const
Get current view's X dimension in device context units.
Definition: mathplot.h:1027
int GetScrY() const
Get current view's Y dimension in device context units.
Definition: mathplot.h:1036
double p2x(wxCoord pixelCoordX)
Converts mpWindow (screen) pixel coordinates into graph (floating point) coordinates,...
Definition: mathplot.h:1080
void LimitView(bool aEnable)
Enable limiting of zooming & panning to the area used by the plots.
Definition: mathplot.h:1256
wxCoord x2p(double x)
Converts graph (floating point) coordinates into mpWindow (screen) pixel coordinates,...
Definition: mathplot.h:1088
int GetXScreen() const
Definition: mathplot.h:1028
int GetMarginTop() const
Definition: mathplot.h:1211
bool DelLayer(mpLayer *layer, bool alsoDeleteObject=false, bool refreshDisplay=true)
Remove a plot layer from the canvas.
Definition: mathplot.cpp:1984
void UpdateAll()
Refresh display.
Definition: mathplot.cpp:2284
wxCoord y2p(double y)
Converts graph (floating point) coordinates into mpWindow (screen) pixel coordinates,...
Definition: mathplot.h:1092
int GetMarginRight() const
Definition: mathplot.h:1213
int GetMarginBottom() const
Definition: mathplot.h:1215
void EnableDoubleBuffer(bool enabled)
Enable/disable the double-buffering of the window, eliminating the flicker (default=disabled).
Definition: mathplot.h:1097
bool AddLayer(mpLayer *layer, bool refreshDisplay=true)
Add a plot layer to the canvas.
Definition: mathplot.cpp:1968
#define _(s)
#define mpALIGN_RIGHT
Aligns label to the right.
Definition: mathplot.h:427
#define mpALIGN_FAR_RIGHT
Aligns label to the right of mpALIGN_RIGHT.
Definition: mathplot.h:441
#define mpALIGN_LEFT
Aligns label to the left.
Definition: mathplot.h:431
#define mpALIGN_BOTTOM
Aligns label to the bottom.
Definition: mathplot.h:435
KICOMMON_API wxFont GetStatusFont(wxWindow *aWindow)
Definition: ui_common.cpp:130
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition: eda_angle.h:424
Class is responsible for providing colors for traces on simulation plot.
wxDEFINE_EVENT(EVT_SIM_CURSOR_UPDATE, wxCommandEvent)
static void getSISuffix(double x, const wxString &unit, int &power, wxString &suffix)
static int countDecimalDigits(double x, int maxDigits)
static wxString formatFloat(double x, int nDigits)
SIM_TRACE_TYPE
Definition: sim_types.h:50
@ SPT_AC_PHASE
Definition: sim_types.h:54
@ SPT_VOLTAGE
Definition: sim_types.h:52
@ SPT_POWER
Definition: sim_types.h:56
@ SPT_CURRENT
Definition: sim_types.h:53
@ ST_SP
Definition: sim_types.h:43
@ ST_TRAN
Definition: sim_types.h:42
@ ST_NOISE
Definition: sim_types.h:37
@ ST_AC
Definition: sim_types.h:34
@ ST_DC
Definition: sim_types.h:35
@ ST_FFT
Definition: sim_types.h:44
constexpr int delta
constexpr ret_type KiROUND(fp_type v)
Round a floating point number to an integer using "round halfway cases away from zero".
Definition: util.h:118