Skip to the content.
import morpholib as morpho
mo = morpho
morpho.importAll()

import math, cmath, random
import numpy as np

from morpholib.tools.basics import *
from morpholib.video import standardAnimation, ratioXY, std_view
from morpholib.tools.color import colormap

morpho.transition.default = morpho.transition.quadease

uniform = morpho.transitions.uniform
quadease = morpho.transitions.quadease
sineease = sinease = morpho.transition.sineease

exportDir = "./"

# Returns a film with some standard axes.
def makeAxes():
    axes = morpho.graph.Axes([-100,100, -100,100],
        xwidth=10, ywidth=10,
        xalpha=1, yalpha=1
        )
    # axes.zdepth = 1000
    axes = morpho.Actor(axes)
    return axes

def mvt():
    axes = makeAxes()

    mation = morpho.video.standardAnimation(axes)
    mainlayer = mation.layers[0]
    mainlayer.camera.first().centerAt(14+7j)

    mation.endDelay()

    a = 6
    b = 25
    tickHeight = 1

    ticka = morpho.grid.Arrow(
        a-tickHeight/2*1j, a+tickHeight/2*1j,
        width=5, headSize=0, color=[0,0,0]
        )
    ticka = morpho.Actor(ticka)
    ticka.newkey(15)
    ticka.first().head = ticka.first().tail
    mainlayer.merge(ticka)

    tickb = morpho.grid.Arrow(
        b-tickHeight/2*1j, b+tickHeight/2*1j,
        width=5, headSize=0, color=[0,0,0]
        )
    tickb = morpho.Actor(tickb)
    tickb.newkey(15)
    tickb.first().head = tickb.first().tail
    mainlayer.append(tickb)

    # Labels
    textHeight = 1.25*tickHeight
    time = mainlayer.lastID()
    labela = morpho.text.Text("a", pos=a, color=[0,0,0], size=64, anchor_x=0, italic=True, alpha=0)
    labela = morpho.Actor(labela)
    labela.newkey(15)
    labela.last().alpha = 1
    labela.last().pos -= textHeight*1j
    mainlayer.merge(labela, atFrame=time)

    labelb = labela.copy()
    for fig in labelb.keys():
        fig.text = "b"
        fig.pos += b-a
    mainlayer.merge(labelb)

    mation.endDelay(15)

    c = 18
    w = 12.5
    h = 12
    f = lambda x: h*math.sqrt(1-(x-c)**2/w**2)
    graph = mo.graph.realgraph(f,a,b, steps=100, width=7, color=[0,0,1])

    graph = morpho.Actor(graph)
    graph.newkey(30)
    graph.first().end = 0
    mainlayer.append(graph)

    # y ticks
    tickfa = morpho.grid.Arrow(
        1j*f(a)-tickHeight/2, 1j*f(a)+tickHeight/2,
        width=5, headSize=0, color=[0,0,0]
        )
    tickfa = morpho.Actor(tickfa)
    tickfa.newkey(15)
    tickfa.first().head = tickfa.first().tail
    mainlayer.append(tickfa)

    time = mation.lastID()
    linea = mo.grid.Arrow(
        f(a)*1j, a+f(a)*1j, color=[0,0,0], width=5,
        headSize=0
        )
    linea.dash = [10]
    linea.zdepth = -1
    linea = mo.Actor(linea)
    linea.newendkey(20)
    linea.first().head = linea.first().tail
    mainlayer.merge(linea, atFrame=time)

    tickfb = morpho.grid.Arrow(
        1j*f(b)-tickHeight/2, 1j*f(b)+tickHeight/2,
        width=5, headSize=0, color=[0,0,0]
        )
    tickfb = morpho.Actor(tickfb)
    tickfb.newkey(15)
    tickfb.first().head = tickfb.first().tail
    mainlayer.merge(tickfb, atFrame=time)

    lineb = mo.grid.Arrow(
        f(b)*1j, b+f(b)*1j, color=[0,0,0], width=5,
        headSize=0
        )
    lineb.dash = [10]
    lineb.zdepth = -1
    lineb = mo.Actor(lineb)
    lineb.newendkey(20)
    lineb.first().head = lineb.first().tail
    mainlayer.append(lineb)

    time = mainlayer.lastID()
    # textHeight *= 1.5
    labelfa = morpho.text.Text("f(a)", pos=f(a)*1j, color=[0,0,0], size=64, anchor_x=0, italic=True, alpha=0)
    labelfa = morpho.Actor(labelfa)
    labelfa.newkey(15)
    labelfa.last().alpha = 1
    labelfa.last().pos -= textHeight*1.5
    mainlayer.merge(labelfa, atFrame=time)

    labelfb = morpho.text.Text("f(b)", pos=f(b)*1j, color=[0,0,0], size=64, anchor_x=0, italic=True, alpha=0)
    labelfb = morpho.Actor(labelfb)
    labelfb.newkey(15)
    labelfb.last().alpha = 1
    labelfb.last().pos -= textHeight*1.5
    mainlayer.merge(labelfb, atFrame=time)

    mation.endDelay()

    time = mation.lastID()
    pta = mo.grid.Point(a+f(a)*1j, strokeWeight=3, size=20, alpha=0)
    pta = mo.Actor(pta)
    pta.newkey(15).alpha = 1
    mainlayer.merge(pta, atFrame=time)

    ptb = pta.copy()
    for fig in ptb.keys():
        fig.pos = b+f(b)*1j
    mainlayer.merge(ptb)

    xmin = -4
    xmax = 32
    m = (f(b)-f(a))/(b-a)
    L = lambda x: m*(x-a) + f(a)
    secline = mo.graph.realgraph(L,xmin,xmax, steps=1, width=7,
        color=mo.color.colormap["green"]
        )
    secline.zdepth = -1
    secline.end = 0
    secline = mo.Actor(secline)
    secline.newkey(30)
    secline.last().end = 1
    mainlayer.append(secline)

    mation.endDelay(15)

    # Secant formula
    secslope = mo.graphics.Image("./resources/secslope.png")
    secslope.pos = 16.5 + 7j
    secslope.align = [0,0]
    secslope.height = 0
    secslope.zdepth = 10
    # secslope.alpha = 0

    secslope = mo.Actor(secslope)
    secslope.newendkey(20)
    secslope.last().pos = a+5 + 2j
    secslope.last().height = 2
    # secslope.last().alpha = 1
    mainlayer.append(secslope)

    mation.endDelay()

    dx = 0.00001
    deriv = lambda f: (lambda x: (f(x+dx) - f(x-dx))/(2*dx))
    df = deriv(f)
    dfcImg = morpho.graphics.Image("./resources/df.png")
    @morpho.SkitParameters({
        "f":f, "df":df, "x":a+dx, "radius":5, "ptalpha":1, "alpha":1,
        "start":0, "end":1
        })
    class Tanline(morpho.Skit):
        def makeFrame(self):
            x = self.x
            radius = self.radius
            ptalpha = self.ptalpha
            alpha = self.alpha
            f = self.f
            df = self.df

            m = df(x)
            y = f(x)
            T = lambda t: m*(t-x)+y
            xrad = radius/math.sqrt(1+m**2)
            tanline = mo.graph.realgraph(T, x-xrad, x+xrad, steps=1,
                color=[0,0,0], width=7, alpha=alpha
                )
            tanline.start = self.start
            tanline.end = self.end

            pt = mo.grid.Point(x+y*1j, strokeWeight=3,
                fill=mo.color.colormap["violet"], alpha=alpha*ptalpha, size=20
                )

            dfc = mo.graphics.Image(dfcImg)
            dfc.pos = pt.pos + 4j
            dfc.height = 1
            dfc.alpha = ptalpha*alpha

            arrow = mo.grid.Arrow(dfc.pos-0.75j, pt.pos+0.75j,
                width=5, color=mo.color.colormap["violet"],
                alpha=ptalpha*alpha
                )

            vert = mo.grid.Arrow(x, pt.pos,
                color=[0,0,0], alpha=ptalpha*alpha, width=5, headSize=0
                )
            vert.dash = [10]
            vert.zdepth = -1

            tickc = morpho.grid.Arrow(
                x-tickHeight/2*1j, x+tickHeight/2*1j,
                width=5, headSize=0, alpha=ptalpha*alpha, color=[0,0,0]
                )

            labelc = morpho.text.Text(
                "c", pos=x-1j*textHeight, color=mo.color.colormap["violet"], size=64,
                anchor_x=0, italic=True, alpha=ptalpha*alpha
                )

            return mo.Frame([tanline, pt, arrow, vert, tickc, labelc, dfc])

    tanline = Tanline()
    tanline.zdepth = 1
    tanline.x = a+2
    tanline.end = 0
    tanline.ptalpha = 0

    tanline = morpho.Actor(tanline)
    mainlayer.append(tanline)

    tanline.newendkey(20)
    tanline.last().end = 1
    tanline.last().ptalpha = 1

    mation.endDelay()

    tanline.newendkey(60).x = b-2
    tanline.newendkey(60).x = a+4
    tanline.newendkey(45).x = 13.757  # First MVT point

    mation.endDelay()

    # Assert parallel
    tline = tanline.last().makeFrame().figures[0]
    tline.zdepth = -0.5
    tline = mo.Actor(tline)
    mainlayer.append(tline)

    tline.newendkey(20)
    for n in range(2):
        z = tline.last().seq[n]
        tline.last().seq[n] = z.real + 1j*(L(z.real)+0.25)

    mation.endDelay()

    tline.newendkey(20, tline.first().copy())
    tline.last().visible = False

    mation.endDelay()

    # Construct equation
    time = mation.lastID()
    dfc = tanline.last().makeFrame().figures[-1]
    dfc.zdepth = 10
    dfc = mo.Actor(dfc)
    dfc.newendkey(30)
    dfc.last().pos = secslope.last().pos + secslope.last().width
    mainlayer.merge(dfc, atFrame=time)

    eq = mo.text.Text(
        "=", pos=16.4+1.7j, color=[0,0,0],
            size=84, anchor_x=0, alpha=0
            )
    eq.zdepth = 10
    eq = mo.Actor(eq)
    eq.newendkey(30).alpha = 1
    mainlayer.merge(eq, atFrame=time)

    mation.endDelay()

    # Enbox formula
    time = mation.lastID()
    box = [6.5,20.5, 0.5,3.5]
    boxer = mo.gadgets.enbox(box, width=5)
    for key in boxer.keys():
        key.zdepth = 10
    mainlayer.append(boxer)

    whitebox = mo.grid.rect(box)
    whitebox.width = 0
    whitebox.fill = [1,1,1]
    whitebox.alpha = 0
    whitebox.zdepth = 9
    whitebox = mo.Actor(whitebox)
    whitebox.newendkey(30).alpha = 1
    mainlayer.merge(whitebox, atFrame=time)

    mation.endDelay()

    boxer.newendkey(20)
    boxer.last().alpha = 0
    boxer.last().visible = False

    whitebox.newendkey(20)
    whitebox.last().alpha = 0
    whitebox.last().visible = False

    # Morph graph
    time = mation.lastID()
    f2 = lambda x: 4*math.sin(-x/2.5)+8
    graph2 = mo.graph.realgraph(f2,a,b, steps=100, width=7, color=[0,0,1])
    graph.newkey(time)
    graph.newendkey(30, graph2)

    # Morph secline
    m2 = (f2(b) - f2(a))/(b-a)
    L2 = lambda x: m2*(x-a) + f2(a)
    secline2 = mo.graph.realgraph(L2,xmin,xmax, steps=1, width=7,
        color=mo.color.colormap["green"]
        )
    secline2.zdepth = -1
    secline.newkey(time)
    secline.newendkey(30, secline2)

    pta.newkey(time)
    pta.newendkey(30).pos = a + 1j*f2(a)

    ptb.newkey(time)
    ptb.newendkey(30).pos = b + 1j*f2(b)

    linea.newkey(time)
    linea.newendkey(30)
    linea.last().tail = f2(a)*1j
    linea.last().head = a + f2(a)*1j

    lineb.newkey(time)
    lineb.newendkey(30)
    lineb.last().tail = f2(b)*1j
    lineb.last().head = b + f2(b)*1j

    tickfa.newkey(time)
    tickfa.newendkey(30)
    tickfa.last().tail += (f2(a)-f(a))*1j
    tickfa.last().head += (f2(a)-f(a))*1j

    tickfb.newkey(time)
    tickfb.newendkey(30)
    tickfb.last().tail += (f2(b)-f(b))*1j
    tickfb.last().head += (f2(b)-f(b))*1j

    labelfa.newkey(time)
    labelfa.newendkey(30)
    labelfa.last().pos += (f2(a)-f(a))*1j

    labelfb.newkey(time)
    labelfb.newendkey(30)
    labelfb.last().pos += (f2(b)-f(b))*1j

    tanline.newkey(time)
    tanline.newendkey(30)
    tanline.last().f = f2
    tanline.last().df = deriv(f2)
    tanline.last().radius = 3.5

    mation.endDelay()

    # Find both new MVT points
    mvt1 = 11.378
    mvt2 = 20.038

    tanline.newendkey(45).x = mvt2

    mation.endDelay(15)

    tan2 = tanline.last().makeFrame().figures[0]
    tan2.zdepth = 10
    mainlayer.append(tan2)
    pt2 = tanline.last().makeFrame().figures[1]
    pt2.zdepth = 10
    mainlayer.append(pt2)

    tanline.newendkey(60).x = mvt1

    tan1 = tanline.last().makeFrame().figures[0]
    tan1.zdepth = 10
    mainlayer.append(tan1)
    pt1 = tanline.last().makeFrame().figures[1]
    pt1.zdepth = 10
    mainlayer.append(pt1)

    mation.endDelay(15)

    tanline.last().transition = mo.transition.uniform
    tanline.newendkey(30).alpha = 0

    mation.endDelay(30*3)

    mation.finitizeDelays(60)

    mation.locatorLayer = 0
    mation.play()

mvt()