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 = aWindow.GetForegroundColour();
369 COLOR4D cursorColor = COLOR4D( m_trace->GetTraceColour() ).Mix( fg, 0.6 );
370 COLOR4D textColor = fg;
371
372 if( cursorColor.Distance( textColor ) < 0.66 )
373 textColor.Invert();
374
375 pen.SetColour( cursorColor.ToColour() );
376 pen.SetStyle( m_continuous ? wxPENSTYLE_SOLID : wxPENSTYLE_LONG_DASH );
377 aDC.SetPen( pen );
378
379 if( topPx < cursorPos.y && cursorPos.y < bottomPx )
380 aDC.DrawLine( leftPx, cursorPos.y, rightPx, cursorPos.y );
381
382 if( leftPx < cursorPos.x && cursorPos.x < rightPx )
383 {
384 aDC.DrawLine( cursorPos.x, topPx, cursorPos.x, bottomPx );
385
386 wxString id = getID();
387 wxSize size = aDC.GetTextExtent( wxS( "M" ) );
388 wxRect textRect( wxPoint( cursorPos.x + 1 - size.x / 2, topPx - 4 - size.y ), size );
389 wxBrush brush;
390 wxPoint poly[3];
391
392 // Because a "1" looks off-center if it's actually centred.
393 if( id == "1" )
394 textRect.x -= 1;
395
396 // We want an equalateral triangle, so use size.y for both axes.
397 size.y += 3;
398 // Make sure it's an even number so the slopes of the sides will be identical.
399 size.y = ( size.y / 2 ) * 2;
400 poly[0] = { cursorPos.x - 1 - size.y / 2, topPx - size.y };
401 poly[1] = { cursorPos.x + 1 + size.y / 2, topPx - size.y };
402 poly[2] = { cursorPos.x, topPx };
403
404 brush.SetStyle( wxBRUSHSTYLE_SOLID );
405 brush.SetColour( m_trace->GetTraceColour() );
406 aDC.SetBrush( brush );
407 aDC.DrawPolygon( 3, poly );
408
409 aDC.SetTextForeground( textColor.ToColour() );
410 aDC.DrawLabel( id, textRect, wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL );
411 }
412}
413
414
415bool CURSOR::Inside( const wxPoint& aPoint ) const
416{
417 if( !m_window || !m_trace )
418 return false;
419
420 return ( std::abs( (double) aPoint.x -
421 m_window->x2p( m_trace->x2s( m_coords.x ) ) ) <= DRAG_MARGIN )
422 || ( std::abs( (double) aPoint.y -
423 m_window->y2p( m_trace->y2s( m_coords.y ) ) ) <= DRAG_MARGIN );
424}
425
426
428{
429 if( !m_window )
430 return;
431
434}
435
436
437SIM_PLOT_TAB::SIM_PLOT_TAB( const wxString& aSimCommand, wxWindow* parent ) :
438 SIM_TAB( aSimCommand, parent ),
439 m_axis_x( nullptr ),
440 m_axis_y1( nullptr ),
441 m_axis_y2( nullptr ),
442 m_axis_y3( nullptr ),
443 m_dotted_cp( false )
444{
445 m_sizer = new wxBoxSizer( wxVERTICAL );
446 m_plotWin = new mpWindow( this, wxID_ANY );
447
448 m_plotWin->LimitView( true );
449 m_plotWin->SetMargins( 30, 70, 45, 70 );
451
452 updateAxes();
453
454 // a mpInfoLegend displays le name of traces on the left top panel corner:
455 m_legend = new mpInfoLegend( wxRect( 0, 0, 200, 40 ), wxTRANSPARENT_BRUSH );
456 m_legend->SetVisible( false );
459
462
463 m_sizer->Add( m_plotWin, 1, wxALL | wxEXPAND, 1 );
464 SetSizer( m_sizer );
465}
466
467
469{
470 // ~mpWindow destroys all the added layers, so there is no need to destroy m_traces contents
471}
472
473
474void SIM_PLOT_TAB::SetY1Scale( bool aLock, double aMin, double aMax )
475{
476 wxCHECK( m_axis_y1, /* void */ );
477 m_axis_y1->SetAxisMinMax( aLock, aMin, aMax );
478}
479
480
481void SIM_PLOT_TAB::SetY2Scale( bool aLock, double aMin, double aMax )
482{
483 wxCHECK( m_axis_y2, /* void */ );
484 m_axis_y2->SetAxisMinMax( aLock, aMin, aMax );
485}
486
487
488void SIM_PLOT_TAB::SetY3Scale( bool aLock, double aMin, double aMax )
489{
490 wxCHECK( m_axis_y3, /* void */ );
491 m_axis_y3->SetAxisMinMax( aLock, aMin, aMax );
492}
493
494
496{
497 LOG_SCALE<mpScaleXLog>* logScale = dynamic_cast<LOG_SCALE<mpScaleXLog>*>( m_axis_x );
498 LIN_SCALE<mpScaleX>* linScale = dynamic_cast<LIN_SCALE<mpScaleX>*>( m_axis_x );
499
500 if( logScale )
501 return logScale->GetUnits();
502 else if( linScale )
503 return linScale->GetUnits();
504 else
505 return wxEmptyString;
506}
507
508
510{
511 LIN_SCALE<mpScaleY>* linScale = dynamic_cast<LIN_SCALE<mpScaleY>*>( m_axis_y1 );
512
513 if( linScale )
514 return linScale->GetUnits();
515 else
516 return wxEmptyString;
517}
518
519
521{
522 LIN_SCALE<mpScaleY>* linScale = dynamic_cast<LIN_SCALE<mpScaleY>*>( m_axis_y2 );
523
524 if( linScale )
525 return linScale->GetUnits();
526 else
527 return wxEmptyString;
528}
529
530
532{
533 LIN_SCALE<mpScaleY>* linScale = dynamic_cast<LIN_SCALE<mpScaleY>*>( m_axis_y3 );
534
535 if( linScale )
536 return linScale->GetUnits();
537 else
538 return wxEmptyString;
539}
540
541
542void SIM_PLOT_TAB::updateAxes( int aNewTraceType )
543{
544 switch( GetSimType() )
545 {
546 case ST_AC:
547 if( !m_axis_x )
548 {
549 m_axis_x = new LOG_SCALE<mpScaleXLog>( wxEmptyString, wxT( "Hz" ), mpALIGN_BOTTOM );
552
553 m_axis_y1 = new LIN_SCALE<mpScaleY>( wxEmptyString, wxT( "dB" ), mpALIGN_LEFT );
556
557 m_axis_y2 = new LIN_SCALE<mpScaleY>( wxEmptyString, wxT( "°" ), mpALIGN_RIGHT );
561 }
562
563 m_axis_x->SetName( _( "Frequency" ) );
564 m_axis_y1->SetName( _( "Gain" ) );
565 m_axis_y2->SetName( _( "Phase" ) );
566 break;
567
568 case ST_SP:
569 if( !m_axis_x )
570 {
571 m_axis_x = new LOG_SCALE<mpScaleXLog>( wxEmptyString, wxT( "Hz" ), mpALIGN_BOTTOM );
574
575 m_axis_y1 = new LIN_SCALE<mpScaleY>( wxEmptyString, wxT( "" ), mpALIGN_LEFT );
578
579 m_axis_y2 = new LIN_SCALE<mpScaleY>( wxEmptyString, wxT( "°" ), mpALIGN_RIGHT );
583 }
584
585 m_axis_x->SetName( _( "Frequency" ) );
586 m_axis_y1->SetName( _( "Amplitude" ) );
587 m_axis_y2->SetName( _( "Phase" ) );
588 break;
589
590 case ST_DC:
591 prepareDCAxes( aNewTraceType );
592 break;
593
594 case ST_NOISE:
595 if( !m_axis_x )
596 {
597 m_axis_x = new LOG_SCALE<mpScaleXLog>( wxEmptyString, wxT( "Hz" ), mpALIGN_BOTTOM );
600
601 if( ( aNewTraceType & SPT_CURRENT ) == 0 )
602 {
603 m_axis_y1 = new LIN_SCALE<mpScaleY>( wxEmptyString, wxT( "" ), mpALIGN_LEFT );
606 }
607 else
608 {
609 m_axis_y2 = new LIN_SCALE<mpScaleY>( wxEmptyString, wxT( "" ), mpALIGN_RIGHT );
612 }
613 }
614
615 m_axis_x->SetName( _( "Frequency" ) );
616
617 if( m_axis_y1 )
618 m_axis_y1->SetName( _( "Noise (V/√Hz)" ) );
619
620 if( m_axis_y2 )
621 m_axis_y2->SetName( _( "Noise (A/√Hz)" ) );
622
623 break;
624
625 case ST_FFT:
626 if( !m_axis_x )
627 {
628 m_axis_x = new LOG_SCALE<mpScaleXLog>( wxEmptyString, wxT( "Hz" ), mpALIGN_BOTTOM );
631
632 m_axis_y1 = new LIN_SCALE<mpScaleY>( wxEmptyString, wxT( "dB" ), mpALIGN_LEFT );
635 }
636
637 m_axis_x->SetName( _( "Frequency" ) );
638 m_axis_y1->SetName( _( "Intensity" ) );
639 break;
640
641 case ST_TRAN:
642 if( !m_axis_x )
643 {
644 m_axis_x = new TIME_SCALE( wxEmptyString, wxT( "s" ), mpALIGN_BOTTOM );
647
648 m_axis_y1 = new LIN_SCALE<mpScaleY>(wxEmptyString, wxT( "V" ), mpALIGN_LEFT );
651
652 m_axis_y2 = new LIN_SCALE<mpScaleY>( wxEmptyString, wxT( "A" ), mpALIGN_RIGHT );
656 }
657
658 m_axis_x->SetName( _( "Time" ) );
659 m_axis_y1->SetName( _( "Voltage" ) );
660 m_axis_y2->SetName( _( "Current" ) );
661
662 if( ( aNewTraceType & SPT_POWER ) && !m_axis_y3 )
663 {
664 m_plotWin->SetMargins( 30, 140, 45, 70 );
665
666 m_axis_y3 = new LIN_SCALE<mpScaleY>( wxEmptyString, wxT( "W" ), mpALIGN_FAR_RIGHT );
670 }
671
672 if( m_axis_y3 )
673 m_axis_y3->SetName( _( "Power" ) );
674
675 break;
676
677 default:
678 // suppress warnings
679 break;
680 }
681
682 if( m_axis_x )
684
685 if( m_axis_y1 )
687
688 if( m_axis_y2 )
690
691 if( m_axis_y3 )
693}
694
695
696void SIM_PLOT_TAB::prepareDCAxes( int aNewTraceType )
697{
698 wxString sim_cmd = GetSimCommand().Lower();
699 wxString rem;
700
701 if( sim_cmd.StartsWith( ".dc", &rem ) )
702 {
703 wxChar ch = 0;
704
705 rem.Trim( false );
706
707 try
708 {
709 ch = rem.GetChar( 0 );
710 }
711 catch( ... )
712 {
713 // Best efforts
714 }
715
716 switch( ch )
717 {
718 // Make sure that we have a reliable default (even if incorrectly labeled)
719 default:
720 case 'v':
721 if( !m_axis_x )
722 {
723 m_axis_x = new LIN_SCALE<mpScaleX>( wxEmptyString, wxT( "V" ), mpALIGN_BOTTOM );
726 }
727
728 m_axis_x->SetName( _( "Voltage (swept)" ) );
729 break;
730
731 case 'i':
732 if( !m_axis_x )
733 {
734 m_axis_x = new LIN_SCALE<mpScaleX>( wxEmptyString, wxT( "A" ), mpALIGN_BOTTOM );
737 }
738
739 m_axis_x->SetName( _( "Current (swept)" ) );
740 break;
741
742 case 'r':
743 if( !m_axis_x )
744 {
745 m_axis_x = new LIN_SCALE<mpScaleX>( wxEmptyString, wxT( "Ω" ), mpALIGN_BOTTOM );
748 }
749
750 m_axis_x->SetName( _( "Resistance (swept)" ) );
751 break;
752
753 case 't':
754 if( !m_axis_x )
755 {
756 m_axis_x = new LIN_SCALE<mpScaleX>( wxEmptyString, wxT( "°C" ), mpALIGN_BOTTOM );
759 }
760
761 m_axis_x->SetName( _( "Temperature (swept)" ) );
762 break;
763 }
764
765 if( !m_axis_y1 )
766 {
767 m_axis_y1 = new LIN_SCALE<mpScaleY>( wxEmptyString, wxT( "V" ), mpALIGN_LEFT );
770 }
771
772 if( !m_axis_y2 )
773 {
774 m_axis_y2 = new LIN_SCALE<mpScaleY>( wxEmptyString, wxT( "A" ), mpALIGN_RIGHT );
777 }
778
779 m_axis_y1->SetName( _( "Voltage (measured)" ) );
780 m_axis_y2->SetName( _( "Current" ) );
781
782 if( ( aNewTraceType & SPT_POWER ) )
784
785 if( m_axis_y3 )
786 m_axis_y3->SetName( _( "Power" ) );
787 }
788}
789
790
792{
793 if( !m_axis_y3 )
794 {
795 m_plotWin->SetMargins( 30, 140, 45, 70 );
796 m_axis_y3 = new LIN_SCALE<mpScaleY>( wxEmptyString, wxT( "W" ), mpALIGN_FAR_RIGHT );
800 }
801}
802
803
805{
806 // Update bg and fg colors:
810
812}
813
814
816{
817 updateAxes();
819}
820
821
823{
824 int type = trace->GetType();
825 wxPenStyle penStyle;
826
827 if( ( type & SPT_AC_GAIN ) > 0 )
828 penStyle = wxPENSTYLE_SOLID;
829 else if( ( type & SPT_AC_PHASE ) > 0 )
830 penStyle = m_dotted_cp ? wxPENSTYLE_DOT : wxPENSTYLE_SOLID;
831 else if( ( type & SPT_CURRENT ) > 0 )
832 penStyle = m_dotted_cp ? wxPENSTYLE_DOT : wxPENSTYLE_SOLID;
833 else
834 penStyle = wxPENSTYLE_SOLID;
835
836 trace->SetPen( wxPen( trace->GetTraceColour(), 2, penStyle ) );
837 m_sessionTraceColors[ trace->GetName() ] = trace->GetTraceColour();
838}
839
840
841TRACE* SIM_PLOT_TAB::GetOrAddTrace( const wxString& aVectorName, int aType )
842{
843 TRACE* trace = GetTrace( aVectorName, aType );
844
845 if( !trace )
846 {
847 updateAxes( aType );
848
849 if( GetSimType() == ST_TRAN || GetSimType() == ST_DC )
850 {
851 bool hasVoltageTraces = false;
852
853 for( const auto& [ id, candidate ] : m_traces )
854 {
855 if( candidate->GetType() & SPT_VOLTAGE )
856 {
857 hasVoltageTraces = true;
858 break;
859 }
860 }
861
862 if( !hasVoltageTraces )
863 {
864 if( m_axis_y2 )
865 m_axis_y2->SetMasterScale( nullptr );
866
867 if( m_axis_y3 )
868 m_axis_y3->SetMasterScale( nullptr );
869 }
870 }
871
872 trace = new TRACE( aVectorName, (SIM_TRACE_TYPE) aType );
873
874 if( m_sessionTraceColors.count( aVectorName ) )
875 trace->SetTraceColour( m_sessionTraceColors[ aVectorName ] );
876 else
878
879 UpdateTraceStyle( trace );
880 m_traces[ getTraceId( aVectorName, aType ) ] = trace;
881
882 m_plotWin->AddLayer( (mpLayer*) trace );
883 }
884
885 return trace;
886}
887
888
889void SIM_PLOT_TAB::SetTraceData( TRACE* trace, std::vector<double>& aX, std::vector<double>& aY,
890 int aSweepCount, size_t aSweepSize )
891{
892 if( dynamic_cast<LOG_SCALE<mpScaleXLog>*>( m_axis_x ) )
893 {
894 // log( 0 ) is not valid.
895 if( aX.size() > 0 && aX[0] == 0 )
896 {
897 aX.erase( aX.begin() );
898 aY.erase( aY.begin() );
899 }
900 }
901
902 if( GetSimType() == ST_AC || GetSimType() == ST_FFT )
903 {
904 if( trace->GetType() & SPT_AC_PHASE )
905 {
906 for( double& pt : aY )
907 pt = pt * 180.0 / M_PI; // convert to degrees
908 }
909 else
910 {
911 for( double& pt : aY )
912 {
913 // log( 0 ) is not valid.
914 if( pt != 0 )
915 pt = 20 * log( pt ) / log( 10.0 ); // convert to dB
916 }
917 }
918 }
919
920 trace->SetData( aX, aY );
921 trace->SetSweepCount( aSweepCount );
922 trace->SetSweepSize( aSweepSize );
923
924 // Phase and currents on second Y axis, except for AC currents, those use the same axis as voltage
925 if( ( trace->GetType() & SPT_AC_PHASE )
926 || ( ( GetSimType() != ST_AC ) && ( trace->GetType() & SPT_CURRENT ) ) )
927 {
928 trace->SetScale( m_axis_x, m_axis_y2 );
929 }
930 else if( trace->GetType() & SPT_POWER )
931 {
932 trace->SetScale( m_axis_x, m_axis_y3 );
933 }
934 else
935 {
936 trace->SetScale( m_axis_x, m_axis_y1 );
937 }
938
939 for( auto& [ cursorId, cursor ] : trace->GetCursors() )
940 {
941 if( cursor )
942 cursor->SetCoordX( cursor->GetCoords().x );
943 }
944}
945
946
948{
949 for( const auto& [ name, trace ] : m_traces )
950 {
951 if( trace == aTrace )
952 {
953 m_traces.erase( name );
954 break;
955 }
956 }
957
958 for( const auto& [ id, cursor ] : aTrace->GetCursors() )
959 {
960 if( cursor )
961 m_plotWin->DelLayer( cursor, true );
962 }
963
964 m_plotWin->DelLayer( aTrace, true, true );
965 ResetScales( false );
966}
967
968
969bool SIM_PLOT_TAB::DeleteTrace( const wxString& aVectorName, int aTraceType )
970{
971 if( TRACE* trace = GetTrace( aVectorName, aTraceType ) )
972 {
973 DeleteTrace( trace );
974 return true;
975 }
976
977 return false;
978}
979
980
981void SIM_PLOT_TAB::EnableCursor( const wxString& aVectorName, int aType, int aCursorId,
982 bool aEnable, const wxString& aSignalName )
983{
984 TRACE* t = GetTrace( aVectorName, aType );
985
986 if( t == nullptr || t->HasCursor( aCursorId ) == aEnable )
987 return;
988
989 if( aEnable )
990 {
991 CURSOR* cursor = new CURSOR( t, this );
992 mpWindow* win = GetPlotWin();
993 int width = win->GetXScreen() - win->GetMarginLeft() - win->GetMarginRight();
994 int center = win->GetMarginLeft() + KiROUND( width * ( aCursorId == 1 ? 0.4 : 0.6 ) );
995
996 cursor->SetName( aSignalName );
997 cursor->SetX( center );
998
999 t->SetCursor( aCursorId, cursor );
1001 }
1002 else
1003 {
1004 CURSOR* cursor = t->GetCursor( aCursorId );
1005 t->SetCursor( aCursorId, nullptr );
1006 m_plotWin->DelLayer( cursor, true );
1007 }
1008
1009 // Notify the parent window about the changes
1010 wxQueueEvent( GetParent(), new wxCommandEvent( EVT_SIM_CURSOR_UPDATE ) );
1011}
1012
1013
1014void SIM_PLOT_TAB::ResetScales( bool aIncludeX )
1015{
1016 if( m_axis_x && aIncludeX )
1017 {
1019
1020 if( GetSimType() == ST_TRAN )
1021 {
1022 wxStringTokenizer tokenizer( GetSimCommand(), wxS( " \t\n\r" ), wxTOKEN_STRTOK );
1023 wxString cmd = tokenizer.GetNextToken().Lower();
1024
1025 wxASSERT( cmd == wxS( ".tran" ) );
1026
1027 SPICE_VALUE step;
1028 SPICE_VALUE end( 1.0 );
1029 SPICE_VALUE start( 0.0 );
1030
1031 if( tokenizer.HasMoreTokens() )
1032 step = SPICE_VALUE( tokenizer.GetNextToken() );
1033
1034 if( tokenizer.HasMoreTokens() )
1035 end = SPICE_VALUE( tokenizer.GetNextToken() );
1036
1037 if( tokenizer.HasMoreTokens() )
1038 start = SPICE_VALUE( tokenizer.GetNextToken() );
1039
1040 static_cast<TIME_SCALE*>( m_axis_x )->SetStartAndEnd( start.ToDouble(), end.ToDouble() );
1041 }
1042 }
1043
1044 if( m_axis_y1 )
1046
1047 if( m_axis_y2 )
1049
1050 if( m_axis_y3 )
1052
1053 for( auto& [ name, trace ] : m_traces )
1054 trace->UpdateScales();
1055}
1056
1057
1058wxDEFINE_EVENT( EVT_SIM_CURSOR_UPDATE, wxCommandEvent );
const char * name
Definition: DXF_plotter.cpp:57
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition: box2.h:990
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
COLOR4D & Invert()
Makes the color inverted, alpha remains the same.
Definition: color4d.h:242
wxColour ToColour() const
Definition: color4d.cpp:220
double Distance(const COLOR4D &other) const
Returns the distance (in RGB space) between two colors.
Definition: color4d.cpp:532
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:406
mpWindow * GetPlotWin() const
Definition: sim_plot_tab.h:350
void prepareDCAxes(int aNewTraceType)
Create/Ensure axes are available for plotting.
wxString GetUnitsY2() const
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:410
void SetY3Scale(bool aLock, double aMin, double aMax)
wxBoxSizer * m_sizer
Definition: sim_plot_tab.h:401
std::map< wxString, TRACE * > m_traces
Definition: sim_plot_tab.h:404
SIM_PLOT_COLORS m_colors
Definition: sim_plot_tab.h:396
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:408
SIM_PLOT_TAB(const wxString &aSimCommand, wxWindow *parent)
void SetTraceData(TRACE *aTrace, std::vector< double > &aX, std::vector< double > &aY, int aSweepCount, size_t aSweepSize)
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:384
TRACE * GetOrAddTrace(const wxString &aVectorName, int aType)
mpScaleY * m_axis_y1
Definition: sim_plot_tab.h:407
mpScaleY * m_axis_y3
Definition: sim_plot_tab.h:409
wxPoint m_LastLegendPosition
Definition: sim_plot_tab.h:365
wxString GetUnitsY1() const
std::map< wxString, wxColour > m_sessionTraceColors
Definition: sim_plot_tab.h:397
mpWindow * m_plotWin
Definition: sim_plot_tab.h:400
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
void SetSweepSize(size_t aSweepSize)
Definition: mathplot.h:1425
void SetSweepCount(int aSweepCount)
Definition: mathplot.h:1424
virtual void SetScale(mpScaleBase *scaleX, mpScaleBase *scaleY)
Definition: mathplot.cpp:2675
double y2s(double y) const
Definition: mathplot.cpp:2712
double x2s(double x) const
Definition: mathplot.cpp:2706
double s2x(double plotCoordX) const
Definition: mathplot.cpp:2694
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:683
void SetNameAlign(int align)
Definition: mathplot.h:629
bool m_rangeSet
Definition: mathplot.h:743
virtual void ResetDataRange()
Definition: mathplot.h:668
double m_maxV
Definition: mathplot.h:742
double m_minV
Definition: mathplot.h:742
void SetMasterScale(mpScaleY *masterScale)
Definition: mathplot.h:853
Canvas for plotting mpLayer implementations.
Definition: mathplot.h:908
void SetColourTheme(const wxColour &bgColour, const wxColour &drawColour, const wxColour &axesColour)
Set Color theme.
Definition: mathplot.cpp:2449
int GetMarginLeft() const
Definition: mathplot.h:1219
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:2385
int GetScrX() const
Get current view's X dimension in device context units.
Definition: mathplot.h:1029
int GetScrY() const
Get current view's Y dimension in device context units.
Definition: mathplot.h:1038
double p2x(wxCoord pixelCoordX)
Converts mpWindow (screen) pixel coordinates into graph (floating point) coordinates,...
Definition: mathplot.h:1082
void LimitView(bool aEnable)
Enable limiting of zooming & panning to the area used by the plots.
Definition: mathplot.h:1258
wxCoord x2p(double x)
Converts graph (floating point) coordinates into mpWindow (screen) pixel coordinates,...
Definition: mathplot.h:1090
int GetXScreen() const
Definition: mathplot.h:1030
int GetMarginTop() const
Definition: mathplot.h:1213
bool DelLayer(mpLayer *layer, bool alsoDeleteObject=false, bool refreshDisplay=true)
Remove a plot layer from the canvas.
Definition: mathplot.cpp:1986
void UpdateAll()
Refresh display.
Definition: mathplot.cpp:2286
wxCoord y2p(double y)
Converts graph (floating point) coordinates into mpWindow (screen) pixel coordinates,...
Definition: mathplot.h:1094
int GetMarginRight() const
Definition: mathplot.h:1215
int GetMarginBottom() const
Definition: mathplot.h:1217
void EnableDoubleBuffer(bool enabled)
Enable/disable the double-buffering of the window, eliminating the flicker (default=disabled).
Definition: mathplot.h:1099
bool AddLayer(mpLayer *layer, bool refreshDisplay=true)
Add a plot layer to the canvas.
Definition: mathplot.cpp:1970
#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:390
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_AC_GAIN
Definition: sim_types.h:55
@ 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