#include #include using namespace Upp; struct ExpressionEvaluator { CParser p; // this will hold our lexical context double x; // 'variable' double Factor(); double Exponent(); double Term(); double Expression(); }; double ExpressionEvaluator::Expression() { // resolve + - double y = Term(); // at least one term for(;;) if(p.Char('+')) y = y + Term(); // add another term else if(p.Char('-')) y = y - Term(); // subtract another term else return y; // no more + - operators } double ExpressionEvaluator::Term() { // resolve * / double y = Exponent(); // at least one member for(;;) if(p.Char('*')) y = y * Exponent(); // multiply by another member else if(p.Char('/')) y = y / Exponent(); // divide by another member else return y; // no more * / operators } double ExpressionEvaluator::Exponent() { // resolve power ^ double y = Factor(); // at least one factor for(;;) if(p.Char('^')) y = pow(y, Factor()); // power by another factor else return y; // no more power ^ operators } double ExpressionEvaluator::Factor() { if(p.Char('-')) // unary - return -Factor(); if(p.Id("abs")) // some functions... return fabs(Factor()); if(p.Id("sqrt")) return sqrt(Factor()); if(p.Id("exp")) return exp(Factor()); if(p.Id("exp2")) return exp2(Factor()); if(p.Id("ln")) return log(Factor()); if(p.Id("log")) return log10(Factor()); if(p.Id("log2")) return log2(Factor()); if(p.Id("sin")) return sin(Factor()); if(p.Id("cos")) return cos(Factor()); if(p.Id("tan")) return tan(Factor()); if(p.Id("asin")) return asin(Factor()); if(p.Id("acos")) return acos(Factor()); if(p.Id("atan")) return atan(Factor()); if(p.Id("sinh")) return sinh(Factor()); if(p.Id("cosh")) return cosh(Factor()); if(p.Id("tanh")) return tanh(Factor()); if(p.Id("asinh")) return asinh(Factor()); if(p.Id("acosh")) return acosh(Factor()); if(p.Id("atanh")) return atanh(Factor()); if(p.Id("x")) // our variable return x; if(p.Id("e")) // e constant return M_E; if(p.Id("pi")) // pi constant return M_PI; double x; if(p.Char('(')) { // resolve parenthesis - recurse back to Sum (+ - operators) x = Expression(); p.PassChar(')'); // make sure there is closing parenthesis } else x = p.ReadDouble(); // last possibility is that we are at number... if(p.Char('!')) { // compute factorial if(x >= 0 && x < 120) { int n = (int)x; if(n == x) { x = 1; for(int q = 1; q <= n; q++) x *= q; return x; } } p.ThrowError("invalid argument"); } return x; } double Evaluate(const char *s, double x) { // evaluate expression for given variable, return Null on any failure or out-of-bounds result (NaN) ExpressionEvaluator v; v.x = x; v.p.Set(s); try { double y = v.Expression(); return y >= -1e200 && y < 1e200 ? y : Null; } catch(CParser::Error) {} return Null; } struct FnGraph : public TopWindow { virtual void Paint(Draw& w); virtual void MouseWheel(Point p, int zdelta, dword keyflags); int zoom = 9; EditString expression; // function to display FnGraph(); }; FnGraph::FnGraph() { Title("Graph of a function"); Add(expression.TopPos(0).HSizePos()); // place widget to the top, horizontally fill the window expression << [=] { Refresh(); }; // when expression changes, repaint the graph Sizeable().Zoomable(); // make the window resizable } void FnGraph::MouseWheel(Point, int zdelta, dword keyflags) { zoom = clamp(zoom + -sgn(zdelta), 1, 20); Refresh(); } void FnGraph::Paint(Draw& w_) { Size sz = GetSize(); DrawPainter w(w_, sz); // Use Painter for smooth sw rendering w.Clear(White()); // clear the background int ecy = expression.GetSize().cy; // query the height of widget w.Offset(0, ecy); // move coordinates out of widget sz.cy -= ecy; // and reduce the size if(sz.cy < 1) // if too small, do nothing (avoid div by zero) return; sz = sz / 2 * 2 - 1; // this trick will force axes to .5, results in sharper AA rendering double pixels_per_unit = sz.cy / zoom; // we want to display y range -4.5 .. 4.5 double xaxis = sz.cy / 2.0; // vertical position of x axis double yaxis = sz.cx / 2.0; // horizontal position of y axis w.Move(0, xaxis).Line(sz.cx, xaxis).Stroke(1, Blue()); // draw x axis w.Move(yaxis, 0).Line(yaxis, sz.cy).Stroke(1, Blue()); // draw y axis Font font = Serif(15); if(pixels_per_unit > 20) // if big enough, paint some axis markers and numbers... for(int i = 1; i < 2 * sz.cx / pixels_per_unit; i++) for(int sgn = -1; sgn < 2; sgn += 2) { String n = AsString(sgn * i); Size tsz = GetTextSize(n, font); double x = yaxis + sgn * i * pixels_per_unit; w.Move(x, xaxis - 5).Line(x, xaxis + 5).Stroke(1, Blue()); w.Text(int(x - tsz.cx / 2.0), int(xaxis + 6), n, font).Fill(Blue()); double y = xaxis - sgn * i * pixels_per_unit; w.Move(yaxis - 5, y).Line(yaxis + 5, y).Stroke(1, Blue()); w.Text(int(yaxis + 6), int(y - tsz.cy / 2.0), n, font).Fill(Blue()); } Vector xp = Split(~~expression, ';'); // get individual functions for(int ii = 0; ii < xp.GetCount(); ii++) { double y0 = Null; // store previous value for(int i = 0; i < sz.cx; i++) { // now iterate through all x pointes and draw the graph double x = (i - sz.cx / 2.0) / pixels_per_unit; double y = Evaluate(xp[ii], x); if(!IsNull(y)) { double gy = sz.cy / 2.0 - y * pixels_per_unit; if(IsNull(y0)) // previous value was defined w.Move(i, gy); else w.Line(i, gy); } y0 = y; } w.Stroke(1, Color(!!(ii & 1) * 150, !!(ii & 2) * 150, !!(ii & 4) * 150)); // finally paint the graph line } } GUI_APP_MAIN { FnGraph().Run(); }