Skip to content

Commit d45e345

Browse files
committed
Update blog post
1 parent 1942f90 commit d45e345

File tree

6 files changed

+146
-211
lines changed

6 files changed

+146
-211
lines changed

_posts/guides/interactive-blog/2025-02-13-interactive-julia-plotting.md

Lines changed: 29 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,11 @@ which provides a step-by-step guide on how to create interactive plots on a web
2525
backend for the [Makie.jl](https://docs.makie.org/stable/) plotting library.
2626

2727
Aaron's blog post is now 4 years old and the method described there unfortunately no
28-
longer works. I was determined, but since I barely know what HTML is I needed a little help.
29-
I found out yet again how great, supportive and helpful the Julia community is. After
30-
posting on the [Julia Discourse](https://discourse.julialang.org/)
31-
I got [a response from Simon Danisch](https://discourse.julialang.org/t/exporting-figures-to-static-html/125896/16?u=langestefan) who is the creator of Makie.jl.
28+
longer works. I was determined, but I needed a little help, and I found out again how
29+
supportive the Julia community is.
30+
[Simon Danisch](https://discourse.julialang.org/t/exporting-figures-to-static-html/125896/16?u=langestefan)
31+
who is the creator of Makie.jl, provided guidance on how to set things up and this blog
32+
post is a result of that.
3233

3334
{% alert note %}
3435
Besides WGLMakie.jl we will also need <a href="https://github.com/SimonDanisch/Bonito.jl">Bonito.jl</a>
@@ -37,10 +38,6 @@ to create the HTML descriptions, which will enable us to embed the plot in a blo
3738

3839
## A first example
3940

40-
This blog post, which can be read as a tutorial or recipe, is a direct translation from
41-
Simon's response into a working example. I hope it helps you to create interactive plots
42-
for your blog posts.
43-
4441
First, we need a location to store the script that generates our plots. I prefer to
4542
group all files for a specific post in a single folder. For this post, I created a
4643
folder called `_posts/guides/interactive-blog/` and saved the script as `plots.jl`. As
@@ -132,7 +129,8 @@ $\vec{x}(t)$.
132129
There are a bunch of interesting questions we can ask in this general problem context.
133130
A few that come to mind are:
134131
- How does the trajectory of the ball change when we change the initial velocity $\vec{v}_0$?
135-
- Given some initial velocity $\vec{v}_0$, what is the maximum horizontal distance the ball can travel?
132+
- Given some initial velocity $\vec{v}_0$, what is the angle of the kick that maximizes
133+
the horizontal distance the ball will travel?
136134
- Given (noisy) observations of a ball's trajectory, can we estimate what the initial
137135
position $\vec{p}_0$ and velocity $\vec{v}_0$ were? Or even more interesting, can we
138136
predict where the ball will land while it is still in the air?
@@ -172,7 +170,7 @@ $$
172170
\end{equation}
173171
$$
174172

175-
Where $C_L$ is the lift coefficient, $\theta$ is the angle of attack and $f(\theta)$ is a
173+
Where $C_L$ is the lift coefficient and $f(\theta)$ is a
176174
function that depends on the spin angle of the ball. Let's assume that the ball is spinning
177175
around the $z$-axis, then $f(\theta) = [-1, 1, 0]^T$ where we assume that the dependence
178176
on the angular velocity $\omega$ is already included in $C_L$.
@@ -193,15 +191,6 @@ constant $H = \frac{\rho A}{2m}$, which will simplify the equations.
193191
Using the fact that $\frac{dx}{dt} = v_x, \frac{dy}{dt} = v_y, \frac{dz}{dt} = v_z$ the
194192
system of differential equations can then be written as:
195193

196-
<!-- $$
197-
\begin{align}
198-
H &= \frac{\rho A}{2m} \\
199-
\left| \vec{v} \right| &= \sqrt{v_x^2 + v_y^2 + v_z^2}
200-
% C_L &= \left| \vec{v} \right|^{-1} \omega r \\ % C_L &= \frac{\omega r}{\left| \vec{v} \right|}\\
201-
% \omega &= \omega_0 \cdot \exp\left(-\frac{t}{\tau}\right)
202-
\end{align}
203-
$$ -->
204-
205194
$$
206195
\begin{align}
207196
\vec{a} &= -\left | \vec{v}\right | H
@@ -214,20 +203,34 @@ $$
214203
$$
215204

216205
The value of $C_L$ is usually derived from experiments and depends on the speed and
217-
angular velocity of the ball. We will assume that $C_L$ takes the following form:
206+
angular velocity of the ball. We will assume that $C_L$ takes the following simplified
207+
form:
218208

219209
$$
220210
\begin{align}
221211
C_L &= \frac{\omega r}{\left| \vec{v} \right|}\\
222-
\omega &= \omega_0 \cdot \exp\left(-\frac{t}{\tau}\right)
212+
\omega &= \omega_0 \cdot \exp\left(-\frac{t}{7}\right)
223213
\end{align}
224214
$$
225215

226-
Where $\omega_0$ is the initial angular velocity and $\tau$ is the time constant. We
227-
will solve this system numerically to get the trajectory of the ball.
216+
Where $\omega_0$ is the initial angular velocity just after the kick. We will solve this
217+
system numerically to obtain the trajectory of the ball after kicking it.
228218
</div>
229219

230-
Now we can finally start writing code! Which should be a breeze after because we have
231-
access to [DifferentialEquations.jl](https://docs.sciml.ai/DiffEqDocs/stable/).
220+
For those playing along at home, a table with all problem constants is given below:
221+
222+
| Symbol | Description | Value |
223+
|--------|-------------|-------|
224+
| $m$ | mass of the ball | 0.43 kg |
225+
| $g$ | acceleration due to gravity | 9.81 m/s$^2$ |
226+
| $\rho$ | air density | 1.225 kg/m$^3$ |
227+
| $A$ | cross-sectional area of the ball | 0.013 m$^2$ |
228+
| $C_D$ | drag coefficient | 0.2 |
229+
| $r$ | radius of the ball | 0.11 m |
230+
| $\omega_0$ | initial angular velocity | 88 rad/s |
231+
232+
233+
Now we can finally start writing code! Which should be a breeze because we have
234+
access to [DifferentialEquations.jl](https://docs.sciml.ai/DiffEqDocs/stable/),
235+
a package that provides a high-level interface for solving differential equations.
232236

233-
{% include_code file="_posts/guides/interactive-blog/plots.jl" lang="julia" start="30" end="50" %}

_posts/guides/interactive-blog/ode_ic.html

Lines changed: 12 additions & 0 deletions
Large diffs are not rendered by default.

_posts/guides/interactive-blog/plots.jl

Lines changed: 101 additions & 181 deletions
Original file line numberDiff line numberDiff line change
@@ -35,184 +35,104 @@ end
3535

3636
# plot 3 - differential equation
3737

38-
# using DifferentialEquations
39-
40-
# # struct to hold problem constants
41-
# struct SoccerConst
42-
# m::Float64 # mass of the ball
43-
# g::Float64 # acceleration due to gravity
44-
# r::Float64 # radius of the ball
45-
# ρ::Float64 # density of the air
46-
# C_L::Float64 # lift coefficient
47-
# C_D::Float64 # drag coefficient
48-
# end
49-
50-
# # inital conditions
51-
# struct SoccerIC
52-
# x::Float64 # initial x position
53-
# y::Float64 # initial y position
54-
# z::Float64 # initial z position
55-
# v_x::Float64 # initial x velocity
56-
# v_y::Float64 # initial y velocity
57-
# v_z::Float64 # initial z velocity
58-
# end
59-
60-
# open("_posts/examples/diffeqviz/contour.html", "w") do io
61-
# app = App() do
62-
# markersize = Bonito.Slider(range(10, stop=100, length=100))
63-
64-
# # Create a scatter plot
65-
# fig, ax = meshscatter(rand(3, 100), markersize=markersize)
66-
67-
# # Return the plot and the slider
68-
# return Bonito.record_states(session, DOM.div(fig, markersize))
69-
# end;
70-
71-
# as_html(io, session, app)
72-
# end
73-
74-
# # plot 2 - volume plot
75-
# open("_posts/examples/diffeqviz/sinc_surface.html", "w") do io
76-
# # create sub session
77-
# sub = Session(session)
78-
79-
# app = App(volume(rand(10, 10, 10)))
80-
# as_html(io, sub, app)
81-
# end
82-
83-
# open("_posts/examples/diffeqviz/diffeq.html", "w") do io
84-
# # Create a Bonito session
85-
# sub = Session(session)
86-
87-
# # Define the Lorenz system
88-
# function lorenz!(du, u, p, t)
89-
# x, y, z = u
90-
# σ, ρ, β = p
91-
# du[1] = σ * (y - x)
92-
# du[2] = x * (ρ - z) - y
93-
# du[3] = x * y - β * z
94-
# end
95-
96-
# # Time span and initial condition
97-
# tspan = (0.0, 40.0)
98-
# u0 = [1.0, 0.0, 0.0]
99-
100-
# # Default parameters (classic Lorenz with adjustable β)
101-
# σ₀ = 10.0
102-
# ρ₀ = 28.0
103-
# β₀ = 2.666
104-
# p₀ = (σ₀, ρ₀, β₀)
105-
106-
# # Create the initial ODE solution
107-
# prob = ODEProblem(lorenz!, u0, tspan, p₀)
108-
# sol = solve(prob, Tsit5(), reltol=1e-8, abstol=1e-8, saveat=0.05)
109-
110-
# # Create a Makie figure with two rows:
111-
# # Row 1: 3D scene (LScene) with the Lorenz attractor.
112-
# # Row 2: A slider grid controlling the parameters.
113-
# fig = Figure(size = (900, 600))
114-
115-
# # --- Row 1: 3D Plot ---
116-
# ax = LScene(fig[1, 1], show_axis=true)
117-
# lineplot = lines!(ax,
118-
# sol[1, :], sol[2, :], sol[3, :],
119-
# linewidth = 2
120-
# )
121-
122-
# # --- Row 2: Slider Grid ---
123-
# sgrid = SliderGrid(fig[2, 1],
124-
# (label = "σ", range = LinRange(0, 20, 100)),
125-
# (label = "ρ", range = LinRange(0, 50, 100)),
126-
# (label = "β", range = LinRange(0, 10, 100))
127-
# )
128-
# σ_slider, ρ_slider, β_slider = sgrid.sliders
129-
130-
# # Set the slider default values.
131-
# σ_slider.value[] = σ₀
132-
# ρ_slider.value[] = ρ₀
133-
# β_slider.value[] = β₀
134-
135-
# # Function to re-solve the ODE and update the plot.
136-
# function update_plot!()
137-
# # Get the current parameter values from the sliders
138-
# σ = σ_slider.value[]
139-
# ρ = ρ_slider.value[]
140-
# β = β_slider.value[]
141-
# new_p = (σ, ρ, β)
142-
# new_prob = remake(prob, p = new_p)
143-
# new_sol = solve(new_prob, Tsit5(), reltol=1e-8, abstol=1e-8, saveat=0.1)
144-
# # Update the line plot with the new solution
145-
# lineplot[1][] = Point3f0.(new_sol[1, :], new_sol[2, :], new_sol[3, :])
146-
# end
147-
148-
# # Connect each slider’s observable to update_plot!
149-
# for slider in (σ_slider, ρ_slider, β_slider)
150-
# on(slider.value) do _
151-
# update_plot!()
152-
# end
153-
# end
154-
155-
# # --- Bonito App ---
156-
# # Wrap the entire Makie figure (which includes the slider grid)
157-
# # in a recorded DOM container so that slider changes propagate.
158-
# app = App() do
159-
# # By calling `Bonito.record_states` on the container,
160-
# # all reactive states (including slider values) are captured.
161-
# Bonito.record_states(sub, DOM.div(fig))
162-
# end
163-
164-
# # Write the Bonito app to an HTML file.
165-
# as_html(io, sub, app)
166-
# end
167-
168-
169-
170-
# # plot 3 - differential equation
171-
# open("_posts/examples/diffeqviz/diffeq.html", "w") do io
172-
173-
# # function lorenz(du, u, p, t)
174-
# # x, y, z = u
175-
# # sigma, rho, beta = p
176-
# # du[1] = sigma * (y - x)
177-
# # du[2] = x * (rho - z) - y
178-
# # du[3] = x * y - beta * z
179-
# # end
180-
181-
# # t_begin = 0.0
182-
# # t_end = 10.0
183-
# # tspan = (t_begin, t_end)
184-
# # u_begin = [1.0, 0.0, 0.0]
185-
186-
# app = App() do session
187-
188-
# # create a slider
189-
# a = Bonito.Slider(range(1, stop=3, length=3))
190-
191-
# # setup the ODE problem
192-
# # sol = lift(a) do a_val
193-
# # p = [10.0, 28.0, a_val]
194-
# # prob = ODEProblem(lorenz, u_begin, tspan, p)
195-
# # solve(prob, Tsit5(), reltol = 1e-8, abstol = 1e-8)
196-
# # end
197-
198-
# # Create a 3D makie plot with the solution
199-
# # fig = Figure(size=(800, 400))
200-
# # ax = Axis3(fig[1, 1])
201-
# # x = Vector{Float64}(sol[][:][1, :])
202-
# # y = Vector{Float64}(sol[][:][2, :])
203-
# # z = Vector{Float64}(sol[][:][3, :])
204-
205-
# # fig, ax = meshscatter(
206-
# # x,
207-
# # y,
208-
# # z,
209-
# # markersize=1
210-
# # )
211-
212-
# # Create a scatter plot
213-
# fig, ax = meshscatter(rand(3, 100), markersize=markersize)
214-
215-
# # Return the plot and the slider
216-
# return Bonito.record_states(session, DOM.div(fig, a))
217-
# end
218-
# end
38+
struct C
39+
m::Float64 # mass of the ball
40+
g::Float64 # acceleration due to gravity
41+
ρ::Float64 # density of the air
42+
A::Float64 # cross-sectional area of the ball
43+
C_D::Float64 # drag coefficient
44+
r::Float64 # radius of the ball
45+
ω₀::Float64 # initial angular velocity
46+
end
47+
48+
struct IC
49+
x::Float64 # initial x position
50+
y::Float64 # initial y position
51+
z::Float64 # initial z position
52+
v_x::Float64 # initial x velocity
53+
v_y::Float64 # initial y velocity
54+
v_z::Float64 # initial z velocity
55+
end
56+
57+
58+
using DifferentialEquations
59+
60+
C₁ = C(0.43, 9.81, 1.225, 0.013, 0.2, 0.11, 88)
61+
IC₁ = IC(0, 0, 0, 20, 0, 0)
62+
63+
# Define the ODE function. The state vector u = [x, y, z, vx, vy, vz]
64+
function projectile!(ddu, du, u, p, t)
65+
# Unpack state variables
66+
x, y, z = u
67+
vx, vy, vz = du
68+
69+
# Unpack parameters
70+
m, g, ρ, A, C_D, r, ω₀ = p
71+
72+
# Define constant H = (ρ * A) / (2 * m)
73+
H =* A) / (2 * m)
74+
75+
# Compute speed (magnitude of velocity)
76+
v = sqrt(vx^2 + vy^2 + vz^2)
77+
78+
# Compute the decaying angular velocity ω(t)
79+
ω = ω₀ * exp(-t/7)
80+
81+
# Compute lift coefficient, avoiding division by zero.
82+
C_L = (v == 0.0 ? 0.0 :* r / v))
83+
84+
# Compute acceleration components
85+
ddu[1] = -v * H * (C_D * vx + C_L * vy)
86+
ddu[2] = -v * H * (C_D * vy - C_L * vx)
87+
ddu[3] = -v * H * (C_D * vz - g)
88+
end
89+
90+
# Problem constants:
91+
# m: mass (kg), g: gravity (m/s²), rho: air density (kg/m³),
92+
# A: cross-sectional area (m²), C_D: drag coefficient,
93+
# r: ball radius (m), omega0: initial angular velocity (rad/s)
94+
p = [C₁.m, C₁.g, C₁.ρ, C₁.A, C₁.C_D, C₁.r, C₁.ω₀]
95+
96+
# Initial conditions.
97+
# Here we assume the ball is kicked from the origin with an initial velocity.
98+
# For example: initial position (0, 0, 0) and initial velocity (30, 0, 30) m/s.
99+
dx₀ = [IC₁.v_x, IC₁.v_y, IC₁.v_z]
100+
x₀ = [IC₁.x, IC₁.y, IC₁.z]
101+
102+
# Time span for the simulation
103+
tspan = (0.0, 50.0)
104+
105+
# Define the ODE problem
106+
prob = SecondOrderODEProblem(projectile!, dx₀, x₀, tspan, p)
107+
sol = solve(prob, Tsit5())
108+
109+
# load image
110+
pitch = Makie.FileIO.load(expanduser("_posts/guides/interactive-blog/soccer_pitch.png"))
111+
112+
# soccer pitch size
113+
x_size = (0, 68)
114+
y_size = (0, 105)
115+
z_size = (0, 20)
116+
117+
# make a plot for the initial conditions and a field
118+
open(output_folder * "ode_ic.html", "w") do io
119+
sub = Session(session)
120+
app = App() do
121+
fig = Figure(size=(500, 500))
122+
ax = Axis3(fig[1, 1])
123+
p0 = Point3f(x₀)
124+
v0 = Vec3f(dx₀)
125+
126+
xlims!(ax, x_size)
127+
ylims!(ax, y_size)
128+
zlims!(ax, z_size)
129+
arrows!(ax, [p0], [v0], color=:red)
130+
131+
# set picture as xy plane
132+
surface!(ax, [0, 0, 68, 68], [0, 105, 105, 0], [0, 0, 0, 0],
133+
color=:lightgreen, transparency=true)
134+
135+
fig
136+
end
137+
as_html(io, sub, app)
138+
end

_posts/guides/interactive-blog/scatter.html

Lines changed: 2 additions & 2 deletions
Large diffs are not rendered by default.
63 KB
Loading

_posts/guides/interactive-blog/volume.html

Lines changed: 2 additions & 2 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)