kicad-source/eeschema/sim/sim_plot_tab.cpp
Jeff Young e5bce16e31 Don't reset cursor x position during a sim refresh.
The sim may not be complete yet.  And even if it is, the user
didn't ask us to move their cursor.  Just leave it where it is
with an undefined y value.

Fixes https://gitlab.com/kicad/code/kicad/-/issues/15672
2023-09-17 19:23:46 +01:00

995 lines
28 KiB
C++

/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2016-2023 CERN
* Copyright (C) 2021-2023 KiCad Developers, see AUTHORS.txt for contributors.
*
* @author Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
* @author Maciej Suminski <maciej.suminski@cern.ch>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 3
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you may find one here:
* https://www.gnu.org/licenses/gpl-3.0.html
* or you may search the http://www.gnu.org website for the version 3 license,
* or you may write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
#include "sim_plot_colors.h"
#include "sim_plot_tab.h"
#include "simulator_frame.h"
#include "core/kicad_algo.h"
#include <algorithm>
#include <limits>
static wxString formatFloat( double x, int nDigits )
{
wxString rv, fmt;
if( nDigits )
fmt.Printf( "%%.0%df", nDigits );
else
fmt = wxT( "%.0f" );
rv.Printf( fmt, x );
return rv;
}
static void getSISuffix( double x, const wxString& unit, int& power, wxString& suffix )
{
const int n_powers = 11;
const struct
{
int exponent;
char suffix;
} powers[] =
{
{ -18, 'a' },
{ -15, 'f' },
{ -12, 'p' },
{ -9, 'n' },
{ -6, 'u' },
{ -3, 'm' },
{ 0, 0 },
{ 3, 'k' },
{ 6, 'M' },
{ 9, 'G' },
{ 12, 'T' },
{ 14, 'P' }
};
power = 0;
suffix = unit;
if( x == 0.0 )
return;
for( int i = 0; i < n_powers - 1; i++ )
{
double r_cur = pow( 10, powers[i].exponent );
if( fabs( x ) >= r_cur && fabs( x ) < r_cur * 1000.0 )
{
power = powers[i].exponent;
if( powers[i].suffix )
suffix = wxString( powers[i].suffix ) + unit;
else
suffix = unit;
return;
}
}
}
static int countDecimalDigits( double x, int maxDigits )
{
if( std::isnan( x ) )
return 0;
auto countSignificantDigits =
[&]( int64_t k )
{
while( k && ( k % 10LL ) == 0LL )
k /= 10LL;
int n = 0;
while( k != 0LL )
{
n++;
k /= 10LL;
}
return n;
};
int64_t k = (int)( ( x - floor( x ) ) * pow( 10.0, (double) maxDigits ) );
int n = countSignificantDigits( k );
// check for trailing 9's
n = std::min( n, countSignificantDigits( k + 1 ) );
return n;
}
template <typename T_PARENT>
class LIN_SCALE : public T_PARENT
{
public:
LIN_SCALE( const wxString& name, const wxString& unit, int flags ) :
T_PARENT( name, flags, false ),
m_unit( unit )
{};
wxString GetUnits() const { return m_unit; }
private:
void formatLabels() override
{
double maxVis = T_PARENT::AbsVisibleMaxValue();
wxString suffix;
int power = 0;
int digits = 0;
int constexpr MAX_DIGITS = 3;
int constexpr MAX_DISAMBIGUATION_DIGITS = 6;
bool duplicateLabels = false;
getSISuffix( maxVis, m_unit, power, suffix );
double sf = pow( 10.0, power );
for( mpScaleBase::TICK_LABEL& l : T_PARENT::m_tickLabels )
digits = std::max( digits, countDecimalDigits( l.pos / sf, MAX_DIGITS ) );
do
{
for( size_t ii = 0; ii < T_PARENT::m_tickLabels.size(); ++ii )
{
mpScaleBase::TICK_LABEL& l = T_PARENT::m_tickLabels[ii];
l.label = formatFloat( l.pos / sf, digits );
l.visible = true;
if( ii > 0 && l.label == T_PARENT::m_tickLabels[ii-1].label )
duplicateLabels = true;
}
}
while( duplicateLabels && ++digits <= MAX_DISAMBIGUATION_DIGITS );
if( m_base_axis_label.IsEmpty() )
m_base_axis_label = T_PARENT::GetName();
T_PARENT::SetName( wxString::Format( "%s (%s)", m_base_axis_label, suffix ) );
}
private:
const wxString m_unit;
wxString m_base_axis_label;
};
class TIME_SCALE : public LIN_SCALE<mpScaleX>
{
public:
TIME_SCALE( const wxString& name, const wxString& unit, int flags ) :
LIN_SCALE( name, unit, flags )
{};
void ExtendDataRange( double minV, double maxV ) override
{
if( !m_rangeSet )
{
m_minV = minV;
m_maxV = maxV;
m_rangeSet = true;
}
else
{
if( minV < m_minV )
m_minV -= abs( maxV - minV );
if( maxV > m_maxV )
m_maxV += abs( maxV - minV );
}
}
};
template <typename T_PARENT>
class LOG_SCALE : public T_PARENT
{
public:
LOG_SCALE( const wxString& name, const wxString& unit, int flags ) :
T_PARENT( name, flags, false ),
m_unit( unit )
{};
wxString GetUnits() const { return m_unit; }
private:
void formatLabels() override
{
wxString suffix;
int power;
int constexpr MAX_DIGITS = 3;
for( mpScaleBase::TICK_LABEL& l : T_PARENT::m_tickLabels )
{
getSISuffix( l.pos, m_unit, power, suffix );
double sf = pow( 10.0, power );
int k = countDecimalDigits( l.pos / sf, MAX_DIGITS );
l.label = formatFloat( l.pos / sf, k ) + suffix;
l.visible = true;
}
}
private:
const wxString m_unit;
};
void CURSOR::SetCoordX( double aValue )
{
wxRealPoint oldCoords = m_coords;
doSetCoordX( aValue );
m_updateRequired = false;
m_updateRef = true;
if( m_window )
{
wxRealPoint delta = m_coords - oldCoords;
mpInfoLayer::Move( wxPoint( m_window->x2p( m_trace->x2s( delta.x ) ),
m_window->y2p( m_trace->y2s( delta.y ) ) ) );
m_window->Refresh();
}
}
void CURSOR::doSetCoordX( double aValue )
{
m_coords.x = aValue;
const std::vector<double>& dataX = m_trace->GetDataX();
const std::vector<double>& dataY = m_trace->GetDataY();
if( dataX.size() <= 1 )
return;
// Find the closest point coordinates
auto maxXIt = std::upper_bound( dataX.begin(), dataX.end(), m_coords.x );
int maxIdx = maxXIt - dataX.begin();
int minIdx = maxIdx - 1;
// Out of bounds checks
if( minIdx < 0 || maxIdx >= (int) dataX.size() )
{
// Simulation may not be complete yet, or we may have a cursor off the beginning or end
// of the data. Either way, that's where the user put it. Don't second guess them; just
// leave its y value undefined.
m_coords.y = NAN;
return;
}
const double leftX = dataX[minIdx];
const double rightX = dataX[maxIdx];
const double leftY = dataY[minIdx];
const double rightY = dataY[maxIdx];
// Linear interpolation
m_coords.y = leftY + ( rightY - leftY ) / ( rightX - leftX ) * ( m_coords.x - leftX );
}
wxString CURSOR::getID()
{
for( const auto& [ id, cursor ] : m_trace->GetCursors() )
{
if( cursor == this )
return wxString::Format( _( "%d" ), id );
}
return wxEmptyString;
}
void CURSOR::Plot( wxDC& aDC, mpWindow& aWindow )
{
if( !m_window )
m_window = &aWindow;
if( !m_visible || m_trace->GetDataX().size() <= 1 )
return;
if( m_updateRequired )
{
doSetCoordX( m_trace->s2x( aWindow.p2x( m_dim.x ) ) );
m_updateRequired = false;
// Notify the parent window about the changes
wxQueueEvent( aWindow.GetParent(), new wxCommandEvent( EVT_SIM_CURSOR_UPDATE ) );
}
else
{
m_updateRef = true;
}
if( m_updateRef )
{
UpdateReference();
m_updateRef = false;
}
// Line length in horizontal and vertical dimensions
const wxPoint cursorPos( aWindow.x2p( m_trace->x2s( m_coords.x ) ),
aWindow.y2p( m_trace->y2s( m_coords.y ) ) );
wxCoord leftPx = aWindow.GetMarginLeft();
wxCoord rightPx = aWindow.GetScrX() - aWindow.GetMarginRight();
wxCoord topPx = aWindow.GetMarginTop();
wxCoord bottomPx = aWindow.GetScrY() - aWindow.GetMarginBottom();
wxPen pen = GetPen();
wxColour fg = GetPen().GetColour();
pen.SetColour( COLOR4D( m_trace->GetTraceColour() ).Mix( fg, 0.6 ).ToColour() );
pen.SetStyle( m_continuous ? wxPENSTYLE_SOLID : wxPENSTYLE_LONG_DASH );
aDC.SetPen( pen );
if( topPx < cursorPos.y && cursorPos.y < bottomPx )
aDC.DrawLine( leftPx, cursorPos.y, rightPx, cursorPos.y );
if( leftPx < cursorPos.x && cursorPos.x < rightPx )
{
aDC.DrawLine( cursorPos.x, topPx, cursorPos.x, bottomPx );
wxString id = getID();
wxSize size = aDC.GetTextExtent( wxS( "M" ) );
wxRect textRect( wxPoint( cursorPos.x + 1 - size.x / 2, topPx - 4 - size.y ), size );
wxBrush brush;
wxPoint poly[3];
// Because a "1" looks off-center if it's actually centred.
if( id == "1" )
textRect.x -= 1;
// We want an equalateral triangle, so use size.y for both axes.
size.y += 3;
// Make sure it's an even number so the slopes of the sides will be identical.
size.y = ( size.y / 2 ) * 2;
poly[0] = { cursorPos.x - 1 - size.y / 2, topPx - size.y };
poly[1] = { cursorPos.x + 1 + size.y / 2, topPx - size.y };
poly[2] = { cursorPos.x, topPx };
brush.SetStyle( wxBRUSHSTYLE_SOLID );
brush.SetColour( m_trace->GetTraceColour() );
aDC.SetBrush( brush );
aDC.DrawPolygon( 3, poly );
aDC.SetTextForeground( fg );
aDC.DrawLabel( id, textRect, wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL );
}
}
bool CURSOR::Inside( const wxPoint& aPoint ) const
{
if( !m_window || !m_trace )
return false;
return ( std::abs( (double) aPoint.x -
m_window->x2p( m_trace->x2s( m_coords.x ) ) ) <= DRAG_MARGIN )
|| ( std::abs( (double) aPoint.y -
m_window->y2p( m_trace->y2s( m_coords.y ) ) ) <= DRAG_MARGIN );
}
void CURSOR::UpdateReference()
{
if( !m_window )
return;
m_reference.x = m_window->x2p( m_trace->x2s( m_coords.x ) );
m_reference.y = m_window->y2p( m_trace->y2s( m_coords.y ) );
}
SIM_PLOT_TAB::SIM_PLOT_TAB( const wxString& aSimCommand, wxWindow* parent ) :
SIM_TAB( aSimCommand, parent ),
m_axis_x( nullptr ),
m_axis_y1( nullptr ),
m_axis_y2( nullptr ),
m_axis_y3( nullptr ),
m_dotted_cp( false )
{
m_sizer = new wxBoxSizer( wxVERTICAL );
m_plotWin = new mpWindow( this, wxID_ANY );
m_plotWin->LimitView( true );
m_plotWin->SetMargins( 30, 70, 45, 70 );
UpdatePlotColors();
updateAxes();
// a mpInfoLegend displays le name of traces on the left top panel corner:
m_legend = new mpInfoLegend( wxRect( 0, 0, 200, 40 ), wxTRANSPARENT_BRUSH );
m_legend->SetVisible( false );
m_plotWin->AddLayer( m_legend );
m_LastLegendPosition = m_legend->GetPosition();
m_plotWin->EnableDoubleBuffer( true );
m_plotWin->UpdateAll();
m_sizer->Add( m_plotWin, 1, wxALL | wxEXPAND, 1 );
SetSizer( m_sizer );
}
SIM_PLOT_TAB::~SIM_PLOT_TAB()
{
// ~mpWindow destroys all the added layers, so there is no need to destroy m_traces contents
}
void SIM_PLOT_TAB::SetY1Scale( bool aLock, double aMin, double aMax )
{
m_axis_y1->SetAxisMinMax( aLock, aMin, aMax );
}
void SIM_PLOT_TAB::SetY2Scale( bool aLock, double aMin, double aMax )
{
m_axis_y2->SetAxisMinMax( aLock, aMin, aMax );
}
void SIM_PLOT_TAB::SetY3Scale( bool aLock, double aMin, double aMax )
{
m_axis_y3->SetAxisMinMax( aLock, aMin, aMax );
}
wxString SIM_PLOT_TAB::GetUnitsX() const
{
LOG_SCALE<mpScaleXLog>* logScale = dynamic_cast<LOG_SCALE<mpScaleXLog>*>( m_axis_x );
LIN_SCALE<mpScaleX>* linScale = dynamic_cast<LIN_SCALE<mpScaleX>*>( m_axis_x );
if( logScale )
return logScale->GetUnits();
else if( linScale )
return linScale->GetUnits();
else
return wxEmptyString;
}
wxString SIM_PLOT_TAB::GetUnitsY1() const
{
LIN_SCALE<mpScaleY>* linScale = dynamic_cast<LIN_SCALE<mpScaleY>*>( m_axis_y1 );
if( linScale )
return linScale->GetUnits();
else
return wxEmptyString;
}
wxString SIM_PLOT_TAB::GetUnitsY2() const
{
LIN_SCALE<mpScaleY>* linScale = dynamic_cast<LIN_SCALE<mpScaleY>*>( m_axis_y2 );
if( linScale )
return linScale->GetUnits();
else
return wxEmptyString;
}
wxString SIM_PLOT_TAB::GetUnitsY3() const
{
LIN_SCALE<mpScaleY>* linScale = dynamic_cast<LIN_SCALE<mpScaleY>*>( m_axis_y3 );
if( linScale )
return linScale->GetUnits();
else
return wxEmptyString;
}
void SIM_PLOT_TAB::updateAxes( int aNewTraceType )
{
switch( GetSimType() )
{
case ST_AC:
if( !m_axis_x )
{
m_axis_x = new LOG_SCALE<mpScaleXLog>( wxEmptyString, wxT( "Hz" ), mpALIGN_BOTTOM );
m_axis_x->SetNameAlign( mpALIGN_BOTTOM );
m_plotWin->AddLayer( m_axis_x );
m_axis_y1 = new LIN_SCALE<mpScaleY>( wxEmptyString, wxT( "dBV" ), mpALIGN_LEFT );
m_axis_y1->SetNameAlign( mpALIGN_LEFT );
m_plotWin->AddLayer( m_axis_y1 );
m_axis_y2 = new LIN_SCALE<mpScaleY>( wxEmptyString, wxT( "°" ), mpALIGN_RIGHT );
m_axis_y2->SetNameAlign( mpALIGN_RIGHT );
m_axis_y2->SetMasterScale( m_axis_y1 );
m_plotWin->AddLayer( m_axis_y2 );
}
m_axis_x->SetName( _( "Frequency" ) );
m_axis_y1->SetName( _( "Gain" ) );
m_axis_y2->SetName( _( "Phase" ) );
break;
case ST_SP:
if( !m_axis_x )
{
m_axis_x = new LOG_SCALE<mpScaleXLog>( wxEmptyString, wxT( "Hz" ), mpALIGN_BOTTOM );
m_axis_x->SetNameAlign( mpALIGN_BOTTOM );
m_plotWin->AddLayer( m_axis_x );
m_axis_y1 = new LIN_SCALE<mpScaleY>( wxEmptyString, wxT( "" ), mpALIGN_LEFT );
m_axis_y1->SetNameAlign( mpALIGN_LEFT );
m_plotWin->AddLayer( m_axis_y1 );
m_axis_y2 = new LIN_SCALE<mpScaleY>( wxEmptyString, wxT( "°" ), mpALIGN_RIGHT );
m_axis_y2->SetNameAlign( mpALIGN_RIGHT );
m_axis_y2->SetMasterScale( m_axis_y1 );
m_plotWin->AddLayer( m_axis_y2 );
}
m_axis_x->SetName( _( "Frequency" ) );
m_axis_y1->SetName( _( "Ampltiude" ) );
m_axis_y2->SetName( _( "Phase" ) );
break;
case ST_DC:
prepareDCAxes( aNewTraceType );
break;
case ST_NOISE:
if( !m_axis_x )
{
m_axis_x = new LOG_SCALE<mpScaleXLog>( wxEmptyString, wxT( "Hz" ), mpALIGN_BOTTOM );
m_axis_x->SetNameAlign( mpALIGN_BOTTOM );
m_plotWin->AddLayer( m_axis_x );
if( ( aNewTraceType & SPT_CURRENT ) == 0 )
{
m_axis_y1 = new LIN_SCALE<mpScaleY>( wxEmptyString, wxT( "" ), mpALIGN_LEFT );
m_axis_y1->SetNameAlign( mpALIGN_LEFT );
m_plotWin->AddLayer( m_axis_y1 );
}
else
{
m_axis_y2 = new LIN_SCALE<mpScaleY>( wxEmptyString, wxT( "" ), mpALIGN_RIGHT );
m_axis_y2->SetNameAlign( mpALIGN_RIGHT );
m_plotWin->AddLayer( m_axis_y2 );
}
}
m_axis_x->SetName( _( "Frequency" ) );
if( m_axis_y1 )
m_axis_y1->SetName( _( "Noise (V/√Hz)" ) );
if( m_axis_y2 )
m_axis_y2->SetName( _( "Noise (A/√Hz)" ) );
break;
case ST_FFT:
if( !m_axis_x )
{
m_axis_x = new LOG_SCALE<mpScaleXLog>( wxEmptyString, wxT( "Hz" ), mpALIGN_BOTTOM );
m_axis_x->SetNameAlign( mpALIGN_BOTTOM );
m_plotWin->AddLayer( m_axis_x );
m_axis_y1 = new LIN_SCALE<mpScaleY>( wxEmptyString, wxT( "dB" ), mpALIGN_LEFT );
m_axis_y1->SetNameAlign( mpALIGN_LEFT );
m_plotWin->AddLayer( m_axis_y1 );
}
m_axis_x->SetName( _( "Frequency" ) );
m_axis_y1->SetName( _( "Intensity" ) );
break;
case ST_TRAN:
if( !m_axis_x )
{
m_axis_x = new TIME_SCALE( wxEmptyString, wxT( "s" ), mpALIGN_BOTTOM );
m_axis_x->SetNameAlign( mpALIGN_BOTTOM );
m_plotWin->AddLayer( m_axis_x );
m_axis_y1 = new LIN_SCALE<mpScaleY>(wxEmptyString, wxT( "V" ), mpALIGN_LEFT );
m_axis_y1->SetNameAlign( mpALIGN_LEFT );
m_plotWin->AddLayer( m_axis_y1 );
m_axis_y2 = new LIN_SCALE<mpScaleY>( wxEmptyString, wxT( "A" ), mpALIGN_RIGHT );
m_axis_y2->SetNameAlign( mpALIGN_RIGHT );
m_axis_y2->SetMasterScale( m_axis_y1 );
m_plotWin->AddLayer( m_axis_y2 );
}
m_axis_x->SetName( _( "Time" ) );
m_axis_y1->SetName( _( "Voltage" ) );
m_axis_y2->SetName( _( "Current" ) );
if( ( aNewTraceType & SPT_POWER ) && !m_axis_y3 )
{
m_plotWin->SetMargins( 30, 140, 45, 70 );
m_axis_y3 = new LIN_SCALE<mpScaleY>( wxEmptyString, wxT( "W" ), mpALIGN_FAR_RIGHT );
m_axis_y3->SetNameAlign( mpALIGN_FAR_RIGHT );
m_axis_y3->SetMasterScale( m_axis_y1 );
m_plotWin->AddLayer( m_axis_y3 );
}
if( m_axis_y3 )
m_axis_y3->SetName( _( "Power" ) );
break;
default:
// suppress warnings
break;
}
if( m_axis_x )
m_axis_x->SetFont( KIUI::GetStatusFont( m_plotWin ) );
if( m_axis_y1 )
m_axis_y1->SetFont( KIUI::GetStatusFont( m_plotWin ) );
if( m_axis_y2 )
m_axis_y2->SetFont( KIUI::GetStatusFont( m_plotWin ) );
if( m_axis_y3 )
m_axis_y3->SetFont( KIUI::GetStatusFont( m_plotWin ) );
}
void SIM_PLOT_TAB::prepareDCAxes( int aNewTraceType )
{
wxString sim_cmd = GetSimCommand().Lower();
wxString rem;
if( sim_cmd.StartsWith( ".dc", &rem ) )
{
wxChar ch = 0;
rem.Trim( false );
try
{
ch = rem.GetChar( 0 );
}
catch( ... )
{
// Best efforts
}
switch( ch )
{
// Make sure that we have a reliable default (even if incorrectly labeled)
default:
case 'v':
if( !m_axis_x )
{
m_axis_x = new LIN_SCALE<mpScaleX>( wxEmptyString, wxT( "V" ), mpALIGN_BOTTOM );
m_axis_x->SetNameAlign( mpALIGN_BOTTOM );
m_plotWin->AddLayer( m_axis_x );
}
m_axis_x->SetName( _( "Voltage (swept)" ) );
break;
case 'i':
if( !m_axis_x )
{
m_axis_x = new LIN_SCALE<mpScaleX>( wxEmptyString, wxT( "A" ), mpALIGN_BOTTOM );
m_axis_x->SetNameAlign( mpALIGN_BOTTOM );
m_plotWin->AddLayer( m_axis_x );
}
m_axis_x->SetName( _( "Current (swept)" ) );
break;
case 'r':
if( !m_axis_x )
{
m_axis_x = new LIN_SCALE<mpScaleX>( wxEmptyString, wxT( "" ), mpALIGN_BOTTOM );
m_axis_x->SetNameAlign( mpALIGN_BOTTOM );
m_plotWin->AddLayer( m_axis_x );
}
m_axis_x->SetName( _( "Resistance (swept)" ) );
break;
case 't':
if( !m_axis_x )
{
m_axis_x = new LIN_SCALE<mpScaleX>( wxEmptyString, wxT( "°C" ), mpALIGN_BOTTOM );
m_axis_x->SetNameAlign( mpALIGN_BOTTOM );
m_plotWin->AddLayer( m_axis_x );
}
m_axis_x->SetName( _( "Temperature (swept)" ) );
break;
}
if( !m_axis_y1 )
{
m_axis_y1 = new LIN_SCALE<mpScaleY>( wxEmptyString, wxT( "V" ), mpALIGN_LEFT );
m_axis_y1->SetNameAlign( mpALIGN_LEFT );
m_plotWin->AddLayer( m_axis_y1 );
}
if( !m_axis_y2 )
{
m_axis_y2 = new LIN_SCALE<mpScaleY>( wxEmptyString, wxT( "A" ), mpALIGN_RIGHT );
m_axis_y2->SetNameAlign( mpALIGN_RIGHT );
m_plotWin->AddLayer( m_axis_y2 );
}
m_axis_y1->SetName( _( "Voltage (measured)" ) );
m_axis_y2->SetName( _( "Current" ) );
if( ( aNewTraceType & SPT_POWER ) && !m_axis_y3 )
{
m_plotWin->SetMargins( 30, 140, 45, 70 );
m_axis_y3 = new LIN_SCALE<mpScaleY>( wxEmptyString, wxT( "W" ), mpALIGN_FAR_RIGHT );
m_axis_y3->SetNameAlign( mpALIGN_FAR_RIGHT );
m_axis_y3->SetMasterScale( m_axis_y1 );
m_plotWin->AddLayer( m_axis_y3 );
}
if( m_axis_y3 )
m_axis_y3->SetName( _( "Power" ) );
}
}
void SIM_PLOT_TAB::UpdatePlotColors()
{
// Update bg and fg colors:
m_plotWin->SetColourTheme( m_colors.GetPlotColor( SIM_PLOT_COLORS::COLOR_SET::BACKGROUND ),
m_colors.GetPlotColor( SIM_PLOT_COLORS::COLOR_SET::FOREGROUND ),
m_colors.GetPlotColor( SIM_PLOT_COLORS::COLOR_SET::AXIS ) );
// Update color of all traces
for( const auto& [ name, trace ] : m_traces )
{
for( const auto& [ id, cursor ] : trace->GetCursors() )
{
if( cursor )
cursor->SetPen( wxPen( m_colors.GetPlotColor( SIM_PLOT_COLORS::COLOR_SET::CURSOR ) ) );
}
}
m_plotWin->UpdateAll();
}
void SIM_PLOT_TAB::OnLanguageChanged()
{
updateAxes();
m_plotWin->UpdateAll();
}
void SIM_PLOT_TAB::UpdateTraceStyle( TRACE* trace )
{
int type = trace->GetType();
wxPenStyle penStyle = ( ( ( type & SPT_AC_PHASE ) || ( type & SPT_CURRENT ) ) && m_dotted_cp )
? wxPENSTYLE_DOT
: wxPENSTYLE_SOLID;
trace->SetPen( wxPen( trace->GetTraceColour(), 2, penStyle ) );
}
TRACE* SIM_PLOT_TAB::AddTrace( const wxString& aVectorName, int aType )
{
TRACE* trace = GetTrace( aVectorName, aType );
if( !trace )
{
updateAxes( aType );
if( GetSimType() == ST_TRAN || GetSimType() == ST_DC )
{
bool hasVoltageTraces = false;
for( const auto& [ id, candidate ] : m_traces )
{
if( candidate->GetType() & SPT_VOLTAGE )
{
hasVoltageTraces = true;
break;
}
}
if( !hasVoltageTraces )
{
if( m_axis_y2 )
m_axis_y2->SetMasterScale( nullptr );
if( m_axis_y3 )
m_axis_y3->SetMasterScale( nullptr );
}
}
trace = new TRACE( aVectorName, (SIM_TRACE_TYPE) aType );
trace->SetTraceColour( m_colors.GenerateColor( m_traces ) );
UpdateTraceStyle( trace );
m_traces[ getTraceId( aVectorName, aType ) ] = trace;
m_plotWin->AddLayer( (mpLayer*) trace );
}
return trace;
}
void SIM_PLOT_TAB::SetTraceData( TRACE* trace, std::vector<double>& aX, std::vector<double>& aY )
{
if( dynamic_cast<LOG_SCALE<mpScaleXLog>*>( m_axis_x ) )
{
// log( 0 ) is not valid.
if( aX.size() > 0 && aX[0] == 0 )
{
aX.erase( aX.begin() );
aY.erase( aY.begin() );
}
}
if( GetSimType() == ST_AC || GetSimType() == ST_FFT )
{
if( trace->GetType() & SPT_AC_PHASE )
{
for( double& pt : aY )
pt = pt * 180.0 / M_PI; // convert to degrees
}
else
{
for( double& pt : aY )
{
// log( 0 ) is not valid.
if( pt != 0 )
pt = 20 * log( pt ) / log( 10.0 ); // convert to dB
}
}
}
trace->SetData( aX, aY );
if( ( trace->GetType() & SPT_AC_PHASE ) || ( trace->GetType() & SPT_CURRENT ) )
trace->SetScale( m_axis_x, m_axis_y2 );
else if( trace->GetType() & SPT_POWER )
trace->SetScale( m_axis_x, m_axis_y3 );
else
trace->SetScale( m_axis_x, m_axis_y1 );
for( auto& [ cursorId, cursor ] : trace->GetCursors() )
{
if( cursor )
cursor->SetCoordX( cursor->GetCoords().x );
}
m_plotWin->UpdateAll();
}
void SIM_PLOT_TAB::DeleteTrace( TRACE* aTrace )
{
for( const auto& [ name, trace ] : m_traces )
{
if( trace == aTrace )
{
m_traces.erase( name );
break;
}
}
for( const auto& [ id, cursor ] : aTrace->GetCursors() )
{
if( cursor )
m_plotWin->DelLayer( cursor, true );
}
m_plotWin->DelLayer( aTrace, true, true );
ResetScales( false );
}
bool SIM_PLOT_TAB::DeleteTrace( const wxString& aVectorName, int aTraceType )
{
if( TRACE* trace = GetTrace( aVectorName, aTraceType ) )
{
DeleteTrace( trace );
return true;
}
return false;
}
void SIM_PLOT_TAB::EnableCursor( const wxString& aVectorName, int aType, int aCursorId,
bool aEnable, const wxString& aSignalName )
{
TRACE* t = GetTrace( aVectorName, aType );
if( t == nullptr || t->HasCursor( aCursorId ) == aEnable )
return;
if( aEnable )
{
CURSOR* cursor = new CURSOR( t, this );
mpWindow* win = GetPlotWin();
int width = win->GetXScreen() - win->GetMarginLeft() - win->GetMarginRight();
int center = win->GetMarginLeft() + KiROUND( width * ( aCursorId == 1 ? 0.4 : 0.6 ) );
cursor->SetName( aSignalName );
cursor->SetX( center );
cursor->SetPen( wxPen( m_colors.GetPlotColor( SIM_PLOT_COLORS::COLOR_SET::CURSOR ) ) );
t->SetCursor( aCursorId, cursor );
m_plotWin->AddLayer( cursor );
}
else
{
CURSOR* cursor = t->GetCursor( aCursorId );
t->SetCursor( aCursorId, nullptr );
m_plotWin->DelLayer( cursor, true );
}
// Notify the parent window about the changes
wxQueueEvent( GetParent(), new wxCommandEvent( EVT_SIM_CURSOR_UPDATE ) );
}
void SIM_PLOT_TAB::ResetScales( bool aIncludeX )
{
if( m_axis_x && aIncludeX )
m_axis_x->ResetDataRange();
if( m_axis_y1 )
m_axis_y1->ResetDataRange();
if( m_axis_y2 )
m_axis_y2->ResetDataRange();
if( m_axis_y3 )
m_axis_y3->ResetDataRange();
for( auto& [ name, trace ] : m_traces )
trace->UpdateScales();
}
wxDEFINE_EVENT( EVT_SIM_CURSOR_UPDATE, wxCommandEvent );