Commit 8fcdb265 authored by David Saxton's avatar David Saxton

Draw curve normal, curve tangent and osculating circle in trace mode (there

aren't any options yet for configuring these).

svn path=/trunk/KDE/kdeedu/kmplot/; revision=554703
parent 4989dd4c
......@@ -7,10 +7,8 @@ TODO
* hermite polynomials
* gamma function
* New plot types:
* Intrinsic: f(x,y) = 0
* Differential e.g. f_n(x,y) d^nY/dx^n + ... + f_1(x,y) dY/dx + f_0(x, y) = 0
* Parameters:
* Give parameters to all types of plots
* Make parameters more powerful
* In tool menu:
* find "nollställen" ( en: where the function's value is 0)
......@@ -32,6 +30,8 @@ IN PROGRESS
DONE
=========================
* Give parameters to all types of plots
* Intrinsic plots: f(x,y) = 0
* Use a better(faster!) algorithm for drawing integrals numeric. It's not urgent anymore since the implemention of Euler's method is better now.
* Fix the unpolished lines?
* Popupmenu and tracemode for parametric- and polar functions
......
......@@ -1105,7 +1105,8 @@ bool View::findRoot( double * x, const Plot & plot, RootAccuracy accuracy )
double h = qMin( m_xmax-m_xmin, m_ymax-m_ymin ) * 1e-5;
double f = value( plot, 0, *x, false );
for ( int k=0; k < max_k; ++k )
int k;
for ( k=0; k < max_k; ++k )
{
double df = XParser::self()->derivative( n, eq, *x, h );
if ( qAbs(df) < 1e-20 )
......@@ -1223,19 +1224,113 @@ void View::paintEvent(QPaintEvent *)
}
else if ( shouldShowCrosshairs() )
{
Function * it = m_currentPlot.function();
// Fadenkreuz zeichnen [draw the cross-hair]
Function * function = m_currentPlot.function();
QPen pen;
if ( !it )
pen.setColor(m_invertedBackgroundColor);
else
if ( function )
{
pen.setColor( it->plotAppearance( m_currentPlot.plotMode ).color );
if ( pen.color() == m_backgroundColor) // if the "Fadenkreuz" [cross-hair] has the same color as the background, the "Fadenkreuz" [cross-hair] will have the inverted color of background so you can see it easier
pen.setColor(m_invertedBackgroundColor);
QColor functionColor = function->plotAppearance( m_currentPlot.plotMode ).color;
pen.setColor( functionColor );
p.setPen( pen );
p.setRenderHint( QPainter::Antialiasing, true );
double x = m_crosshairPosition.x();
double y = m_crosshairPosition.y();
p.save();
p.setMatrix( wm );
p.setClipRect( wm.inverted().mapRect( area ) );
p.restore();
p.setClipRect( area );
//BEGIN calculate curvature, normal
double k = 0;
double normalAngle = 0;
switch ( function->type() )
{
case Function::Parametric:
case Function::Polar:
normalAngle = pixelNormal( m_currentPlot, m_trace_x );
k = pixelCurvature( m_currentPlot, m_trace_x );
break;
case Function::Cartesian:
case Function::Implicit:
normalAngle = pixelNormal( m_currentPlot, x, y );
k = pixelCurvature( m_currentPlot, x, y );
break;
}
if ( k < 0 )
{
k = -k;
normalAngle += M_PI;
}
//END calculate curvature, normal
if ( k > 1e-5 )
{
p.save();
// Transform the painter so that the center of the osculating circle is the origin,
// with the normal line coming in frmo the left.
QPointF center = m_crosshairPixelCoords + (1/k) * QPointF( cos( normalAngle ), sin( normalAngle ) );
p.translate( center );
p.rotate( normalAngle * 180 / M_PI );
// draw osculating circle
// pen.setColor( QColor( 0x4f, 0xb3, 0xff ) );
// pen.setColor( Qt::black );
pen.setColor( functionColor );
p.setPen( pen );
p.drawEllipse( QRectF( -QPointF( 1/k, 1/k ), QSizeF( 2/k, 2/k ) ) );
// draw normal
// pen.setColor( QColor( 0xff, 0x37, 0x61 ) );
pen.setColor( functionColor );
p.setPen( pen );
p.setBrush( pen.color() );
p.drawLine( QLineF( -1/k, 0, 0, 0 ) );
// draw normal arrow
QPolygonF arrowHead(3);
arrowHead[0] = QPointF( 0, 0 );
arrowHead[1] = QPointF( -3, -2 );
arrowHead[2] = QPointF( -3, +2 );
p.drawPolygon( arrowHead );
// draw tangent
double tangent_scale = 1.2; // make the tangent look better
p.drawLine( QLineF( -1/k, -1/k * tangent_scale, -1/k, 1/k * tangent_scale ) );
// draw perpendicular symbol
QPolygonF perp(3);
perp[0] = QPointF( -1/k, 10 );
perp[1] = QPointF( -1/k + 10, 10 );
perp[2] = QPointF( -1/k + 10, 0 );
p.drawPolyline( perp );
// draw intersection blob
p.drawRect( QRectF( -1/k-1, -1, 2, 2 ) );
p.restore();
}
else
{
// Curve is practically straight
}
functionColor.setAlpha( 63 );
pen.setColor( functionColor );
}
else
pen.setColor(m_invertedBackgroundColor);
p.setPen( pen );
p.Lineh( area.left(), m_crosshairPixelCoords.y(), area.right() );
p.Linev( m_crosshairPixelCoords.x(), area.bottom(), area.top());
......@@ -1244,6 +1339,160 @@ void View::paintEvent(QPaintEvent *)
p.end();
}
double View::pixelNormal( const Plot & plot, double x, double y )
{
Function * f = plot.function();
assert( f );
plot.updateFunctionParameter();
Plot diff1 = plot;
diff1.differentiate();
// For converting from real to pixels
double sx = area.width() / (m_xmax - m_xmin);
double sy = area.height() / (m_ymax - m_ymin);
double dx = 0;
double dy = 0;
switch ( f->type() )
{
case Function::Cartesian:
{
double df = value( diff1, 0, x, false );
return - arctan( df * (sy/sx) ) - (M_PI/2);
}
case Function::Implicit:
{
dx = XParser::self()->partialDerivative( 1, 0, f->eq[0], x, y, 1e-5, 1e-5 ) / sx;
dy = XParser::self()->partialDerivative( 0, 1, f->eq[0], x, y, 1e-5, 1e-5 ) / sy;
double theta = -arctan( dy / dx );
if ( dx < 0 )
theta += M_PI;
theta += M_PI;
return theta;
}
case Function::Polar:
{
double r = XParser::self()->derivative( 0, f->eq[0], x, 1e-5 );
double dr = XParser::self()->derivative( 1, f->eq[0], x, 1e-5 );
dx = (dr * cos(x) - r * sin(x)) * sx;
dy = (dr * sin(x) + r * cos(x)) * sy;
break;
}
case Function::Parametric:
{
dx = XParser::self()->derivative( 1, f->eq[0], x, 1e-5 ) * sx;
dy = XParser::self()->derivative( 1, f->eq[1], x, 1e-5 ) * sy;
break;
}
}
double theta = - arctan( dy / dx ) - (M_PI/2);
if ( dx < 0 )
theta += M_PI;
return theta;
}
double View::pixelCurvature( const Plot & plot, double x, double y )
{
Function * f = plot.function();
// For converting from real to pixels
double sx = area.width() / (m_xmax - m_xmin);
double sy = area.height() / (m_ymax - m_ymin);
double fdx = 0;
double fdy = 0;
double fddx = 0;
double fddy = 0;
double fdxy = 0;
switch ( f->type() )
{
case Function::Cartesian:
{
fdx = sx;
fddx = 0;
fdy = XParser::self()->derivative( 1, f->eq[0], x, (m_xmax-m_xmin)/1e5 ) * sy;
fddy = XParser::self()->derivative( 2, f->eq[0], x, (m_xmax-m_xmin)/1e5 ) * sy;
break;
}
case Function::Polar:
{
double r = XParser::self()->derivative( 0, f->eq[0], x, 1e-5 );
double dr = XParser::self()->derivative( 1, f->eq[0], x, 1e-5 );
double ddr = XParser::self()->derivative( 2, f->eq[0], x, 1e-5 );
fdx = (dr * cos(x) - r * sin(x)) * sx;
fdy = (dr * sin(x) + r * cos(x)) * sy;
fddx = (ddr * cos(x) - 2 * dr * sin(x) - r * cos(x)) * sx;
fddy = (ddr * sin(x) + 2 * dr * cos(x) - r * sin(x)) * sy;
break;
}
case Function::Parametric:
{
fdx = XParser::self()->derivative( 1, f->eq[0], x, 1e-5 ) * sx;
fdy = XParser::self()->derivative( 1, f->eq[1], x, 1e-5 ) * sy;
fddx = XParser::self()->derivative( 2, f->eq[0], x, 1e-5 ) * sx;
fddy = XParser::self()->derivative( 2, f->eq[1], x, 1e-5 ) * sy;
break;
}
case Function::Implicit:
{
fdx = XParser::self()->partialDerivative( 1, 0, f->eq[0], x, y, 1e-5, 1e-5 ) / sx;
fdy = XParser::self()->partialDerivative( 0, 1, f->eq[0], x, y, 1e-5, 1e-5 ) / sy;
fddx = XParser::self()->partialDerivative( 2, 0, f->eq[0], x, y, 1e-5, 1e-5 ) / (sx*sx);
fddy = XParser::self()->partialDerivative( 0, 2, f->eq[0], x, y, 1e-5, 1e-5 ) / (sy*sy);
fdxy = XParser::self()->partialDerivative( 1, 1, f->eq[0], x, y, 1e-5, 1e-5 ) / (sx*sy);
break;
}
}
double mod = pow( fdx*fdx + fdy*fdy, 1.5 );
switch ( f->type() )
{
case Function::Cartesian:
case Function::Parametric:
case Function::Polar:
return (fdx * fddy - fdy * fddx) / mod;
case Function::Implicit:
return ( fdx*fdx*fddy + fdy*fdy*fddx - 2*fdx*fdy*fdxy ) / mod;
}
kError() << "Unknown function type!\n";
return 0;
}
void View::resizeEvent(QResizeEvent *)
{
if (m_isDrawing) //stop drawing integrals
......
......@@ -162,6 +162,16 @@ class View : public QWidget
* value(), but returns both coordinates).
*/
QPointF realValue( const Plot & plot, double x, bool updateParameter );
/**
* \return the (signed) curvature (in screen coordinates) of the plot
* at \p x (and \p y for implicit functions).
*/
double pixelCurvature( const Plot & plot, double x, double y = 0 );
/**
* \return the angle of the normal (in radians) of the plot when viewed
* on the screen.
*/
double pixelNormal( const Plot & plot, double x, double y = 0 );
public slots:
/// Called when the user want to cancel the drawing
......
......@@ -69,7 +69,7 @@ bool Value::operator == ( const Value & other )
//BEGIN class PlotAppearance
PlotAppearance::PlotAppearance( )
{
lineWidth = 0.3;
lineWidth = 0.2;
color = Qt::black;
visible = false;
style = Qt::SolidLine;
......
......@@ -140,9 +140,9 @@ bool XParser::getext( Function *item, const QString fstr )
double XParser::derivative( int n, Equation * eq, double x, double h )
{
if ( n < -1 )
if ( n < 0 )
{
kError() << k_funcinfo << "Can't handle derivative < -1\n";
kError() << k_funcinfo << "Can't handle derivative < 0\n";
return 0.0;
}
......@@ -163,6 +163,25 @@ double XParser::derivative( int n, Equation * eq, double x, double h )
}
double XParser::partialDerivative( int n1, int n2, Equation * eq, double x, double y, double h1, double h2 )
{
if ( n1 < 0 || n2 < 0 )
{
kError() << k_funcinfo << "Can't handle derivative < 0\n";
return 0.0;
}
if ( n1 > 0 )
return ( partialDerivative( n1-1, n2, eq, x+(h1/2), y, (h1/4), h2 ) - partialDerivative( n1-1, n2, eq, x-(h1/2), y, (h1/4), h2 ) ) / h1;
Function * f = eq->parent();
f->m_implicitMode = Function::FixedX;
f->x = x;
return derivative( n2, eq, y, h2 );
}
void XParser::findFunctionName(QString &function_name, int const id, int const type)
{
char last_character;
......
......@@ -52,6 +52,10 @@ class XParser : public Parser
* stepsize \p h.
*/
double derivative( int n, Equation * eq, double x, double h );
/**
* For use with functions of two variables.
*/
double partialDerivative( int n1, int n2, Equation * eq, double x, double y, double h1, double h2 );
/**
* Calculates the value of the equation using numerical integration
* with the given step size \p h (which is only used as a hint).
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment