This solution uses rgl and creates this graph:

It uses this function, which takes 3 arguments:
df : a data.frame exactly the same as your "M" abovex : a numeric vector (or a 1-col data.frame`) for the x axiscols : (optionnal) a vector of colors to repeat. If absent, a black line is drawn
Here is the function:
nik_plot <- function(df, x, cols){ require(rgl) # if a data.frame is if (is.data.frame(x) && ncol(x)==1) x <- as.numeric(x[, 1]) # prepare a vector of colors if (missing(cols)) cols <- rep_len("#000000", nrow(df)) else cols <- rep_len(cols, nrow(df)) # initialize an empty 3D plot plot3d(NA, xlim=range(x), ylim=c(1, ncol(df)-1), zlim=range(df), xlab="Mass/Charge (M/Z)", ylab="Time", zlab="Ion Spectra", box=FALSE) # draw lines, silently silence_please <- sapply(1:ncol(df), function(i) lines3d(x=x, y=i, z=df[, i], col=cols[i])) }
Note that you can remove require(rgl) from the function and library(rgl) somewhere in the script, for example, at the beginning.
If you do not have rgl , then install.packages("rgl") .
Black lines by default can cause a moire effect, but a repeating color palette is worse. It may be brain dependent. One color will also avoid artificial measurement (and strong).
Example below:
# black lines nik_plot(M, x)
The 3D window can be moved with the mouse.
Do you need something else?
EDIT
You can build your second plot using the function below. The range of your data is so large, and I think the whole idea of shifting up and up on each line prevents the presence of the Y axis with a reliable scale. Here I normalized all signals (0 <= signal <= 1). In addition, the gap parameter can be used for this. We could disable two behaviors, but I think it’s nice. Try different gap values and see examples below.
df : a data.frame exactly the same as your "M" abovex : a numeric vector (or a 1-col data.frame`) for the x axiscols : (optionnal) a vector of colors to repeat. If absent, a black line is drawngap : gap coefficient between individual linesmore_gap_each : every n lines get a bigger gap ...more_gap_relative : ... and there will be a gap x more_gap_relative wide
Here is the function:
nik_plot2D <- function(df, x, cols, gap=10, more_gap_each=1, more_gap_relative=0){ if (is.data.frame(x) && ncol(x)==1) x <- as.numeric(x[, 1])
We can use it with (default):
nik_plot2D(M, x)
And you get this plot:

or
nik_plot2D(M, x, 50)

or, with flowers:
nik_plot2D(M, x, gap=20, cols=1:3) nik_plot2D(M, x, gap=20, cols=rep(1:3, each=5))
or, still with flowers, but with big gaps:
nik_plot2D(M, x, gap=20, cols=terrain.colors(10), more_gap_each = 1, more_gap_relative = 0)
