Contour plots with varying locations for x-axis?

Hi, I have a set of 2-dimensional data, defined over an array of timesteps, but at each timestep the position x-values are different. I’m trying to get contour plots, but the plots come up blank, and each time I get the error message: Arrays have incorrect length or dimension. An example code model excerpt goes like:

using Plots

tNum = 4; xNum = 3; 
# These numbers will be >1000 in the real calc

FunctionForPlot = Array{Float64,2}(undef, tNum, xNum)
xValsArr = Array{Float64,2}(undef, tNum, xNum)
tValsArr =  Array{Float64}(undef, tNum)

for i in 1:tNum
        tValsArr[i] = i
    for j in 1:xNum
        xValsArr[i,j] = i + j^2
        FunctionForPlot[i,j] = i*sin(35*j)
    end
end

contour([xValsArr[i,j] for i=1:tNum, j=1:xNum],
         [tValsArr[i] for i=1:tNum, j=1:xNum],
        [FunctionForPlot[i,j] for i=1:tNum, j=1:xNum], fill=true)

Any suggestions on how to make this work, or why I’m not getting the plot I expected, would be welcome. Thank you!

To draw the contour plot you must interpolate from your initial data, x, y, z, that define a function f(x[i,j], y[i, j])=z[i,j], the value of f at a regular grid, defined by
xi= 2:0.5:13, yi=1:4. I tried Dierckx.jl, but the Spline2D cannot be called because the flattened x-array contains the repeating value 5. Maybe Interpolations.jl provides an adequate function for 2D interpolation.

Hi empet, thanks for the response!

Thinking about it, it does seem likely that Julia cannot do contour plots (or similar 2D plots) on an irregularly-spaced grid (as defined by irregularly-spaced data), but that the data must somehow be interpolated to a regularly-spaced grid for contour (or similar) plots.

If that’s incorrect, and anyone does know how to extend contour plots to idiosyncratically-spaced data, please let me know…!

Since I was not able to use Dierckx.jl and have no experience with Interpolations.jl (I’m a Julia beginner) I tried with Python scipy.interpolate and your contour plot looks like this:
contour

The steps for getting a regular grid of values Xi, Yi, zi for which the Plots.jl contour could work:

x, y, z are the array passed by you as arguments for contour
xi = 2:0.5:13
yi = 1:0.5:4
fxy = scipy.interpolate.interp2d(x, y, z) #you should find the Interpolations.jl 
                                           #function that works like this scipy.interpolation function
zi = fxy(xi, yi)
Xi = xi' .* ones(length(yi))
Yi = ones(length(xi))'.*yi
contour(Xi, Yi, zi)

The commonly used algorithm (marching squares) requires a regular grid. There are other algorithms for eg triangle meshes, but for the purposes of plotting it is common to just approximate the surface and then compute on a regular grid.

Hi Tamas, thanks for the clarification. I understand what I have to do a little better now.

Empet, thanks for the sample scipy code, I’m sure this will come in handy for interpolating the data, which I plan to do soon.

I think I know how to proceed, thanks again guys!

Hi again, I’m revisiting this attempt, trying to mimic what I want with a 2D scatterplot (with distinct x and y values for each point), where the value of the function for each point is indicated by its color, on a color scale. I’ve searched online and tried doing this with PGFPlotsX and Gadfly, but I can’t find enough info to get the syntax right. Does anyone know how to get a simple example like the following to work:

using Plots
xData = [100.0*rand(Float64) for i in 1:100]
yData = [50.0*rand(Float64) for j in 1:100]
zData = [(xData[i] + yData[j]) for i=1:100, j=1:100]

scatter(xData , yData , color = zData) # This does NOT work, simplest fix??

It would also be nice if I could get each (variously colored) point to be plotted as a filled square (instead of just a point), so it would kind of look like a heatmap or contour plot (with gaps I guess).

Thanks for any suggestions…!

@CosmoProf

I succeeded to define a plot following your requirement, using PlotlyJS.jl:

using PlotlyJS
m=75
n=100

xData = [100.0*rand(Float64) for i in 1:n]
yData = [50.0*rand(Float64) for j in 1:m]
X, Y = [x for y in yData, x in xData], [y for y in yData, x in xData]
zData = X+Y
trace = PlotlyJS.scatter(x=vec(X), y=vec(Y), mode="markers", 
                       marker=attr(symbol="square",  color=vec(zData),
                                   showscale=true, colorbar_thickness=23))
pl = PlotlyJS.plot(trace, Layout(width=500, height=500, title_text="Your title",
        xaxis=attr(range=[minimum(X), maximum(X)], showgrid=false),
        yaxis=attr(range=[minimum(Y), maximum(Y)], showgrid=false)))

scatter2

The above “heatmap” was defined with the default size for the marker.
Setting the marker attribute size=2.5, and the plasma colormap:

plasma=[[0.0, "#0c0786"],
 [0.1, "#40039c"],
 [0.2, "#6a00a7"],
 [0.3, "#8f0da3"],
 [0.4, "#b02a8f"],
 [0.5, "#cb4777"],
 [0.6, "#e06461"],
 [0.7, "#f2844b"],
 [0.8, "#fca635"],
 [0.9, "#fcce25"],
 [1.0, "#eff821"]]

the plot is as follows:
scatter2_5

Your intuition was good, and the heatmap with gaps is very interesting :slight_smile:

If I got you right, the following should work

using Plots
xData = [100.0*rand(Float64) for i in 1:100]
yData = [50.0*rand(Float64) for j in 1:100]
zData = [(xData[i] + yData[j]) for i=1:100, j=1:100]

scatter(xData , yData , marker_z = zData, markershape = :square)

Hello BeastyBlacksmith, I tried your suggestion, but it does not seem to produce a proper 2-dimensional plot. If you replace the zData function with something sinusoidally-varying instead, it varies in the x-direction but not in the y-direction (it should do both). See the following example, which does not produce the fully two-dimensionally varying result expected:

using Plots
xData = [100.0*rand(Float64) for i in 1:1000]
yData = [50.0*rand(Float64) for j in 1:1000]
zData = [(sin(xData[i]/5)*cos(yData[j]/3)) for i=1:1000, j=1:1000]

scatter(xData , yData , marker_z = zData, markershape = :square)

I also tried creating a hybrid solution by putting empet’s suggestions together with Beasty’s, and I’m getting the same problem (no variation in the y-direction):

using Plots
nPix = 50
xData = [(100.0*(i/50)) for i in 1:nPix, j in 1:nPix]
yData = [(50.0*(j/27)) for j in 1:nPix, j in 1:nPix]
zData = [(sin(xData[i]/5)*cos(yData[j]/3)) for i=1:nPix, j=1:nPix]

x2D = vec(xData)
y2D = vec(yData)
z2D = vec(zData)

scatter(x2D, y2D, marker_z = z2D, markershape = :square)

BTW, this is actually more of what I had in mind… but I still can’t get the proper sine/cosine variation in the y-direction, for some reason:

using Plots
nPix = 50
xData = [(100.0*(i/50))*(j^(1/4)) for i in 1:nPix, j in 1:nPix]
yData = [(50.0*(j/27))*(i^(1/4)) for i in 1:nPix, j in 1:nPix]
zData = [(sin(xData[i]/5)*cos(yData[j]/3)) for i=1:nPix, j=1:nPix]

x2D = vec(xData)
y2D = vec(yData)
z2D = vec(zData)

scatter(x2D, y2D, marker_z = z2D, markershape = :square)

Oops, found my mistake…


using Plots
nPix = 50
xData = [(100.0*(i/50)) for i in 1:nPix, j in 1:nPix]
yData = [(50.0*(j/27)) for i in 1:nPix, j in 1:nPix]
zData = [(sin(xData[i,j]/5)*cos(yData[i,j]/3)) for i=1:nPix, j=1:nPix]

x2D = vec(xData)
y2D = vec(yData)
z2D = vec(zData)

scatter(x2D, y2D, marker_z = z2D, markershape = :square)

Even if it was generated by a mistake, the previous one looks great.