1 /*
2     KmPlot - a math. function plotter for the KDE-Desktop
3 
4     SPDX-FileCopyrightText: 1998, 1999, 2000, 2002 Klaus-Dieter Möller <kd.moeller@t-online.de>
5     SPDX-FileCopyrightText: 2006 David Saxton <david@bluehaze.org>
6 
7     This file is part of the KDE Project.
8     KmPlot is part of the KDE-EDU Project.
9 
10     SPDX-License-Identifier: GPL-2.0-or-later
11 
12 */
13 
14 #include "function.h"
15 #include "ksliderwindow.h"
16 #include "settings.h"
17 #include "view.h"
18 #include "xparser.h"
19 
20 #include <QImage>
21 #include <QLinearGradient>
22 #include <QPainter>
23 
24 #include <assert.h>
25 #include <cmath>
26 
27 int MAX_PM = 4;
28 
29 
30 //BEGIN class Value
Value(const QString & expression)31 Value::Value( const QString & expression )
32 {
33 	m_value = 0.0;
34 	if ( expression.isEmpty() )
35 		m_expression = '0';
36 	else
37 		updateExpression( expression );
38 }
39 
40 
Value(double value)41 Value::Value( double value )
42 {
43 	updateExpression( value );
44 }
45 
46 
updateExpression(const QString & expression)47 bool Value::updateExpression( const QString & expression )
48 {
49 	Parser::Error error;
50 	double newValue = XParser::self()->eval( expression, & error );
51 	if ( error != Parser::ParseSuccess )
52 		return false;
53 
54 	m_value = newValue;
55 	m_expression = expression;
56 	return true;
57 }
58 
59 
updateExpression(double value)60 void Value::updateExpression( double value )
61 {
62 	m_value = value;
63 	m_expression = Parser::number( value );
64 }
65 
66 
operator ==(const Value & other) const67 bool Value::operator == ( const Value & other ) const
68 {
69 	return m_expression == other.expression();
70 }
71 //END class Value
72 
73 
74 
75 //BEGIN class PlotAppearance
PlotAppearance()76 PlotAppearance::PlotAppearance( )
77 {
78 	lineWidth = 0.3;
79 	color = Qt::black;
80 	useGradient = false;
81 	visible = false;
82 	style = Qt::SolidLine;
83 	showExtrema = false;
84 	showTangentField = false;
85 	showPlotName = false;
86 }
87 
88 
operator !=(const PlotAppearance & other) const89 bool PlotAppearance::operator !=( const PlotAppearance & other ) const
90 {
91 	return
92 			(lineWidth != other.lineWidth) ||
93 			(color != other.color) ||
94 			(useGradient != other.useGradient) ||
95 			(gradient.stops() != other.gradient.stops()) ||
96 			(visible != other.visible) ||
97 			(style != other.style) ||
98 			(showExtrema != other.showExtrema) ||
99 			(showTangentField != other.showTangentField) ||
100 			(showPlotName != other.showPlotName);
101 }
102 
103 
penStyleToString(Qt::PenStyle style)104 QString PlotAppearance::penStyleToString( Qt::PenStyle style )
105 {
106 	switch ( style )
107 	{
108 		case Qt::NoPen:
109 			return "NoPen";
110 
111 		case Qt::SolidLine:
112 			return "SolidLine";
113 
114 		case Qt::DashLine:
115 			return "DashLine";
116 
117 		case Qt::DotLine:
118 			return "DotLine";
119 
120 		case Qt::DashDotLine:
121 			return "DashDotLine";
122 
123 		case Qt::DashDotDotLine:
124 			return "DashDotDotLine";
125 
126 		case Qt::MPenStyle:
127 		case Qt::CustomDashLine:
128 			qWarning() << "Unsupported pen style\n";
129 			break;
130 	}
131 
132 	qWarning() << "Unknown style " << style ;
133 	return "SolidLine";
134 }
135 
136 
stringToPenStyle(const QString & style)137 Qt::PenStyle PlotAppearance::stringToPenStyle( const QString & style )
138 {
139 	if ( style == "NoPen" )
140 		return Qt::NoPen;
141 
142 	if ( style == "SolidLine" )
143 		return Qt::SolidLine;
144 
145 	if ( style == "DashLine" )
146 		return Qt::DashLine;
147 
148 	if ( style == "DotLine" )
149 		return Qt::DotLine;
150 
151 	if ( style == "DashDotLine" )
152 		return Qt::DashDotLine;
153 
154 	if ( style == "DashDotDotLine" )
155 		return Qt::DashDotDotLine;
156 
157 	qWarning() << "Unknown style " << style ;
158 	return Qt::SolidLine;
159 }
160 //END class PlotAppearance
161 
162 
163 
164 //BEGIN class DifferentialState
DifferentialState()165 DifferentialState::DifferentialState()
166 {
167 	x = 0;
168 }
169 
170 
DifferentialState(int order)171 DifferentialState::DifferentialState( int order )
172 {
173 	x = 0;
174 	setOrder( order );
175 }
176 
177 
setOrder(int order)178 void DifferentialState::setOrder( int order )
179 {
180 	bool orderWasZero = (y0.size() == 0);
181 
182 	y.resize( order );
183 	y0.resize( order );
184 
185 	if ( orderWasZero && order >= 1 )
186 		y0[0].updateExpression( "1" );
187 
188 	resetToInitial();
189 }
190 
191 
setStep(const Value & step)192 bool DifferentialStates::setStep( const Value & step )
193 {
194 	if ( step.value() <= 0 )
195 		return false;
196 
197 	m_step = step;
198 	return true;
199 }
200 
201 
resetToInitial()202 void DifferentialState::resetToInitial()
203 {
204 	x = x0.value();
205 	y = y0;
206 }
207 
208 
operator ==(const DifferentialState & other) const209 bool DifferentialState::operator == ( const DifferentialState & other ) const
210 {
211 	return (x0 == other.x0) && (x == other.x) && (y0 == other.y0) && (y == other.y);
212 }
213 //END class DifferentialState
214 
215 
216 
217 //BEGIN class DifferentialStates
DifferentialStates()218 DifferentialStates::DifferentialStates()
219 {
220 	m_uniqueState = false;
221 	m_order = 0;
222 	m_step.updateExpression( 0.05 );
223 }
224 
225 
setOrder(int order)226 void DifferentialStates::setOrder( int order )
227 {
228 	m_order = order;
229 	for ( int i = 0; i < m_data.size(); ++i )
230 		m_data[i].setOrder( order );
231 }
232 
233 
add()234 DifferentialState * DifferentialStates::add()
235 {
236 	if ( !m_uniqueState || m_data.isEmpty() )
237 		m_data << DifferentialState( order() );
238 	else
239 		qDebug() << "Unable to add another state!\n";
240 
241 	return & m_data[ size() - 1 ];
242 }
243 
244 
setUniqueState(bool unique)245 void DifferentialStates::setUniqueState( bool unique )
246 {
247 	m_uniqueState = unique;
248 	if ( m_uniqueState && m_data.size() > 1 )
249 	{
250 		// Remove any states other than the first
251 		m_data.resize( 1 );
252 	}
253 }
254 
255 
resetToInitial()256 void DifferentialStates::resetToInitial( )
257 {
258 	for ( int i = 0; i < m_data.size(); ++i )
259 		m_data[i].resetToInitial();
260 }
261 //END class DifferentialStates
262 
263 
264 
265 //BEGIN class Equation
Equation(Type type,Function * parent)266 Equation::Equation( Type type, Function * parent )
267 	: m_type( type ),
268 	  m_parent( parent )
269 {
270 	m_usesParameter = false;
271 	mptr = 0;
272 
273 	if ( type == Differential || type == Cartesian )
274 	{
275 		differentialStates.setUniqueState( type == Cartesian );
276 		differentialStates.setOrder( order() );
277 		differentialStates.add();
278 	}
279 }
280 
281 
~Equation()282 Equation::~ Equation()
283 {
284 }
285 
286 
order() const287 int Equation::order( ) const
288 {
289 	if ( type() == Cartesian )
290 	{
291 		// For drawing integrals
292 		return 1;
293 	}
294 	else
295 		return name(false).count( '\'' );
296 }
297 
298 
pmCount() const299 int Equation::pmCount() const
300 {
301 	return m_fstr.count( PmSymbol );
302 }
303 
304 
name(bool removePrimes) const305 QString Equation::name( bool removePrimes ) const
306 {
307 	if ( m_fstr.isEmpty() )
308 		return QString();
309 
310 	int open = m_fstr.indexOf( '(' );
311 	int equals = m_fstr.indexOf( '=' );
312 
313 	if ( (equals == -1) && (open == -1) )
314 		return QString();
315 
316 	int pos;
317 	if ( ((equals > open) && (open != -1)) || (equals == -1) )
318 		pos = open;
319 	else
320 		pos = equals;
321 
322 	QString n = m_fstr.left( pos ).trimmed();
323 
324 	if ( removePrimes )
325 		n.remove( '\'' );
326 
327 	return n;
328 }
329 
330 
looksLikeFunction() const331 bool Equation::looksLikeFunction( ) const
332 {
333 	int open = m_fstr.indexOf( '(' );
334 	int equals = m_fstr.indexOf( '=' );
335 
336 	if ( (open != -1) && (open < equals) )
337 		return true;
338 
339 	switch ( type() )
340 	{
341 		case Cartesian:
342 		case Differential:
343 		case ParametricY:
344 			return (name() != "y");
345 
346 		case Polar:
347 			return (name() != "r");
348 
349 		case ParametricX:
350 			return (name() != "x");
351 
352 		case Implicit:
353 			return false;
354 
355         case Constant:
356             return false;
357 	}
358 
359 	return true;
360 }
361 
362 
updateVariables()363 void Equation::updateVariables()
364 {
365     if ( type() == Constant )
366     {
367         return;
368     }
369 
370 	m_variables.clear();
371 
372 	if ( looksLikeFunction() )
373 	{
374 		int p1 = m_fstr.indexOf( '(' );
375 		int p2 = m_fstr.indexOf( ')' );
376 
377 		const QStringList listSplit = ( (p1 != -1) && (p2 != -1) ) ? m_fstr.mid( p1+1, p2-p1-1 ).split( ',', QString::SkipEmptyParts ) : QStringList();
378 
379 		// Variables shouldn't contain spaces!
380 		for ( QString s : listSplit ) {
381 			s = s.remove(' ');
382 			if ( !s.isEmpty() )
383 				m_variables << s;
384 		}
385 	}
386 	else switch ( type() )
387 	{
388 		case Cartesian:
389 		case Differential:
390 			m_variables << "x" << "k";
391 			break;
392 
393 		case Polar:
394 			m_variables << QChar( 0x3b8 ) << "k"; // (theta)
395 			break;
396 
397 		case ParametricX:
398 		case ParametricY:
399 			m_variables << "t" << "k";
400 			break;
401 
402 		case Implicit:
403 			m_variables << "x" << "y" << "k";
404 			break;
405 
406         case Constant:
407             break;
408 	}
409 
410 	// If we are a differential equation, then add on y, y', etc
411 	if ( type() == Differential && !name().isEmpty() )
412 	{
413 		QString n = name();
414 
415 		int order = this->order();
416 		for ( int i = 0; i < order; ++i )
417 		{
418 			m_variables << n;
419 			n += '\'';
420 		}
421 	}
422 
423 
424 	//BEGIN Update whether we accept a parameter or not
425 	int expectedNumVariables = 0;
426 
427 	switch ( m_type )
428 	{
429 		case Cartesian:
430 		case ParametricX:
431 		case ParametricY:
432 		case Polar:
433 			expectedNumVariables = 1;
434 			break;
435 
436 		case Implicit:
437 			expectedNumVariables = 2;
438 			break;
439 
440 		case Differential:
441 			expectedNumVariables = order()+1;
442 			break;
443 
444 		case Constant:
445 			expectedNumVariables = 0;
446 			break;
447 	}
448 
449 	m_usesParameter = (variables().size() > expectedNumVariables);
450 	//END Update whether we accept a parameter or not
451 }
452 
453 
parameterName() const454 QString Equation::parameterName( ) const
455 {
456 	if ( !usesParameter() )
457 		return QString();
458 
459 	int parAt = (type() == Implicit) ? 2 : 1;
460 	return variables()[parAt];
461 }
462 
463 
setFstr(const QString & fstr,int * error,int * errorPosition,bool force)464 bool Equation::setFstr( const QString & fstr, int * error, int * errorPosition, bool force )
465 {
466 #define HANDLE_ERROR \
467 	if ( !force ) \
468 	{ \
469 		m_fstr = prevFstr; \
470 		updateVariables(); \
471 	} \
472 	else \
473 	{ \
474 		qDebug() << "fstr "<<fstr<<" invalid, but forcing anyway: " << Parser::errorString( Parser::Error(*error) ) << " at position " << *errorPosition; \
475 		mem.clear(); \
476 	}
477 
478 	int temp1, temp2;
479 	if ( !error )
480 		error = & temp1;
481 	if ( !errorPosition )
482 		errorPosition = & temp2;
483 
484 	*error = Parser::ParseSuccess;
485 	*errorPosition = -1;
486 
487 	QString prevFstr = m_fstr;
488 	m_fstr = fstr;
489 	updateVariables();
490 
491 	if ( !fstr.contains('=') || QString(fstr).right( fstr.length() - fstr.indexOf('=') - 1).simplified().isEmpty() )
492 	{
493 		*error = Parser::SyntaxError;
494 		HANDLE_ERROR;
495 		return false;
496 	}
497 
498 	// require order to be greater than 0 for differential equations
499 	if ( (type() == Differential) && (order() < 1) )
500 	{
501 		*error = Parser::ZeroOrder;
502 		HANDLE_ERROR;
503 		/// \todo indicate the position of the error
504 		return false;
505 	}
506 
507 	int maxArg = order() + (( type() == Implicit ) ? 3 : 2);
508 	if ( variables().size() > maxArg )
509 	{
510 		*error = Parser::TooManyArguments;
511 		HANDLE_ERROR;
512 		/// \todo indicate the position of the invalid argument?
513 		return false;
514 	}
515 
516 	XParser::self()->initEquation( this, (Parser::Error*)error, errorPosition );
517 	if ( *error != Parser::ParseSuccess )
518 	{
519 		HANDLE_ERROR;
520 		if ( !force )
521 			XParser::self()->initEquation( this );
522 		return false;
523 	}
524 
525 	differentialStates.setOrder( order() );
526 	return true;
527 }
528 
529 
setPMSignature(QVector<bool> pmSignature)530 void Equation::setPMSignature( QVector< bool > pmSignature )
531 {
532 	differentialStates.resetToInitial();
533 	m_pmSignature = pmSignature;
534 }
535 
536 
operator !=(const Equation & other)537 bool Equation::operator !=( const Equation & other )
538 {
539 	return (fstr() != other.fstr()) ||
540 			(differentialStates != other.differentialStates);
541 }
542 
543 
operator =(const Equation & other)544 Equation & Equation::operator =( const Equation & other )
545 {
546 	setFstr( other.fstr() );
547 	differentialStates = other.differentialStates;
548 
549 	return * this;
550 }
551 //END class Equation
552 
553 
554 
555 //BEGIN class Function
Function(Type type)556 Function::Function( Type type )
557 	: m_type( type )
558 {
559 	x = y = 0;
560 	m_implicitMode = UnfixedXY;
561 
562 	usecustomxmin = false;
563 	usecustomxmax = false;
564 
565 	dmin.updateExpression( QChar('0') );
566 	if ( Settings::anglemode() == Parser::Radians )
567 		dmax.updateExpression( QString(QChar('2')) + PiSymbol );
568 	else
569 		dmax.updateExpression( "360" );
570 
571 	switch ( m_type )
572 	{
573 		case Cartesian:
574 			eq << new Equation( Equation::Cartesian, this );
575 			break;
576 
577 		case Polar:
578 			eq << new Equation( Equation::Polar, this );
579 			usecustomxmin = true;
580 			usecustomxmax = true;
581 			break;
582 
583 		case Parametric:
584 			eq << new Equation( Equation::ParametricX, this );
585 			eq << new Equation( Equation::ParametricY, this );
586 			usecustomxmin = true;
587 			usecustomxmax = true;
588 			break;
589 
590 		case Implicit:
591 			eq << new Equation( Equation::Implicit, this );
592 			break;
593 
594 		case Differential:
595 			eq << new Equation( Equation::Differential, this );
596 			break;
597 	}
598 
599 	m_id = 0;
600 	f0.visible = true;
601 
602 	k = 0;
603 }
604 
605 
~Function()606 Function::~Function()
607 {
608 	for ( Equation * e : qAsConst(eq) )
609 		delete e;
610 }
611 
612 
copyFrom(const Function & function)613 bool Function::copyFrom( const Function & function )
614 {
615 	bool changed = false;
616 	int i = 0;
617 #define COPY_AND_CHECK(s) \
618 	{ \
619 		if ( s != function.s ) \
620 		{ \
621 			s = function.s; \
622 			changed = true; \
623 		} \
624 	} \
625 	i++;
626 
627 	COPY_AND_CHECK( f0 );				// 0
628 	if ( type() == Cartesian )
629 	{
630 		COPY_AND_CHECK( f1 );				// 1
631 		COPY_AND_CHECK( f2 );				// 2
632 		COPY_AND_CHECK( f3 );				// 3
633 		COPY_AND_CHECK( integral );			// 4
634 	}
635 	COPY_AND_CHECK( dmin );				// 5,1
636 	COPY_AND_CHECK( dmax );				// 6,2
637 	COPY_AND_CHECK( usecustomxmin );	// 7,3
638 	COPY_AND_CHECK( usecustomxmax );	// 8,4
639 	COPY_AND_CHECK( m_parameters );		// 9,5
640 
641 	// handle equations separately
642 	for ( int i = 0; i < eq.size(); ++i )
643 	{
644 		if ( *eq[i] != *function.eq[i] )
645 		{
646 			changed = true;
647 			*eq[i] = *function.eq[i];
648 		}
649 	}
650 
651 	return changed;
652 }
653 
654 
name() const655 QString Function::name() const
656 {
657 	QString n = eq[0]->fstr();
658 	for ( int i = 1; i < eq.size(); ++i )
659 		n += '\n' + eq[i]->fstr();
660 
661 	return n;
662 }
663 
664 
plotAppearance(PMode plot)665 PlotAppearance & Function::plotAppearance( PMode plot )
666 {
667 	// NOTE: This function is identical to the const one, so changes to this should be applied to both
668 
669 	switch ( plot )
670 	{
671 		case Function::Derivative0:
672 			return f0;
673 		case Function::Derivative1:
674 			return f1;
675 		case Function::Derivative2:
676 			return f2;
677 		case Function::Derivative3:
678 			return f3;
679 		case Function::Integral:
680 			return integral;
681 	}
682 
683 	qCritical() << "Unknown plot " << plot;
684 	return f0;
685 }
plotAppearance(PMode plot) const686 PlotAppearance Function::plotAppearance( PMode plot ) const
687 {
688 	// NOTE: This function is identical to the none-const one, so changes to this should be applied to both
689 
690 	switch ( plot )
691 	{
692 		case Function::Derivative0:
693 			return f0;
694 		case Function::Derivative1:
695 			return f1;
696 		case Function::Derivative2:
697 			return f2;
698 		case Function::Derivative3:
699 			return f3;
700 		case Function::Integral:
701 			return integral;
702 	}
703 
704 	qCritical() << "Unknown plot " << plot;
705 	return f0;
706 }
707 
708 
allPlotsAreHidden() const709 bool Function::allPlotsAreHidden( ) const
710 {
711 	return !f0.visible && !f1.visible && !f2.visible && !integral.visible;
712 }
713 
714 
typeToString(Type type)715 QString Function::typeToString( Type type )
716 {
717 	switch ( type )
718 	{
719 		case Cartesian:
720 			return "cartesian";
721 
722 		case Parametric:
723 			return "parametric";
724 
725 		case Polar:
726 			return "polar";
727 
728 		case Implicit:
729 			return "implicit";
730 
731 		case Differential:
732 			return "differential";
733 	}
734 
735 	qWarning() << "Unknown type " << type ;
736 	return "unknown";
737 }
738 
739 
stringToType(const QString & type)740 Function::Type Function::stringToType( const QString & type )
741 {
742 	if ( type == "cartesian" )
743 		return Cartesian;
744 
745 	if ( type == "parametric" )
746 		return Parametric;
747 
748 	if ( type == "polar" )
749 		return Polar;
750 
751 	if ( type == "implicit" )
752 		return Implicit;
753 
754 	if ( type == "differential" )
755 		return Differential;
756 
757 	qWarning() << "Unknown type " << type ;
758 	return Cartesian;
759 }
760 
761 
plots(PlotCombinations combinations) const762 QList< Plot > Function::plots( PlotCombinations combinations ) const
763 {
764 	QList< Plot > list;
765 
766 	if ( allPlotsAreHidden() )
767 		return list;
768 
769 	Plot plot;
770 	plot.setFunctionID( id() );
771 	plot.plotNumberCount = m_parameters.useList ? m_parameters.list.size() + (m_parameters.useSlider?1:0) : 1;
772 
773 	bool singlePlot = (!m_parameters.useList && !m_parameters.useSlider) ||
774 			m_parameters.animating ||
775 			(~combinations & DifferentParameters) ||
776 			(!m_parameters.useSlider && m_parameters.useList && m_parameters.list.isEmpty());
777 
778 	if ( singlePlot )
779 	{
780 		if ( m_parameters.animating )
781 			plot.parameter = Parameter( Parameter::Animated );
782 
783 		list << plot;
784 	}
785 	else
786 	{
787 		int i = 0;
788 
789 		if ( m_parameters.useSlider )
790 		{
791 			Parameter param( Parameter::Slider );
792 			param.setSliderID( m_parameters.sliderID );
793 			plot.parameter = param;
794 			plot.plotNumber = i++;
795 			list << plot;
796 		}
797 
798 		if ( m_parameters.useList )
799 		{
800 			const int listsize = m_parameters.list.size();
801 			for ( int pos = 0; pos < listsize; ++pos )
802 			{
803 				Parameter param( Parameter::List );
804 				param.setListPos( pos );
805 				plot.parameter = param;
806 				plot.plotNumber = i++;
807 				list << plot;
808 			}
809 		}
810 	}
811 
812 
813 	// Copy each plot in the list for other variations
814 	if ( (type() == Cartesian) && (combinations & DifferentDerivatives) )
815 	{
816 		QList< Plot > duplicated;
817 
818 		for ( PMode p = Derivative0; p <= Integral; p = PMode(p+1) )
819 		{
820 			for ( Plot plot : qAsConst(list) ) {
821 				if ( !plotAppearance(p).visible )
822 					continue;
823 				plot.plotMode = p;
824 				duplicated << plot;
825 			}
826 		}
827 
828 		list = duplicated;
829 	}
830 
831 	if ( (type() == Differential) && (combinations & DifferentInitialStates) )
832 	{
833 		QList< Plot > duplicated;
834 
835 		for ( int i = 0; i < eq[0]->differentialStates.size(); ++i )
836 		{
837 			for ( Plot plot : qAsConst(list) ) {
838 				plot.stateNumber = i;
839 				duplicated << plot;
840 			}
841 		}
842 
843 		list = duplicated;
844 	}
845 
846 	if ( combinations & DifferentPMSignatures )
847 	{
848 		int size = 0;
849 		for ( Equation * equation : qAsConst(eq) )
850 			size += equation->pmCount();
851 
852 		unsigned max = unsigned( std::pow( 2.0, (double)size ) );
853 		QVector< QVector<bool> > signatures( max );
854 
855 		for ( unsigned i = 0; i < max; ++i )
856 		{
857 			QVector<bool> sig( size );
858 
859 			for ( int j = 0; j < size; ++j )
860 				sig[ j ] = i & (1<<j);
861 
862 			signatures[i] = sig;
863 		}
864 
865 		// Generate a plot for each signature in signatures
866 		QList< Plot > duplicated;
867 		for ( const QVector<bool> &signature : qAsConst(signatures) )
868 		{
869 			int at = 0;
870 			QList< QVector<bool> > pmSignature;
871 
872 			for ( Equation * equation : qAsConst(eq) )
873 			{
874 				int pmCount = equation->pmCount();
875 				QVector<bool> sig( pmCount );
876 				for ( int i = 0; i < pmCount; ++i )
877 					sig[i] = signature[ i + at];
878 				at += pmCount;
879 
880 				pmSignature << sig;
881 			}
882 
883 			for ( Plot plot : qAsConst(list) ) {
884 				plot.pmSignature = pmSignature;
885 				duplicated << plot;
886 			}
887 		}
888 		list = duplicated;
889 	}
890 
891 	return list;
892 }
893 
894 
addFunctionDependency(Function * function)895 void Function::addFunctionDependency( Function * function )
896 {
897 	if ( !function || m_dependencies.contains( function->id() ) )
898 		return;
899 
900 	Q_ASSERT_X( !function->dependsOn( this ), "addFunctionDependency", "circular dependency" );
901 
902 	m_dependencies << function->id();
903 }
904 
905 
dependsOn(Function * function) const906 bool Function::dependsOn( Function * function ) const
907 {
908 	if ( !function )
909 		return false;
910 
911 	if ( m_dependencies.contains( function->id() ) )
912 		return true;
913 
914 	for ( int functionId : qAsConst(m_dependencies) )
915 	{
916 		Function * f = XParser::self()->functionWithID( functionId );
917 
918 		if ( f->dependsOn( function ) )
919 			return true;
920 	}
921 
922 	return false;
923 }
924 //END class Function
925 
926 
927 
928 //BEGIN class ParameterSettings
ParameterSettings()929 ParameterSettings::ParameterSettings()
930 {
931 	animating = false;
932 	useSlider = false;
933 	sliderID = 0;
934 	useList = false;
935 }
936 
937 
operator ==(const ParameterSettings & other) const938 bool ParameterSettings::operator == ( const ParameterSettings & other ) const
939 {
940 	return ( useSlider == other.useSlider ) &&
941 			( sliderID == other.sliderID ) &&
942 			( useList == other.useList ) &&
943 			( list == other.list );
944 }
945 //END class ParameterSettings
946 
947 
948 
949 //BEGIN class Parameter
Parameter(Type type)950 Parameter::Parameter( Type type )
951 	: m_type( type )
952 {
953 	m_sliderID = -1;
954 	m_listPos = -1;
955 }
956 
957 
operator ==(const Parameter & other) const958 bool Parameter::operator == ( const Parameter & other ) const
959 {
960 	return ( type() == other.type() ) &&
961 			( listPos() == other.listPos() ) &&
962 			( sliderID() == other.sliderID() );
963 }
964 //END class Parameter
965 
966 
967 
968 //BEGIN class Plot
Plot()969 Plot::Plot( )
970 {
971 	stateNumber = -1;
972 	plotNumberCount = 1;
973 	plotNumber = 0;
974 	m_function = 0;
975 	m_functionID = -1;
976 	plotMode = Function::Derivative0;
977 }
978 
979 
operator ==(const Plot & other) const980 bool Plot::operator ==( const Plot & other ) const
981 {
982 	return ( m_functionID == other.functionID() ) &&
983 			( plotMode == other.plotMode ) &&
984 			( parameter == other.parameter ) &&
985 			( stateNumber == other.stateNumber );
986 }
987 
988 
setFunctionID(int id)989 void Plot::setFunctionID( int id )
990 {
991 	m_functionID = id;
992 	updateCached();
993 }
994 
995 
updateCached()996 void Plot::updateCached()
997 {
998 	m_function = XParser::self()->functionWithID( m_functionID );
999 }
1000 
1001 
name() const1002 QString Plot::name( ) const
1003 {
1004 	if ( !m_function )
1005 		return QString();
1006 
1007 	QString n = m_function->name();
1008 
1009 	if ( m_function->eq[0]->usesParameter() )
1010 		n += QString( "\n%1 = %2" ).arg( m_function->eq[0]->parameterName() ).arg( Parser::number( parameterValue() ) );
1011 
1012 	if ( plotMode == Function::Derivative1 )
1013 		n = n.section('=', 0, 0).replace('(', "\'(");
1014 
1015 	if ( plotMode == Function::Derivative2 )
1016 		n = n.section('=', 0, 0).replace('(', "\'\'(");
1017 
1018 	if ( plotMode == Function::Integral )
1019 	{
1020 		QString functionName = n.section('=', 0, 0);
1021 		n = QChar(0x222B) + ' ' + functionName + 'd' + functionName.section('(', 1, 1).remove(')').section(',', 0, 0);
1022 	}
1023 
1024 	return n;
1025 }
1026 
1027 
updateFunction() const1028 void Plot::updateFunction() const
1029 {
1030 	if ( !m_function )
1031 		return;
1032 
1033 	// Update the plus-minus signature
1034 	assert( pmSignature.size() <= m_function->eq.size() );
1035 	for ( int i = 0; i < pmSignature.size(); ++i )
1036 		m_function->eq[i]->setPMSignature( pmSignature[i] );
1037 
1038 	if ( parameter.type() != Parameter::Animated )
1039 		m_function->setParameter( parameterValue() );
1040 }
1041 
1042 
parameterValue() const1043 double Plot::parameterValue() const
1044 {
1045 	switch ( parameter.type() )
1046 	{
1047 		case Parameter::Unknown:
1048 			return 0;
1049 
1050 		case Parameter::Slider:
1051 		{
1052 			KSliderWindow * sw = View::self()->m_sliderWindow;
1053 
1054 			if ( !sw )
1055 			{
1056 				// Slider window isn't open. Ask View to open it
1057 				View::self()->updateSliders();
1058 
1059 				// It should now be open
1060 				sw = View::self()->m_sliderWindow;
1061 				assert( sw );
1062 			}
1063 
1064 			return sw->value( parameter.sliderID() );
1065 		}
1066 
1067 		case Parameter::List:
1068 		{
1069 			if ( (parameter.listPos() >= 0) && (parameter.listPos() < m_function->m_parameters.list.size()) )
1070 				return m_function->m_parameters.list[ parameter.listPos() ].value();
1071 			return 0;
1072 		}
1073 
1074 		case Parameter::Animated:
1075 		{
1076 			qWarning() << "Shouldn't use this function for animated parameter!\n";
1077 			return 0;
1078 		}
1079 	}
1080 
1081 	return 0;
1082 }
1083 
1084 
differentiate()1085 void Plot::differentiate()
1086 {
1087 	switch ( plotMode )
1088 	{
1089 		case Function::Integral:
1090 			plotMode = Function::Derivative0;
1091 			break;
1092 
1093 		case Function::Derivative0:
1094 			plotMode = Function::Derivative1;
1095 			break;
1096 
1097 		case Function::Derivative1:
1098 			plotMode = Function::Derivative2;
1099 			break;
1100 
1101 		case Function::Derivative2:
1102 			plotMode = Function::Derivative3;
1103 			break;
1104 
1105 		case Function::Derivative3:
1106 			qWarning() << "Can't handle this yet!\n";
1107 			break;
1108 	}
1109 }
1110 
1111 
integrate()1112 void Plot::integrate()
1113 {
1114 	switch ( plotMode )
1115 	{
1116 		case Function::Integral:
1117 			qWarning() << "Can't handle this yet!\n";
1118 			break;
1119 
1120 		case Function::Derivative0:
1121 			plotMode = Function::Integral;
1122 			break;
1123 
1124 		case Function::Derivative1:
1125 			plotMode = Function::Derivative0;
1126 			break;
1127 
1128 		case Function::Derivative2:
1129 			plotMode = Function::Derivative1;
1130 			break;
1131 
1132 		case Function::Derivative3:
1133 			plotMode = Function::Derivative2;
1134 			break;
1135 	}
1136 }
1137 
1138 
color() const1139 QColor Plot::color( ) const
1140 {
1141 	Function * f = function();
1142 	assert(f); // Shouldn't call color without a function
1143 	PlotAppearance appearance = f->plotAppearance( plotMode );
1144 
1145 	if ( (plotNumberCount <= 1) || !appearance.useGradient )
1146 		return appearance.color;
1147 
1148 	// Is a gradient
1149 
1150 	int x = plotNumber;
1151 	int l = plotNumberCount;
1152 
1153 	QLinearGradient lg( 0, 0, l-1, 0 );
1154 	lg.setStops( appearance.gradient.stops() );
1155 	QImage im( l, 1, QImage::Format_RGB32 );
1156 	QPainter p(&im);
1157 	p.setPen( QPen( lg, 1 ) );
1158 	p.drawLine( 0, 0, l, 0 );
1159 	return im.pixel( x, 0 );
1160 }
1161 
1162 
derivativeNumber() const1163 int Plot::derivativeNumber( ) const
1164 {
1165 	switch ( plotMode )
1166 	{
1167 		case Function::Integral:
1168 			return -1;
1169 		case Function::Derivative0:
1170 			return 0;
1171 		case Function::Derivative1:
1172 			return 1;
1173 		case Function::Derivative2:
1174 			return 2;
1175 		case Function::Derivative3:
1176 			return 3;
1177 	}
1178 
1179 	qWarning() << "Unknown derivative number.\n";
1180 	return 0;
1181 }
1182 
1183 
state() const1184 DifferentialState * Plot::state( ) const
1185 {
1186 	if ( !function() || (stateNumber < 0) )
1187 		return 0;
1188 
1189 	if ( function()->eq[0]->differentialStates.size() <= stateNumber )
1190 		return 0;
1191 
1192 	return & function()->eq[0]->differentialStates[stateNumber];
1193 }
1194 //END class Plot
1195 
1196