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 );
445
447
448 updateAxes();
449
450 // a mpInfoLegend displays le name of traces on the left top panel corner:
451 m_legend = new mpInfoLegend( wxRect( 0, 0, 200, 40 ), wxTRANSPARENT_BRUSH );
452 m_legend->SetVisible( false );
455
458
459 m_sizer->Add( m_plotWin, 1, wxALL | wxEXPAND, 1 );
460 SetSizer( m_sizer );
461}
462
463
465{
466 // ~mpWindow destroys all the added layers, so there is no need to destroy m_traces contents
467}
468
469
470void SIM_PLOT_TAB::SetY1Scale( bool aLock, double aMin, double aMax )
471{
472 m_axis_y1->SetAxisMinMax( aLock, aMin, aMax );
473}
474
475
476void SIM_PLOT_TAB::SetY2Scale( bool aLock, double aMin, double aMax )
477{
478 m_axis_y2->SetAxisMinMax( aLock, aMin, aMax );
479}
480
481
482void SIM_PLOT_TAB::SetY3Scale( bool aLock, double aMin, double aMax )
483{
484 m_axis_y3->SetAxisMinMax( aLock, aMin, aMax );
485}
486
487
489{
490 LOG_SCALE<mpScaleXLog>* logScale = dynamic_cast<LOG_SCALE<mpScaleXLog>*>( m_axis_x );
491 LIN_SCALE<mpScaleX>* linScale = dynamic_cast<LIN_SCALE<mpScaleX>*>( m_axis_x );
492
493 if( logScale )
494 return logScale->GetUnits();
495 else if( linScale )
496 return linScale->GetUnits();
497 else
498 return wxEmptyString;
499}
500
501
503{
504 LIN_SCALE<mpScaleY>* linScale = dynamic_cast<LIN_SCALE<mpScaleY>*>( m_axis_y1 );
505
506 if( linScale )
507 return linScale->GetUnits();
508 else
509 return wxEmptyString;
510}
511
512
514{
515 LIN_SCALE<mpScaleY>* linScale = dynamic_cast<LIN_SCALE<mpScaleY>*>( m_axis_y2 );
516
517 if( linScale )
518 return linScale->GetUnits();
519 else
520 return wxEmptyString;
521}
522
523
525{
526 LIN_SCALE<mpScaleY>* linScale = dynamic_cast<LIN_SCALE<mpScaleY>*>( m_axis_y3 );
527
528 if( linScale )
529 return linScale->GetUnits();
530 else
531 return wxEmptyString;
532}
533
534
535void SIM_PLOT_TAB::updateAxes( int aNewTraceType )
536{
537 switch( GetSimType() )
538 {
539 case ST_AC:
540 if( !m_axis_x )
541 {
542 m_axis_x = new LOG_SCALE<mpScaleXLog>( wxEmptyString, wxT( "Hz" ), mpALIGN_BOTTOM );
545
546 m_axis_y1 = new LIN_SCALE<mpScaleY>( wxEmptyString, wxT( "dBV" ), mpALIGN_LEFT );
549
550 m_axis_y2 = new LIN_SCALE<mpScaleY>( wxEmptyString, wxT( "°" ), mpALIGN_RIGHT );
554 }
555
556 m_axis_x->SetName( _( "Frequency" ) );
557 m_axis_y1->SetName( _( "Gain" ) );
558 m_axis_y2->SetName( _( "Phase" ) );
559 break;
560
561 case ST_SP:
562 if( !m_axis_x )
563 {
564 m_axis_x = new LOG_SCALE<mpScaleXLog>( wxEmptyString, wxT( "Hz" ), mpALIGN_BOTTOM );
567
568 m_axis_y1 = new LIN_SCALE<mpScaleY>( wxEmptyString, wxT( "" ), mpALIGN_LEFT );
571
572 m_axis_y2 = new LIN_SCALE<mpScaleY>( wxEmptyString, wxT( "°" ), mpALIGN_RIGHT );
576 }
577
578 m_axis_x->SetName( _( "Frequency" ) );
579 m_axis_y1->SetName( _( "Ampltiude" ) );
580 m_axis_y2->SetName( _( "Phase" ) );
581 break;
582
583 case ST_DC:
584 prepareDCAxes( aNewTraceType );
585 break;
586
587 case ST_NOISE:
588 if( !m_axis_x )
589 {
590 m_axis_x = new LOG_SCALE<mpScaleXLog>( wxEmptyString, wxT( "Hz" ), mpALIGN_BOTTOM );
593
594 if( ( aNewTraceType & SPT_CURRENT ) == 0 )
595 {
596 m_axis_y1 = new LIN_SCALE<mpScaleY>( wxEmptyString, wxT( "" ), mpALIGN_LEFT );
599 }
600 else
601 {
602 m_axis_y2 = new LIN_SCALE<mpScaleY>( wxEmptyString, wxT( "" ), mpALIGN_RIGHT );
605 }
606 }
607
608 m_axis_x->SetName( _( "Frequency" ) );
609
610 if( m_axis_y1 )
611 m_axis_y1->SetName( _( "Noise (V/√Hz)" ) );
612
613 if( m_axis_y2 )
614 m_axis_y2->SetName( _( "Noise (A/√Hz)" ) );
615
616 break;
617
618 case ST_FFT:
619 if( !m_axis_x )
620 {
621 m_axis_x = new LOG_SCALE<mpScaleXLog>( wxEmptyString, wxT( "Hz" ), mpALIGN_BOTTOM );
624
625 m_axis_y1 = new LIN_SCALE<mpScaleY>( wxEmptyString, wxT( "dB" ), mpALIGN_LEFT );
628 }
629
630 m_axis_x->SetName( _( "Frequency" ) );
631 m_axis_y1->SetName( _( "Intensity" ) );
632 break;
633
634 case ST_TRAN:
635 if( !m_axis_x )
636 {
637 m_axis_x = new TIME_SCALE( wxEmptyString, wxT( "s" ), mpALIGN_BOTTOM );
640
641 m_axis_y1 = new LIN_SCALE<mpScaleY>(wxEmptyString, wxT( "V" ), mpALIGN_LEFT );
644
645 m_axis_y2 = new LIN_SCALE<mpScaleY>( wxEmptyString, wxT( "A" ), mpALIGN_RIGHT );
649 }
650
651 m_axis_x->SetName( _( "Time" ) );
652 m_axis_y1->SetName( _( "Voltage" ) );
653 m_axis_y2->SetName( _( "Current" ) );
654
655 if( ( aNewTraceType & SPT_POWER ) && !m_axis_y3 )
656 {
657 m_plotWin->SetMargins( 30, 140, 45, 70 );
658
659 m_axis_y3 = new LIN_SCALE<mpScaleY>( wxEmptyString, wxT( "W" ), mpALIGN_FAR_RIGHT );
663 }
664
665 if( m_axis_y3 )
666 m_axis_y3->SetName( _( "Power" ) );
667
668 break;
669
670 default:
671 // suppress warnings
672 break;
673 }
674
675 if( m_axis_x )
677
678 if( m_axis_y1 )
680
681 if( m_axis_y2 )
683
684 if( m_axis_y3 )
686}
687
688
689void SIM_PLOT_TAB::prepareDCAxes( int aNewTraceType )
690{
691 wxString sim_cmd = GetSimCommand().Lower();
692 wxString rem;
693
694 if( sim_cmd.StartsWith( ".dc", &rem ) )
695 {
696 wxChar ch = 0;
697
698 rem.Trim( false );
699
700 try
701 {
702 ch = rem.GetChar( 0 );
703 }
704 catch( ... )
705 {
706 // Best efforts
707 }
708
709 switch( ch )
710 {
711 // Make sure that we have a reliable default (even if incorrectly labeled)
712 default:
713 case 'v':
714 if( !m_axis_x )
715 {
716 m_axis_x = new LIN_SCALE<mpScaleX>( wxEmptyString, wxT( "V" ), mpALIGN_BOTTOM );
719 }
720
721 m_axis_x->SetName( _( "Voltage (swept)" ) );
722 break;
723
724 case 'i':
725 if( !m_axis_x )
726 {
727 m_axis_x = new LIN_SCALE<mpScaleX>( wxEmptyString, wxT( "A" ), mpALIGN_BOTTOM );
730 }
731
732 m_axis_x->SetName( _( "Current (swept)" ) );
733 break;
734
735 case 'r':
736 if( !m_axis_x )
737 {
738 m_axis_x = new LIN_SCALE<mpScaleX>( wxEmptyString, wxT( "Ω" ), mpALIGN_BOTTOM );
741 }
742
743 m_axis_x->SetName( _( "Resistance (swept)" ) );
744 break;
745
746 case 't':
747 if( !m_axis_x )
748 {
749 m_axis_x = new LIN_SCALE<mpScaleX>( wxEmptyString, wxT( "°C" ), mpALIGN_BOTTOM );
752 }
753
754 m_axis_x->SetName( _( "Temperature (swept)" ) );
755 break;
756 }
757
758 if( !m_axis_y1 )
759 {
760 m_axis_y1 = new LIN_SCALE<mpScaleY>( wxEmptyString, wxT( "V" ), mpALIGN_LEFT );
763 }
764
765 if( !m_axis_y2 )
766 {
767 m_axis_y2 = new LIN_SCALE<mpScaleY>( wxEmptyString, wxT( "A" ), mpALIGN_RIGHT );
770 }
771
772 m_axis_y1->SetName( _( "Voltage (measured)" ) );
773 m_axis_y2->SetName( _( "Current" ) );
774
775 if( ( aNewTraceType & SPT_POWER ) && !m_axis_y3 )
776 {
777 m_plotWin->SetMargins( 30, 140, 45, 70 );
778
779 m_axis_y3 = new LIN_SCALE<mpScaleY>( wxEmptyString, wxT( "W" ), mpALIGN_FAR_RIGHT );
783 }
784
785 if( m_axis_y3 )
786 m_axis_y3->SetName( _( "Power" ) );
787 }
788}
789
790
792{
793 // Update bg and fg colors:
797
799}
800
801
803{
804 updateAxes();
806}
807
808
810{
811 int type = trace->GetType();
812 wxPenStyle penStyle = ( ( ( type & SPT_AC_PHASE ) || ( type & SPT_CURRENT ) ) && m_dotted_cp )
813 ? wxPENSTYLE_DOT
814 : wxPENSTYLE_SOLID;
815 trace->SetPen( wxPen( trace->GetTraceColour(), 2, penStyle ) );
816
817 m_sessionTraceColors[ trace->GetName() ] = trace->GetTraceColour();
818}
819
820
821TRACE* SIM_PLOT_TAB::GetOrAddTrace( const wxString& aVectorName, int aType )
822{
823 TRACE* trace = GetTrace( aVectorName, aType );
824
825 if( !trace )
826 {
827 updateAxes( aType );
828
829 if( GetSimType() == ST_TRAN || GetSimType() == ST_DC )
830 {
831 bool hasVoltageTraces = false;
832
833 for( const auto& [ id, candidate ] : m_traces )
834 {
835 if( candidate->GetType() & SPT_VOLTAGE )
836 {
837 hasVoltageTraces = true;
838 break;
839 }
840 }
841
842 if( !hasVoltageTraces )
843 {
844 if( m_axis_y2 )
845 m_axis_y2->SetMasterScale( nullptr );
846
847 if( m_axis_y3 )
848 m_axis_y3->SetMasterScale( nullptr );
849 }
850 }
851
852 trace = new TRACE( aVectorName, (SIM_TRACE_TYPE) aType );
853
854 if( m_sessionTraceColors.count( aVectorName ) )
855 trace->SetTraceColour( m_sessionTraceColors[ aVectorName ] );
856 else
858
859 UpdateTraceStyle( trace );
860 m_traces[ getTraceId( aVectorName, aType ) ] = trace;
861
862 m_plotWin->AddLayer( (mpLayer*) trace );
863 }
864
865 return trace;
866}
867
868
869void SIM_PLOT_TAB::SetTraceData( TRACE* trace, std::vector<double>& aX, std::vector<double>& aY )
870{
871 if( dynamic_cast<LOG_SCALE<mpScaleXLog>*>( m_axis_x ) )
872 {
873 // log( 0 ) is not valid.
874 if( aX.size() > 0 && aX[0] == 0 )
875 {
876 aX.erase( aX.begin() );
877 aY.erase( aY.begin() );
878 }
879 }
880
881 if( GetSimType() == ST_AC || GetSimType() == ST_FFT )
882 {
883 if( trace->GetType() & SPT_AC_PHASE )
884 {
885 for( double& pt : aY )
886 pt = pt * 180.0 / M_PI; // convert to degrees
887 }
888 else
889 {
890 for( double& pt : aY )
891 {
892 // log( 0 ) is not valid.
893 if( pt != 0 )
894 pt = 20 * log( pt ) / log( 10.0 ); // convert to dB
895 }
896 }
897 }
898
899 trace->SetData( aX, aY );
900
901 if( ( trace->GetType() & SPT_AC_PHASE ) || ( trace->GetType() & SPT_CURRENT ) )
902 trace->SetScale( m_axis_x, m_axis_y2 );
903 else if( trace->GetType() & SPT_POWER )
904 trace->SetScale( m_axis_x, m_axis_y3 );
905 else
906 trace->SetScale( m_axis_x, m_axis_y1 );
907
908 for( auto& [ cursorId, cursor ] : trace->GetCursors() )
909 {
910 if( cursor )
911 cursor->SetCoordX( cursor->GetCoords().x );
912 }
913}
914
915
917{
918 for( const auto& [ name, trace ] : m_traces )
919 {
920 if( trace == aTrace )
921 {
922 m_traces.erase( name );
923 break;
924 }
925 }
926
927 for( const auto& [ id, cursor ] : aTrace->GetCursors() )
928 {
929 if( cursor )
930 m_plotWin->DelLayer( cursor, true );
931 }
932
933 m_plotWin->DelLayer( aTrace, true, true );
934 ResetScales( false );
935}
936
937
938bool SIM_PLOT_TAB::DeleteTrace( const wxString& aVectorName, int aTraceType )
939{
940 if( TRACE* trace = GetTrace( aVectorName, aTraceType ) )
941 {
942 DeleteTrace( trace );
943 return true;
944 }
945
946 return false;
947}
948
949
950void SIM_PLOT_TAB::EnableCursor( const wxString& aVectorName, int aType, int aCursorId,
951 bool aEnable, const wxString& aSignalName )
952{
953 TRACE* t = GetTrace( aVectorName, aType );
954
955 if( t == nullptr || t->HasCursor( aCursorId ) == aEnable )
956 return;
957
958 if( aEnable )
959 {
960 CURSOR* cursor = new CURSOR( t, this );
961 mpWindow* win = GetPlotWin();
962 int width = win->GetXScreen() - win->GetMarginLeft() - win->GetMarginRight();
963 int center = win->GetMarginLeft() + KiROUND( width * ( aCursorId == 1 ? 0.4 : 0.6 ) );
964
965 cursor->SetName( aSignalName );
966 cursor->SetX( center );
967
968 t->SetCursor( aCursorId, cursor );
970 }
971 else
972 {
973 CURSOR* cursor = t->GetCursor( aCursorId );
974 t->SetCursor( aCursorId, nullptr );
975 m_plotWin->DelLayer( cursor, true );
976 }
977
978 // Notify the parent window about the changes
979 wxQueueEvent( GetParent(), new wxCommandEvent( EVT_SIM_CURSOR_UPDATE ) );
980}
981
982
983void SIM_PLOT_TAB::ResetScales( bool aIncludeX )
984{
985 if( m_axis_x && aIncludeX )
986 {
988
989 if( GetSimType() == ST_TRAN )
990 {
991 wxStringTokenizer tokenizer( GetSimCommand(), wxS( " \t\n\r" ), wxTOKEN_STRTOK );
992 wxString cmd = tokenizer.GetNextToken().Lower();
993
994 wxASSERT( cmd == wxS( ".tran" ) );
995
996 SPICE_VALUE step;
997 SPICE_VALUE end( 1.0 );
998 SPICE_VALUE start( 0.0 );
999
1000 if( tokenizer.HasMoreTokens() )
1001 step = SPICE_VALUE( tokenizer.GetNextToken() );
1002
1003 if( tokenizer.HasMoreTokens() )
1004 end = SPICE_VALUE( tokenizer.GetNextToken() );
1005
1006 if( tokenizer.HasMoreTokens() )
1007 start = SPICE_VALUE( tokenizer.GetNextToken() );
1008
1009 static_cast<TIME_SCALE*>( m_axis_x )->SetStartAndEnd( start.ToDouble(), end.ToDouble() );
1010 }
1011 }
1012
1013 if( m_axis_y1 )
1015
1016 if( m_axis_y2 )
1018
1019 if( m_axis_y3 )
1021
1022 for( auto& [ name, trace ] : m_traces )
1023 trace->UpdateScales();
1024}
1025
1026
1027wxDEFINE_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:382
mpWindow * GetPlotWin() const
Definition: sim_plot_tab.h:345
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:256
virtual ~SIM_PLOT_TAB()
void SetY1Scale(bool aLock, double aMin, double aMax)
mpInfoLegend * m_legend
Definition: sim_plot_tab.h:386
void SetY3Scale(bool aLock, double aMin, double aMax)
wxBoxSizer * m_sizer
Definition: sim_plot_tab.h:377
std::map< wxString, TRACE * > m_traces
Definition: sim_plot_tab.h:380
SIM_PLOT_COLORS m_colors
Definition: sim_plot_tab.h:372
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:384
SIM_PLOT_TAB(const wxString &aSimCommand, wxWindow *parent)
wxString GetUnitsX() const
void OnLanguageChanged() override
Getter for math plot window.
wxString getTraceId(const wxString &aVectorName, int aType) const
Construct the plot axes for DC simulation plot.
Definition: sim_plot_tab.h:360
TRACE * GetOrAddTrace(const wxString &aVectorName, int aType)
mpScaleY * m_axis_y1
Definition: sim_plot_tab.h:383
mpScaleY * m_axis_y3
Definition: sim_plot_tab.h:385
wxPoint m_LastLegendPosition
Definition: sim_plot_tab.h:357
wxString GetUnitsY1() const
std::map< wxString, wxColour > m_sessionTraceColors
Definition: sim_plot_tab.h:373
mpWindow * m_plotWin
Definition: sim_plot_tab.h:376
void updateAxes(int aNewTraceType=SIM_TRACE_TYPE::SPT_UNKNOWN)
wxString GetUnitsY3() const
SIM_TYPE GetSimType() const
Definition: sim_tab.cpp:71
const wxString & GetSimCommand() const
Definition: sim_tab.h:49
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:2536
double y2s(double y) const
Definition: mathplot.cpp:2573
double x2s(double x) const
Definition: mathplot.cpp:2567
double s2x(double plotCoordX) const
Definition: mathplot.cpp:2555
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:2366
int GetMarginLeft() const
Definition: mathplot.h:1184
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:2302
int GetScrX() const
Get current view's X dimension in device context units.
Definition: mathplot.h:995
int GetScrY() const
Get current view's Y dimension in device context units.
Definition: mathplot.h:1004
double p2x(wxCoord pixelCoordX)
Converts mpWindow (screen) pixel coordinates into graph (floating point) coordinates,...
Definition: mathplot.h:1048
void LimitView(bool aEnable)
Limit zooming & panning to the area used by the plots.
Definition: mathplot.h:1223
wxCoord x2p(double x)
Converts graph (floating point) coordinates into mpWindow (screen) pixel coordinates,...
Definition: mathplot.h:1056
int GetXScreen() const
Definition: mathplot.h:996
int GetMarginTop() const
Definition: mathplot.h:1178
bool DelLayer(mpLayer *layer, bool alsoDeleteObject=false, bool refreshDisplay=true)
Remove a plot layer from the canvas.
Definition: mathplot.cpp:2093
void UpdateAll()
Refresh display.
Definition: mathplot.cpp:2203
wxCoord y2p(double y)
Converts graph (floating point) coordinates into mpWindow (screen) pixel coordinates,...
Definition: mathplot.h:1060
int GetMarginRight() const
Definition: mathplot.h:1180
int GetMarginBottom() const
Definition: mathplot.h:1182
void EnableDoubleBuffer(bool enabled)
Enable/disable the double-buffering of the window, eliminating the flicker (default=disabled).
Definition: mathplot.h:1065
bool AddLayer(mpLayer *layer, bool refreshDisplay=true)
Add a plot layer to the canvas.
Definition: mathplot.cpp:2077
#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
wxFont GetStatusFont(wxWindow *aWindow)
Definition: ui_common.cpp:127
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition: eda_angle.h:426
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:85