You didn't mention 3D earlier.

Using the same code as before,

**plot3d( sol15, x=0..1, t=0..2 )**

Also somewhat fun,

restart;
L:=1: c:=2: g:=0:
f:=(8*x*(L-x)^2)/L^3:
pde:=diff(u(x,t),t$2)=c^2*diff(u(x,t),x$2):
bc:=u(0,t)=0,u(L,t)=0:
ic:=u(x,0)=f,D[2](u)(x,0)=g:
sol:=pdsolve([pde, ic, bc],u(x,t)):
sol15 := rhs(subs(infinity=15,sol)):
tfinal := 2.0:
bg := plot3d( sol15, x=0..1, t=0..tfinal, transparency=0.5 ):
d23 := (p,T) -> plottools:-transform((X,Y)->[X,T,Y])(p):
mk3 := T -> d23(plot(eval(sol15,t=T), x=0..1,
color=red, thickness=3), T):
plots:-animate(mk3,[T],T=0..tfinal, frames=50,
background=bg, orientation=[-20,70]);

[edited, 17/04/2020] Or, another. I know, I know, this is not constructed efficiently. I do prefer using **plot** with its adaptive plotting instead of **spacecurve**. But still it would be more efficient to build up the surface as row upon row computed inplace on a re-usable Matrix passed to **surfdata**, and the rendered surface would appear more stable visually. Maybe when I get a little time.

restart;
L:=1: c:=2: g:=0:
f:=(8*x*(L-x)^2)/L^3:
pde:=diff(u(x,t),t$2)=c^2*diff(u(x,t),x$2):
bc:=u(0,t)=0,u(L,t)=0:
ic:=u(x,0)=f,D[2](u)(x,0)=g:
sol:=pdsolve([pde, ic, bc],u(x,t)):
sol15 := rhs(subs(infinity=15,sol)):
tfinal := 2.0:
d23 := (p,T) -> plottools:-transform((X,Y)->[X,T,Y])(p):
mk3 := proc(T)
plots:-display(
d23(plot(eval(sol15,t=T), x=0..1,
color=red, thickness=5), T),
plot3d( sol15, x=0..1, t=0..T,
style=surface,
grid=[49,5+ceil(45*tfinal/(max(1,T)))] ));
end proc:
plots:-animate(mk3,[T],T=0..tfinal, frames=100,
orientation=[45,70]);

The following constructs more efficiently (although it could easily be made even more so, by ripping the spacecurve data from Array AA instead of recomputing it, forming AA by layer without the nested `if`, etc). But the animation now gets constructed fast enough. (I could make it construct this animation yet ten or more times faster, but one would hardly notice as it's already quick. But the GUI would just get overwhelmed playing it, if such improvements in the construction speed used to build it with far more resolution or frames.) A more important improvement here is that the surface drawn "so far" doesn't change from frame to frame, even when the wireframe lines are included.

restart;
L:=1: c:=2: g:=0:
f:=(8*x*(L-x)^2)/L^3:
pde:=diff(u(x,t),t$2)=c^2*diff(u(x,t),x$2):
bc:=u(0,t)=0,u(L,t)=0:
ic:=u(x,0)=f,D[2](u)(x,0)=g:
sol:=pdsolve([pde, ic, bc],u(x,t)):
sol15 := rhs(subs(infinity=15,sol)):
(xa,xb,ta,tb):=0,1,0,2:
M,N := 49,85:
d23 := proc(i::posint, N)
plots:-spacecurve(eval([x, t, sol15],t=ta+(tb-ta)*(i-1)/(N-1)),
x=0..1, numpoints=M, color=red, thickness=4):
end proc:
AA:=Array(1..M,1..N,1..3,(i,j,k)->
evalhf(`if`(k=1,xa+(xb-xa)*(i-1)/(M-1),
`if`(k=2,ta+(tb-ta)*(j-1)/(N-1),
eval(sol15,[x=xa+(xb-xa)*(i-1)/(M-1),
t=ta+(tb-ta)*(j-1)/(N-1)])))),
datatype=float[8]):
mk3 := proc(i,N) local temp; uses plots;
temp := d23(trunc(i),N);
if i=1 then temp;
else display(temp,surfdata(AA[..,1..trunc(i),..]));
end if;
end proc:
plots:-animate(mk3, [i,N], i=1..N, frames=N,
orientation=[45,70], paraminfo=false);