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 The 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, see <https://www.gnu.org/licenses/>.
22 */
23
24#include <wx/tokenzr.h>
25
26#include <bitmaps.h>
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 <cmath>
35#include <limits>
36
37
38
39static wxString formatFloat( double x, int nDigits )
40{
41 wxString rv, fmt;
42
43 if( nDigits )
44 fmt.Printf( "%%.0%df", nDigits );
45 else
46 fmt = wxT( "%.0f" );
47
48 rv.Printf( fmt, x );
49
50 return rv;
51}
52
53
54static void getSISuffix( double x, const wxString& unit, int& power, wxString& suffix )
55{
56 const int n_powers = 11;
57
58 const struct
59 {
60 int exponent;
61 char suffix;
62 } powers[] =
63 {
64 { -18, 'a' },
65 { -15, 'f' },
66 { -12, 'p' },
67 { -9, 'n' },
68 { -6, 'u' },
69 { -3, 'm' },
70 { 0, 0 },
71 { 3, 'k' },
72 { 6, 'M' },
73 { 9, 'G' },
74 { 12, 'T' },
75 { 14, 'P' }
76 };
77
78 power = 0;
79 suffix = unit;
80
81 if( x == 0.0 )
82 return;
83
84 for( int i = 0; i < n_powers - 1; i++ )
85 {
86 double r_cur = pow( 10, powers[i].exponent );
87
88 if( fabs( x ) >= r_cur && fabs( x ) < r_cur * 1000.0 )
89 {
90 power = powers[i].exponent;
91
92 if( powers[i].suffix )
93 suffix = wxString( powers[i].suffix ) + unit;
94 else
95 suffix = unit;
96
97 return;
98 }
99 }
100}
101
102
103static int countDecimalDigits( double x, int maxDigits )
104{
105 if( std::isnan( x ) )
106 return 0;
107
108 auto countSignificantDigits =
109 [&]( int64_t k )
110 {
111 while( k && ( k % 10LL ) == 0LL )
112 k /= 10LL;
113
114 int n = 0;
115
116 while( k != 0LL )
117 {
118 n++;
119 k /= 10LL;
120 }
121
122 return n;
123 };
124
125 int64_t k = (int)( ( x - floor( x ) ) * pow( 10.0, (double) maxDigits ) );
126 int n = countSignificantDigits( k );
127
128 // check for trailing 9's
129 n = std::min( n, countSignificantDigits( k + 1 ) );
130
131 return n;
132}
133
134
135template <typename T_PARENT>
136class LIN_SCALE : public T_PARENT
137{
138public:
139 LIN_SCALE( const wxString& name, const wxString& unit, int flags ) :
140 T_PARENT( name, flags, false ),
141 m_unit( unit )
142 {};
143
144 wxString GetUnits() const { return m_unit; }
145
146private:
147 void formatLabels() override
148 {
149 double maxVis = T_PARENT::AbsVisibleMaxValue();
150
151 wxString suffix;
152 int power = 0;
153 int digits = 0;
154 int constexpr MAX_DIGITS = 3;
155 int constexpr MAX_DISAMBIGUATION_DIGITS = 6;
156 bool duplicateLabels = false;
157
158 getSISuffix( maxVis, m_unit, power, suffix );
159
160 double sf = pow( 10.0, power );
161
162 for( mpScaleBase::TICK_LABEL& l : T_PARENT::m_tickLabels )
163 digits = std::max( digits, countDecimalDigits( l.pos / sf, MAX_DIGITS ) );
164
165 do
166 {
167 for( size_t ii = 0; ii < T_PARENT::m_tickLabels.size(); ++ii )
168 {
169 mpScaleBase::TICK_LABEL& l = T_PARENT::m_tickLabels[ii];
170
171 l.label = formatFloat( l.pos / sf, digits );
172 l.visible = true;
173
174 if( ii > 0 && l.label == T_PARENT::m_tickLabels[ii-1].label )
175 duplicateLabels = true;
176 }
177 }
178 while( duplicateLabels && ++digits <= MAX_DISAMBIGUATION_DIGITS );
179
180 if( m_base_axis_label.IsEmpty() )
181 m_base_axis_label = T_PARENT::GetName();
182
183 T_PARENT::SetName( wxString::Format( "%s (%s)", m_base_axis_label, suffix ) );
184 }
185
186private:
187 const wxString m_unit;
189};
190
191
192class TIME_SCALE : public LIN_SCALE<mpScaleX>
193{
194public:
195 TIME_SCALE( const wxString& name, const wxString& unit, int flags ) :
196 LIN_SCALE( name, unit, flags ),
197 m_startTime( 0.0 ),
198 m_endTime( 1.0 )
199 {};
200
201 void ExtendDataRange( double minV, double maxV ) override
202 {
203 LIN_SCALE::ExtendDataRange( minV, maxV );
204
205 // Time is never longer than the simulation itself
206 if( m_minV < m_startTime )
208
209 if( m_maxV > m_endTime )
211 };
212
213 void SetStartAndEnd( double aStartTime, double aEndTime )
214 {
215 m_startTime = aStartTime;
216 m_endTime = aEndTime;
218 }
219
220 void ResetDataRange() override
221 {
224 m_rangeSet = true;
225 }
226
227protected:
229 double m_endTime;
230};
231
232
233template <typename T_PARENT>
234class LOG_SCALE : public T_PARENT
235{
236public:
237 LOG_SCALE( const wxString& name, const wxString& unit, int flags ) :
238 T_PARENT( name, flags, false ),
239 m_unit( unit )
240 {};
241
242 wxString GetUnits() const { return m_unit; }
243
244private:
245 void formatLabels() override
246 {
247 wxString suffix;
248 int power;
249 int constexpr MAX_DIGITS = 3;
250
251 for( mpScaleBase::TICK_LABEL& l : T_PARENT::m_tickLabels )
252 {
253 getSISuffix( l.pos, m_unit, power, suffix );
254 double sf = pow( 10.0, power );
255 int k = countDecimalDigits( l.pos / sf, MAX_DIGITS );
256
257 l.label = formatFloat( l.pos / sf, k ) + suffix;
258 l.visible = true;
259 }
260 }
261
262private:
263 const wxString m_unit;
264};
265
266
267bool SMITH_GRID::GetChartView( mpWindow& aWindow, double aZoom, const wxRealPoint& aPan, SMITH_VIEW& aView )
268{
269 int mL = aWindow.GetMarginLeft(), mT = aWindow.GetMarginTop();
270 int plotW = aWindow.GetScrX() - mL - aWindow.GetMarginRight();
271 int plotH = aWindow.GetScrY() - mT - aWindow.GetMarginBottom();
272 int radius = std::min( plotW, plotH ) / 2 - 15;
273
274 if( radius <= 20 )
275 return false;
276
277 aView.center = wxPoint( mL + plotW / 2, mT + plotH / 2 );
278 aView.radius = radius * aZoom;
279 aView.zoom = aZoom;
280 aView.pan = aPan;
281 aView.plotRect = wxRect( mL, mT, plotW, plotH );
282
283 return true;
284}
285
286
287static bool smithView( mpWindow& aWindow, SMITH_VIEW& aView )
288{
289 // the layers of a Smith chart are drawn on the SIM_VIEW carrying its own pan and zoom
290 SIM_VIEW* view = dynamic_cast<SIM_VIEW*>( &aWindow );
291 double zoom = view ? view->GetSmithZoom() : 1.0;
292 wxRealPoint pan = view ? view->GetSmithPan() : wxRealPoint( 0.0, 0.0 );
293
294 return SMITH_GRID::GetChartView( aWindow, zoom, pan, aView );
295}
296
297
298void SMITH_GRID::Plot( wxDC& aDC, mpWindow& aWindow )
299{
300 if( !m_visible )
301 return;
302
303 SMITH_VIEW view;
304
305 if( !smithView( aWindow, view ) )
306 return;
307
308 const wxRect& plotRect = view.plotRect;
309 int mL = plotRect.x, mT = plotRect.y, plotW = plotRect.width, plotH = plotRect.height;
310
311 aDC.SetClippingRegion( plotRect );
312
313 // enough segments to keep the chord error under a pixel at this circle's screen radius
314 auto segmentsFor =
315 [&]( double aR ) -> int
316 {
317 return std::clamp( KiROUND( M_PI * std::sqrt( aR * view.radius ) ), 64, 4096 );
318 };
319
320 // draw a gamma-plane circle, keeping only the parts inside the unit circle
321 auto drawClippedCircle =
322 [&]( double aCx, double aCy, double aR )
323 {
324 constexpr double LIMIT = 1.0002;
325
326 std::vector<wxPoint> run;
327 int segments = segmentsFor( aR );
328
329 for( int ii = 0; ii <= segments; ii++ )
330 {
331 double angle = 2.0 * M_PI * ii / segments;
332 double re = aCx + aR * cos( angle );
333 double im = aCy + aR * sin( angle );
334
335 if( re * re + im * im <= LIMIT )
336 {
337 run.push_back( view.ToScreen( re, im ) );
338 }
339 else
340 {
341 if( run.size() > 1 )
342 aDC.DrawLines( (int) run.size(), run.data() );
343
344 run.clear();
345 }
346 }
347
348 if( run.size() > 1 )
349 aDC.DrawLines( (int) run.size(), run.data() );
350 };
351
352 auto formatValue =
353 []( double aValue ) -> wxString
354 {
355 return wxString::Format( wxS( "%g" ), aValue );
356 };
357
358 // ohm labels for a single reference impedance, normalized labels without one
359 double labelScale = m_z0 > 0.0 ? m_z0 : 1.0;
360
361 static const std::vector<double> baseVals = { 0.2, 0.5, 1.0, 2.0, 5.0 };
362 std::vector<double> gridVals = baseVals;
363
364 constexpr double FINEST_SCALE = 2.0 / ( 1.0 + 20.0 );
365
366 wxRealPoint lowGamma = view.ToGamma( wxPoint( mL, mT + plotH ) );
367 wxRealPoint highGamma = view.ToGamma( wxPoint( mL + plotW, mT ) );
368
369 auto poleDistance =
370 [&]( double aRe )
371 {
372 return std::hypot( std::max( { lowGamma.x - aRe, aRe - highGamma.x, 0.0 } ),
373 std::max( { lowGamma.y, -highGamma.y, 0.0 } ) );
374 };
375
376 bool zoomedIn = ( plotW / 2.0 ) / view.radius < 1.0;
377 bool converged = zoomedIn && std::min( poleDistance( 1.0 ), poleDistance( -1.0 ) ) < FINEST_SCALE;
378
379 if( view.zoom >= 2.0 && !converged )
380 gridVals.insert( gridVals.end(), { 0.1, 0.3, 0.4, 0.7, 1.5, 3.0, 10.0 } );
381
382 if( view.zoom >= 5.0 && !converged )
383 gridVals.insert( gridVals.end(), { 0.05, 0.15, 0.6, 0.8, 1.2, 1.7, 2.5, 4.0, 7.0, 20.0 } );
384
385 wxPen gridPen = m_pen;
386 gridPen.SetStyle( wxPENSTYLE_DOT );
387
388 aDC.SetBrush( *wxTRANSPARENT_BRUSH );
389 aDC.SetFont( GetPlotFont() );
390 aDC.SetTextForeground( m_pen.GetColour() );
391 aDC.SetPen( gridPen );
392
393 // constant resistance circles, centered on the real axis, tangent at gamma = 1
394 for( double r : gridVals )
395 drawClippedCircle( r / ( 1.0 + r ), 0.0, 1.0 / ( 1.0 + r ) );
396
397 // constant reactance arcs, one per sign, clipped to the unit circle
398 for( double x : gridVals )
399 {
400 drawClippedCircle( 1.0, 1.0 / x, 1.0 / x );
401 drawClippedCircle( 1.0, -1.0 / x, 1.0 / x );
402 }
403
404 aDC.SetPen( m_pen );
405 aDC.DrawLine( view.ToScreen( -1.0, 0.0 ), view.ToScreen( 1.0, 0.0 ) );
406 aDC.DrawCircle( view.ToScreen( 0.0, 0.0 ), KiROUND( view.radius ) );
407
408 // Label each gridline at its axis/rim anchor, or at the plot edge when the anchor is
409 // panned/zoomed out of view.
410 constexpr double LABEL_LIMIT = 1.0002;
411
412 auto anchorPos =
413 [&]( double aRe, double aIm, wxPoint& aOut ) -> bool
414 {
415 if( aRe * aRe + aIm * aIm > LABEL_LIMIT )
416 return false;
417
418 wxPoint p = view.ToScreen( aRe, aIm );
419
420 if( !plotRect.Contains( p ) )
421 return false;
422
423 aOut = p;
424 return true;
425 };
426
427 // anchor off-screen, put the label where the gridline meets the plot edge
428 auto edgePos =
429 [&]( double aCx, double aCy, double aR, wxPoint& aOut ) -> bool
430 {
431 int bestMargin = std::numeric_limits<int>::max();
432 bool found = false;
433 int segments = segmentsFor( aR );
434
435 for( int ii = 0; ii <= segments; ii++ )
436 {
437 double angle = 2.0 * M_PI * ii / segments;
438 double re = aCx + aR * cos( angle );
439 double im = aCy + aR * sin( angle );
440
441 if( re * re + im * im > LABEL_LIMIT )
442 continue;
443
444 wxPoint p = view.ToScreen( re, im );
445
446 if( !plotRect.Contains( p ) )
447 continue;
448
449 int margin = std::min( { p.x - mL, mL + plotW - p.x, p.y - mT, mT + plotH - p.y } );
450
451 if( margin < bestMargin )
452 {
453 bestMargin = margin;
454 aOut = p;
455 found = true;
456 }
457 }
458
459 return found;
460 };
461
462 auto clampToPlot =
463 [&]( const wxPoint& aPos, const wxSize& aExt ) -> wxPoint
464 {
465 return wxPoint( std::clamp( aPos.x, mL + 1, mL + plotW - aExt.x - 1 ),
466 std::clamp( aPos.y, mT + 1, mT + plotH - aExt.y - 1 ) );
467 };
468
469 std::vector<wxRect> placed;
470
471 auto drawLabel =
472 [&]( const wxString& aLabel, const wxPoint& aPos, const wxSize& aExt )
473 {
474 wxPoint pos = clampToPlot( aPos, aExt );
475 wxRect box( pos, aExt );
476
477 box.Inflate( 2, 1 );
478
479 for( const wxRect& seen : placed )
480 {
481 if( seen.Intersects( box ) )
482 return;
483 }
484
485 placed.push_back( box );
486 aDC.DrawText( aLabel, pos );
487 };
488
489 auto drawEdgeLabel =
490 [&]( const wxString& aLabel, const wxPoint& aAt )
491 {
492 wxSize ext = aDC.GetTextExtent( aLabel );
493
494 drawLabel( aLabel, wxPoint( aAt.x - ext.x / 2, aAt.y + 3 ), ext );
495 };
496
497 // push reactance labels just outside the rim, clamped so j50 and -j50 are not clipped
498 auto drawRimLabel = [&]( const wxString& aLabel, const wxPoint& aAt, double aRe, double aIm )
499 {
500 wxSize ext = aDC.GetTextExtent( aLabel );
501 wxPoint pos = aAt;
502
503 pos.x += KiROUND( aRe * 6 );
504 pos.y -= KiROUND( aIm * 6 );
505 pos.x -= KiROUND( ext.x * ( 1.0 - aRe ) / 2.0 );
506 pos.y -= KiROUND( ext.y * ( 1.0 + aIm ) / 2.0 );
507
508 drawLabel( aLabel, pos, ext );
509 };
510
511 wxPoint at;
512
513 // short (r = 0)
514 if( anchorPos( -1.0, 0.0, at ) )
515 drawRimLabel( wxS( "0" ), at, -1.0, 0.0 );
516 else if( edgePos( 0.0, 0.0, 1.0, at ) )
517 drawEdgeLabel( wxS( "0" ), at );
518
519 for( double r : gridVals )
520 {
521 if( anchorPos( ( r - 1.0 ) / ( r + 1.0 ), 0.0, at ) || edgePos( r / ( 1.0 + r ), 0.0, 1.0 / ( 1.0 + r ), at ) )
522 {
523 drawEdgeLabel( formatValue( r * labelScale ), at );
524 }
525 }
526
527 for( double x : gridVals )
528 {
529 double d = x * x + 1.0;
530 double re = ( x * x - 1.0 ) / d;
531 double im = 2.0 * x / d;
532 wxString posLabel = wxS( "j" ) + formatValue( x * labelScale );
533 wxString negLabel = wxS( "-j" ) + formatValue( x * labelScale );
534
535 if( anchorPos( re, im, at ) )
536 drawRimLabel( posLabel, at, re, im );
537 else if( edgePos( 1.0, 1.0 / x, 1.0 / x, at ) )
538 drawEdgeLabel( posLabel, at );
539
540 if( anchorPos( re, -im, at ) )
541 drawRimLabel( negLabel, at, re, -im );
542 else if( edgePos( 1.0, -1.0 / x, 1.0 / x, at ) )
543 drawEdgeLabel( negLabel, at );
544 }
545
546 // the ohm labels mean nothing unless the reference impedance is named
547 wxString note;
548
549 if( m_z0 > 0.0 )
550 note = wxString::Format( wxS( "Z0 = %s Ω" ), formatValue( m_z0 ) );
551 else if( m_mixedReferences )
552 note = _( "Normalized Z/Z0 (ports differ)" );
553 else
554 note = _( "Normalized Z/Z0" );
555
556 wxSize ext = aDC.GetTextExtent( note );
557
558 aDC.DrawText( note, mL + 4, mT + plotH - ext.y - 4 );
559
560 aDC.DestroyClippingRegion();
561}
562
563
564void SMITH_TRACE::Plot( wxDC& aDC, mpWindow& aWindow )
565{
566 if( !m_visible )
567 return;
568
569 SMITH_VIEW view;
570
571 if( !smithView( aWindow, view ) )
572 return;
573
574 const std::vector<double>& xs = GetDataX();
575 const std::vector<double>& ys = GetDataY();
576 size_t count = std::min( xs.size(), ys.size() );
577
578 if( count == 0 )
579 return;
580
581 aDC.SetPen( m_pen );
582 aDC.SetClippingRegion( view.plotRect );
583
584 size_t chunk = GetSweepSize();
585
586 if( GetSweepCount() <= 1 || chunk == std::numeric_limits<size_t>::max() || chunk == 0 )
587 chunk = count;
588
589 std::vector<wxPoint> pts;
590
591 auto flush =
592 [&]()
593 {
594 if( pts.size() > 1 )
595 {
596 aDC.DrawLines( (int) pts.size(), pts.data() );
597 }
598 else if( pts.size() == 1 )
599 {
600 aDC.SetBrush( wxBrush( m_pen.GetColour() ) );
601 aDC.DrawCircle( pts[0], 2 );
602 }
603
604 pts.clear();
605 };
606
607 for( size_t start = 0; start < count; start += chunk )
608 {
609 size_t end = std::min( count, start + chunk );
610
611 for( size_t ii = start; ii < end; ii++ )
612 {
613 // a non-finite sample breaks the locus rather than drawing a bogus segment
614 if( !std::isfinite( xs[ii] ) || !std::isfinite( ys[ii] ) )
615 {
616 flush();
617 continue;
618 }
619
620 pts.emplace_back( view.ToScreen( xs[ii], ys[ii] ) );
621 }
622
623 flush();
624 }
625
626 aDC.DestroyClippingRegion();
627}
628
629
631{
632 const std::vector<double>& re = m_trace->GetDataX();
633 const std::vector<double>& im = m_trace->GetDataY();
634 const std::vector<double>& freqs = static_cast<SMITH_TRACE*>( m_trace )->GetFrequencies();
635
636 size_t count = std::min( re.size(), im.size() );
637
638 if( count == 0 )
639 return;
640
641 m_index = std::clamp( aIndex, 0, (int) count - 1 );
642 m_gamma = wxRealPoint( re[m_index], im[m_index] );
643
644 // no frequency data for this sample, keep the previous x so a saved position stays finite
645 double freq = m_index < (int) freqs.size() ? freqs[m_index] : m_coords.x;
646
647 m_coords = wxRealPoint( freq, std::hypot( m_gamma.x, m_gamma.y ) );
648}
649
650
652{
653 const std::vector<double>& freqs = static_cast<SMITH_TRACE*>( m_trace )->GetFrequencies();
654 const std::vector<double>& re = m_trace->GetDataX();
655 const std::vector<double>& im = m_trace->GetDataY();
656
657 // frequencies repeat identically per run, search only the run the cursor is on
658 // so a frequency-keyed move cannot silently hop to run 0
659 size_t begin = 0;
660 size_t end = freqs.size();
661 size_t chunk = m_trace->GetSweepSize();
662
663 if( m_trace->GetSweepCount() > 1
664 && chunk > 0
665 && chunk != std::numeric_limits<size_t>::max()
666 && m_index >= 0
667 && (size_t) m_index < freqs.size() )
668 {
669 begin = ( (size_t) m_index / chunk ) * chunk;
670 end = std::min( freqs.size(), begin + chunk );
671 }
672
673 int best = -1;
674 double bestDist = std::numeric_limits<double>::max();
675
676 for( size_t ii = begin; ii < end; ii++ )
677 {
678 if( !std::isfinite( freqs[ii] ) )
679 continue;
680
681 if( ii < re.size() && ii < im.size() && ( !std::isfinite( re[ii] ) || !std::isfinite( im[ii] ) ) )
682 continue;
683
684 double dist = std::fabs( freqs[ii] - aFreq );
685
686 if( dist < bestDist )
687 {
688 bestDist = dist;
689 best = (int) ii;
690 }
691 }
692
693 if( best >= 0 )
694 snapToIndex( best );
695}
696
697
698void SMITH_CURSOR::SetCoordX( double aValue )
699{
700 m_requestFreq = aValue;
701
703
704 if( m_window )
705 m_window->Refresh();
706}
707
708
710{
711 if( static_cast<SMITH_TRACE*>( m_trace )->GetFrequencies().empty() )
712 {
713 // no data yet, remember the frequency and resolve it once the sim fills in
715 m_pendingFreq = true;
716 m_updateRequired = false;
717 return;
718 }
719
721 m_pendingFreq = false;
722 m_updateRequired = false;
723 m_updateRef = true;
724}
725
726
727void SMITH_CURSOR::Move( wxPoint aDelta )
728{
729 m_dragging = true;
730 Update();
731 mpInfoLayer::Move( aDelta );
732}
733
734
736{
737 // skip CURSOR's axis reference, the marker follows the locus
739}
740
741
742bool SMITH_CURSOR::Inside( const wxPoint& aPoint ) const
743{
744 if( !m_window || m_index < 0 )
745 return false;
746
747 SMITH_VIEW view;
748
749 if( !smithView( *m_window, view ) )
750 return false;
751
752 wxPoint marker = view.ToScreen( m_gamma.x, m_gamma.y );
753
754 return std::abs( aPoint.x - marker.x ) <= DRAG_MARGIN && std::abs( aPoint.y - marker.y ) <= DRAG_MARGIN;
755}
756
757
758void SMITH_CURSOR::Plot( wxDC& aDC, mpWindow& aWindow )
759{
760 if( !m_window )
761 m_window = &aWindow;
762
763 if( !m_visible )
764 return;
765
766 SMITH_VIEW view;
767
768 if( !smithView( aWindow, view ) )
769 return;
770
771 const std::vector<double>& re = m_trace->GetDataX();
772 const std::vector<double>& im = m_trace->GetDataY();
773 size_t count = std::min( re.size(), im.size() );
774
775 if( count == 0 )
776 return;
777
778 if( m_pendingFreq )
779 {
780 // sim data has arrived, restore the frequency saved from the workbook
782 m_pendingFreq = false;
783 m_updateRequired = false;
784 m_updateRef = true;
785 }
786 else if( m_updateRequired )
787 {
788 if( m_dragging )
789 {
790 // snap to the locus sample closest to the drag position
791 int best = -1;
792 double bestDist = std::numeric_limits<double>::max();
793
794 for( size_t ii = 0; ii < count; ii++ )
795 {
796 if( !std::isfinite( re[ii] ) || !std::isfinite( im[ii] ) )
797 continue;
798
799 wxPoint p = view.ToScreen( re[ii], im[ii] );
800 double dx = (double) p.x - m_dim.x;
801 double dy = (double) p.y - m_dim.y;
802 double dist = dx * dx + dy * dy;
803
804 if( dist < bestDist )
805 {
806 bestDist = dist;
807 best = (int) ii;
808 }
809 }
810
811 if( best >= 0 )
812 {
813 snapToIndex( best );
815 }
816
817 m_dragging = false;
818 }
819 else
820 {
821 // the trace data changed under the cursor, follow the frequency rather than
822 // the screen position so a re-run cannot hop to another point of the locus
824 }
825
826 m_updateRequired = false;
827
828 // Notify the parent window about the changes
829 wxQueueEvent( aWindow.GetParent(), new wxCommandEvent( EVT_SIM_CURSOR_UPDATE ) );
830 }
831 else
832 {
833 if( m_index < 0 )
834 {
835 snapToIndex( (int) count / 2 );
837 }
838
839 m_updateRef = true;
840 }
841
842 wxPoint marker = view.ToScreen( m_gamma.x, m_gamma.y );
843
844 m_dim.SetX( marker.x );
845 m_dim.SetY( marker.y );
846
847 if( m_updateRef )
848 {
850 m_updateRef = false;
851 }
852
853 wxPen pen = GetPen();
854 wxColour fg = aWindow.GetForegroundColour();
855 COLOR4D cursorColor = COLOR4D( m_trace->GetTraceColour() ).Mix( fg, 0.6 );
856
857 pen.SetColour( cursorColor.ToColour() );
858 pen.SetStyle( wxPENSTYLE_SOLID );
859 aDC.SetPen( pen );
860 aDC.SetBrush( *wxTRANSPARENT_BRUSH );
861
862 aDC.DrawCircle( marker, 4 );
863 aDC.DrawLine( marker.x - 8, marker.y, marker.x - 4, marker.y );
864 aDC.DrawLine( marker.x + 4, marker.y, marker.x + 8, marker.y );
865 aDC.DrawLine( marker.x, marker.y - 8, marker.x, marker.y - 4 );
866 aDC.DrawLine( marker.x, marker.y + 4, marker.x, marker.y + 8 );
867
868 double gm = std::hypot( m_gamma.x, m_gamma.y );
869 double z0 = static_cast<SMITH_TRACE*>( m_trace )->GetReferenceImpedance();
870 double freq = m_coords.x;
871 double zr, zi;
872
873 auto formatSI = []( double aValue, const wxString& aUnit ) -> wxString
874 {
875 if( std::isnan( aValue ) )
876 return wxS( "--" );
877
878 int power = 0;
879 wxString suffix;
880
881 getSISuffix( aValue, aUnit, power, suffix );
882
883 double sf = pow( 10.0, power );
884
885 return formatFloat( aValue / sf, 3 ) + wxS( " " ) + suffix;
886 };
887
888 std::vector<wxString> lines;
889
890 lines.push_back( getID() + wxS( ": f = " ) + formatSI( freq, wxS( "Hz" ) ) );
891
892 // ohms need the port impedance, without one only the normalized z is known
893 bool absolute = z0 > 0.0;
894
895 if( !SMITH_MATH::GammaToImpedance( m_gamma.x, m_gamma.y, absolute ? z0 : 1.0, zr, zi ) )
896 {
897 lines.push_back( wxS( "Z = inf" ) );
898 }
899 else if( absolute )
900 {
901 lines.push_back( wxString::Format( wxS( "Z = %s %s j%s" ),
902 formatSI( zr, wxS( "Ω" ) ),
903 zi < 0 ? wxS( "-" ) : wxS( "+" ),
904 formatSI( std::fabs( zi ), wxS( "Ω" ) ) ) );
905
906 // series equivalent of the reactance at the marker frequency
907 if( std::isfinite( freq ) && freq > 0.0 && zi != 0.0 )
908 {
909 if( zi > 0.0 )
910 lines.push_back( wxS( "L = " ) + formatSI( SMITH_MATH::SeriesInductance( zi, freq ), wxS( "H" ) ) );
911 else
912 lines.push_back( wxS( "C = " ) + formatSI( SMITH_MATH::SeriesCapacitance( zi, freq ), wxS( "F" ) ) );
913 }
914 }
915 else
916 {
917 lines.push_back( wxString::Format( wxS( "z = %s %s j%s" ),
918 formatFloat( zr, 3 ),
919 zi < 0 ? wxS( "-" ) : wxS( "+" ),
920 formatFloat( std::fabs( zi ), 3 ) ) );
921 }
922
923 double rl = SMITH_MATH::ReturnLoss( gm );
924 double vswr = SMITH_MATH::VSWR( gm );
925
926 if( std::isfinite( rl ) )
927 lines.push_back( wxString::Format( wxS( "RL = %s dB" ), formatFloat( rl, 1 ) ) );
928 else
929 lines.push_back( wxS( "RL = inf" ) );
930
931 if( std::isfinite( vswr ) )
932 lines.push_back( wxString::Format( wxS( "VSWR = %s" ), formatFloat( vswr, 2 ) ) );
933 else
934 lines.push_back( wxS( "VSWR = inf" ) );
935
936 aDC.SetFont( GetPlotFont() );
937
938 int boxW = 0;
939 int boxH = 0;
940 int lineH = aDC.GetTextExtent( wxS( "M" ) ).y;
941
942 for( const wxString& line : lines )
943 boxW = std::max( boxW, aDC.GetTextExtent( line ).x );
944
945 boxW += 8;
946 boxH = (int) lines.size() * lineH + 6;
947
948 wxPoint boxPos( marker.x + ( marker.x < aWindow.GetScrX() / 2 ? 12 : -12 - boxW ),
949 marker.y + ( marker.y < aWindow.GetScrY() / 2 ? 12 : -12 - boxH ) );
950
951 boxPos.x = std::clamp( boxPos.x, 0, std::max( 0, aWindow.GetScrX() - boxW ) );
952 boxPos.y = std::clamp( boxPos.y, 0, std::max( 0, aWindow.GetScrY() - boxH ) );
953
954 wxBrush labelBrush( aWindow.GetBackgroundColour() );
955
956 aDC.SetBrush( labelBrush );
957 aDC.DrawRectangle( wxRect( boxPos, wxSize( boxW, boxH ) ) );
958 aDC.SetTextForeground( cursorColor.ToColour() );
959
960 for( size_t ii = 0; ii < lines.size(); ii++ )
961 aDC.DrawText( lines[ii], boxPos.x + 4, boxPos.y + 3 + (int) ii * lineH );
962}
963
964
965void CURSOR::SetCoordX( double aValue )
966{
967 wxRealPoint oldCoords = m_coords;
968
969 doSetCoordX( aValue );
970 m_updateRequired = false;
971 m_updateRef = true;
972
973 if( m_window )
974 {
975 wxRealPoint delta = m_coords - oldCoords;
976 mpInfoLayer::Move( wxPoint( m_window->x2p( m_trace->x2s( delta.x ) ),
977 m_window->y2p( m_trace->y2s( delta.y ) ) ) );
978
979 m_window->Refresh();
980 }
981}
982
983
984void CURSOR::Move( wxPoint aDelta )
985{
986 Update();
987
988 if( m_trace->IsMultiRun() && m_window
989 && m_trace->GetSweepCount() > 1
990 && m_trace->GetSweepSize() != std::numeric_limits<size_t>::max() )
991 {
992 int newY = m_reference.y + aDelta.y;
993
994 double plotY = m_window->p2y( newY );
995 m_snapTargetY = m_trace->s2y( plotY );
996 m_snapToNearest = true;
997 }
998
999 mpInfoLayer::Move( aDelta );
1000}
1001
1002
1003bool CURSOR::OnDoubleClick( const wxPoint& aPoint, mpWindow& aWindow )
1004{
1005 if( !Inside( aPoint ) )
1006 return false;
1007
1008 if( !m_trace->IsMultiRun() )
1009 return false;
1010
1011 int sweepCount = m_trace->GetSweepCount();
1012 size_t sweepSize = m_trace->GetSweepSize();
1013
1014 if( sweepCount <= 1 )
1015 return false;
1016
1017 if( sweepSize == std::numeric_limits<size_t>::max() || sweepSize == 0 )
1018 return false;
1019
1020 if( m_sweepIndex < 0 || m_sweepIndex >= sweepCount )
1021 m_sweepIndex = 0;
1022
1023 m_sweepIndex = ( m_sweepIndex + 1 ) % sweepCount;
1024
1025 Update();
1026 m_updateRef = true;
1027 m_window = &aWindow;
1028 aWindow.Refresh();
1029
1030 return true;
1031}
1032
1033
1034void CURSOR::doSetCoordX( double aValue )
1035{
1036 m_coords.x = aValue;
1037
1038 const std::vector<double>& dataX = m_trace->GetDataX();
1039 const std::vector<double>& dataY = m_trace->GetDataY();
1040
1041 if( dataX.size() <= 1 )
1042 return;
1043
1044 bool snapToNearest = m_snapToNearest;
1045 double snapTargetY = m_snapTargetY;
1046 m_snapToNearest = false;
1047
1048 size_t startIdx = 0;
1049 size_t endIdx = dataX.size();
1050 int sweepCount = m_trace->GetSweepCount();
1051 size_t sweepSize = m_trace->GetSweepSize();
1052
1053 if( snapToNearest && m_trace->IsMultiRun()
1054 && sweepCount > 1
1055 && sweepSize != std::numeric_limits<size_t>::max()
1056 && sweepSize > 0
1057 && std::isfinite( snapTargetY ) )
1058 {
1059 double bestDistance = std::numeric_limits<double>::infinity();
1060 int bestSweep = m_sweepIndex;
1061 bool found = false;
1062
1063 for( int sweepIdx = 0; sweepIdx < sweepCount; ++sweepIdx )
1064 {
1065 size_t candidateStart = static_cast<size_t>( sweepIdx ) * sweepSize;
1066 size_t candidateEnd = std::min( dataX.size(), candidateStart + sweepSize );
1067
1068 if( candidateStart >= candidateEnd )
1069 continue;
1070
1071 auto candidateBegin = dataX.begin() + candidateStart;
1072 auto candidateEndIt = dataX.begin() + candidateEnd;
1073 auto candidateMaxIt = std::upper_bound( candidateBegin, candidateEndIt, m_coords.x );
1074 int candidateMaxIdx = candidateMaxIt - dataX.begin();
1075 int candidateMinIdx = candidateMaxIdx - 1;
1076
1077 if( candidateMinIdx < (int) candidateStart
1078 || candidateMaxIdx >= (int) candidateEnd
1079 || candidateMaxIdx >= (int) dataX.size() )
1080 {
1081 continue;
1082 }
1083
1084 double leftX = dataX[candidateMinIdx];
1085 double rightX = dataX[candidateMaxIdx];
1086
1087 if( leftX == rightX )
1088 continue;
1089
1090 double leftY = dataY[candidateMinIdx];
1091 double rightY = dataY[candidateMaxIdx];
1092 double value = leftY + ( rightY - leftY ) / ( rightX - leftX ) * ( m_coords.x - leftX );
1093 double distance = std::fabs( value - snapTargetY );
1094
1095 if( distance < bestDistance )
1096 {
1097 bestDistance = distance;
1098 bestSweep = sweepIdx;
1099 found = true;
1100 }
1101 }
1102
1103 if( found )
1104 m_sweepIndex = bestSweep;
1105 }
1106
1107 if( m_trace->IsMultiRun()
1108 && sweepCount > 1
1109 && sweepSize != std::numeric_limits<size_t>::max()
1110 && sweepSize > 0 )
1111 {
1112 size_t available = static_cast<size_t>( sweepCount ) * sweepSize;
1113
1114 if( available <= dataX.size() )
1115 {
1116 if( m_sweepIndex < 0 || m_sweepIndex >= sweepCount )
1117 m_sweepIndex = std::max( sweepCount - 1, 0 );
1118
1119 startIdx = static_cast<size_t>( m_sweepIndex ) * sweepSize;
1120 endIdx = std::min( dataX.size(), startIdx + sweepSize );
1121 }
1122 else
1123 {
1124 m_sweepIndex = 0;
1125 }
1126 }
1127 else
1128 {
1129 m_sweepIndex = 0;
1130 }
1131
1132 if( startIdx >= endIdx )
1133 {
1134 m_coords.y = NAN;
1135 return;
1136 }
1137
1138 auto beginIt = dataX.begin() + startIdx;
1139 auto endIt = dataX.begin() + endIdx;
1140
1141 // Find the closest point coordinates
1142 auto maxXIt = std::upper_bound( beginIt, endIt, m_coords.x );
1143 int maxIdx = maxXIt - dataX.begin();
1144 int minIdx = maxIdx - 1;
1145
1146 // Out of bounds checks
1147 if( minIdx < (int) startIdx || maxIdx >= (int) endIdx || maxIdx >= (int) dataX.size() )
1148 {
1149 // Simulation may not be complete yet, or we may have a cursor off the beginning or end
1150 // of the data. Either way, that's where the user put it. Don't second guess them; just
1151 // leave its y value undefined.
1152 m_coords.y = NAN;
1153 return;
1154 }
1155
1156 const double leftX = dataX[minIdx];
1157 const double rightX = dataX[maxIdx];
1158 const double leftY = dataY[minIdx];
1159 const double rightY = dataY[maxIdx];
1160
1161 // Linear interpolation
1162 m_coords.y = leftY + ( rightY - leftY ) / ( rightX - leftX ) * ( m_coords.x - leftX );
1163}
1164
1165
1167{
1168 for( const auto& [ id, cursor ] : m_trace->GetCursors() )
1169 {
1170 if( cursor == this )
1171 return wxString::Format( _( "%d" ), id );
1172 }
1173
1174 return wxEmptyString;
1175}
1176
1177
1178void CURSOR::Plot( wxDC& aDC, mpWindow& aWindow )
1179{
1180 if( !m_window )
1181 m_window = &aWindow;
1182
1183 if( !m_visible || m_trace->GetDataX().size() <= 1 )
1184 return;
1185
1186 if( m_updateRequired )
1187 {
1188 doSetCoordX( m_trace->s2x( aWindow.p2x( m_dim.x ) ) );
1189 m_updateRequired = false;
1190
1191 // Notify the parent window about the changes
1192 wxQueueEvent( aWindow.GetParent(), new wxCommandEvent( EVT_SIM_CURSOR_UPDATE ) );
1193 }
1194 else
1195 {
1196 m_updateRef = true;
1197 }
1198
1199 if( m_updateRef )
1200 {
1202 m_updateRef = false;
1203 }
1204
1205 if( !std::isfinite( m_coords.x ) )
1206 return;
1207
1208 // A silent trace interpolates to no y value at all, and converting that to a pixel is
1209 // undefined behaviour, so carry the x cursor on its own
1210 const bool hasY = std::isfinite( m_coords.y );
1211
1212 // Line length in horizontal and vertical dimensions
1213 const wxPoint cursorPos( aWindow.x2p( m_trace->x2s( m_coords.x ) ),
1214 hasY ? aWindow.y2p( m_trace->y2s( m_coords.y ) ) : 0 );
1215
1216 wxCoord leftPx = aWindow.GetMarginLeft();
1217 wxCoord rightPx = aWindow.GetScrX() - aWindow.GetMarginRight();
1218 wxCoord topPx = aWindow.GetMarginTop();
1219 wxCoord bottomPx = aWindow.GetScrY() - aWindow.GetMarginBottom();
1220
1221 wxPen pen = GetPen();
1222 wxColour fg = aWindow.GetForegroundColour();
1223 COLOR4D cursorColor = COLOR4D( m_trace->GetTraceColour() ).Mix( fg, 0.6 );
1224 COLOR4D textColor = fg;
1225
1226 if( cursorColor.Distance( textColor ) < 0.66 )
1227 textColor.Invert();
1228
1229 pen.SetColour( cursorColor.ToColour() );
1230 pen.SetStyle( m_continuous ? wxPENSTYLE_SOLID : wxPENSTYLE_LONG_DASH );
1231 aDC.SetPen( pen );
1232
1233 if( hasY && topPx < cursorPos.y && cursorPos.y < bottomPx )
1234 aDC.DrawLine( leftPx, cursorPos.y, rightPx, cursorPos.y );
1235
1236 if( leftPx < cursorPos.x && cursorPos.x < rightPx )
1237 {
1238 aDC.DrawLine( cursorPos.x, topPx, cursorPos.x, bottomPx );
1239
1240 wxString id = getID();
1241 wxSize size = aDC.GetTextExtent( wxS( "M" ) );
1242 wxRect textRect( wxPoint( cursorPos.x + 1 - size.x / 2, topPx - 4 - size.y ), size );
1243 wxBrush brush;
1244 wxPoint poly[3];
1245
1246 // Because a "1" looks off-center if it's actually centred.
1247 if( id == "1" )
1248 textRect.x -= 1;
1249
1250 // We want an equalateral triangle, so use size.y for both axes.
1251 size.y += 3;
1252 // Make sure it's an even number so the slopes of the sides will be identical.
1253 size.y = ( size.y / 2 ) * 2;
1254 poly[0] = { cursorPos.x - 1 - size.y / 2, topPx - size.y };
1255 poly[1] = { cursorPos.x + 1 + size.y / 2, topPx - size.y };
1256 poly[2] = { cursorPos.x, topPx };
1257
1258 brush.SetStyle( wxBRUSHSTYLE_SOLID );
1259 brush.SetColour( m_trace->GetTraceColour() );
1260 aDC.SetBrush( brush );
1261 aDC.DrawPolygon( 3, poly );
1262
1263 aDC.SetTextForeground( textColor.ToColour() );
1264 aDC.DrawLabel( id, textRect, wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL );
1265
1266 if( m_trace->IsMultiRun() && m_trace->GetSweepCount() > 1
1267 && m_trace->GetSweepSize() != std::numeric_limits<size_t>::max() )
1268 {
1269 wxString runLabel;
1270 const std::vector<wxString>& labels = m_trace->GetMultiRunLabels();
1271
1272 if( m_sweepIndex >= 0 && m_sweepIndex < (int) labels.size() )
1273 {
1274 runLabel = labels[m_sweepIndex];
1275 }
1276 else
1277 {
1278 runLabel = wxString::Format( _( "Run %d" ), m_sweepIndex + 1 );
1279 }
1280
1281 wxSize runSize = aDC.GetTextExtent( runLabel );
1282 int runX = textRect.GetRight() + 6;
1283 wxRect runRect( wxPoint( runX, textRect.y ), runSize );
1284
1285 runRect.Inflate( 3, 1 );
1286
1287 wxBrush labelBrush( aWindow.GetBackgroundColour() );
1288 wxPen labelPen( cursorColor.ToColour() );
1289
1290 aDC.SetPen( labelPen );
1291 aDC.SetBrush( labelBrush );
1292 aDC.DrawRectangle( runRect );
1293 aDC.SetTextForeground( cursorColor.ToColour() );
1294 aDC.DrawLabel( runLabel, runRect, wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL );
1295 }
1296 }
1297}
1298
1299
1300bool CURSOR::Inside( const wxPoint& aPoint ) const
1301{
1302 if( !m_window || !m_trace )
1303 return false;
1304
1305 // An undefined coordinate draws no line, so it offers nothing to grab
1306 bool nearX = std::isfinite( m_coords.x )
1307 && std::abs( (double) aPoint.x - m_window->x2p( m_trace->x2s( m_coords.x ) ) ) <= DRAG_MARGIN;
1308 bool nearY = std::isfinite( m_coords.y )
1309 && std::abs( (double) aPoint.y - m_window->y2p( m_trace->y2s( m_coords.y ) ) ) <= DRAG_MARGIN;
1310
1311 return nearX || nearY;
1312}
1313
1314
1316{
1317 if( !m_window )
1318 return;
1319
1320 // An undefined coordinate has no pixel, so keep the last good reference for a drag to
1321 // measure against
1322 if( std::isfinite( m_coords.x ) )
1323 m_reference.x = m_window->x2p( m_trace->x2s( m_coords.x ) );
1324
1325 if( std::isfinite( m_coords.y ) )
1326 m_reference.y = m_window->y2p( m_trace->y2s( m_coords.y ) );
1327}
1328
1329
1330SIM_VIEW::SIM_VIEW( SIM_PLOT_TAB* aPlotTab, wxWindow* aParent ) :
1331 mpWindow( aParent, wxID_ANY ),
1332 m_axis_x( nullptr ),
1333 m_axis_y1( nullptr ),
1334 m_axis_y2( nullptr ),
1335 m_axis_y3( nullptr ),
1336 m_legend( nullptr ),
1337 m_plotTab( aPlotTab ),
1338 m_smithGrid( nullptr ),
1339 m_smithChart( false ),
1340 m_smithZoom( 1.0 ),
1341 m_smithPanning( false ),
1342 m_smithLeftSkipped( false )
1343{
1344 // Smith-mode pan/zoom, these run before mpWindow's handlers and skip when not a Smith chart
1345 Bind( wxEVT_MOUSEWHEEL, &SIM_VIEW::onSmithMouseWheel, this );
1346 Bind( wxEVT_MAGNIFY, &SIM_VIEW::onSmithMagnify, this );
1347 Bind( wxEVT_MIDDLE_DOWN, &SIM_VIEW::onSmithMiddleDown, this );
1348 Bind( wxEVT_LEFT_DOWN, &SIM_VIEW::onSmithLeftDown, this );
1349 Bind( wxEVT_MOTION, &SIM_VIEW::onSmithMotion, this );
1350 Bind( wxEVT_LEFT_UP, &SIM_VIEW::onSmithLeftUp, this );
1351 Bind( wxEVT_LEFT_DCLICK, &SIM_VIEW::onSmithDClick, this );
1352 Bind( wxEVT_RIGHT_DOWN, &SIM_VIEW::onSmithRightDown, this );
1353 Bind( wxEVT_RIGHT_UP, &SIM_VIEW::onSmithRightUp, this );
1354
1355 // route the context-menu zoom commands to the Smith view
1356 for( int id : { mpID_ZOOM_IN, mpID_ZOOM_OUT, mpID_FIT, mpID_CENTER } )
1357 Bind( wxEVT_MENU, &SIM_VIEW::onSmithMenuCommand, this, id );
1358}
1359
1360
1362{
1363 if( m_plotTab )
1364 m_plotTab->SyncXView( this );
1365}
1366
1367
1368void SIM_VIEW::SetY1Scale( bool aLock, double aMin, double aMax )
1369{
1370 wxCHECK( m_axis_y1, /* void */ );
1371 m_axis_y1->SetAxisMinMax( aLock, aMin, aMax );
1372}
1373
1374
1375void SIM_VIEW::SetY2Scale( bool aLock, double aMin, double aMax )
1376{
1377 wxCHECK( m_axis_y2, /* void */ );
1378 m_axis_y2->SetAxisMinMax( aLock, aMin, aMax );
1379}
1380
1381
1382void SIM_VIEW::SetY3Scale( bool aLock, double aMin, double aMax )
1383{
1384 wxCHECK( m_axis_y3, /* void */ );
1385 m_axis_y3->SetAxisMinMax( aLock, aMin, aMax );
1386}
1387
1388
1389wxString SIM_VIEW::GetUnitsX() const
1390{
1391 LOG_SCALE<mpScaleXLog>* logScale = dynamic_cast<LOG_SCALE<mpScaleXLog>*>( m_axis_x );
1392 LIN_SCALE<mpScaleX>* linScale = dynamic_cast<LIN_SCALE<mpScaleX>*>( m_axis_x );
1393
1394 if( logScale )
1395 return logScale->GetUnits();
1396 else if( linScale )
1397 return linScale->GetUnits();
1398 else
1399 return wxEmptyString;
1400}
1401
1402
1403wxString SIM_VIEW::GetUnitsY1() const
1404{
1405 LIN_SCALE<mpScaleY>* linScale = dynamic_cast<LIN_SCALE<mpScaleY>*>( m_axis_y1 );
1406
1407 if( linScale )
1408 return linScale->GetUnits();
1409 else
1410 return wxEmptyString;
1411}
1412
1413
1414wxString SIM_VIEW::GetUnitsY2() const
1415{
1416 LIN_SCALE<mpScaleY>* linScale = dynamic_cast<LIN_SCALE<mpScaleY>*>( m_axis_y2 );
1417
1418 if( linScale )
1419 return linScale->GetUnits();
1420 else
1421 return wxEmptyString;
1422}
1423
1424
1425wxString SIM_VIEW::GetUnitsY3() const
1426{
1427 LIN_SCALE<mpScaleY>* linScale = dynamic_cast<LIN_SCALE<mpScaleY>*>( m_axis_y3 );
1428
1429 if( linScale )
1430 return linScale->GetUnits();
1431 else
1432 return wxEmptyString;
1433}
1434
1435
1436wxString SIM_VIEW::GetUnitsForTrace( TRACE* aTrace ) const
1437{
1438 if( m_plotTab->GetSimType() == ST_AC )
1439 {
1440 if( aTrace->GetType() & SPT_AC_PHASE )
1441 return GetUnitsY2();
1442 else
1443 return GetUnitsY1();
1444 }
1445 else
1446 {
1447 if( aTrace->GetType() & SPT_POWER )
1448 return GetUnitsY3();
1449 else if( aTrace->GetType() & SPT_CURRENT )
1450 return GetUnitsY2();
1451 else
1452 return GetUnitsY1();
1453 }
1454}
1455
1456
1457int SIM_VIEW::GetAxisSlot( TRACE* aTrace ) const
1458{
1459 if( ( aTrace->GetType() & SPT_AC_PHASE )
1460 || ( ( m_plotTab->GetSimType() != ST_AC ) && ( aTrace->GetType() & SPT_CURRENT ) ) )
1461 {
1462 return 2;
1463 }
1464 else if( aTrace->GetType() & SPT_POWER )
1465 {
1466 return 3;
1467 }
1468 else
1469 {
1470 return 1;
1471 }
1472}
1473
1474
1476{
1477 switch( aSlot )
1478 {
1479 case 2: return m_axis_y2;
1480 case 3: return m_axis_y3;
1481 default: return m_axis_y1;
1482 }
1483}
1484
1485
1486void SIM_VIEW::updateAxes( int aNewTraceType )
1487{
1488 switch( m_plotTab->GetSimType() )
1489 {
1490 case ST_AC:
1491 if( !m_axis_x )
1492 {
1493 m_axis_x = new LOG_SCALE<mpScaleXLog>( wxEmptyString, wxT( "Hz" ), mpALIGN_BOTTOM );
1494 m_axis_x->SetNameAlign( mpALIGN_BOTTOM );
1495 AddLayer( m_axis_x );
1496
1497 m_axis_y1 = new LIN_SCALE<mpScaleY>( wxEmptyString, wxT( "dB" ), mpALIGN_LEFT );
1498 m_axis_y1->SetNameAlign( mpALIGN_LEFT );
1500
1501 m_axis_y2 = new LIN_SCALE<mpScaleY>( wxEmptyString, wxT( "°" ), mpALIGN_RIGHT );
1502 m_axis_y2->SetNameAlign( mpALIGN_RIGHT );
1503 m_axis_y2->SetMasterScale( m_axis_y1 );
1505 }
1506
1507 m_axis_x->SetName( _( "Frequency" ) );
1508 m_axis_y1->SetName( _( "Gain" ) );
1509 m_axis_y2->SetName( _( "Phase" ) );
1510 break;
1511
1512 case ST_SP:
1513 if( !m_axis_x )
1514 {
1515 m_axis_x = new LOG_SCALE<mpScaleXLog>( wxEmptyString, wxT( "Hz" ), mpALIGN_BOTTOM );
1516 m_axis_x->SetNameAlign( mpALIGN_BOTTOM );
1517 AddLayer( m_axis_x );
1518
1519 m_axis_y1 = new LIN_SCALE<mpScaleY>( wxEmptyString, wxT( "" ), mpALIGN_LEFT );
1520 m_axis_y1->SetNameAlign( mpALIGN_LEFT );
1522
1523 m_axis_y2 = new LIN_SCALE<mpScaleY>( wxEmptyString, wxT( "°" ), mpALIGN_RIGHT );
1524 m_axis_y2->SetNameAlign( mpALIGN_RIGHT );
1525 m_axis_y2->SetMasterScale( m_axis_y1 );
1527 }
1528
1529 m_axis_x->SetName( _( "Frequency" ) );
1530 m_axis_y1->SetName( _( "Amplitude" ) );
1531 m_axis_y2->SetName( _( "Phase" ) );
1532 break;
1533
1534 case ST_DC: prepareDCAxes( aNewTraceType ); break;
1535
1536 case ST_NOISE:
1537 if( !m_axis_x )
1538 {
1539 m_axis_x = new LOG_SCALE<mpScaleXLog>( wxEmptyString, wxT( "Hz" ), mpALIGN_BOTTOM );
1540 m_axis_x->SetNameAlign( mpALIGN_BOTTOM );
1541 AddLayer( m_axis_x );
1542
1543 if( ( aNewTraceType & SPT_CURRENT ) == 0 )
1544 {
1545 m_axis_y1 = new LIN_SCALE<mpScaleY>( wxEmptyString, wxT( "" ), mpALIGN_LEFT );
1546 m_axis_y1->SetNameAlign( mpALIGN_LEFT );
1548 }
1549 else
1550 {
1551 m_axis_y2 = new LIN_SCALE<mpScaleY>( wxEmptyString, wxT( "" ), mpALIGN_RIGHT );
1552 m_axis_y2->SetNameAlign( mpALIGN_RIGHT );
1554 }
1555 }
1556
1557 m_axis_x->SetName( _( "Frequency" ) );
1558
1559 if( m_axis_y1 )
1560 m_axis_y1->SetName( _( "Noise (V/√Hz)" ) );
1561
1562 if( m_axis_y2 )
1563 m_axis_y2->SetName( _( "Noise (A/√Hz)" ) );
1564
1565 break;
1566
1567 case ST_FFT:
1568 if( !m_axis_x )
1569 {
1570 m_axis_x = new LOG_SCALE<mpScaleXLog>( wxEmptyString, wxT( "Hz" ), mpALIGN_BOTTOM );
1571 m_axis_x->SetNameAlign( mpALIGN_BOTTOM );
1572 AddLayer( m_axis_x );
1573
1574 m_axis_y1 = new LIN_SCALE<mpScaleY>( wxEmptyString, wxT( "dB" ), mpALIGN_LEFT );
1575 m_axis_y1->SetNameAlign( mpALIGN_LEFT );
1577 }
1578
1579 m_axis_x->SetName( _( "Frequency" ) );
1580 m_axis_y1->SetName( _( "Intensity" ) );
1581 break;
1582
1583 case ST_TRAN:
1584 if( !m_axis_x )
1585 {
1586 m_axis_x = new TIME_SCALE( wxEmptyString, wxT( "s" ), mpALIGN_BOTTOM );
1587 m_axis_x->SetNameAlign( mpALIGN_BOTTOM );
1588 AddLayer( m_axis_x );
1589
1590 m_axis_y1 = new LIN_SCALE<mpScaleY>( wxEmptyString, wxT( "V" ), mpALIGN_LEFT );
1591 m_axis_y1->SetNameAlign( mpALIGN_LEFT );
1593
1594 m_axis_y2 = new LIN_SCALE<mpScaleY>( wxEmptyString, wxT( "A" ), mpALIGN_RIGHT );
1595 m_axis_y2->SetNameAlign( mpALIGN_RIGHT );
1596 m_axis_y2->SetMasterScale( m_axis_y1 );
1598 }
1599
1600 m_axis_x->SetName( _( "Time" ) );
1601 m_axis_y1->SetName( _( "Voltage" ) );
1602 m_axis_y2->SetName( _( "Current" ) );
1603
1604 if( aNewTraceType & SPT_POWER )
1606
1607 if( m_axis_y3 )
1608 m_axis_y3->SetName( _( "Power" ) );
1609
1610 break;
1611
1612 default:
1613 // suppress warnings
1614 break;
1615 }
1616
1617 if( m_plotTab->GetSimType() == ST_TRAN || m_plotTab->GetSimType() == ST_DC )
1618 {
1619 if( m_axis_y3 )
1620 {
1621 SetMargins( 30, 160, 45, 70 );
1622
1623 if( m_axis_y2 )
1624 m_axis_y2->SetNameAlign( mpALIGN_BORDER_RIGHT );
1625
1626 m_axis_y3->SetAlign( mpALIGN_BORDER_RIGHT );
1627 m_axis_y3->SetNameAlign( mpALIGN_BORDER_RIGHT );
1628 }
1629 else
1630 {
1631 SetMargins( 30, 70, 45, 70 );
1632
1633 if( m_axis_y2 )
1634 m_axis_y2->SetNameAlign( mpALIGN_RIGHT );
1635 }
1636 }
1637
1638 if( m_axis_x )
1639 m_axis_x->SetFont( KIUI::GetStatusFont( this ) );
1640
1641 if( m_axis_y1 )
1642 m_axis_y1->SetFont( KIUI::GetStatusFont( this ) );
1643
1644 if( m_axis_y2 )
1645 m_axis_y2->SetFont( KIUI::GetStatusFont( this ) );
1646
1647 if( m_axis_y3 )
1648 m_axis_y3->SetFont( KIUI::GetStatusFont( this ) );
1649
1651}
1652
1653
1654void SIM_VIEW::prepareDCAxes( int aNewTraceType )
1655{
1656 wxString sim_cmd = m_plotTab->GetSimCommand().Lower();
1657 wxString rem;
1658
1659 if( sim_cmd.StartsWith( ".dc", &rem ) )
1660 {
1661 wxChar ch = 0;
1662
1663 rem.Trim( false );
1664
1665 try
1666 {
1667 ch = rem.GetChar( 0 );
1668 }
1669 catch( ... )
1670 {
1671 // Best efforts
1672 }
1673
1674 switch( ch )
1675 {
1676 // Make sure that we have a reliable default (even if incorrectly labeled)
1677 default:
1678 case 'v':
1679 if( !m_axis_x )
1680 {
1681 m_axis_x = new LIN_SCALE<mpScaleX>( wxEmptyString, wxT( "V" ), mpALIGN_BOTTOM );
1682 m_axis_x->SetNameAlign( mpALIGN_BOTTOM );
1683 AddLayer( m_axis_x );
1684 }
1685
1686 m_axis_x->SetName( _( "Voltage (swept)" ) );
1687 break;
1688
1689 case 'i':
1690 if( !m_axis_x )
1691 {
1692 m_axis_x = new LIN_SCALE<mpScaleX>( wxEmptyString, wxT( "A" ), mpALIGN_BOTTOM );
1693 m_axis_x->SetNameAlign( mpALIGN_BOTTOM );
1694 AddLayer( m_axis_x );
1695 }
1696
1697 m_axis_x->SetName( _( "Current (swept)" ) );
1698 break;
1699
1700 case 'r':
1701 if( !m_axis_x )
1702 {
1703 m_axis_x = new LIN_SCALE<mpScaleX>( wxEmptyString, wxT( "Ω" ), mpALIGN_BOTTOM );
1704 m_axis_x->SetNameAlign( mpALIGN_BOTTOM );
1705 AddLayer( m_axis_x );
1706 }
1707
1708 m_axis_x->SetName( _( "Resistance (swept)" ) );
1709 break;
1710
1711 case 't':
1712 if( !m_axis_x )
1713 {
1714 m_axis_x = new LIN_SCALE<mpScaleX>( wxEmptyString, wxT( "°C" ), mpALIGN_BOTTOM );
1715 m_axis_x->SetNameAlign( mpALIGN_BOTTOM );
1716 AddLayer( m_axis_x );
1717 }
1718
1719 m_axis_x->SetName( _( "Temperature (swept)" ) );
1720 break;
1721 }
1722
1723 if( !m_axis_y1 )
1724 {
1725 m_axis_y1 = new LIN_SCALE<mpScaleY>( wxEmptyString, wxT( "V" ), mpALIGN_LEFT );
1726 m_axis_y1->SetNameAlign( mpALIGN_LEFT );
1728 }
1729
1730 if( !m_axis_y2 )
1731 {
1732 m_axis_y2 = new LIN_SCALE<mpScaleY>( wxEmptyString, wxT( "A" ), mpALIGN_RIGHT );
1733 m_axis_y2->SetNameAlign( mpALIGN_RIGHT );
1735 }
1736
1737 m_axis_y1->SetName( _( "Voltage (measured)" ) );
1738 m_axis_y2->SetName( _( "Current" ) );
1739
1740 if( ( aNewTraceType & SPT_POWER ) )
1742
1743 if( m_axis_y3 )
1744 m_axis_y3->SetName( _( "Power" ) );
1745 }
1746}
1747
1748
1750{
1751 if( !m_axis_y3 )
1752 {
1753 SetMargins( 30, 160, 45, 70 );
1754 m_axis_y3 = new LIN_SCALE<mpScaleY>( wxEmptyString, wxT( "W" ), mpALIGN_BORDER_RIGHT );
1755 m_axis_y3->SetNameAlign( mpALIGN_BORDER_RIGHT );
1756 m_axis_y3->SetMasterScale( m_axis_y1 );
1758 }
1759
1760 if( m_axis_y3 )
1761 {
1762 m_axis_y3->SetAlign( mpALIGN_BORDER_RIGHT );
1763 m_axis_y3->SetNameAlign( mpALIGN_BORDER_RIGHT );
1764 }
1765
1766 if( m_axis_y2 )
1767 m_axis_y2->SetNameAlign( mpALIGN_BORDER_RIGHT );
1768}
1769
1770
1772{
1773 bool hasY1Traces = false;
1774 bool hasY2Traces = false;
1775 bool hasY3Traces = false;
1776
1777 if( !m_smithChart )
1778 {
1779 for( const auto& [name, trace] : m_plotTab->GetTraces() )
1780 {
1781 if( !trace || trace->GetView() != this )
1782 continue;
1783
1784 if( trace->GetType() & SPT_POWER )
1785 {
1786 hasY3Traces = true;
1787 }
1788 else if( ( trace->GetType() & SPT_AC_PHASE )
1789 || ( ( m_plotTab->GetSimType() != ST_AC ) && ( trace->GetType() & SPT_CURRENT ) ) )
1790 {
1791 hasY2Traces = true;
1792 }
1793 else
1794 {
1795 hasY1Traces = true;
1796 }
1797 }
1798 }
1799
1800 bool visibilityChanged = false;
1801
1802 if( m_axis_x && m_axis_x->IsVisible() != !m_smithChart )
1803 {
1804 m_axis_x->SetVisible( !m_smithChart );
1805 visibilityChanged = true;
1806 }
1807
1808 if( m_axis_y1 && m_axis_y1->IsVisible() != hasY1Traces )
1809 {
1810 m_axis_y1->SetVisible( hasY1Traces );
1811 visibilityChanged = true;
1812 }
1813
1814 if( m_axis_y2 && m_axis_y2->IsVisible() != hasY2Traces )
1815 {
1816 m_axis_y2->SetVisible( hasY2Traces );
1817 visibilityChanged = true;
1818 }
1819
1820 if( m_axis_y3 && m_axis_y3->IsVisible() != hasY3Traces )
1821 {
1822 m_axis_y3->SetVisible( hasY3Traces );
1823 visibilityChanged = true;
1824 }
1825
1826 if( visibilityChanged )
1827 UpdateAll();
1828}
1829
1830
1831void SIM_VIEW::ResetScales( bool aIncludeX )
1832{
1833 if( m_axis_x && aIncludeX )
1834 {
1835 m_axis_x->ResetDataRange();
1836
1837 if( m_plotTab->GetSimType() == ST_TRAN )
1838 {
1839 wxStringTokenizer tokenizer( m_plotTab->GetSimCommand(), " \t\r\n", wxTOKEN_STRTOK );
1840 wxString cmd = tokenizer.GetNextToken().Lower();
1841
1842 wxASSERT( cmd == wxS( ".tran" ) );
1843
1844 SPICE_VALUE step;
1845 SPICE_VALUE end( 1.0 );
1846 SPICE_VALUE start( 0.0 );
1847
1848 if( tokenizer.HasMoreTokens() )
1849 step = SPICE_VALUE( tokenizer.GetNextToken() );
1850
1851 if( tokenizer.HasMoreTokens() )
1852 end = SPICE_VALUE( tokenizer.GetNextToken() );
1853
1854 if( tokenizer.HasMoreTokens() )
1855 start = SPICE_VALUE( tokenizer.GetNextToken() );
1856
1857 static_cast<TIME_SCALE*>( m_axis_x )->SetStartAndEnd( start.ToDouble(), end.ToDouble() );
1858 }
1859 }
1860
1861 if( m_axis_y1 )
1862 m_axis_y1->ResetDataRange();
1863
1864 if( m_axis_y2 )
1865 m_axis_y2->ResetDataRange();
1866
1867 if( m_axis_y3 )
1868 m_axis_y3->ResetDataRange();
1869
1870 for( auto& [name, trace] : m_plotTab->GetTraces() )
1871 {
1872 if( trace->GetView() == this )
1873 trace->UpdateScales();
1874 }
1875}
1876
1877
1878void SIM_VIEW::SetSmithChart( bool aEnable )
1879{
1880 if( m_smithChart == aEnable )
1881 return;
1882
1883 m_smithChart = aEnable;
1884
1885 // a mode switch mid-gesture must not leave a pan or a skipped click behind
1886 m_smithPanning = false;
1887 m_smithLeftSkipped = false;
1888
1889 if( aEnable && !m_smithGrid )
1890 {
1891 m_smithGrid = new SMITH_GRID();
1892 m_smithGrid->SetFont( KIUI::GetStatusFont( this ) );
1893 m_smithGrid->SetPen( wxPen( m_plotTab->GetPlotColor( SIM_PLOT_COLORS::COLOR_SET::AXIS ), 1 ) );
1895 }
1896
1897 if( m_smithGrid )
1898 m_smithGrid->SetVisible( aEnable );
1899
1900 if( aEnable )
1902
1904
1906 UpdateAll();
1907}
1908
1909
1911{
1912 if( !m_smithGrid )
1913 return;
1914
1915 double z0 = 0.0;
1916 bool mixed = false;
1917 bool unresolved = false;
1918
1919 for( const auto& [name, trace] : m_plotTab->GetTraces() )
1920 {
1921 SMITH_TRACE* smithTrace = dynamic_cast<SMITH_TRACE*>( trace );
1922
1923 if( !smithTrace || smithTrace->GetView() != this )
1924 continue;
1925
1926 double traceZ0 = smithTrace->GetReferenceImpedance();
1927
1928 if( traceZ0 <= 0.0 )
1929 unresolved = true;
1930 else if( z0 == 0.0 )
1931 z0 = traceZ0;
1932 else if( traceZ0 != z0 )
1933 mixed = true;
1934 }
1935
1936 // a trace whose port impedance is unknown leaves no single reference to name either
1937 m_smithGrid->SetReferenceImpedance( mixed || unresolved ? 0.0 : z0 );
1938 m_smithGrid->SetMixedReferences( mixed );
1939}
1940
1941
1943{
1944 if( m_smithGrid )
1945 m_smithGrid->SetPen( wxPen( m_plotTab->GetPlotColor( SIM_PLOT_COLORS::COLOR_SET::AXIS ), 1 ) );
1946}
1947
1948
1950{
1951 return SMITH_GRID::GetChartView( const_cast<SIM_VIEW&>( *this ), m_smithZoom, m_smithPan, aView );
1952}
1953
1954
1955void SIM_VIEW::SmithZoomAt( const wxPoint& aPos, double aFactor )
1956{
1957 SMITH_VIEW view;
1958
1959 if( !getSmithView( view ) )
1960 return;
1961
1962 double newZoom = std::clamp( m_smithZoom * aFactor, 1.0, 50.0 );
1963
1964 if( newZoom <= 1.0 )
1965 {
1967 Refresh();
1968 return;
1969 }
1970
1971 // keep the point under the cursor fixed while zooming
1972 m_smithPan = SMITH_MATH::ZoomAboutPoint( view, aPos, newZoom );
1973 m_smithZoom = newZoom;
1974
1975 Refresh();
1976}
1977
1978
1979void SIM_VIEW::SmithPanBy( const wxPoint& aDelta )
1980{
1981 SMITH_VIEW view;
1982
1983 if( !getSmithView( view ) )
1984 return;
1985
1986 m_smithPan.x -= aDelta.x / view.radius;
1987 m_smithPan.y += aDelta.y / view.radius;
1988
1989 Refresh();
1990}
1991
1992
1993void SIM_VIEW::onSmithMouseWheel( wxMouseEvent& aEvent )
1994{
1995 if( !m_smithChart )
1996 {
1997 aEvent.Skip();
1998 return;
1999 }
2000
2001 // swallow horizontal scroll too, mpWindow would pan its hidden axes with it
2002 if( aEvent.GetWheelAxis() != wxMOUSE_WHEEL_VERTICAL || aEvent.GetWheelRotation() == 0 )
2003 return;
2004
2005 // do not skip, or mpWindow would also zoom its hidden axes
2006 SmithZoomAt( aEvent.GetPosition(), aEvent.GetWheelRotation() > 0 ? 1.2 : 1.0 / 1.2 );
2007}
2008
2009
2010void SIM_VIEW::onSmithMagnify( wxMouseEvent& aEvent )
2011{
2012 if( !m_smithChart )
2013 {
2014 aEvent.Skip();
2015 return;
2016 }
2017
2018 double factor = aEvent.GetMagnification() + 1.0;
2019
2020 if( factor > 0.0 )
2021 SmithZoomAt( aEvent.GetPosition(), factor );
2022}
2023
2024
2025void SIM_VIEW::onSmithMiddleDown( wxMouseEvent& aEvent )
2026{
2027 // keep mpWindow's middle-button pan off the hidden axes
2028 if( !m_smithChart )
2029 aEvent.Skip();
2030}
2031
2032
2033void SIM_VIEW::onSmithLeftDown( wxMouseEvent& aEvent )
2034{
2035 // clear stale pan state so it cannot hijack a cursor grab
2036 m_smithPanning = false;
2037 m_smithLeftSkipped = false;
2038
2039 wxPoint pos = aEvent.GetPosition();
2040
2041 if( m_smithChart && !IsInsideInfoLayer( pos ) )
2042 {
2043 m_smithPanning = true;
2044 m_smithPanLast = pos;
2045 return;
2046 }
2047
2048 // on a cursor or the legend, let mpWindow drag it, and remember that it saw the click
2049 // so the matching motions and release reach it too
2050 m_smithLeftSkipped = true;
2051 aEvent.Skip();
2052}
2053
2054
2055void SIM_VIEW::onSmithMotion( wxMouseEvent& aEvent )
2056{
2057 if( m_smithChart && aEvent.Dragging() )
2058 {
2059 if( aEvent.LeftIsDown() && !m_smithLeftSkipped )
2060 {
2061 // a left drag mpWindow did not see the start of, pan if one is active, and
2062 // swallow either way so mpWindow cannot rubber-band from a stale click point
2063 if( m_smithPanning )
2064 {
2065 wxPoint pos = aEvent.GetPosition();
2066
2067 SmithPanBy( pos - m_smithPanLast );
2068 m_smithPanLast = pos;
2069 }
2070
2071 return;
2072 }
2073
2074 // keep mpWindow's middle-button pan off the hidden axes
2075 if( aEvent.MiddleIsDown() )
2076 return;
2077 }
2078
2079 aEvent.Skip();
2080}
2081
2082
2083void SIM_VIEW::onSmithLeftUp( wxMouseEvent& aEvent )
2084{
2085 if( m_smithChart )
2086 {
2087 m_smithPanning = false;
2088
2089 // a release mpWindow saw no click for would zoom the hidden axes to the rect
2090 // between its stale click point and this position
2091 if( !m_smithLeftSkipped )
2092 return;
2093
2094 m_smithLeftSkipped = false;
2095 }
2096
2097 aEvent.Skip();
2098}
2099
2100
2101void SIM_VIEW::onSmithDClick( wxMouseEvent& aEvent )
2102{
2103 wxPoint pos = aEvent.GetPosition();
2104
2105 if( m_smithChart && !IsInsideInfoLayer( pos ) )
2106 {
2108 Refresh();
2109 return;
2110 }
2111
2112 aEvent.Skip();
2113}
2114
2115
2116void SIM_VIEW::onSmithRightDown( wxMouseEvent& aEvent )
2117{
2118 // remember where the menu opened, for its zoom commands
2119 if( m_smithChart )
2120 m_smithMenuPos = aEvent.GetPosition();
2121
2122 aEvent.Skip();
2123}
2124
2125
2126void SIM_VIEW::onSmithRightUp( wxMouseEvent& aEvent )
2127{
2128 if( !m_smithChart )
2129 {
2130 aEvent.Skip();
2131 return;
2132 }
2133
2134 // no zoom history here, so grey out Undo/Redo and show the menu ourselves
2135 wxMenu* menu = GetPopupMenu();
2136
2137 menu->Enable( mpID_ZOOM_UNDO, false );
2138 menu->Enable( mpID_ZOOM_REDO, false );
2139
2140 PopupMenu( menu, aEvent.GetPosition() );
2141}
2142
2143
2144void SIM_VIEW::onSmithMenuCommand( wxCommandEvent& aEvent )
2145{
2146 if( !m_smithChart )
2147 {
2148 aEvent.Skip();
2149 return;
2150 }
2151
2152 switch( aEvent.GetId() )
2153 {
2154 case mpID_ZOOM_IN: SmithZoomAt( m_smithMenuPos, 1.5 ); break;
2155 case mpID_ZOOM_OUT: SmithZoomAt( m_smithMenuPos, 1.0 / 1.5 ); break;
2156
2157 case mpID_FIT:
2159 Refresh();
2160 break;
2161
2162 case mpID_CENTER:
2163 {
2164 SMITH_VIEW view;
2165
2166 if( getSmithView( view ) )
2167 {
2169 Refresh();
2170 }
2171
2172 break;
2173 }
2174
2175 default: aEvent.Skip();
2176 }
2177}
2178
2179
2180SIM_PLOT_TAB::SIM_PLOT_TAB( const wxString& aSimCommand, wxWindow* parent ) :
2181 SIM_TAB( aSimCommand, parent ),
2182 m_dotted_cp( false ),
2183 m_syncingXView( false ),
2184 m_smithMode( false )
2185{
2187
2188 wxBoxSizer* outerSizer = new wxBoxSizer( wxVERTICAL );
2189
2190 m_viewsSizer = new wxBoxSizer( wxVERTICAL );
2191 outerSizer->Add( m_viewsSizer, 1, wxEXPAND, 0 );
2192
2193 wxBoxSizer* viewButtonsSizer = new wxBoxSizer( wxHORIZONTAL );
2194
2195 STD_BITMAP_BUTTON* addViewButton = new STD_BITMAP_BUTTON( this, wxID_ANY, wxNullBitmap );
2196 addViewButton->SetBitmap( KiBitmapBundle( BITMAPS::small_plus ) );
2197 addViewButton->SetToolTip( _( "Add a new signal view" ) );
2198
2199 STD_BITMAP_BUTTON* removeViewButton = new STD_BITMAP_BUTTON( this, wxID_ANY, wxNullBitmap );
2200 removeViewButton->SetBitmap( KiBitmapBundle( BITMAPS::small_trash ) );
2201 removeViewButton->SetToolTip( _( "Remove the last signal view" ) );
2202
2203 viewButtonsSizer->Add( addViewButton, 0, wxLEFT, 5 );
2204 viewButtonsSizer->Add( removeViewButton, 0, wxLEFT, 30 );
2205
2206 addViewButton->Bind( wxEVT_BUTTON,
2207 [this]( wxCommandEvent& aEvent )
2208 {
2209 AddView();
2210 } );
2211
2212 removeViewButton->Bind( wxEVT_BUTTON,
2213 [this]( wxCommandEvent& aEvent )
2214 {
2215 const std::vector<SIM_VIEW*>& views = GetViews();
2216
2217 if( !views.empty() )
2218 RemoveView( views.back() );
2219 } );
2220
2221 outerSizer->Add( viewButtonsSizer, 0, wxEXPAND | wxTOP | wxBOTTOM, 3 );
2222
2223 SetSizer( outerSizer );
2224
2225 AddView();
2226}
2227
2228
2230{
2231 // ~mpWindow destroys all the added layers, so there is no need to destroy m_traces contents
2232}
2233
2234
2236{
2238
2239 for( SIM_VIEW* view : m_views )
2240 view->SetMouseWheelActions( m_mouseWheelActions );
2241}
2242
2243
2245{
2246 SIM_VIEW* view = new SIM_VIEW( this, this );
2247
2248 view->LimitView( true );
2249 view->SetMargins( 30, 70, 45, 70 );
2251
2252 view->updateAxes();
2253
2254 // a mpInfoLegend displays the name of traces on the left top panel corner:
2255 mpInfoLegend* legend = new mpInfoLegend( wxRect( 0, 0, 200, 40 ), wxTRANSPARENT_BRUSH );
2256 legend->SetVisible( false );
2257 view->AddLayer( legend );
2258 view->m_legend = legend;
2259 view->m_lastLegendPosition = legend->GetPosition();
2260
2261 view->EnableDoubleBuffer( true );
2262 view->SetSmithChart( m_smithMode );
2263
2264 m_views.push_back( view );
2265 m_viewsSizer->Add( view, 1, wxALL | wxEXPAND, 1 );
2266
2268
2269 if( m_views.size() > 1 )
2270 SyncXView( m_views[0] );
2271
2272 view->UpdateAll();
2273 Layout();
2274
2275 wxQueueEvent( this, new wxCommandEvent( EVT_SIM_VIEWS_CHANGED ) );
2276
2277 return view;
2278}
2279
2280
2282{
2283 if( m_views.size() <= 1 )
2284 return false;
2285
2286 auto it = std::find( m_views.begin(), m_views.end(), aView );
2287
2288 if( it == m_views.end() )
2289 return false;
2290
2291 // The traces routed to this view lose their axes along with it, so drop them rather than
2292 // leaving them behind holding a destroyed view (and its destroyed axis layers). Their
2293 // signals stay in the Signals grid, simply no longer plotted, and can be assigned to one
2294 // of the remaining views.
2295 std::vector<TRACE*> plottedHere;
2296
2297 for( const auto& [name, trace] : m_traces )
2298 {
2299 trace->ClearYScaleViewIf( aView );
2300
2301 if( trace->GetView() == aView )
2302 plottedHere.push_back( trace );
2303 }
2304
2305 // DeleteTrace() erases from m_traces, so it cannot run while iterating it
2306 for( TRACE* trace : plottedHere )
2307 DeleteTrace( trace );
2308
2309 m_viewsSizer->Detach( aView );
2310 m_views.erase( it );
2311 aView->Destroy();
2312
2313 Layout();
2314
2315 // Any Y-scale ranges that were merged with the removed view are now stale; recompute them
2316 // (and refresh) so the remaining views reflect only the traces that are still present.
2317 ResetScales( false );
2318
2319 for( SIM_VIEW* view : m_views )
2320 view->UpdateAll();
2321
2322 wxQueueEvent( this, new wxCommandEvent( EVT_SIM_VIEWS_CHANGED ) );
2323
2324 return true;
2325}
2326
2327
2329{
2330 for( size_t i = 0; i < m_views.size(); ++i )
2331 {
2332 if( m_views[i] == aView )
2333 return (int) i;
2334 }
2335
2336 return -1;
2337}
2338
2339
2341{
2342 if( m_syncingXView || !aSource )
2343 return;
2344
2345 m_syncingXView = true;
2346
2347 double scaleX = aSource->GetScaleX();
2348 double pos = aSource->GetPosX();
2349 double desiredMin = aSource->GetDesiredXmin();
2350 double desiredMax = aSource->GetDesiredXmax();
2351
2352 for( SIM_VIEW* view : m_views )
2353 {
2354 if( view == aSource )
2355 continue;
2356
2357 view->SetScaleX( scaleX );
2358 view->SetXRange( pos, desiredMax, desiredMin );
2359 view->UpdateAll();
2360 }
2361
2362 m_syncingXView = false;
2363}
2364
2365
2367{
2368 SIM_VIEW* view = GetDefaultView();
2369 return view ? view->GetLabelX() : wxString( wxS( "" ) );
2370}
2371
2372
2374{
2375 SIM_VIEW* view = GetDefaultView();
2376 return view ? view->GetUnitsX() : wxString( wxS( "" ) );
2377}
2378
2379
2380wxString SIM_PLOT_TAB::GetUnitsForTrace( TRACE* aTrace ) const
2381{
2382 SIM_VIEW* view = aTrace->GetView() ? aTrace->GetView() : GetDefaultView();
2383 return view ? view->GetUnitsForTrace( aTrace ) : wxString( wxS( "" ) );
2384}
2385
2386
2387void SIM_PLOT_TAB::ShowGrid( bool aEnable )
2388{
2389 for( SIM_VIEW* view : m_views )
2390 view->ShowGrid( aEnable );
2391}
2392
2393
2395{
2396 SIM_VIEW* view = GetDefaultView();
2397 return view && view->IsGridShown();
2398}
2399
2400
2401void SIM_PLOT_TAB::ShowLegend( bool aEnable )
2402{
2403 for( SIM_VIEW* view : m_views )
2404 view->ShowLegend( aEnable );
2405}
2406
2407
2409{
2410 SIM_VIEW* view = GetDefaultView();
2411 return view && view->IsLegendShown();
2412}
2413
2414
2416{
2417 SIM_VIEW* view = GetDefaultView();
2418 return view ? view->GetLegendPosition() : wxPoint();
2419}
2420
2421
2422void SIM_PLOT_TAB::SetLegendPosition( const wxPoint& aPosition )
2423{
2424 if( SIM_VIEW* view = GetDefaultView() )
2425 view->SetLegendPosition( aPosition );
2426
2427 m_LastLegendPosition = aPosition;
2428}
2429
2430
2432{
2433 SIM_VIEW* view = GetDefaultView();
2434 return view ? view->GetLabelY1() : wxString( wxS( "" ) );
2435}
2436
2437
2439{
2440 SIM_VIEW* view = GetDefaultView();
2441 return view ? view->GetLabelY2() : wxString( wxS( "" ) );
2442}
2443
2444
2446{
2447 SIM_VIEW* view = GetDefaultView();
2448 return view ? view->GetLabelY3() : wxString( wxS( "" ) );
2449}
2450
2451
2453{
2454 SIM_VIEW* view = GetDefaultView();
2455 return view ? view->GetUnitsY1() : wxString( wxS( "" ) );
2456}
2457
2458
2460{
2461 SIM_VIEW* view = GetDefaultView();
2462 return view ? view->GetUnitsY2() : wxString( wxS( "" ) );
2463}
2464
2465
2467{
2468 SIM_VIEW* view = GetDefaultView();
2469 return view ? view->GetUnitsY3() : wxString( wxS( "" ) );
2470}
2471
2472
2473bool SIM_PLOT_TAB::GetY1Scale( double* aMin, double* aMax ) const
2474{
2475 SIM_VIEW* view = GetDefaultView();
2476 return view && view->GetY1Scale( aMin, aMax );
2477}
2478
2479
2480bool SIM_PLOT_TAB::GetY2Scale( double* aMin, double* aMax ) const
2481{
2482 SIM_VIEW* view = GetDefaultView();
2483 return view && view->GetY2Scale( aMin, aMax );
2484}
2485
2486
2487bool SIM_PLOT_TAB::GetY3Scale( double* aMin, double* aMax ) const
2488{
2489 SIM_VIEW* view = GetDefaultView();
2490 return view && view->GetY3Scale( aMin, aMax );
2491}
2492
2493
2494void SIM_PLOT_TAB::SetY1Scale( bool aLock, double aMin, double aMax )
2495{
2496 if( SIM_VIEW* view = GetDefaultView() )
2497 view->SetY1Scale( aLock, aMin, aMax );
2498}
2499
2500
2501void SIM_PLOT_TAB::SetY2Scale( bool aLock, double aMin, double aMax )
2502{
2503 if( SIM_VIEW* view = GetDefaultView() )
2504 view->SetY2Scale( aLock, aMin, aMax );
2505}
2506
2507
2508void SIM_PLOT_TAB::SetY3Scale( bool aLock, double aMin, double aMax )
2509{
2510 if( SIM_VIEW* view = GetDefaultView() )
2511 view->SetY3Scale( aLock, aMin, aMax );
2512}
2513
2514
2516{
2517 if( SIM_VIEW* view = GetDefaultView() )
2518 view->EnsureThirdYAxisExists();
2519}
2520
2521
2523{
2524 for( SIM_VIEW* view : m_views )
2525 {
2526 view->SetColourTheme( m_colors.GetPlotColor( SIM_PLOT_COLORS::COLOR_SET::BACKGROUND ),
2529
2530 view->UpdateSmithGridColor();
2531 view->UpdateAll();
2532 }
2533}
2534
2535
2536void SIM_PLOT_TAB::SetSmithMode( bool aEnable )
2537{
2538 // only S-parameter tabs have a Smith view, a stale workbook cannot force one elsewhere
2539 if( aEnable && GetSimType() != ST_SP )
2540 return;
2541
2542 if( m_smithMode == aEnable )
2543 return;
2544
2545 m_smithMode = aEnable;
2546
2547 for( SIM_VIEW* view : m_views )
2548 view->SetSmithChart( aEnable );
2549}
2550
2551
2553{
2554 for( SIM_VIEW* view : m_views )
2555 view->UpdateSmithReferenceImpedance();
2556}
2557
2558
2560{
2561 SIM_VIEW* view = GetDefaultView();
2562 return view ? view->GetSmithZoom() : 1.0;
2563}
2564
2565
2566wxRealPoint SIM_PLOT_TAB::GetSmithPan() const
2567{
2568 SIM_VIEW* view = GetDefaultView();
2569 return view ? view->GetSmithPan() : wxRealPoint( 0.0, 0.0 );
2570}
2571
2572
2574{
2575 for( SIM_VIEW* view : m_views )
2576 {
2577 view->ResetSmithView();
2578 view->Refresh();
2579 }
2580}
2581
2582
2583void SIM_PLOT_TAB::SetSmithView( double aZoom, double aPanX, double aPanY )
2584{
2585 for( SIM_VIEW* view : m_views )
2586 view->SetSmithView( aZoom, aPanX, aPanY );
2587}
2588
2589
2590void SIM_PLOT_TAB::SmithZoomAt( const wxPoint& aPos, double aFactor )
2591{
2592 for( SIM_VIEW* view : m_views )
2593 view->SmithZoomAt( aPos, aFactor );
2594}
2595
2596
2597void SIM_PLOT_TAB::SmithPanBy( const wxPoint& aDelta )
2598{
2599 for( SIM_VIEW* view : m_views )
2600 view->SmithPanBy( aDelta );
2601}
2602
2603
2605{
2606 for( SIM_VIEW* view : m_views )
2607 {
2608 view->updateAxes();
2609 view->UpdateAll();
2610 }
2611}
2612
2613
2615{
2616 int type = trace->GetType();
2617 wxPenStyle penStyle;
2618
2619 if( ( type & SPT_AC_GAIN ) > 0 )
2620 penStyle = wxPENSTYLE_SOLID;
2621 else if( ( type & SPT_AC_PHASE ) > 0 )
2622 penStyle = m_dotted_cp ? wxPENSTYLE_DOT : wxPENSTYLE_SOLID;
2623 else if( ( type & SPT_CURRENT ) > 0 )
2624 penStyle = m_dotted_cp ? wxPENSTYLE_DOT : wxPENSTYLE_SOLID;
2625 else
2626 penStyle = wxPENSTYLE_SOLID;
2627
2628 trace->SetPen( wxPen( trace->GetTraceColour(), 2, penStyle ) );
2629 m_sessionTraceColors[trace->GetName()] = trace->GetTraceColour();
2630}
2631
2632
2633TRACE* SIM_PLOT_TAB::GetOrAddTrace( const wxString& aVectorName, int aType, SIM_VIEW* aView )
2634{
2635 TRACE* trace = GetTrace( aVectorName, aType );
2636
2637 if( !trace && aView )
2638 {
2639 aView->updateAxes( aType );
2640
2641 if( GetSimType() == ST_TRAN || GetSimType() == ST_DC )
2642 {
2643 bool hasVoltageTraces = false;
2644
2645 for( const auto& [id, candidate] : m_traces )
2646 {
2647 if( candidate->GetView() == aView && ( candidate->GetType() & SPT_VOLTAGE ) )
2648 {
2649 hasVoltageTraces = true;
2650 break;
2651 }
2652 }
2653
2654 if( !hasVoltageTraces )
2655 {
2656 if( aView->m_axis_y2 )
2657 aView->m_axis_y2->SetMasterScale( nullptr );
2658
2659 if( aView->m_axis_y3 )
2660 aView->m_axis_y3->SetMasterScale( nullptr );
2661 }
2662 }
2663
2664 if( aType & SPT_SP_SMITH )
2665 trace = new SMITH_TRACE( aVectorName, (SIM_TRACE_TYPE) aType );
2666 else
2667 trace = new TRACE( aVectorName, (SIM_TRACE_TYPE) aType );
2668
2669 if( m_sessionTraceColors.count( aVectorName ) )
2670 trace->SetTraceColour( m_sessionTraceColors[aVectorName] );
2671 else
2672 trace->SetTraceColour( m_colors.GenerateColor( m_sessionTraceColors ) );
2673
2674 UpdateTraceStyle( trace );
2675 m_traces[getTraceId( aVectorName, aType )] = trace;
2676
2677 trace->SetView( aView );
2678 aView->AddLayer( (mpLayer*) trace );
2679 }
2680
2681 return trace;
2682}
2683
2684
2685void SIM_PLOT_TAB::SetTraceData( TRACE* trace, std::vector<double>& aX, std::vector<double>& aY, int aSweepCount,
2686 size_t aSweepSize, bool aIsMultiRun, const std::vector<wxString>& aMultiRunLabels )
2687{
2688 SIM_VIEW* view = trace->GetView();
2689
2690 wxCHECK( view, /* void */ );
2691
2692 // smith traces carry Re/Im of the reflection coefficient, not frequency
2693 bool smithTrace = ( trace->GetType() & SPT_SP_SMITH ) > 0;
2694
2695 if( dynamic_cast<LOG_SCALE<mpScaleXLog>*>( view->m_axis_x ) && !smithTrace )
2696 {
2697 // log( 0 ) is not valid.
2698 if( aX.size() > 0 && aX[0] == 0 )
2699 {
2700 aX.erase( aX.begin() );
2701 aY.erase( aY.begin() );
2702 }
2703 }
2704
2705 if( GetSimType() == ST_AC || GetSimType() == ST_FFT )
2706 {
2707 if( trace->GetType() & SPT_AC_PHASE )
2708 {
2709 for( double& pt : aY )
2710 pt = pt * 180.0 / M_PI; // convert to degrees
2711 }
2712 else
2713 {
2714 for( double& pt : aY )
2715 pt = MagnitudeToDb( pt ); // NaN where there is no signal
2716 }
2717 }
2718
2719 trace->SetData( aX, aY );
2720 trace->SetSweepCount( aSweepCount );
2721 trace->SetSweepSize( aSweepSize );
2722 trace->SetIsMultiRun( aIsMultiRun );
2723 trace->SetMultiRunLabels( aMultiRunLabels );
2724
2725 // Phase and currents on second Y axis, except for AC currents, those use the same axis as voltage
2726 if( smithTrace )
2727 {
2728 // drawn through the chart geometry, not the axis transforms
2729 trace->SetScale( nullptr, nullptr );
2730 }
2731 else if( ( trace->GetType() & SPT_AC_PHASE )
2732 || ( ( GetSimType() != ST_AC ) && ( trace->GetType() & SPT_CURRENT ) ) )
2733 {
2734 trace->SetScale( view->m_axis_x, view->m_axis_y2 );
2735 }
2736 else if( trace->GetType() & SPT_POWER )
2737 {
2738 trace->SetScale( view->m_axis_x, view->m_axis_y3 );
2739 }
2740 else
2741 {
2742 trace->SetScale( view->m_axis_x, view->m_axis_y1 );
2743 }
2744
2745 for( auto& [cursorId, cursor] : trace->GetCursors() )
2746 {
2747 if( cursor )
2748 cursor->UpdateForNewData();
2749 }
2750
2751 view->UpdateAxisVisibility();
2752}
2753
2754
2756{
2757 for( const auto& [name, trace] : m_traces )
2758 {
2759 if( trace == aTrace )
2760 {
2761 m_traces.erase( name );
2762 break;
2763 }
2764 }
2765
2766 if( SIM_VIEW* view = aTrace->GetView() )
2767 {
2768 for( const auto& [id, cursor] : aTrace->GetCursors() )
2769 {
2770 if( cursor )
2771 view->DelLayer( cursor, true );
2772 }
2773
2774 view->DelLayer( aTrace, true, true );
2775 view->UpdateAxisVisibility();
2776 view->UpdateSmithReferenceImpedance();
2777 }
2778
2779 ResetScales( false );
2780}
2781
2782
2783bool SIM_PLOT_TAB::DeleteTrace( const wxString& aVectorName, int aTraceType )
2784{
2785 if( TRACE* trace = GetTrace( aVectorName, aTraceType ) )
2786 {
2787 DeleteTrace( trace );
2788 return true;
2789 }
2790
2791 return false;
2792}
2793
2794
2795void SIM_PLOT_TAB::EnableCursor( TRACE* aTrace, int aCursorId, const wxString& aSignalName )
2796{
2797 SIM_VIEW* view = aTrace->GetView();
2798
2799 wxCHECK( view, /* void */ );
2800
2801 CURSOR* cursor;
2802
2803 if( aTrace->GetType() & SPT_SP_SMITH )
2804 {
2805 SMITH_TRACE* smithTrace = static_cast<SMITH_TRACE*>( aTrace );
2806
2807 cursor = new SMITH_CURSOR( smithTrace, this );
2808
2809 // start somewhere on the locus, biased per cursor id like the rectangular case
2810 const std::vector<double>& freqs = smithTrace->GetFrequencies();
2811
2812 if( !freqs.empty() )
2813 cursor->SetCoordX( freqs[freqs.size() * ( aCursorId == 1 ? 2 : 3 ) / 5] );
2814 }
2815 else
2816 {
2817 int width = view->GetXScreen() - view->GetMarginLeft() - view->GetMarginRight();
2818 int center = view->GetMarginLeft() + KiROUND( width * ( aCursorId == 1 ? 0.4 : 0.6 ) );
2819
2820 cursor = new CURSOR( aTrace, this );
2821
2822 cursor->SetX( center );
2823 }
2824
2825 cursor->SetName( aSignalName );
2826
2827 aTrace->SetCursor( aCursorId, cursor );
2828 view->AddLayer( cursor );
2829
2830 // Notify the parent window about the changes
2831 wxQueueEvent( this, new wxCommandEvent( EVT_SIM_CURSOR_UPDATE ) );
2832}
2833
2834
2835void SIM_PLOT_TAB::DisableCursor( TRACE* aTrace, int aCursorId )
2836{
2837 if( CURSOR* cursor = aTrace->GetCursor( aCursorId ) )
2838 {
2839 aTrace->SetCursor( aCursorId, nullptr );
2840
2841 if( SIM_VIEW* view = aTrace->GetView() )
2842 view->DelLayer( cursor, true );
2843
2844 // Notify the parent window about the changes
2845 wxQueueEvent( this, new wxCommandEvent( EVT_SIM_CURSOR_UPDATE ) );
2846 }
2847}
2848
2849
2850void SIM_PLOT_TAB::ResetScales( bool aIncludeX )
2851{
2852 for( SIM_VIEW* view : m_views )
2853 view->ResetScales( aIncludeX );
2854
2855 // Merge Y-axis auto-fit ranges for traces whose "Y Scale" has been explicitly linked to
2856 // another view, so that the linked axes end up sharing the same numeric range -- as if all
2857 // the involved traces were plotted together in a single view.
2858 std::map<std::pair<SIM_VIEW*, int>, std::vector<TRACE*>> groups;
2859
2860 for( const auto& [name, trace] : m_traces )
2861 {
2862 if( SIM_VIEW* view = trace->GetView() )
2863 groups[{ trace->GetYScaleView(), view->GetAxisSlot( trace ) }].push_back( trace );
2864 }
2865
2866 for( const auto& [key, traces] : groups )
2867 {
2868 std::vector<SIM_VIEW*> actualViews;
2869
2870 for( TRACE* trace : traces )
2871 {
2872 SIM_VIEW* view = trace->GetView();
2873
2874 if( std::find( actualViews.begin(), actualViews.end(), view ) == actualViews.end() )
2875 actualViews.push_back( view );
2876 }
2877
2878 if( actualViews.size() <= 1 )
2879 continue; // All traces already share the same, single axis object.
2880
2881 bool haveRange = false;
2882 double minV = 0.0;
2883 double maxV = 0.0;
2884
2885 for( TRACE* trace : traces )
2886 {
2887 double lo = trace->GetMinY();
2888 double hi = trace->GetMaxY();
2889
2890 if( !std::isfinite( lo ) || !std::isfinite( hi ) )
2891 continue;
2892
2893 if( !haveRange )
2894 {
2895 minV = lo;
2896 maxV = hi;
2897 haveRange = true;
2898 }
2899 else
2900 {
2901 minV = std::min( minV, lo );
2902 maxV = std::max( maxV, hi );
2903 }
2904 }
2905
2906 if( !haveRange )
2907 continue;
2908
2909 for( SIM_VIEW* view : actualViews )
2910 {
2911 if( mpScaleY* axis = view->GetAxisBySlot( key.second ) )
2912 axis->SetDataRange( minV, maxV );
2913 }
2914 }
2915}
2916
2917
2919{
2920 return GetDefaultView();
2921}
2922
2923
2924wxDEFINE_EVENT( EVT_SIM_CURSOR_UPDATE, wxCommandEvent );
2925wxDEFINE_EVENT( EVT_SIM_VIEWS_CHANGED, wxCommandEvent );
const char * name
wxBitmapBundle KiBitmapBundle(BITMAPS aBitmap, int aMinHeight)
Definition bitmap.cpp:106
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:982
The SIMULATOR_FRAME holds the main user-interface for running simulations.
mpWindow * m_window
void Move(wxPoint aDelta) override
Moves the layer rectangle of given pixel deltas.
wxString getID()
wxRealPoint m_coords
bool m_updateRef
static constexpr int DRAG_MARGIN
bool Inside(const wxPoint &aPoint) const override
Checks whether a point is inside the info box rectangle.
void doSetCoordX(double aValue)
int m_sweepIndex
bool OnDoubleClick(const wxPoint &aPoint, mpWindow &aWindow) override
virtual void SetCoordX(double aValue)
void UpdateReference() override
Updates the rectangle reference point.
bool m_updateRequired
double m_snapTargetY
TRACE * m_trace
void Plot(wxDC &aDC, mpWindow &aWindow) override
Plot method.
bool m_snapToNearest
void Update()
A color representation with 4 components: red, green, blue, alpha.
Definition color4d.h:101
COLOR4D & Invert()
Makes the color inverted, alpha remains the same.
Definition color4d.h:239
wxColour ToColour() const
Definition color4d.cpp:221
double Distance(const COLOR4D &other) const
Returns the distance (in RGB space) between two colors.
Definition color4d.cpp:551
COLOR4D Mix(const COLOR4D &aColor, double aFactor) const
Return a color that is mixed with the input by a factor.
Definition color4d.h:293
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
bool DeleteTrace(const wxString &aVectorName, int aTraceType)
double GetSmithZoom() const
The saved/restored pan and zoom track the default view.
void UpdateSmithReferenceImpedance()
Refresh the grid z0 from the shown Smith traces, on every view.
wxString GetLabelY1() const
mpWindow * GetPlotWin() const
Getter for the default view's math plot window (back-compat for zoom undo/redo, export).
void SetSmithView(double aZoom, double aPanX, double aPanY)
Restore a saved view, values are validated and clamped.
void ShowGrid(bool aEnable)
void SetTraceData(TRACE *aTrace, std::vector< double > &aX, std::vector< double > &aY, int aSweepCount, size_t aSweepSize, bool aIsMultiRun=false, const std::vector< wxString > &aMultiRunLabels={})
void SyncXView(SIM_VIEW *aSource)
Mirror aSource's current X range onto every other view of this tab.
const std::vector< SIM_VIEW * > & GetViews() const
wxString GetUnitsY2() const
void SetY2Scale(bool aLock, double aMin, double aMax)
void SmithZoomAt(const wxPoint &aPos, double aFactor)
TRACE * GetTrace(const wxString &aVecName, int aType) const
void SetSmithMode(bool aEnable)
Smith mode is tab-wide: every view of the tab shows a Smith chart while it is on.
virtual ~SIM_PLOT_TAB()
wxString GetLabelX() const
bool IsGridShown() const
wxRealPoint GetSmithPan() const
wxString GetLabelY3() const
void SetY1Scale(bool aLock, double aMin, double aMax)
wxPoint GetLegendPosition() const
void SetY3Scale(bool aLock, double aMin, double aMax)
std::map< wxString, TRACE * > m_traces
bool IsLegendShown() const
SIM_VIEW * GetDefaultView() const
The view new traces default to, and legacy (single-view) settings apply to.
SIM_VIEW * AddView()
Add a new (empty) view, stacked below the existing ones.
wxString GetUnitsForTrace(TRACE *aTrace) const
Get the display units (e.g. "V", "A", "dB") for a given trace's own view.
SIM_PLOT_COLORS m_colors
void UpdateTraceStyle(TRACE *trace)
Update trace line style.
void SetLegendPosition(const wxPoint &aPosition)
TRACE * GetOrAddTrace(const wxString &aVectorName, int aType, SIM_VIEW *aView)
Get (or create, on aView) the TRACE for a signal.
void ResetScales(bool aIncludeX)
Reset scale ranges to fit the current traces, on every view.
void UpdatePlotColors()
Update plot colors.
void ShowLegend(bool aEnable)
bool GetY3Scale(double *aMin, double *aMax) const
SIM_PLOT_TAB(const wxString &aSimCommand, wxWindow *parent)
wxString GetLabelY2() const
void EnableCursor(TRACE *aTrace, int aCursorId, const wxString &aSignalName)
Turn on/off the cursor for a particular trace.
int GetViewIndex(SIM_VIEW *aView) const
wxString GetUnitsX() const
void OnLanguageChanged() override
void EnsureThirdYAxisExists()
wxString getTraceId(const wxString &aVectorName, int aType) const
void ApplyPreferences(const SIM_PREFERENCES &aPrefs) override
wxBoxSizer * m_viewsSizer
mpWindow::MouseWheelActionSet m_mouseWheelActions
bool RemoveView(SIM_VIEW *aView)
Remove a view.
void SmithPanBy(const wxPoint &aDelta)
std::vector< SIM_VIEW * > m_views
wxPoint m_LastLegendPosition
wxString GetUnitsY1() const
std::map< wxString, wxColour > m_sessionTraceColors
static mpWindow::MouseWheelActionSet convertMouseWheelActions(const SIM_MOUSE_WHEEL_ACTION_SET &s)
void DisableCursor(TRACE *aTrace, int aCursorId)
bool GetY2Scale(double *aMin, double *aMax) const
bool GetY1Scale(double *aMin, double *aMax) const
wxString GetUnitsY3() const
SIM_TAB()
Definition sim_tab.cpp:29
SIM_TYPE GetSimType() const
Definition sim_tab.cpp:71
A single stacked plot area within a SIM_PLOT_TAB.
wxString GetLabelY2() const
void OnXViewChanged() override
Mirrors this view's X range onto every other view of the same tab.
void updateAxes(int aNewTraceType=SIM_TRACE_TYPE::SPT_UNKNOWN)
Create/Ensure axes are available for plotting.
void SmithZoomAt(const wxPoint &aPos, double aFactor)
bool GetY3Scale(double *aMin, double *aMax) const
void onSmithMenuCommand(wxCommandEvent &aEvent)
mpScaleY * m_axis_y3
wxString GetUnitsForTrace(TRACE *aTrace) const
Get the display units (e.g. "V", "A", "dB") for a given trace plotted on this view.
bool m_smithPanning
void UpdateSmithGridColor()
Re-read the Smith grid pen from the tab's color theme.
void SetY2Scale(bool aLock, double aMin, double aMax)
void EnsureThirdYAxisExists()
wxPoint m_smithMenuPos
void SmithPanBy(const wxPoint &aDelta)
mpInfoLegend * m_legend
void ResetSmithView()
void onSmithDClick(wxMouseEvent &aEvent)
wxString GetLabelX() const
bool m_smithLeftSkipped
wxPoint m_lastLegendPosition
wxString GetLabelY3() const
wxString GetUnitsY3() const
wxPoint GetLegendPosition() const
void onSmithMagnify(wxMouseEvent &aEvent)
void SetY1Scale(bool aLock, double aMin, double aMax)
const wxRealPoint & GetSmithPan() const
void SetY3Scale(bool aLock, double aMin, double aMax)
void onSmithMiddleDown(wxMouseEvent &aEvent)
void onSmithRightDown(wxMouseEvent &aEvent)
SIM_VIEW(SIM_PLOT_TAB *aPlotTab, wxWindow *aParent)
bool IsLegendShown() const
mpScaleY * GetAxisBySlot(int aSlot) const
Get the Y-axis scale object for a given slot (1, 2 or 3), or nullptr if not created yet.
double GetSmithZoom() const
bool getSmithView(SMITH_VIEW &aView) const
bool GetY2Scale(double *aMin, double *aMax) const
bool GetY1Scale(double *aMin, double *aMax) const
void onSmithLeftUp(wxMouseEvent &aEvent)
wxString GetLabelY1() const
void UpdateSmithReferenceImpedance()
Refresh the grid z0 from the Smith traces shown on this view.
mpScaleY * m_axis_y2
SMITH_GRID * m_smithGrid
void onSmithLeftDown(wxMouseEvent &aEvent)
void ResetScales(bool aIncludeX)
Reset the Y (and, if requested, X) scale ranges to fit this view's own traces.
mpScaleXBase * m_axis_x
int GetAxisSlot(TRACE *aTrace) const
Get the Y-axis slot (1, 2 or 3) a trace of this type is plotted on.
SIM_PLOT_TAB * m_plotTab
mpScaleY * m_axis_y1
wxString GetUnitsX() const
void onSmithRightUp(wxMouseEvent &aEvent)
void SetSmithChart(bool aEnable)
Show/hide this view's Smith chart overlay.
bool m_smithChart
void UpdateAxisVisibility()
void onSmithMotion(wxMouseEvent &aEvent)
wxString GetUnitsY2() const
wxPoint m_smithPanLast
wxString GetUnitsY1() const
void onSmithMouseWheel(wxMouseEvent &aEvent)
double m_smithZoom
bool IsGridShown() const
wxRealPoint m_smithPan
void prepareDCAxes(int aNewTraceType)
Construct the plot axes for DC simulation plot.
Cursor that snaps along a Smith chart locus, keyed by frequency.
void UpdateReference() override
Updates the rectangle reference point.
void Plot(wxDC &aDC, mpWindow &aWindow) override
Plot method.
bool Inside(const wxPoint &aPoint) const override
Checks whether a point is inside the info box rectangle.
void SetCoordX(double aValue) override
double m_requestFreq
void snapToIndex(int aIndex)
void snapToFrequency(double aFreq)
void UpdateForNewData() override
wxRealPoint m_gamma
void Move(wxPoint aDelta) override
Moves the layer rectangle of given pixel deltas.
Overlay layer drawing the Smith chart grid (constant resistance and reactance circles)
static bool GetChartView(mpWindow &aWindow, double aZoom, const wxRealPoint &aPan, SMITH_VIEW &aView)
Chart placement including pan/zoom.
void Plot(wxDC &aDC, mpWindow &aWindow) override
Plot given view of layer to the given device context.
bool m_mixedReferences
Reflection coefficient locus, Re in X and Im in Y, drawn on the Smith chart.
void Plot(wxDC &aDC, mpWindow &aWindow) override
Layer plot handler.
const std::vector< double > & GetFrequencies() const
double GetReferenceImpedance() const
Helper class to handle Spice way of expressing values (e.g. 10.5 Meg)
Definition spice_value.h:51
double ToDouble() const
A bitmap button widget that behaves like a standard dialog button except with an icon.
void SetBitmap(const wxBitmapBundle &aBmp)
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)
void SetIsMultiRun(bool aIsMultiRun)
void SetTraceColour(const wxColour &aColour)
void SetMultiRunLabels(const std::vector< wxString > &aLabels)
std::map< int, CURSOR * > & GetCursors()
SIM_VIEW * GetView() const
The SIM_VIEW this trace is currently plotted on (nullptr if not plotted anywhere).
void SetView(SIM_VIEW *aView)
SIM_TRACE_TYPE GetType() const
void SetData(const std::vector< double > &aX, const std::vector< double > &aY) override
Assigns new data set for the trace.
void SetCursor(int aCursorId, CURSOR *aCursor)
const std::vector< double > & GetDataY() const
wxColour GetTraceColour() const
const std::vector< double > & GetDataX() const
CURSOR * GetCursor(int aCursorId)
void SetSweepSize(size_t aSweepSize)
Definition mathplot.h:1495
size_t GetSweepSize() const
Definition mathplot.h:1496
void SetSweepCount(int aSweepCount)
Definition mathplot.h:1494
int GetSweepCount() const override
Definition mathplot.h:1520
virtual void SetScale(mpScaleBase *scaleX, mpScaleBase *scaleY)
wxPoint m_reference
Definition mathplot.h:417
wxRect m_dim
Definition mathplot.h:415
wxPoint GetPosition() const
Returns the position of the upper left corner of the box (in pixels)
Definition mathplot.cpp:197
virtual void UpdateReference()
Updates the rectangle reference point.
Definition mathplot.cpp:152
virtual void Move(wxPoint delta)
Moves the layer rectangle of given pixel deltas.
Definition mathplot.cpp:145
Implements the legend to be added to the plot This layer allows you to add a legend to describe the p...
Definition mathplot.h:430
const wxString & GetName() const
Get layer name.
Definition mathplot.h:256
bool m_continuous
Specify if the layer will be plotted as a continuous line or a set of points.
Definition mathplot.h:345
bool m_visible
Toggles layer visibility.
Definition mathplot.h:348
const wxPen & GetPen() const
Get pen set for this layer.
Definition mathplot.h:287
void SetVisible(bool show)
Sets layer visibility.
Definition mathplot.h:328
const wxFont & GetPlotFont() const
Get the font to draw this layer with, which is the default one when none was set.
Definition mathplot.h:276
void SetPen(const wxPen &pen)
Set layer pen.
Definition mathplot.h:316
wxPen m_pen
Layer's pen.
Definition mathplot.h:341
bool m_rangeSet
Definition mathplot.h:798
double m_maxV
Definition mathplot.h:797
double m_minV
Definition mathplot.h:797
Plot layer implementing a y-scale ruler.
Definition mathplot.h:886
void SetMasterScale(mpScaleY *masterScale)
Definition mathplot.h:911
Canvas for plotting mpLayer implementations.
Definition mathplot.h:967
int GetMarginLeft() const
Definition mathplot.h:1282
void SetMargins(int top, int right, int bottom, int left)
Set window margins, creating a blank area where some kinds of layers cannot draw.
int GetScrX() const
Get current view's X dimension in device context units.
Definition mathplot.h:1088
int GetScrY() const
Get current view's Y dimension in device context units.
Definition mathplot.h:1097
void SetMouseWheelActions(const MouseWheelActionSet &s)
Set the pan/zoom actions corresponding to mousewheel/trackpad events.
Definition mathplot.h:1169
double p2x(wxCoord pixelCoordX)
Converts mpWindow (screen) pixel coordinates into graph (floating point) coordinates,...
Definition mathplot.h:1142
void LimitView(bool aEnable)
Enable limiting of zooming & panning to the area used by the plots.
Definition mathplot.h:1321
wxCoord x2p(double x)
Converts graph (floating point) coordinates into mpWindow (screen) pixel coordinates,...
Definition mathplot.h:1152
mpInfoLayer * IsInsideInfoLayer(wxPoint &point)
Check if a given point is inside the area of a mpInfoLayer and eventually returns its pointer.
int GetXScreen() const
Definition mathplot.h:1089
int GetMarginTop() const
Definition mathplot.h:1276
void UpdateAll()
Refresh display.
wxCoord y2p(double y)
Converts graph (floating point) coordinates into mpWindow (screen) pixel coordinates,...
Definition mathplot.h:1157
double GetDesiredXmax() const
Returns the right-border layer coordinate that the user wants the mpWindow to show (it may be not exa...
Definition mathplot.h:1225
wxMenu * GetPopupMenu()
Get reference to context menu of the plot canvas.
Definition mathplot.h:1008
double GetDesiredXmin() const
Returns the left-border layer coordinate that the user wants the mpWindow to show (it may be not exac...
Definition mathplot.h:1219
int GetMarginRight() const
Definition mathplot.h:1278
int GetMarginBottom() const
Definition mathplot.h:1280
void EnableDoubleBuffer(bool enabled)
Enable/disable the double-buffering of the window, eliminating the flicker (default=disabled).
Definition mathplot.h:1162
double GetScaleX() const
Get current view's X scale.
Definition mathplot.h:1062
bool AddLayer(mpLayer *layer, bool refreshDisplay=true)
Add a plot layer to the canvas.
double GetPosX() const
Get current view's X position.
Definition mathplot.h:1074
static bool empty(const wxTextEntryBase *aCtrl)
#define _(s)
#define mpALIGN_BORDER_RIGHT
Aligns Y axis to right border.
Definition mathplot.h:493
#define mpALIGN_RIGHT
Aligns label to the right.
Definition mathplot.h:464
#define mpALIGN_LEFT
Aligns label to the left.
Definition mathplot.h:468
@ mpID_ZOOM_REDO
Definition mathplot.h:134
@ mpID_FIT
Definition mathplot.h:132
@ mpID_ZOOM_IN
Definition mathplot.h:135
@ mpID_CENTER
Definition mathplot.h:137
@ mpID_ZOOM_UNDO
Definition mathplot.h:133
@ mpID_ZOOM_OUT
Definition mathplot.h:136
#define mpALIGN_BOTTOM
Aligns label to the bottom.
Definition mathplot.h:472
KICOMMON_API wxFont GetStatusFont(wxWindow *aWindow)
double SeriesCapacitance(double aReactance, double aFreq)
Definition smith_math.h:101
double SeriesInductance(double aReactance, double aFreq)
Series equivalent element of a reactance at one frequency, henries for aReactance > 0,...
Definition smith_math.h:96
double VSWR(double aGammaMag)
Definition smith_math.h:78
wxRealPoint ZoomAboutPoint(const SMITH_VIEW &aView, const wxPoint &aPos, double aNewZoom)
Pan that keeps the gamma point under aPos fixed when the view zooms to aNewZoom.
Definition smith_math.h:107
bool GammaToImpedance(double aRe, double aIm, double aZ0, double &aResistance, double &aReactance)
Impedance of a reflection coefficient, z = z0 ( 1 + gamma ) / ( 1 - gamma ), false at the gamma = 1 s...
Definition smith_math.h:65
double ReturnLoss(double aGammaMag)
Definition smith_math.h:86
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:437
static float distance(const SFVEC2UI &a, const SFVEC2UI &b)
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 bool smithView(mpWindow &aWindow, SMITH_VIEW &aView)
static wxString formatFloat(double x, int nDigits)
SIM_TRACE_TYPE
Possible trace types.
Definition sim_types.h:49
@ SPT_AC_PHASE
Definition sim_types.h:53
@ SPT_AC_GAIN
Definition sim_types.h:54
@ SPT_VOLTAGE
Definition sim_types.h:51
@ SPT_POWER
Definition sim_types.h:55
@ SPT_CURRENT
Definition sim_types.h:52
@ SPT_SP_SMITH
Definition sim_types.h:57
double MagnitudeToDb(double aMagnitude)
Convert a linear magnitude to decibels.
Definition sim_types.h:82
@ ST_SP
Definition sim_types.h:42
@ ST_TRAN
Definition sim_types.h:41
@ ST_NOISE
Definition sim_types.h:36
@ ST_AC
Definition sim_types.h:33
@ ST_DC
Definition sim_types.h:34
@ ST_FFT
Definition sim_types.h:43
static SIM_MOUSE_WHEEL_ACTION_SET GetMouseDefaults()
Contains preferences pertaining to the simulator.
SIM_MOUSE_WHEEL_ACTION_SET mouse_wheel_actions
Smith chart placement plus pan/zoom, maps a gamma point to a screen pixel and back.
Definition smith_math.h:32
wxRealPoint pan
Definition smith_math.h:36
double zoom
Definition smith_math.h:35
wxRealPoint ToGamma(const wxPoint &aPt) const
Definition smith_math.h:53
wxPoint center
Definition smith_math.h:33
wxPoint ToScreen(double aRe, double aIm) const
Definition smith_math.h:39
double radius
Definition smith_math.h:34
wxRect plotRect
Definition smith_math.h:37
VECTOR2I center
int radius
VECTOR2I end
int delta
#define M_PI