From 50132119542ca03cba828e2ff7d508b42c611bf3 Mon Sep 17 00:00:00 2001 From: jverzani Date: Wed, 2 Jul 2025 06:25:10 -0400 Subject: [PATCH] work on better figures --- quarto/_common_code.qmd | 22 + quarto/_quarto.yml | 4 +- quarto/adjust_plotly.jl | 14 +- quarto/index.qmd | 2 +- quarto/integrals/area_between_curves.qmd | 82 +++- quarto/integrals/volumes_slice.qmd | 399 ++++++++++++++++++- quarto/limits/intermediate_value_theorem.qmd | 69 +++- quarto/limits/limits.qmd | 75 ++-- quarto/precalc/functions.qmd | 203 +++++++++- quarto/precalc/inversefunctions.qmd | 176 +++++++- quarto/precalc/plotting.qmd | 104 ++++- quarto/references.bib | 9 + 12 files changed, 1098 insertions(+), 61 deletions(-) diff --git a/quarto/_common_code.qmd b/quarto/_common_code.qmd index fcc1575..db01bfd 100644 --- a/quarto/_common_code.qmd +++ b/quarto/_common_code.qmd @@ -2,6 +2,28 @@ #| output: false #| echo: false +# Some style choices for `Plots.jl` + +empty_style = (xaxis=([], false), + yaxis=([], false), + framestyle=:origin, + legend=false) +axis_style = (arrow=true, side=:head, line=(:gray, 1)) +text_style = (10,) +fn_style = (;line=(:black, 3)) +fn2_style = (;line=(:red, 4)) +mark_style = (;line=(:gray, 1, :dot)) + +domain_style = (;fill=(:orange, 0.35)) +range_style = (; fill=(:blue, 0.35)) + +nothing +``` + +```{julia} +#| output: false +#| echo: false + ## Formatting options are included here; not in CalculusWithJulia.WeaveSupport using QuizQuestions nothing diff --git a/quarto/_quarto.yml b/quarto/_quarto.yml index 178bc7e..ddd335a 100644 --- a/quarto/_quarto.yml +++ b/quarto/_quarto.yml @@ -158,7 +158,7 @@ format: execute: error: false - freeze: false -# freeze: auto +# freeze: false + freeze: auto # cache: false # enabled: true \ No newline at end of file diff --git a/quarto/adjust_plotly.jl b/quarto/adjust_plotly.jl index 34a0d01..ec06770 100644 --- a/quarto/adjust_plotly.jl +++ b/quarto/adjust_plotly.jl @@ -7,16 +7,19 @@ #alternatives/plotly_plotting.html function _add_plotly(f) - lineno = 117 + #lineno = 117 -str = """ - -""" r = readlines(f) + inserted = false open(f, "w") do io for (i,l) ∈ enumerate(r) - i == lineno && println(io, str) + if contains(l, "require.min.js") + !inserted && println(io, """ + +""") + inserted = true + end println(io, l) end end @@ -29,6 +32,7 @@ function (@main)(args...) for fᵢ ∈ files f = joinpath(root, fᵢ) if endswith(f, ".html") + dirname(f) == "_book" && continue _add_plotly(f) end end diff --git a/quarto/index.qmd b/quarto/index.qmd index 46d6eca..0529f15 100644 --- a/quarto/index.qmd +++ b/quarto/index.qmd @@ -59,7 +59,7 @@ in a spirit similar to a section of a book. Just like a book, there are try-it-yourself questions at the end of each page. All have a limited number of self-graded answers. These notes borrow ideas from many sources, for example @Strang, @Knill, @Schey, @Thomas, -@RogawskiAdams, several Wikipedia pages, and other sources. +@RogawskiAdams, @Angenent, several Wikipedia pages, and other sources. These notes are accompanied by a `Julia` package `CalculusWithJulia` that provides some simple functions to streamline some common tasks diff --git a/quarto/integrals/area_between_curves.qmd b/quarto/integrals/area_between_curves.qmd index cce4ab5..b279dea 100644 --- a/quarto/integrals/area_between_curves.qmd +++ b/quarto/integrals/area_between_curves.qmd @@ -64,7 +64,88 @@ $$ #### Examples +Find the area between +$$ +\begin{align*} +f(x) &= \frac{x^3 \cdot (2-x)}{2} \text{ and } \\ +g(x) &= e^{x/3} + (1-\frac{x}{1.7})^6 - 0.6 +\end{align*} +$$ + +over the interval $[0.2, 1.7]$. The area is illustrated in the figure below. + +```{julia} +f(x) = x^3*(2-x)/2 +g(x) = exp(x/3) + (1 - (x/1.7))^6 - 0.6 +a, b = 0.2, 1.7 +h(x) = g(x) - f(x) +answer, _ = quadgk(h, a, b) +answer +``` + + +::: {#fig-area-between-f-g} + +```{julia} +#| echo: false +p = let + gr() + # area between graphs + # https://github.com/SigurdAngenent/WisconsinCalculus/blob/master/figures/221/09areabetweengraphs.pdf + + f(x) = 1/6+x^3*(2-x)/2 + g(x) = 1/6+exp(x/3)+(1-x/1.7)^6-0.6 + a,b =0.2, 1.7 + A, B = 0, 2 + A′, B′ = A + .1, B - .1 + n = 20 + + plot(; empty_style..., aspect_ratio=:equal, xlims=(A,B)) + plot!(f, A′, B′; fn_style...) + plot!(g, A′, B′; fn_style...) + + xp = range(a, b, n) + marked = n ÷ 2 + for i in 1:n-1 + x0, x1 = xp[i], xp[i+1] + mpt = (x0 + x1)/2 + R = Shape([x0,x1,x1,x0], [f(mpt),f(mpt),g(mpt),g(mpt)]) + color = i == marked ? :gray : :white + plot!(R; fill=(color, 0.5), line=(:black, 1)) + end + + # axis + plot!([(A,0),(B,0)]; axis_style...) + + # hightlight + x0, x1 = xp[marked], xp[marked+1] + _style = (;line=(:gray, 1, :dash)) + plot!([(a,0), (a, f(a))]; _style...) + plot!([(b,0), (b,f(b))]; _style...) + plot!([(x0,0), (x0, f(x0))]; _style...) + plot!([(x1,0), (x1, f(x1))]; _style...) + + annotate!([ + (B′, f(B′), text(L"f(x)", 10, :left,:top)), + (B′, g(B′), text(L"g(x)", 10, :left, :bottom)), + (a, 0, text(L"a=x_0", 10, :top, :left)), + (b, 0, text(L"b=x_n", 10, :top, :left)), + (x0, 0, text(L"x_i", 10, :top)), + (x1, 0, text(L"x_{i+1}", 10, :top,:left)) + ]) + + current() +end + +plotly() +p +``` + +Illustration of a Riemann sum approximation to estimate the area between $f(x)$ and $g(x)$ over an interval $[a,b]$. (Figure follows one by @Angenent.) +::: + +##### Example Find the area bounded by the line $y=2x$ and the curve $y=2 - x^2$. @@ -970,4 +1051,3 @@ choices = ["The two enclosed areas should be equal", "The two enclosed areas are clearly different, as they do not overap"] radioq(choices, 1) ``` - diff --git a/quarto/integrals/volumes_slice.qmd b/quarto/integrals/volumes_slice.qmd index 2a891ff..4b4d0d7 100644 --- a/quarto/integrals/volumes_slice.qmd +++ b/quarto/integrals/volumes_slice.qmd @@ -20,6 +20,7 @@ using SymPy #| echo: false #| results: "hidden" import LinearAlgebra: norm +using SplitApplyCombine nothing ``` @@ -122,6 +123,145 @@ The formula is for a rotation around the $x$-axis, but can easily be generalized ::: +::: {#fig-solid-of-revolution} + +```{julia} +#| echo: false +plt = let + gr() + # Follow lead of # https://github.com/SigurdAngenent/WisconsinCalculus/blob/master/figures/221/09surf_of_rotation2.py + # plot surface of revolution around x axis between [0, 3] + # best if r(t) descreases + + rad(x) = 2/(1 + exp(x))#2/(2.0+x) + viewp = [2,-2,1] + + ## + unitize(x) = x / norm(x) + + """Orthogonal projection along the vector viewp""" + function make_Pmat(viewp) + a = unitize( [-viewp[2], viewp[1], 0] ) + b = unitize( [-viewp[3]*viewp[1], + -viewp[3]*viewp[2], + viewp[1]^2 + viewp[2]^2] ) + collect(zip(a,b)) + end + + #linear projection of R^3 onto R^2 + function proj(X, viewp) + Pmat = make_Pmat(viewp) + x=sum([Pmat[i][1]*X[i] for i in 1:3]) + y=sum([Pmat[i][2]*X[i] for i in 1:3]) + (x, y) # a point + end + proj(X) = proj(X, viewp) + + # discrete determinant of Jacobian; area multiplier? + function jac(X, u, v) + ϵ = 0.000001 + A = map((p,q) -> (p-q)/ϵ, X(u+ϵ/2, v), X(u-ϵ/2, v)) + B = map((p,q) -> (p-q)/ϵ, X(u, v+ϵ/2), X(u, v-ϵ/2)) + return A[1]*B[2]-A[2]*B[1] + end + + + # surface of revolution + surf(t, z) = [t, rad(t)*cos(z), rad(t)*sin(z)] + + # project the surface at (t, a=theta) + psurf(t,z) = proj(surf(t,z)) + + + bisect(f, a, b) = find_zero(f, (a,b), Bisection()) + _fold(t, zmin, zmax) = bisect(z -> jac(psurf, t, z), zmin, zmax) + + # create shape holding project disc + drawdiscF(t) = Shape(invert([psurf(t, 2*i*pi/100) for i in 1:101])...) + + # project a line between two points + pline!(p,q; kwargs...) = plot!([proj(p),proj(q)]; + line_style..., kwargs...) + α = 1.0 + line_style = (; line=(:black, 1)) + + plot(; empty_style..., aspect_ratio=:equal) + + + + # by layering, we get x-axis as desired + pline!([-1,0,0], [0,0,0]) + plot!(drawdiscF(0); fill =(:lightgray, α)) + pline!([0,0,0], [1,0,0]) + plot!(drawdiscF(1); fill =(:black, α)) # black to lightgray gives thickness + plot!(drawdiscF(1.1); fill=(:lightgray, α)) + pline!([1.1,0,0], [2,0,0]) + plot!(drawdiscF(2); fill=(:lightgray, α)) + pline!([2,0,0], [3,0,0]) + plot!(drawdiscF(3); fill=(:lightgray, α)) + pline!([3,0,0], [4,0,0]; arrow=true, side=:head) + pline!([0,0,0], [0,0,1.25]; arrow=true, side=:head) + + + tt = range(0, pi, 30) + curve = [psurf(t, pi/2) for t in tt] + plot!(curve; line=(:black, 2)) + + f1 = [[t, _fold(t, 0, pi)] for t in tt] + curve = [psurf(f[1], f[2]) for f in f1] + plot!(curve; line=(:black,)) + + f2 = [[t, _fold(t, pi, 2*pi)] for t in tt] + curve = [psurf(f[1], f[2]) for f in f2] + plot!(curve; line=(:black,)) + + + + tt= [0.025*i for i in 1:121] + f1 = [[t, _fold(t, pi, 2*pi)] for t in tt] + for f in f1 + plot!([psurf(f[1], f[2]-k*0.01*(4-f[1])) for k in 1:21]; + line=(:black,1)) + end + tt= [0.05*i for i in 1:61] + f1 = [[t, _fold(t, pi, 2*pi)] for t in tt] + for f in f1 + plot!([psurf( f[1], f[2]-k*0.01*(6-f[1]) ) + for k in 1:21]; line=(:black, 1)) + end +#= + ts = 0:.1:2.95 + θs = range(pi + pi/4, 3pi/2, 25) + for ti in ts + plot!([psurf(ti,θ) for θ in θs]; line=(:black, 1)) + end + θs = range(pi + pi/6, 3pi/2, 25) + for ti in ts + plot!([psurf(ti +0.05,θ) for θ in θs]; line=(:black, 1)) + end +=# + current() + + #ts = range(0, 3, 100) + #θs = range(0, 2pi, 100) + #contour(ts, θs, (t,z) -> jac(psurf,t,z), levels=[0]) + +end +plt +``` + +```{julia} +#| echo: false +plotly() +nothing +``` + + +Illustration of a figure being rotated around the $x$-axis. The discs have approximate volume given by the area of the base times the height or $\pi r(x)^2 \Delta x$. (Figure ported from @Angenent.) + +::: + + For a numeric example, we consider the original Red [Solo](http://en.wikipedia.org/wiki/Red_Solo_Cup) Cup. The dimensions of the cup were basically: a top diameter of $d_1 = 3~ \frac{3}{4}$ inches, a bottom diameter of $d_0 = 2~ \frac{1}{2}$ inches and a height of $h = 4~ \frac{3}{4}$ inches. @@ -352,6 +492,136 @@ $$ V = \int_a^b \pi \cdot (R(x)^2 - r(x)^2) dx. $$ +::: {#fig-washer-illustration} +```{julia} +#| echo: false +plt = let + gr() + # Follow lead of # https://github.com/SigurdAngenent/WisconsinCalculus/blob/master/figures/221/09surf_of_rotation2.py + # plot surface of revolution around x axis between [0, 3] + # best if r(t) descreases + + rad(x) = 2/(1 + exp(x))#2/(2.0+x) + viewp = [2,-2,1] + + ## + unitize(x) = x / norm(x) + + """Orthogonal projection along the vector viewp""" + function make_Pmat(viewp) + a = unitize( [-viewp[2], viewp[1], 0] ) + b = unitize( [-viewp[3]*viewp[1], + -viewp[3]*viewp[2], + viewp[1]^2 + viewp[2]^2] ) + collect(zip(a,b)) + end + + #linear projection of R^3 onto R^2 + function proj(X, viewp) + Pmat = make_Pmat(viewp) + x=sum([Pmat[i][1]*X[i] for i in 1:3]) + y=sum([Pmat[i][2]*X[i] for i in 1:3]) + (x, y) # a point + end + proj(X) = proj(X, viewp) + + # discrete determinant of Jacobian; area multiplier? + function jac(X, u, v) + ϵ = 0.000001 + A = map((p,q) -> (p-q)/ϵ, X(u+ϵ/2, v), X(u-ϵ/2, v)) + B = map((p,q) -> (p-q)/ϵ, X(u, v+ϵ/2), X(u, v-ϵ/2)) + return A[1]*B[2]-A[2]*B[1] + end + + + # surface of revolution + surf(t, z) = [t, rad(t)*cos(z), rad(t)*sin(z)] + surf2(t, z) = (t, rad(t)*cos(z)/2, rad(t)*sin(z)/2) + + # project the surface at (t, a=theta) + psurf(t,z) = proj(surf(t,z)) + psurf2(t, z) = proj(surf2(t,z)) + + bisect(f, a, b) = find_zero(f, (a,b), Bisection()) + _fold(t, zmin, zmax) = bisect(z -> jac(psurf, t, z), zmin, zmax) + + # create shape holding project disc + drawdiscF(t) = Shape(invert([psurf(t, 2*i*pi/100) for i in 1:101])...) + drawdiscI(t) = Shape([psurf2(t, 2*i*pi/100) for i in 1:101]) + + # project a line between two points + pline!(p,q; kwargs...) = plot!([proj(p),proj(q)]; + line_style..., kwargs...) + α = 1.0 + line_style = (; line=(:black, 1)) + + plot(; empty_style..., aspect_ratio=:equal) + + + + # by layering, we get x-axis as desired + pline!([-1,0,0], [0,0,0]) + plot!(drawdiscF(0); fill =(:lightgray, α)) + plot!(drawdiscI(0); fill=(:white, .5)) + pline!([0,0,0], [1,0,0]) + plot!(drawdiscF(1); fill =(:black, α)) # black to lightgray gives thickness + plot!(drawdiscI(1); fill=(:white, .5)) + plot!(drawdiscF(1.1); fill=(:lightgray, α)) + plot!(drawdiscI(1.1); fill=(:white, .5)) + pline!([1.1,0,0], [2,0,0]) + plot!(drawdiscF(2); fill=(:lightgray, α)) + plot!(drawdiscI(2); fill=(:white, .5)) + pline!([2,0,0], [3,0,0]) + plot!(drawdiscF(3); fill=(:lightgray, α)) + plot!(drawdiscI(3); fill=(:white, .5)) + pline!([3,0,0], [4,0,0]; arrow=true, side=:head) + pline!([0,0,0], [0,0,1.25]; arrow=true, side=:head) + + + tt = range(0, pi, 30) + curve = [psurf(t, pi/2) for t in tt] + plot!(curve; line=(:black, 2)) + + f1 = [[t, _fold(t, 0, pi)] for t in tt] + curve = [psurf(f[1], f[2]) for f in f1] + plot!(curve; line=(:black,)) + + f2 = [[t, _fold(t, pi, 2*pi)] for t in tt] + curve = [psurf(f[1], f[2]) for f in f2] + plot!(curve; line=(:black,)) + + + + tt= [0.025*i for i in 1:121] + f1 = [[t, _fold(t, pi, 2*pi)] for t in tt] + for f in f1 + plot!([psurf(f[1], f[2]-k*0.01*(4-f[1])) for k in 1:21]; + line=(:black,1)) + end + tt= [0.05*i for i in 1:61] + f1 = [[t, _fold(t, pi, 2*pi)] for t in tt] + for f in f1 + plot!([psurf( f[1], f[2]-k*0.01*(6-f[1]) ) + for k in 1:21]; line=(:black, 1)) + end + + current() + + +end +plt +``` + +```{julia} +#| echo: false +plotly() +nothing +``` + +Modification of earlier figure to show washer method. The interior volumn would be given by $\int_a^b \pi r(x)^2 dx$, the entire volume by $\int_a^b \pi R(x)^2 dx$. The difference then is the volume computed by the washer method. + +::: + ##### Example @@ -412,19 +682,21 @@ Let $h$ be the distance from the apex to the base. Consider cones with the prope ```{julia} -#| hold: true #| echo: false -h = 5 -R, r, rho = 1, 1/4, 1/4 -f(t) = (R-r) * cos(t) + rho * cos((R-r)/r * t) -g(t) = (R-r) * sin(t) - rho * sin((R-r)/r * t) -ts = range(0, 2pi, length=100) +plt = let + h = 5 + R, r, rho = 1, 1/4, 1/4 + f(t) = (R-r) * cos(t) + rho * cos((R-r)/r * t) + g(t) = (R-r) * sin(t) - rho * sin((R-r)/r * t) + ts = range(0, 2pi, length=100) -p = plot(f.(ts), g.(ts), zero.(ts), legend=false) -for t ∈ range(0, 2pi, length=25) - plot!(p, [0,f(t)], [0,g(t)], [h, 0], linecolor=:red) + plot(f.(ts), g.(ts), zero.(ts), legend=false) + for t ∈ range(0, 2pi, length=25) + plot!([0,f(t)], [0,g(t)], [h, 0], linecolor=:red) + end + current() end -p +plot ``` A right circular cone is one where this shape is a circle. This definition can be more general, as a square-based right pyramid is also such a cone. After possibly reorienting the cone in space so the base is at $u=0$ and the apex at $u=h$ the volume of the cone can be found from: @@ -450,6 +722,72 @@ $$ This gives a general formula for the volume of such cones. +::: {#fig-cross-sections} + +```{julia} +#| echo: false +plt = let + gr() + # sections + # https://github.com/SigurdAngenent/WisconsinCalculus/blob/master/figures/221/09Xsections.py + + x(h,z) = 0.3*h^2+(0.6-0.2*h)*cos(z) + y(h,z) = h+(0.3-0.2*h)*sin(z)+0.05*sin(4*z) + r(h,z) = (x(h,z), y(h,z)) + r1(h,z) = (2,0) .+ r(h,z) + + Nh=30 + heights = range(-1/2, 1/2, Nh) + h0=heights[Nh ÷ 2] + h1=heights[Nh ÷ 2 + 1] + hs = [heights[1], h0, h1, heights[end]] + ts = range(0, 2pi, 300) + + plot(; empty_style..., aspect_ratio=:equal) + + # stack the curves + for h in heights + curve = r.(h, ts) + plot!(Shape(curve); fill=(:white, 1.0), line=(:black, 1)) + end + + # shape pull outs; use black to give thickness + for (h, color) in zip(hs, (:white, :black, :white, :white)) + curve = r1.(h,ts) + plot!(Shape(curve); fill=(color,1.0), line=(:black, 1,)) + end + + # axis with marked points + plot!([(-1,-1), (-1, 1)]; axis_style...) + pts = [(-1, y(h, pi)) for h in hs] + scatter!(pts, marker=(5, :circle)) + + # connect with dashes + for h in hs + plot!([(-1, y(h, pi)), r(h,pi)]; line=(:black, 1, :dash)) + plot!([r(h,0), r1(h,pi)]; line=(:black, 1, :dash)) + end + + + + + + current() + + +end +plt +``` + +This figure shows the volume of a figure being comprised of slices. A discrete approximation would be found by estimating the volume of each slice by the cross sectional area times a small $\Delta h$. +(This figure was ported from @Angenent.) +::: + +```{julia} +#| echo: false +plotly() +nothing +``` ### Cavalieri's method @@ -457,6 +795,47 @@ This gives a general formula for the volume of such cones. [Cavalieri's](http://tinyurl.com/oda9xd9) Principle is "Suppose two regions in three-space (solids) are included between two parallel planes. If every plane parallel to these two planes intersects both regions in cross-sections of equal area, then the two regions have equal volumes." (Wikipedia). +::: {#fig-Cavalieris-first} + +```{julia} +#| echo: false +plt = let + gr() + + x(h,z) = (0.6-0.2*h) * cos(z) + y(h,z) = h + (0.2-0.15*h) * sin(z) + 0.01 * sin(4*z) + xa(h,z) = 2 + 0.1 * cos(7*pi*h) + (0.6-0.2*h)*cos(z) + + + heights = range(-1/2, 1/2, 50) + ts = range(0, 2pi, 300) + h0 = heights[25] + h1 = heights[26] + + plot(; empty_style..., aspect_ratio=:equal) + + for h in heights + curve=[(x(h, t), y(h, t)) for t in ts] + plot!(Shape(curve); fill=(:white,), line=(:black,1)) + + curve=[(xa(h, t), y(h, t)) for t in ts] + plot!(Shape(curve); fill=(:white,), line=(:black,1)) + end + current() +end +plt +``` + +```{julia} +#| echo: false +plotly() +nothing +``` + +Illustration of Cavalieri's first principle. The discs from the left are moved around to form the left volume, but as the volumes of each cross-sectional disc remains the same, the two valumes are equally approximated. (This figure ported from @Angenent.) + +::: + With the formula for the volume of solids based on cross sections, this is a trivial observation, as the functions giving the cross-sectional area are identical. Still, it can be surprising. Consider a sphere with an interior cylinder bored out of it. (The [Napkin](http://tinyurl.com/o237v83) ring problem.) The bore has height $h$ - for larger radius spheres this means very wide bores. diff --git a/quarto/limits/intermediate_value_theorem.qmd b/quarto/limits/intermediate_value_theorem.qmd index ab07071..5562eeb 100644 --- a/quarto/limits/intermediate_value_theorem.qmd +++ b/quarto/limits/intermediate_value_theorem.qmd @@ -32,12 +32,69 @@ If $f$ is continuous on $[a,b]$ with, say, $f(a) < f(b)$, then for any $y$ with ::: +::: {#fig-IVT} ```{julia} #| hold: true #| echo: false #| cache: true ### {{{IVT}}} -gr() +plt = let + gr() + # IVT + empty_style = (xaxis=([], false), + yaxis=([], false), + framestyle=:origin, + legend=false) + axis_style = (arrow=true, side=:head, line=(:gray, 1)) + text_style = (10,) + fn_style = (;line=(:black, 3)) + fn2_style = (;line=(:red, 4)) + mark_style = (;line=(:gray, 1, :dot)) + domain_style = (;fill=(:orange, 0.35), line=nothing) + range_style = (; fill=(:blue, 0.35), line=nothing) + + f(x) = x + sinpi(3x) + 5sin(2x) + 3cospi(2x) + a, b = -1, 5 + xs = range(a, b, 251) + ys = f.(xs) + y0, y1 = extrema(ys) + + plot(; empty_style...) + plot!(f, a, b; fn_style...) + + plot!([a-.2, b + .2],[0,0]; axis_style...) + plot!([a-.1, a-.1], [y0-2, y1+2]; axis_style...) + + plot!([(a,0),(a,f(a))]; line=(:black, 1, :dash)) + plot!([(b,0),(b,f(b))]; line=(:black, 1, :dash)) + + m = f(a/2 + b/2) + plot!([a, b], [m,m]; line=(:black, 1, :dashdot)) + + δx = 0.03 + plot!(Shape([a,b,b,a], 4*δx*[-1,-1,1,1]); + domain_style...) + plot!(Shape((a-.1) .+ 2δx * [-1,1,1,-1], [f(a),f(a),f(b), f(b)]); + range_style...) + plot!(Shape((a-.1) .+ δx/2 * [-1,1,1,-1], [y0,y0,y1,y1]); + range_style...) + + zs = find_zeros(f, (a,b)) + c = zs[2] + plot!([(c,0), (c,f(c))]; line=(:black, 1, :dashdot)) + + annotate!([ + (a, 0, text(L"a", 12, :bottom)), + (b, 0, text(L"b", 12, :top)), + (c, 0, text(L"c", 12, :top)), + (a-.1, f(a), text(L"f(a)", 12, :right)), + (a-.1, f(b), text(L"f(b)", 12, :right)), + (b, m, text(L"y", 12, :left)), + ]) +end +plt + +#= function IVT_graph(n) f(x) = sin(pi*x) + 9x/10 a,b = [0,3] @@ -76,7 +133,17 @@ with $f(x)=y$. plotly() ImageFile(imgfile, caption) +=# ``` +```{julia} +#| echo: false +plotly() +nothing +``` + +Illustration of the intermediate value theorem. The theorem implies that any randomly chosen $y$ value between $f(a)$ and $f(b)$ will have at least one $c$ in $[a,b]$ with $f(c)=y$. This graphic shows one of several possible values for the given choice of $y$. + +::: In the early years of calculus, the intermediate value theorem was intricately connected with the definition of continuity, now it is a consequence. diff --git a/quarto/limits/limits.qmd b/quarto/limits/limits.qmd index 59c24b4..614319d 100644 --- a/quarto/limits/limits.qmd +++ b/quarto/limits/limits.qmd @@ -218,35 +218,64 @@ This bounds the expression $\sin(x)/x$ between $1$ and $\cos(x)$ and as $x$ gets The above bound comes from this figure, for small $x > 0$: +::: {#fig-sin-cos-bound} ```{julia} #| hold: true #| echo: false -gr() -p = plot(x -> sqrt(1 - x^2), 0, 1, legend=false, aspect_ratio=:equal, - linewidth=3, color=:black) -θ = π/6 -y,x = sincos(θ) -col=RGBA(0.0,0.0,1.0, 0.25) -plot!(range(0,x, length=2), zero, fillrange=u->y/x*u, color=col) -plot!(range(x, 1, length=50), zero, fillrange = u -> sqrt(1 - u^2), color=col) -plot!([x,x],[0,y], linestyle=:dash, linewidth=3, color=:black) -plot!([x,1],[y,0], linestyle=:dot, linewidth=3, color=:black) -plot!([1,1], [0,y/x], linewidth=3, color=:black) -plot!([0,1], [0,y/x], linewidth=3, color=:black) -plot!([0,1], [0,0], linewidth=3, color=:black) -Δ = 0.05 -annotate!([(0,0+Δ,"A"), (x-Δ,y+Δ/4, "B"), (1+Δ/2,y/x, "C"), - (1+Δ/2,0+Δ/2,"D")]) -annotate!([(.2*cos(θ/2), 0.2*sin(θ/2), "θ")]) -imgfile = tempname() * ".png" -savefig(p, imgfile) -caption = "Triangle ``ABD`` has less area than the shaded wedge, which has less area than triangle ``ACD``. Their respective areas are ``(1/2)\\sin(\\theta)``, ``(1/2)\\theta``, and ``(1/2)\\tan(\\theta)``. The inequality used to show ``\\sin(x)/x`` is bounded below by ``\\cos(x)`` and above by ``1`` comes from a division by ``(1/2) \\sin(x)`` and taking reciprocals. -" -plotly() -ImageFile(imgfile, caption) +plt = let + gr() + + empty_style = (xaxis=([], false), + yaxis=([], false), + framestyle=:origin, + legend=false) + axis_style = (arrow=true, side=:head, line=(:gray, 1)) + text_style = (10,) + fn_style = (;line=(:black, 3)) + fn2_style = (;line=(:red, 4)) + mark_style = (;line=(:gray, 1, :dot)) + domain_style = (;fill=(:orange, 0.35), line=nothing) + range_style = (; fill=(:blue, 0.35), line=nothing) + + plot(; empty_style..., aspect_ratio=:equal) + plot!(x -> sqrt(1 - x^2), 0, 1; line=(:black, 2)) + + + θ = π/6 + y,x = sincos(θ) + col=RGBA(0.0,0.0,1.0, 0.25) + + plot!(range(0,x, length=2), zero, fillrange=u->y/x*u, color=col) + plot!(range(x, 1, length=50), zero, fillrange = u -> sqrt(1 - u^2), color=col) + + plot!([x,x],[0,y], line=(:dash, 2, :black)) + plot!([x,1],[y,0], line=(:dot, 2, :black)) + plot!([1,1], [0,y/x], line=(2, :black)) + plot!([0,1], [0,y/x], line=(2, :black)) + plot!([0,1], [0,0], line=(2, :black)) + Δ = 0.05 + annotate!([(0,0+Δ, text(L"A", 10)), + (x-Δ,y+Δ/4, text(L"B",10)), + (1+Δ/2,y/x, text(L"C", 10)), + (1+Δ/2,0+Δ/2, text(L"D", 10)), + (0.2*cos(θ/2), 0.2*sin(θ/2), text(L"\theta", 12)) + ]) + current() +end +plt ``` +```{julia} +#| echo: false +plotly() +nothing +``` + +Triangle $\triangle ABD$ has less area than the shaded wedge, which has less area than triangle $\triangle ACD$. Their respective areas are $(1/2)\sin(\theta)$, $(1/2)\theta$, and $(1/2)\tan(\theta)$. The inequality used to show $\sin(x)/x$ is bounded below by $\cos(x)$ and above by $1$ comes from a division by $(1/2) \sin(x)$ and taking reciprocals. + +::: + To discuss the case of $(1+x)^{1/x}$ it proved convenient to assume $x = 1/m$ for integer values of $m$. At the time of Cauchy, log tables were available to identify the approximate value of the limit. Cauchy computed the following value from logarithm tables: diff --git a/quarto/precalc/functions.qmd b/quarto/precalc/functions.qmd index b51b5a2..2df9f4b 100644 --- a/quarto/precalc/functions.qmd +++ b/quarto/precalc/functions.qmd @@ -42,6 +42,101 @@ For these examples, the domain of both $f(x)$ and $g(x)$ is all real values of $ In general the range is harder to identify than the domain, and this is the case for these functions too. For $f(x)$ we may know the $\cos$ function is trapped in $[-1,1]$ and it is intuitively clear than all values in that set are possible. The function $h(x)$ would have range $[0,\infty)$. The $s(x)$ function is either $-1$ or $1$, so only has two possible values in its range. What about $g(x)$? It is a parabola that opens upward, so any $y$ values below the $y$ value of its vertex will not appear in the range. In this case, the symmetry indicates that the vertex will be at $(1/2, -1/4)$, so the range is $[-1/4, \infty)$. +::: {#fig-domain-range layout-nrow=2} +```{julia} +#| echo: false +plt = let + gr() + # domain/range shade + λ = 1.2 + a, b = .1, 3 + f(x) = (x-1/2) + sin((x-1)^2) + + empty_style = (xaxis=([], false), + yaxis=([], false), + framestyle=:origin, + legend=false) + axis_style = (arrow=true, side=:head, line=(:gray, 1)) + text_style = (10,) + fn_style = (;line=(:black, 3)) + fn2_style = (;line=(:red, 4)) + mark_style = (;line=(:gray, 1, :dot)) + + domain_style = (;fill=(:orange, 0.35)) + range_style = (; fill=(:blue, 0.35)) + + xs = range(a,b, 1000) + y0,y1 = extrema(f.(xs)) + Δy = (y1-y0)/60 + Δx = (b - a)/75 + + plot(; aspect_ratio=:equal, empty_style...) + plot!([-.25,3.25],[0,0]; axis_style...) + plot!([0,0], [min(-2Δy, y0 - Δy), y1 + 4Δy]; axis_style... ) + + plot!(f, a, b; fn_style...) + plot!(Shape([a,b,b,a], Δy * [-1,-1,1,1] ); domain_style...) + plot!(Shape(Δx*[-1,1,1,-1], [y0,y0,y1,y1]); range_style...) + + plot!([a,a], [0, f(a)]; mark_style...) + plot!([b,b], [0, f(b)]; mark_style...) + plot!([a, b], [f(a), f(a)]; mark_style...) + plot!([a, b], [f(b), f(b)]; mark_style...) +end +plot +``` + +```{julia} +#| echo: false +plt = let + a, b = 0, 2pi + λ = 1.1 + Δx, Δy = .033, .1 + δx = 0.05 + f(x) = sec(x) + g(x) = abs(f(x)) < 5 ? f(x) : NaN + + empty_style = (xaxis=([], false), + yaxis=([], false), + framestyle=:origin, + legend=false) + axis_style = (arrow=true, side=:head, line=(:gray, 1)) + text_style = (10,) + fn_style = (;line=(:black, 3)) + fn2_style = (;line=(:red, 4)) + mark_style = (;line=(:gray, 1, :dot)) + + domain_style = (;fill=(:orange, 0.35)) + range_style = (; fill=(:blue, 0.35)) + + + plot(; empty_style...) + plot!(g, a+0.1, b; fn_style...) + plot!(λ*[a,b],[0,0]; axis_style...) + plot!([0,0], λ*[-5,5+1/2]; axis_style...) + vline!([pi/2, 3pi/2]; line=(:gray, :dash)) + + plot!(Shape([a,a+pi/2-δx,a+pi/2-δx,a], Δy*[-1,-1,1,1]); domain_style...) + plot!(Shape([a+pi/2+δx, a+pi/2+pi-δx,a+pi/2+pi-δx,a+pi/2+δx], + Δy*[-1,-1,1,1]); domain_style...) + plot!(Shape([3pi/2 + δx, 2pi, 2pi, 3pi/2+δx], + Δy*[-1,-1,1,1]); domain_style...) + plot!(Shape(Δx*[-1,1,1,-1], [-5, -5,-1,-1]); range_style...) + plot!(Shape(Δx*[-1,1,1,-1], [ 5, 5,1,1]); range_style...) +end +plot +``` + + +```{julia} +#| echo: false +plotly() +nothing +``` + +The top figure shows the domain and range of the function as highlighted intervals. The bottom figure shows that the domain may be a collection of intervals. (In this case the $\sec$ function is not defined at $\pi/2 + k \pi$ for integer $k$) and the range may be a collection of intervals. (In this case, the $\sec$ function never have a value in $(-1,1)$. +::: + :::{.callout-note} ## Note @@ -90,7 +185,7 @@ Whereas function definitions and usage in `Julia` mirrors standard math notation ::: -### The domain of a function +### The domain of a function in Julia Functions in `Julia` have an implicit domain, just as they do mathematically. In the case of $f(x)$ and $g(x)$, the right-hand side is defined for all real values of $x$, so the domain is all $x$. For $h(x)$ this isn't the case, of course. Trying to call $h(x)$ when $x < 0$ will give an error: @@ -347,10 +442,27 @@ In our example, we see that in trying to find an answer to $f(x) = 0$ ( $\sqrt{2 ```{julia} #| echo: false -plot(q, a, b, linewidth=5, legend=false) -plot!(zero, a, b) -plot!([a, b], q.([a, b])) -scatter!([c], [q(c)]) +plt = let +gr() +plt = plot(q, a, b, linewidth=5, legend=false) +plot!(plt, zero, a, b) +plot!(plt, [a, b], q.([a, b])) +scatter!(plt, [c], [q(c)]; marker=(:circle,)) +scatter!(plt, [a,b], [q(a), q(b)]; marker=(:square,)) + +annotate!(plt, [ +(a, 0, text(L"a", 10,:top)), +(b, 0, text(L"b", 10, :top)), +(c, 0, text(L"c", 10, :bottom)) +]) +end +plt +``` + +```{julia} +#| echo: false +plotly() +nothing ``` Still, `q(c)` is not really close to $0$: @@ -398,6 +510,7 @@ c = secant_intersection(f, a, b) p = plot(f, a, b, linewidth=5, legend=false) plot!(p, zero, a, b) +scatter!([a,b], [f(a), f(b)]; marker=(:square,)) plot!(p, [a,b], f.([a,b])); scatter!(p, [c], [f(c)]) @@ -918,6 +1031,86 @@ answ = 1 radioq(choices, answ) ``` +##### Question + +::: {#fig-floor-function} + +```{julia} +#| echo: false +plt = let + gr() + empty_style = (xaxis=([], false), + yaxis=([], false), + framestyle=:origin, + legend=false) + axis_style = (arrow=true, side=:head, line=(:gray, 1)) + text_style = (10,) + fn_style = (;line=(:black, 3)) + fn2_style = (;line=(:red, 4)) + mark_style = (;line=(:gray, 1, :dot)) + domain_style = (;fill=(:orange, 0.35), line=nothing) + range_style = (; fill=(:blue, 0.35), line=nothing) + + ts = range(0, 2pi, 100) + xys = sincos.(ts) + xys = [.1 .* xy for xy in xys] + + plot(; empty_style..., aspect_ratio=:equal) + plot!([-4.25,4.25], [0,0]; axis_style...) + plot!([0,0], [-4.25, 4.25]; axis_style...) + + for k in -4:4 + P,Q = (k,k),(k+1,k) + plot!([P,Q], line=(:black,1)) + S = Shape([k .+ xy for xy in xys]) + plot!(S; fill=(:black,)) + S = Shape([(k+1,k) .+ xy for xy in xys]) + plot!(S; fill=(:white,), line=(:black,1)) + end + current() +end +plotly() +plt +``` + +The `floor` function rounds down. For example, any value in $[k,k+1)$ rounds to $k$ for integer $k$. +::: + +The figure shows the `floor` function which is useful in programming. It rounds down to the first integer value. + + +From the graph, what is the domain of the function? + + +```{julia} +#| hold: true +#| echo: false +choices = [ + "The entire real line", + "The entire real line except for integer values", + "The integers" +] +answer = 1 +radioq(choices, answ) +``` + +From the graph, what is the range of the function? + + +```{julia} +#| hold: true +#| echo: false +choices = [ + "The entire real line", + "The entire real line except for integer values", + "The integers" +] +answer = 3 +radioq(choices, answ) +``` + +(This graphic uses the convention that a filled in point is present, but an open point is not, hence each bar represents some $[k, k+1)$.) + ###### Question diff --git a/quarto/precalc/inversefunctions.qmd b/quarto/precalc/inversefunctions.qmd index 324a4da..822b3cf 100644 --- a/quarto/precalc/inversefunctions.qmd +++ b/quarto/precalc/inversefunctions.qmd @@ -30,20 +30,57 @@ Why is this useful? When available, it can help us solve equations. If we can wr Let's explore when we can "solve" for an inverse function. -Consider the graph of the function $f(x) = 2^x$: - +Consider this graph of the function $f(x) = 2^x$ ```{julia} -#| hold: true -f(x) = 2^x -plot(f, 0, 4, legend=false) -plot!([2,2,0], [0,f(2),f(2)]) +#| echo: false +p = let + gr() + empty_style = (xaxis=([], false), + yaxis=([], false), + framestyle=:origin, + legend=false) + axis_style = (arrow=true, side=:head, line=(:gray, 1)) + text_style = (10,) + fn_style = (;line=(:black, 3)) + fn2_style = (;line=(:red, 4)) + mark_style = (;line=(:gray, 1, :dot)) + domain_style = (;fill=(:orange, 0.35), line=nothing) + range_style = (; fill=(:blue, 0.35), line=nothing) + + f(x) = 2^x + a, b = 0, 2 + plot(; empty_style...) + xs = range(a, b, 200) + ys = f.(xs) + + plot!( xs, ys; fn_style...) + plot!([a-1/4, b+.2], [0,0]; axis_style...) + plot!([0, 0], [-.1, f(2.1)]; axis_style...) + + x = 1 + y = (f(b)+f(a))/2 + plot!([x,x,0],[0,f(x),f(x)]; line=(:black, 1, :dash), arrow=true, side=:head) + plot!([0,log2(y), log2(y)], [y,y,0]; line=(:black,1,:dash), arrow=true, side=:head) + + annotate!([ + (x, 0, text(L"c", 10, :top)), + (0,f(x), text(L"f(c)", 10, :right)), + (0, y, text(L"y=f(d)", 10, :right)), + (log2(y), 0, text(L"d", 10, :top)) + + ]) + + +end +plotly() +p ``` The graph of a function is a representation of points $(x,f(x))$, so to *find* $y = f(c)$ from the graph, we begin on the $x$ axis at $c$, move vertically to the graph (the point $(c, f(c))$), and then move horizontally to the $y$ axis, intersecting it at $y = f(c)$. The figure shows this for $c=2$, from which we can read that $f(c)$ is about $4$. This is how an $x$ is associated to a single $y$. -If we were to *reverse* the direction, starting at $y = f(c)$ on the $y$ axis and then moving horizontally to the graph, and then vertically to the $x$-axis we end up at a value $c$ with the correct $f(c)$. This allows solving for $x$ knowing $y$ in $y=f(x)$. +If we were to *reverse* the direction, starting at $y = f(d)$ on the $y$ axis and then moving horizontally to the graph, and then vertically to the $x$-axis we end up at a value $d$ with the correct $f(d)$. This allows solving for $x$ knowing $y$ in $y=f(x)$. The operation described will form a function **if** the initial movement horizontally is guaranteed to find *no more than one* value on the graph. That is, to have an inverse function, there can not be two $x$ values corresponding to a given $y$ value. This observation is often visualized through the "horizontal line test" - the graph of a function with an inverse function can only intersect a horizontal line at most in one place. @@ -309,6 +346,131 @@ What do we see? In blue, we can see the familiar square root graph along with a This is reminiscent of the formula for the slope of a perpendicular line, $-1/m$, but quite different, as this formula implies the two lines have either both positive slopes or both negative slopes, unlike the relationship in slopes between a line and a perpendicular line. +::: {#fig-inverse-normal layout-ncol=1} +```{julia} +#| echo: false +# inverse function slope +gr() +p1 = let + f(x) = x^2 + df(x) = 2x + + empty_style = (xaxis=([], false), + yaxis=([], false), + framestyle=:origin, + legend=false) + axis_style = (arrow=true, side=:head, line=(:gray, 1)) + text_style = (10,) + fn_style = (;line=(:black, 3)) + fn2_style = (;line=(:red, 4)) + mark_style = (;line=(:gray, 1, :dot)) + + plot(; aspect_ratio=:equal, empty_style...) + xs = range(0, 1.25, 100) + plot!(xs,f.(xs); fn_style...) + plot!(f.(xs), xs; fn2_style...) + plot!(identity, -1/4, 2; line=(:gray, 1, :dot)) + + #plot!([-.1, 1.35],[0,0]; axis_style...) + #plot!([0,0], [-0.1, f(1.3)]; axis_style...) + + c = .4 + m = df(c) + tl(x) = f(c) + df(c)*(x-c) + plot!(tl; line=(:black, 1, :dash)) + d = c + .6 + p1, p2, p3 = (c, tl(c)), (d, tl(c)), (d, tl(d)) + q1, q2, q3 = (tl(c),c), (tl(c),d), (tl(d), d) + + plot!([p1, p2, p3]; line=(:black, 1, :dot)) + + + tl1(x) = c + (1/m)*(x - f(c)) + plot!(tl1; line=(:red, 1, :dash)) + plot!([q1, q2, q3]; line=(:red, 1, :dot)) + + annotate!([ + ((c+d)/2, f(c), text(L"\Delta x", 10, :top, :black)), + (d, (tl(c)+tl(d))/2, text(L"\Delta y", 10, :left, :black)), + (f(c), (c+d)/2, text(L"\Delta x", 10, :right, :red)), + ((tl(c)+tl(d))/2, d, text(L"\Delta y", 10, :bottom, :red)), + (d, tl(d), + text(L"rise/run = $m = \Delta y / \Delta x$", 10, :top, :left, + rotation= rad2deg(atan(m)))), + (tl(d), d, + text(L"rise/run = $\Delta x / \Delta y = 1/m$", 10, :bottom, :left, rotation=rad2deg(atan(1/m)))), + (1.9, 1.9, text(L"y=x", 10, :top, rotation=45)) + + + + ]) + current() +end + +# normal line + +p2 = let + f(x) = 4 - (x-2)^2 + df(x) = -2*(x-2) + + empty_style = (xaxis=([], false), + yaxis=([], false), + framestyle=:origin, + legend=false) + axis_style = (arrow=true, side=:head, line=(:gray, 1)) + text_style = (10,) + fn_style = (;line=(:black, 3)) + fn2_style = (;line=(:red, 4)) + mark_style = (;line=(:gray, 1, :dot)) + + plot(; aspect_ratio=:equal, empty_style..., + xlims=(1, 2.5), ylims=(3, 4.5)) + xs = range(.99, 2.01, 100) + plot!(xs,f.(xs); fn_style...) + + c = 1.5 + tl(x) = f(c) + df(c)*(x-c) + nl(x) = f(c) - (x-c)/df(c) + + xs = range(1, 2, 10) + plot!(xs, tl.(xs); fn2_style...) + + xs = range(.9, 2, 10) + plot!(xs, nl.(xs); fn2_style...) + + ylims!((f(.85), nl(.95))) + + o = 1/3 + plot!([c,c+o, c+o], [tl(c), tl(c), tl(c+o)]; mark_style...) + m = (tl(c+o) - tl(c)) + plot!([c,c,c+m], [nl(c),nl(c + m),nl(c+m)]; mark_style...) + + theta = rad2deg(atan(tl(c+o)-tl(c), o)) + annotate!([ + (c + o/2, f(c), text(L"1", :top, 10)), + (c + o, (f(c)+f(c+o))/2, text(L"m", :right, 10)), + (c, (nl(c) + nl(c+m))/2, text(L"-1", :right, 10)), + (c+m/2, nl(c+m), text(L"m", :top, 10)), + (c + o/2, tl(c+o), text(L"rise/run $=m/1$", 10, :top, + rotation=theta)), + (c + 1.1*o, nl(c+1.1*o), text(L"rise/run $=(-1)/m$", 10, :bottom, + rotation=theta-90)) + ]) + current() +end + +plot(p1, p2) +``` + +The inverse function has slope at a corresponding point that is the *reciprocal*; the "normal line" for a function at a point has slope that is the *negative reciprocal* of the "tangent line" at a point. + +::: + +```{julia} +#| echo: false +plotly() +nothing +``` The key here is that the shape of $f(x)$ near $x=c$ is somewhat related to the shape of $f^{-1}(x)$ at $f(c)$. In this case, if we use the tangent line as a fill in for how steep a function is, we see from the relationship that if $f(x)$ is "steep" at $x=c$, then $f^{-1}(x)$ will be "shallow" at $x=f(c)$. diff --git a/quarto/precalc/plotting.qmd b/quarto/precalc/plotting.qmd index c98c2dd..4218c76 100644 --- a/quarto/precalc/plotting.qmd +++ b/quarto/precalc/plotting.qmd @@ -108,11 +108,6 @@ Plotting a function is then this simple: `plot(f, xmin, xmax)`. -:::{.callout-note} -## Note -The time to first plot can feel sluggish, but subsequent plots will be speedy. See the technical note at the end of this section for an explanation. - -::: Let's see some other graphs. @@ -193,7 +188,70 @@ Some types we will encounter, such as the one for symbolic values or the special ::: ---- +:::{.callout-note} +## Viewing window + +The default style for `Plots.jl` is to use a frame style where the viewing window is emphasized. This is a rectangular region, $[x_0, x_1] \times [y_0, y_1]$, which is seen through the tick labeling, the bounding scales on the left and bottom, and emphasized through the grid. + +This choices does *not* show the $x-y$ axes. As such, we might layer on the axes when these are of interest. + +To emphasize concepts, we may stylize a function graph, rather than display the basic graphic. For example, in this graphic highlighting the amount the function goes up as it moves from $1$ to $x$: + +```{julia} +gr() +#| echo: false +let + f(x) = x^2 + + empty_style = (xaxis=([], false), + yaxis=([], false), + framestyle=:origin, + legend=false) + axis_style = (arrow=true, side=:head, line=(:gray, 1)) + text_style = (10,) + fn_style = (;line=(:black, 3)) + fn2_style = (;line=(:red, 4)) + mark_style = (;line=(:gray, 1, :dot)) + + plot(; empty_style..., aspect_ratio=:equal) + a, b = 0, 1.25 + x = 1.15 + + plot!(f, a, b; fn_style...) + plot!([-.1, 1.5], [0,0]; axis_style...) + plot!([0,0], [-.1, f(1.35)]; axis_style...) + + plot!([1,x,x], [f(1),f(1),f(x)]; line=(:black, 1)) + plot!([1,1],[0,f(1)]; mark_style...) + plot!([x,x],[0,f(1)]; mark_style...) + plot!([0,1],[f(1),f(1)]; mark_style...) + plot!([0,x],[f(x),f(x)]; mark_style...) + + annotate!([ + (1, 0, text(L"1", 10, :top)), + (x, 0, text(L"x", 10, :top)), + (0, f(1), text(L"1", 10, :right)), + (0, f(x), text(L"x^2", 10, :right)), + (1, f(1), text(L"P", 10, :right, :bottom)), + (x, f(x), text(L"Q", 10, :right, :bottom)), + ((1 + x)/2, f(1), text(L"\Delta x", 10, :top)), + (x, (f(1) + f(x))/2, text(L"\Delta y", 10, :left)) + ]) + current() +end +``` + +```{julia} +#| echo: false +plotly() +nothing +``` + +::: + + + +---- Making a graph with `Plots` is easy, but producing a graph that is informative can be a challenge, as the choice of a viewing window can make a big difference in what is seen. For example, trying to make a graph of $f(x) = \tan(x)$, as below, will result in a bit of a mess - the chosen viewing window crosses several places where the function blows up: @@ -699,6 +757,40 @@ plot(f, g, 0, max((R-r)/r, r/(R-r))*2pi) In the above, one can fix $R=1$. Then different values for `r` and `rho` will produce different graphs. These graphs will be periodic if $(R-r)/r$ is a rational. (Nothing about these equations requires $\rho < r$.) +## Points, lines, polygons + +Two basic objects to graph are points and lines. + +A point in two-dimensional space has two coordinates, often denoted by $(x,y)$. In `Julia`, the same notation produces a `tuple`. Using square brackets, as in `[x,y]`, produces a vector. Vectors are usually used, as we have seen there are algebraic operations defined for them. However, tuples have other advantages and are how `Plots` designates a point. + +The plot command `plot(xs, ys)` plots the points $(x_1,y_1), \dots, (x_n, y_n)$ and then connects adjacent points with with lines. The command `scatter(xs, ys)` just plots the points. + +However, the points might be more naturally specified as coordinate pairs. If tuples are used to pair them off, then `Plots` will plot a vector of tuples as a sequence of points: + +```{julia} +pts = [(1, 0), (1/4, 1/4), (0, 1), (-1/4, 1/4), (-1, 0), + (-1/4, -1/4), (0, -1), (1/4, -1/4)] +scatter(pts; legend=false) +``` + +A line segment simply connects two points. While these can be specified as vectors of $x$ and $y$ values, again it may be more convenient to use coordinate pairs to specify the points. Continuing the above, we can connect adjacent points with line segments: + +```{julia} +plot!(pts; line=(:gray, 0.5, :dash)) +``` + +This uses the shorthand notation of `Plots` to specify `linecolor=:gray, linealpha=0.5, linestyle=:dash`. To plot just a line segment, just specifying two points suffices. + +The four-pointed star is not closed off, as there isn't a value from the last point to the first point. A polygon closes itself off. The `Shape` function can take a vector of points or a pair of `xs` and `ys` to specify a polygon. When these are plotted, the arguments to `fill` describe the interior of the polygon, the arguments to `line` the boundary: + + +```{julia} +plot(Shape(pts); fill=(:gray, 0.25), line=(:black, 2), legend=false) +scatter!(pts) +``` + + + ## Questions diff --git a/quarto/references.bib b/quarto/references.bib index b096c05..a324c0b 100644 --- a/quarto/references.bib +++ b/quarto/references.bib @@ -18,6 +18,15 @@ +@Misc{Angenent, + key = {WisconsinCalculus}, + author = {Sigurd Angenent}, + title = {Wisconsin Calculus}, + howpublished = {https://github.com/SigurdAngenent/WisconsinCalculus/tree/master}, + year = 2012, + note = {GNU Free Documentation License, Version 1.2} + } + @Book{Schey, author = {H.M. Schey}, title = {Div, Grad, Curl, and all that},