How to Plot Step Functions [x] Correctly in Julia?

Hi all,

I have this code:

using Plots, LaTeXStrings
gr()

f(x)=floor(x)
plot(f,0,10,ylim=(-12,12))

Since [ x ] denotes the gretest integer less than or equal to x, I want to plot it like this:
Capture d’écran_2022-07-12_14-04-59

Anyone know how?

One way:

using Plots; gr()

xp = [[i, i+1 - 5*eps()] for i in -12:11]
p = plot(ylim=(-12,12), framestyle = :origin)
for x in xp
    plot!(x, floor.(x), c=:black)
    scatter!(x, floor.(x), ms=3, msc=:black, mc=[:black, :white])
end
p

NB: it would be faster not to loop and plot everything in one go. It would require inserting NaNs at the breaks.

3 Likes

Creating step functions is not as simple as just using floor().

I tried to reproduce the original plot (not the best code, but it has been fun):

f(x) = floor(x)
x = -5:1:4
y = f.(x)
plot(x[1:2], [y[1], y[1]], label=L"f(x) = \lfloor x \rfloor", legend=:bottomleft,legend_foreground_color=:white, legend_font_color=:blue, color=:blue, showaxis=false, tickfontcolor=:white)
# axis and 
hline!((0,0), color=:black, label="")
vline!((0,0), color=:black, label="")
for i in x
	if i!= 0
		annotate!(i,-0.2, (i, 7))
	end
end
for i in y
	if i != 0
		annotate!(-.2, i, (i, 7))
	end
end
annotate!(-.2, -.2, ("0", 7))
for i in 2:length(x)-1
	plot!(x[i:i+1], [y[i], y[i]], label="", color=:blue, lw=2)
	scatter!(x[i], y[i], marker=:circle, markersize=8, label="")
end
scatter!(x[1:end-1], y[1:end-1], markersize=3, markercolor=:blue, label="")
scatter!(x[2:end], y[1:end-1], markersize=3, markercolor=:white, label="")

1 Like

Here is a version without the plot loop using NaN at the breaks:

using Plots; gr()

x = -12:12
xp = [[i, i+1 - 5*eps(), NaN] for i in x[1:end-1]]
xx = collect(Iterators.flatten(xp))
yy = floor.(xx)
plot(xx, yy, c=:black, label=false, framestyle = :origin)
scatter!(xx', yy', msc=:black, mc=[:black :white :white], ms=3)

It is really beautiful,

I want to ask

plot(x[1:2], [y[1], y[1]]

why there are two y[1] ? I want to know the details behind it.

Note that if you want the axes centered at the origin, you should use plot argument: framestyle = :origin. NB: updated in examples above

Thanks for the explanation.

Because the first step goes from point (x[1], y[1]) to (x[2], y[1]). It’s the same for the rest of the steps inside the for loop.

I have cleaned my version a little bit (thanks to @rafael.guerra):

# Plot configuration
plot(legend_title=L"f(x) = \lfloor x \rfloor", legend=:bottomleft,legend_foreground_color=:white, legend_title_font_color=:blue, color=:blue, framestyle=:origin, ticks=x)
# Steps drawing
for i in 1:length(x)-1
	plot!(x[i:i+1], [y[i], y[i]], label="", color=:blue, lw=2)
end
# Points at the beginning ot the steps
scatter!(x[1:end-1], y[1:end-1], markersize=3, markercolor=:blue, label="")
# End points
scatter!(x[2:end], y[1:end-1], markersize=3, markercolor=:white, label="")
# Adds '0' close to origin
annotate!(-.22, -.26, ("0", 8))

I’m sure it could be improved, but I think it’s quite close to the original.

2 Likes

Tried this in Jupyter Notebook and compare with your old code.

It is simpler, so we can remove the old

f(x) = floor(x)
x = -5:1:4
y = f.(x)
plot(x[1:2], [y[1], y[1]], label=L"f(x) = \lfloor x \rfloor", legend=:topleft,legend_foreground_color=:white, legend_font_color=:blue3, color=:blue3, showaxis=false, tickfontcolor=:white)
# axis and 
hline!((0,0), color=:black, label="")
vline!((0,0), color=:black, label="")
for i in x
    if i!= 0
        annotate!(i,-0.2, (i, 7))
    end
end
for i in y
    if i != 0
        annotate!(-.2, i, (i, 7))
    end
end

for i in 2:length(x)-1
    plot!(x[i:i+1], [y[i], y[i]], label="", color=:blue3, lw=2)
    scatter!(x[i], y[i], marker=:circle, markersize=8, label="")
end

Hi @runjaj

I am trying to plot [x + 1/2] do you know what to edit in your code? Thanks

Hi!

One simple option is just changing the function to:

f(x) = floor(x + 1/2)

And the step of the x to 0.5:

x = -5:.5:4

In this case, you’ll get:

floor x+0.5

Is this what you want?

If you want to avoid plotting the central point of each step, the easiest way is set x to:

x = -5.5:1:4.5

floor x+0.5 v2

This is the code I used:

f(x) = floor(x + 1/2)
x = -5.5:1:4.5
y = f.(x)

plot(x[1:2], [y[1], y[1]], label=L"f(x) = \lfloor x + 1/2 \rfloor", legend=:bottomleft,legend_foreground_color=:white,
legend_font_color=:blue, color=:blue, showaxis=false, tickfontcolor=:white)

# axis
hline!((0,0), color=:black, label="")
vline!((0,0), color=:black, label="")

# Scale values
for i in x
	if i!= 0
		annotate!(i,-0.2, (i, 7))
	end
end

for i in y
	if i != 0
		annotate!(-.2, i, (i, 7))
	end
end

annotate!(-.2, -.2, ("0", 7))

# Plot function
for i in 2:length(x)-1
	plot!(x[i:i+1], [y[i], y[i]], label="", color=:blue, lw=2)
end

Javier

1 Like

I actually tried this

using Plots, LaTeXStrings, Plots.PlotMeasures
gr()

f(x) = floor(x + 1/2)
x = -5:.5:4
y = f.(x)
plot(x[1:2], [y[1], y[1]], label=L"f(x) = \lfloor x \rfloor", legend=:topleft,legend_foreground_color=:white, legend_font_color=:blue3, color=:blue3, showaxis=false, tickfontcolor=:white)
# axis and 
hline!((0,0), color=:black, label="")
vline!((0,0), color=:black, label="")
for i in x
    if i!= 0
        annotate!(i,-0.2, (i, 7))
    end
end
for i in y
    if i != 0
        annotate!(-.2, i, (i, 7))
    end
end

for i in 2:length(x)-1
    plot!(x[i:i+1], [y[i], y[i]], label="", color=:blue3, lw=2)
    scatter!(x[i], y[i], marker=:circle, markersize=8, label="")
end

and it returns error:

LoadError: Cannot convert Float64 to series data for plotting

seems the scatter function is problematic for using float or decimal value.

Then I try your code:

using Plots, LaTeXStrings, Plots.PlotMeasures
pyplot()

f(x) = floor(x + 1/2)
x = -5.5:1:4.5
y = f.(x)

plot(x[1:2], [y[1], y[1]], label=L"f(x) = \lfloor x + 1/2 \rfloor", legend=:bottomleft,legend_foreground_color=:white,
legend_font_color=:blue, color=:blue, showaxis=false, tickfontcolor=:white)

# axis
hline!((0,0), color=:black, label="")
vline!((0,0), color=:black, label="")

# Scale values
for i in x
	if i!= 0
		annotate!(i,-0.2, (i, 7))
	end
end

for i in y
	if i != 0
		annotate!(-.2, i, (i, 7))
	end
end

annotate!(-.2, -.2, ("0", 7))

# Plot function
for i in 2:length(x)-1
	plot!(x[i:i+1], [y[i], y[i]], label="", color=:blue, lw=2)
end

the GUI window is not showing up, weird, but the REPL shows it is done…

scatter expects a vector of numbers, you need scatter!([x[i]], [y[i]]) although at that point you might as well just pull it out of the loop and write scatter!(x[2:length(x)-1], y[2:length(x)-1]) (which will also make the plot window show up as per my response in the other thread)

1 Like

I just read your reply more carefully and after lots of trial it works with this code:

using Plots; gr()

f(x) = floor(x + 1/2)
x = -5.5:1:4.5
y = f.(x)
plot(x[1:2], [y[1], y[1]], label=L"f(x) = \lfloor x + 1/2 \rfloor", legend=:bottomleft,legend_foreground_color=:white, legend_font_color=:blue, color=:blue, showaxis=false, tickfontcolor=:white)
# axis and 
hline!((0,0), color=:black, label="")
vline!((0,0), color=:black, label="")
for i in x
	if i!= 0
		annotate!(i,-0.2, (i, 7))
	end
end
for i in y
	if i != 0
		annotate!(-.2, i, (i, 7))
	end
end
annotate!(-.2, -.2, ("0", 7))
for i in 2:length(x)-1
	plot!(x[i:i+1], [y[i], y[i]], label="", color=:blue, lw=2)	
end
scatter!(x[1:end-1], y[1:end-1], markersize=3, markercolor=:blue, label="")
scatter!(x[2:end], y[1:end-1], markersize=3, markercolor=:white, label="")