Other Quiz #11 - Drawing functions

Erel

B4X founder
Staff member
Licensed User
Longtime User
Edit: Don't miss the bonus question in post #11.

Back to high school quiz.

In your code should be a sub with the following signature:
B4X:
Sub Func(x As Double) As Double

The user implements this function. The user enters (directly in the code) any math function they like.

The quiz is to draw the math function.

Some examples (the red graph will be explained soon):
B4X:
Sub Func(x As Double) As Double
   Return x
End Sub

SS-2014-01-26_10.57.37.png


B4X:
Return x * x

SS-2014-01-26_10.58.40.png


B4X:
Return Sin(x) * Cos(x)

SS-2014-01-26_10.59.26.png


B4X:
Return Logarithm (x, cE)

SS-2014-01-26_11.02.50.png


B4X:
Return Abs(x)

SS-2014-01-26_11.05.23.png


The recommended signature of the drawing sub should be:
B4X:
Sub Draw(MinX As Double, MaxX As Double, MinY As Double, MaxY As Double)

As you have probably guessed the red graph is the function derivative.
Note that the only "user" input is the implementation of the Func sub.

6 (out of 10) points - Correctly draw the function.
10 points - Correctly draw the function and its derivative.


You can implement it in B4J or B4A.
 
Last edited:

derez

Expert
Licensed User
Longtime User
I've already made such an application.
The user (not the programmer) may write up to 4 functions in four windoes and get them drawn. If the second window text is "DY" you get the function and its derivative, if it has "INT" - you get the integral.
The applicatin uses expression evaluation library.
example1.png Untitled2.png
example3.pngexample4.png
B4X:
Sub Draw_Graph(exp As String, color As Int)
    Dim oldx,oldy, x As Double
    Dim n As Int = (hix-lowx)/dx
    For i = 0 To n
        x = lowx + dx * i
        ev.SetGlobal("x", x)
        res = ev.Evaluate(exp)
        If ev.ErrorFlag Then
            Msgbox("Error in 2",ev.Error)
            Exit
        Else
            If i > 0 Then
                cnvs.DrawLine((x-lowx) * xratio, (hiy-res) * yratio ,(oldx-lowx) * xratio, (hiy-oldy) * yratio ,color,2)
            End If
        End If
        oldx = x
        oldy = res
    Next
              
End Sub

Sub Draw_Derivative(exp As String)
    Dim oldx,oldy, x As Double
    Dim n As Int = (hix-lowx)/dx
    Dim res0,res1, dely As Double
    For i = 0 To n-1
        x = lowx + dx * i
        ev.SetGlobal("x", x)
        res0 = ev.Evaluate(exp)
        If ev.ErrorFlag Then
            Msgbox("Error in 2",ev.Error)
            Exit
        End If
        x = lowx + dx * (i+1)
        ev.SetGlobal("x", x)
        res1 = ev.Evaluate(exp)
        If ev.ErrorFlag Then
            Msgbox("Error in 2",ev.Error)
            Exit
        Else
            If i > 0 Then
                dely = (res1 - res0)/dx
                cnvs.DrawLine((x-lowx) * xratio, (hiy-dely) * yratio ,(oldx-lowx) * xratio, (hiy-oldy) * yratio ,Colors.red,2)
            End If
        End If
        oldx = x
        oldy = dely
    Next
End Sub

Sub Draw_Integral(exp As String)
    Dim oldx,oldy, x As Double
    Dim n As Int = (x1-x0)/dx
    Dim res0,res1, sum As Double
    sum = 0
    For i = 0 To n-1
        x = x0 + dx * i
        ev.SetGlobal("x", x)
        res0 = ev.Evaluate(exp)
        If ev.ErrorFlag Then
            Msgbox("Error in 2",ev.Error)
            Exit
        End If
        x = x0 + dx * (i+1)
        ev.SetGlobal("x", x)
        res1 = ev.Evaluate(exp)
        If ev.ErrorFlag Then
            Msgbox("Error in 2",ev.Error)
            Exit
        Else
            If i > 0 Then
                sum = sum + dx*(res1 + res0)/2
                cnvs.DrawLine((x-lowx) * xratio, (hiy-sum) * yratio ,(oldx-lowx) * xratio, (hiy-oldy) * yratio ,Colors.red,2)
            End If
        End If
        oldx = x
        oldy = sum
    Next
End Sub
 
Last edited:
Upvote 0

strat

Active Member
Licensed User
Longtime User
This is my solution. Y coordinate is increasing to opposite side because of screen coordinates. So some function that you found on math books may appear different.


B4X:
Sub Process_Globals
End Sub

Sub Globals
    Dim ImageView1 As ImageView
    Dim Button1 As Button
End Sub

Sub Activity_Create(FirstTime As Boolean)
    Button1.Initialize("Button1")
    ImageView1.Initialize("Imageview1")
    Activity.AddView(ImageView1,0,0,100%x,100%x)
    Activity.AddView(Button1,35%x,100%x+5,30%x,20%x)
    Button1.Text="Draw"
End Sub

Sub Activity_Resume

End Sub

Sub Activity_Pause (UserClosed As Boolean)

End Sub

Sub func(x As Double) As Double
    Return x*x
    'Return Sin(x)
    'return cos(x)
    'Return Logarithm(x,10)
    'Return Abs(x)
    'return x*x*x
    'Return Tan(x)
    'return (x*x-1)/(x*x+1)
End Sub

Sub Draw(MinX As Double, MaxX As Double, MinY As Double, MaxY As Double)
    Dim j As Float
    'Dim y As Float
    Dim x1,y1,x2,y2 As Float
    Dim maxvalx,maxvaly,kx,ky As Float
    Dim centerx,centery As Float
    Dim stepsize,ydiv As Float
    Dim value,maxvalue As Float
    Dim c As Canvas
    Dim w,h As Float
    centerx=ImageView1.Width/2
    centery=ImageView1.Height/2
    w=ImageView1.Width
    h=ImageView1.Height
    stepsize=(MaxX-MinX)/w
    ydiv=(MaxY-MinY)/h
   
    If (Abs(MaxX))>(Abs(MinX)) Then
        maxvalx=Abs(MaxX)
    Else
        maxvalx=Abs(MinX)
    End If
    kx=w/(maxvalx*2)
   
    maxvalue=Abs(func(MinX))
    For j=MinX To MaxX Step stepsize
        value=Abs(func(j))
        If value>maxvalue Then maxvalue=value
    Next
    maxvaly=maxvalue
    ky=h/(maxvaly*2)
    c.Initialize(ImageView1)
    c.DrawLine(0,centery,w,centery,Colors.Blue,1)
    c.DrawLine(centerx,0,centerx,h,Colors.Blue,1)
    x1=MinX*kx
    y1=func(MinX)*ky*ydiv
    For j=MinX To MaxX Step stepsize
        y2=func(j)*ky*ydiv
        x2=j*kx
        If (-y1>=MinY) AND (-y2<=MaxY) Then
            c.DrawLine(centerx+x1,centery-y1,centerx+x2,centery-y2,Colors.Red,1)
        End If
        'Log("x1="&x1&"  y1="&y1&"  x2="&x2&"  y2="&y2)
        x1=x2
    y1=y2
    Next
End Sub   
       
Sub Button1_Click
    'Draw(1,100,-360,360) 'For  Logarithm
    Draw(-10,10,-360,360)
End Sub
 
Upvote 0

klaus

Expert
Licensed User
Longtime User
Here is my solution.
B4X:
Sub Draw(MinX As Double, MaxX As Double, MinY As Double, MaxY As Double)
    Dim NbPoints = 200 As Int
    Dim ScaleX, ScaleY As Double
    Dim i, xmax, ymax, x1, x2, y0, y1, y2 As Int
    Dim dx, dy1, dy2, valx1, valx2, valy1, valy2, valdx, valdy2 As Double
    Dim rectDraw As Rect

    xmax = pnlDraw.Width
    ymax = pnlDraw.Height

    rectDraw.Initialize(0, 0, xmax, ymax)
    cvsDraw.DrawRect(rectDraw, Colors.White, True, 1)

    ScaleX = pnlDraw.Width / (MaxX - MinX)
    ScaleY = pnlDraw.Height / (MaxY - MinY)

    valdx = (MaxX - MinX) / NbPoints
    dx = xmax / NbPoints
    y0 = ymax + MinY * ScaleY

    If MinX < 0 AND MaxX > 0 Then
        cvsDraw.DrawLine(-MinX * ScaleX, 0, -MinX * ScaleX, ymax, Colors.Black, 1dip)
    End If
    If MinY < 0 AND MaxY > 0 Then
        cvsDraw.DrawLine(0, ymax + MinY * ScaleY, xmax, ymax + MinY * ScaleY, Colors.Black, 1dip)
    End If

    For i = 0 To NbPoints - 1
        If i = 0 Then
            valx1 = MinX
            valy1 = Func(valx1)
            x1 = 0
            y1 = y0 - valy1 * ScaleY
        End If
  
        x2 = (i + 1) * dx

        valx2 = MinX + (i + 1) * valdx
        valy2 = Func(valx2)
        y2 = y0 - valy1 * ScaleY
        cvsDraw.DrawLine(x1, y1, x2, y2, Colors.Blue, 1dip)

        valdy2 = (valy2 - valy1) / valdx
        dy2 = y0 - valdy2 * ScaleY
        If i = 0 Then
            dy1 = dy2
        End If

        cvsDraw.DrawLine(x1, dy1, x2, dy2, Colors.Red, 1dip)
        valy1 = valy2
        x1 = x2
        y1 = y2
        dy1 = dy2
    Next
    pnlDraw.Invalidate
End Sub

EDIT: Amended the typo reported by Erel in post#10.
 

Attachments

  • Quiz11.png
    Quiz11.png
    11 KB · Views: 366
  • Quiz11.zip
    7.3 KB · Views: 342
Last edited:
Upvote 0

Erel

B4X founder
Staff member
Licensed User
Longtime User
Very nice.
@klaus I think that there is a typo in your code. It should be pnlDraw.Height in ScaleY calculation.

Here is my solution:

B4X:
Sub Process_Globals
   Private fx As JFX
   Private MainForm As Form
   Private cvs As Canvas
End Sub

Sub AppStart (Form1 As Form, Args() As String)
   MainForm = Form1
   MainForm.Show
   cvs.Initialize("")
   MainForm.RootPane.AddNode(cvs, 0, 0, 500, 500)
   Draw(-10, 10, -10, 10)
End Sub

Sub Func(x As Double) As Double
   Return Abs(x)
End Sub

Sub Draw(MinX As Double, MaxX As Double, MinY As Double, MaxY As Double)
   Dim x As Double
   Dim intervalX As Double = (MaxX - MinX) / cvs.Width
   Dim intervalY As Double = (MaxY - MinY) / cvs.Height
   DrawAxis(MinX, intervalX, MinY, intervalY)
   Dim prevScreenX = 0, prevScreenY = cvs.Height - (Func(MinX) - MinY) / intervalY As Double
   Dim prevDY As Double
   Dim py As Double
   For x = MinX To MaxX Step intervalX
     Dim y As Double = Func(x)
     Dim screenX As Int = Round((x - MinX) / intervalX)
     Dim screenY As Int = Round(cvs.Height - (y - MinY) / intervalY)
     cvs.DrawLine(prevScreenX, prevScreenY, screenX, screenY, fx.Colors.Black, 1)
     If x >= MinX + intervalX Then
       Dim dy As Double = (y - py) / intervalX
       Dim screenDY As Int = Round(cvs.Height - (dy - MinY) / intervalY)
       If x >= MinX + intervalX * 2 Then
         cvs.DrawLine(prevScreenX, prevDY, screenX, screenDY, fx.Colors.Red, 1)
       End If
       prevDY = screenDY
     End If
     py = y
     prevScreenX = screenX
     prevScreenY = screenY
   Next
End Sub

Sub DrawAxis(MinX As Double, IntervalX As Double, MinY As Double, IntervalY As Double)
   Dim y As Int = Round(cvs.Height - (0 - MinY) / IntervalY)
   cvs.DrawLine(0, y, cvs.Width, y, fx.Colors.Gray, 1)
   Dim x As Int = Round((0 - MinX) / IntervalX)
   cvs.DrawLine(x, 0, x, cvs.Height, fx.Colors.Gray, 1)
End Sub

I wrote it in B4J, it should be simple to convert it to B4A. The main difference is that in B4J Canvas is a node (view / control).

The nice thing about it is that it is very simple to calculate the (pseudo) derivative. We just treat the graph as a set of lines. The derivative is the value of (CurrentY - PreviousY) / DeltaX.
 
Upvote 0

Erel

B4X founder
Staff member
Licensed User
Longtime User
Bonus question:

Explain how this sinus graph was drawn:

SS-2014-01-26_18.02.07.png


As you can see in the code there is no Func sub this time...

B4X:
Sub Process_Globals
   Private fx As JFX
   Private MainForm As Form
   Private cvs As Canvas
End Sub

Sub AppStart (Form1 As Form, Args() As String)
   MainForm = Form1
   MainForm.Show
   cvs.Initialize("")
   MainForm.RootPane.AddNode(cvs, 0, 0, 500, 500)
   Draw(0, 4 * cPI, -2, 2)
End Sub


Sub Draw(MinX As Double, MaxX As Double, MinY As Double, MaxY As Double)
   Dim x As Double
   Dim intervalX As Double = (MaxX - MinX) / cvs.Width
   Dim intervalY As Double = (MaxY - MinY) / cvs.Height
   DrawAxis(MinX, intervalX, MinY, intervalY)
   Dim prevScreenX = 0, prevScreenY = cvs.Height - (0 - MinY) / intervalY As Double
   Dim dy As Double = 1
   Dim y As Double = 0
   For x = MinX To MaxX Step intervalX
     dy = dy - y * intervalX
     y = y + dy * intervalX
     Dim screenX As Int = Round((x - MinX) / intervalX)
     Dim screenY As Int = Round(cvs.Height - (y - MinY) / intervalY)
     cvs.DrawLine(prevScreenX, prevScreenY, screenX, screenY, fx.Colors.Black, 1)
     prevScreenX = screenX
     prevScreenY = screenY
   Next
End Sub

Sub DrawAxis(MinX As Double, IntervalX As Double, MinY As Double, IntervalY As Double)
   Dim y As Int = Round(cvs.Height - (0 - MinY) / IntervalY)
   cvs.DrawLine(0, y, cvs.Width, y, fx.Colors.Gray, 1)
   Dim x As Int = Round((0 - MinX) / IntervalX)
   cvs.DrawLine(x, 0, x, cvs.Height, fx.Colors.Gray, 1)
End Sub


This is quite amazing, don't you think? o_O
 
Upvote 0

klaus

Expert
Licensed User
Longtime User
@klaus I think that there is a typo in your code. It should be pnlDraw.Height in ScaleY calculation.
You are right Erel :).
At the beginning I was reasoning with a square panel and changed it afterwards and of course forgot to change it. I changed it in the post#9.
I wrote at the beginning a drawaing routine with a more precice calculation of the derivative.
Instead of using (CurrentY - PreviousY) / DeltaX
I used (NextY - PreviousY) / 2 / DeltaX this needs to know three points, previous, current and next point) to calculate the derivative (assuming a parabola going through the three points instead of a line going through two points.
But then I went to the 'easy' solution.
 
Upvote 0

mc73

Well-Known Member
Licensed User
Longtime User
Bonus question:

Explain how this sinus graph was drawn:

SS-2014-01-26_18.02.07.png


As you can see in the code there is no Func sub this time...

B4X:
Sub Process_Globals
   Private fx As JFX
   Private MainForm As Form
   Private cvs As Canvas
End Sub

Sub AppStart (Form1 As Form, Args() As String)
   MainForm = Form1
   MainForm.Show
   cvs.Initialize("")
   MainForm.RootPane.AddNode(cvs, 0, 0, 500, 500)
   Draw(0, 4 * cPI, -2, 2)
End Sub


Sub Draw(MinX As Double, MaxX As Double, MinY As Double, MaxY As Double)
   Dim x As Double
   Dim intervalX As Double = (MaxX - MinX) / cvs.Width
   Dim intervalY As Double = (MaxY - MinY) / cvs.Height
   DrawAxis(MinX, intervalX, MinY, intervalY)
   Dim prevScreenX = 0, prevScreenY = cvs.Height - (0 - MinY) / intervalY As Double
   Dim dy As Double = 1
   Dim y As Double = 0
   For x = MinX To MaxX Step intervalX
     dy = dy - y * intervalX
     y = y + dy * intervalX
     Dim screenX As Int = Round((x - MinX) / intervalX)
     Dim screenY As Int = Round(cvs.Height - (y - MinY) / intervalY)
     cvs.DrawLine(prevScreenX, prevScreenY, screenX, screenY, fx.Colors.Black, 1)
     prevScreenX = screenX
     prevScreenY = screenY
   Next
End Sub

Sub DrawAxis(MinX As Double, IntervalX As Double, MinY As Double, IntervalY As Double)
   Dim y As Int = Round(cvs.Height - (0 - MinY) / IntervalY)
   cvs.DrawLine(0, y, cvs.Width, y, fx.Colors.Gray, 1)
   Dim x As Int = Round((0 - MinX) / IntervalX)
   cvs.DrawLine(x, 0, x, cvs.Height, fx.Colors.Gray, 1)
End Sub


This is quite amazing, don't you think? o_O

What we have is an approximate coding of the integral sin(x)=\int cos(x)dx
At an arbitrary point, we have
dsin(x+dx)/dx=cos(x+dx)=cos(x)cos(dx)-sin(x)sin(dx) \approx cos(x)-sin(x)dx, cause cos(dx) \approx 1 and sin(dx) \approx dx when dx<<1.
Thus we have, dy(new)=dy(old)-y(old)dx.
Then, y(new)=y(old)+dy(new)dx

Amazing ;)
 
Upvote 0

mc73

Well-Known Member
Licensed User
Longtime User
I dont 100% understand this equation yet, but I find it very interesting.
Can you elaborate a little bit more?

when you get cos(x) - sin(x)dx
How is dy(old) = cos(x) ?

And also I dont understand the last step.

Thanks in advance for any explanation.

dy(old) is the previous (old) derivative of sin(x), thus cos(x).
The last step is just the integral's calculation, currentValue=previousValue+currentDerivativesValue*step.
 
Upvote 0

mc73

Well-Known Member
Licensed User
Longtime User
I see you have a tendency to use the like button. Let me add you at fb, unfortunately not many friends like me posts there, you could help? :( :D
 
Upvote 0

thedesolatesoul

Expert
Licensed User
Longtime User
Lol. I like posts which have useful information contributed to the forum. It helps to find them easier, and promote the people that give good information :)
I dont usually 'like' posts on FB, maybe because my stream if full of stupid posts I dont care about :( ...I am drifting towards G+ though now.
 
Upvote 0

Erel

B4X founder
Staff member
Licensed User
Longtime User
I will try to explain it in different words:

The derivative of Sin(x) is Cos(x). The derivative of Cos(x) is -Sin(x).
So the second derivative of Sin(x) is -Sin(x).

This is the interesting code:
B4X:
Dim dy As Double = 1 '
   Dim y As Double = 0
   For x = MinX To MaxX Step intervalX
     dy = dy - y * intervalX
     y = y + dy * intervalX
Note that MinX is 0. We know that Sin(0) = 0 and Cos(0) = 1
The first step is to update the Cos(x) function:
dy = dy - y * intervalX => Cos(x + dx) ~= Cos(x) + d(cos(x)) / dx * dx => dy = dy + (-y) * intervalX
The second step is to update Sin(x) function in a similar way.
 
Upvote 0
Top