I have a pet project to create images of maps, where I draw the roads and other stuff over a contour plot of the terrain elevation. It is intended to plan mountain bike routes (I have made some vectorial drawings by hand, in the past, and they work great for visualization).
Currently, I download Digital Elevation Model, in GeoTIFF, from here:
http://www.ecologia.ufrgs.br/labgeo/arquivos/downloads/dados/SRTM/geotiff/rs.rar
and then create the plot with GDAL and Matplotlib contourf function:
from osgeo import gdal
import matplotlib
import matplotlib.pyplot as plt
from pylab import cm
import numpy
f = 'rs.tif'
elev = gdal.Open(f)
a = elev.GetRasterBand(1).ReadAsArray()
w = elev.RasterXSize
h = elev.RasterYSize
print w, h
altura = (0.35, 0.42)
largura = (0.70, 0.82)
a = a[int(h*altura[0]):int(h*altura[1]),
int(w*largura[0]):int(w*largura[1])]
cont = plt.contourf(a, origin='upper', cmap=cm.gist_earth, levels=numpy.arange(0,1000,20))
plt.title('Altitudes - max: %d m; min: %d m' % (numpy.amax(a), numpy.amin(a)))
plt.show()
Which gives:
The problem is that contour lines are "white", and generate some visual pollution, which is undesired since I want to plot roads and rivers later.
So, I am trying to modify the way contourf create these lighter lines, either via parameter setting, or via hack (changing source code), similar to the one proposed here:
How to format contour lines from Matplotlib
Also, if anyone knows how to generate such a map in a more elegant way, using other libraries, I would appreciate the tip very much!
Thanks for reading.
I finally found a proper solution to this long-standing problem (currently in Matplotlib 3), which does not require multiple calls to contour or rasterizing the figure.
Note that the problem illustrated in the question appears only in saved publication-quality figures formats like PDF, not in lower-quality raster files like PNG.
My solution was inspired by this answer, related to a similar problem with the colorbar. A similar solution turns out to solve the contour plot as well, as follows:
import numpy as np
import matplotlib.pyplot as plt
np.random.seed(123)
x, y = np.random.uniform(size=(100, 2)).T
z = np.exp(-x**2 - y**2)
levels = np.linspace(0, 1, 100)
cnt = plt.tricontourf(x, y, z, levels=levels, cmap="ocean")
# This is the fix for the white lines between contour levels
for c in cnt.collections:
c.set_edgecolor("face")
plt.savefig("test.pdf")
Here below is an example of contours before the fix
And here below is the same figure after the above fix
The solution to the problem, despite not being a real solution, but more a workaround, is simple: Just repeat the same contourf command and this will magically get rid of spurious contours.
As stated by the OP, spurious contours show up when doing contour fill (contourf) with intervals too close to each other. We can reproduce this behavior by setting a very large number of intervals, e.g.:
plt.contourf(plon,plat,ssh,np.arange(-1,1.001,0.001)) # 2001 intervals
This gives us as output:
The thin spurious contours obviously affect the net color of the contour fill.
If you do the command twice:
plt.contourf(plon,plat,ssh,np.arange(-1,1.001,0.001)) # Not once,
plt.contourf(plon,plat,ssh,np.arange(-1,1.001,0.001)) # but twice!
gives me:
Much better now. Here's the finest one, with 3 successive contourf commands:
I can't see any thin contours anymore! Unfortunately, this may slow down your scripts significantly, depending on array size and number of contour intervals. The spurious contours stand out more if more contour intervals are used. What usually works best for me is to use 50 to 100 contour intervals, and do the contourf twice.
Notice that the version of matplotlib I am using is not the latest one. This issue might have been resolved in version 1.1.0. If it has, please let me know.
Python 2.7.1 |EPD 7.0-2 (32-bit)| (r271:86832, Nov 29 2010, 13:52:51)
In [1]: matplotlib.__version__
Out[1]: '1.0.1'
The posted solution did not work for me with alpha set to <1, but I found the answer in this solution: Matplotlib Contourf Plots Unwanted Outlines when Alpha < 1
Adding the argument antialiased=True solved the issue for me.
Try adding the kw argrument to plt.contourf(...) call: either lw=0 or ls=None.
http://matplotlib.sourceforge.net/api/pyplot_api.html#matplotlib.pyplot.contourf
Related
I made a couple of plots before using Python 2.7 and everything is fine. Now I am trying to pick it up in Python 3 as I am trying to visualize some of the data output of the project I'm working on. So I tried to see if this works:
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
# fake data:
a = np.random.normal(size=1000)
b = a*3 + np.random.normal(size=1000)
plt.hist2d(a, b, (50, 50), cmap=plt.cm.jet)
plt.colorbar()
The result is quite confusing for me: it shows the plot but before the plot it also shows the list of value of a and b, as shown in the picture below:
All I need is a clean graph of the plot. So what have I done wrong here? Haven't used matplotlib for a long time so I guess I have made some big mistakes here.
Thanks for your time in advance.
I am not an expert but what happens is that you are getting as results the variables that make your plot. I have run it into Spyder and on the right (variables section) I also get your results. What you need to do however, is to write explicitly "show the plot":
....
plt.colorbar
plt.show()
This will plot automatically your plot in a new window without showing all the arrays. Here some explanation Previous post.
I have made a three way venn diagram. I have three issues with it that I can't seem to solve.
What is the code to move the circle labels (i.e."Set1","Set2","Set3") because right now one is too far away from the circle.
What is the code to make the circles be three equal sizes/change the circle size?
What is the code to move the circles around the plot. Right now, set2 is within set3 (but coloured differently), I would like the diagram to look more like the "standard" way of showing a venn diagram (i.e. 3 separate circles with some overlap in the middle).
On another note, I found it difficult to find what the commands such as "set_x", "set_alpha" should be; if anyone knew of a manual that would answer by above questions I would appreciate it, I couldn't seem to find one place with all the information I needed.
import sys
import numpy
import scipy
from matplotlib_venn import venn3,venn3_circles
from matplotlib import pyplot as plt
#Build three lists to make 3 way venn diagram with
list_line = lambda x: set([line.strip() for line in open(sys.argv[x])])
set1,set2,set3 = list_line(1),list_line(2),list_line(3)
#Make venn diagram
vd = venn3([set1,set2,set3],set_labels=("Set1","Set2","Set3"))
#Colours: get the HTML codes from the net
vd.get_patch_by_id("100").set_color("#FF8000")
vd.get_patch_by_id("001").set_color("#5858FA")
vd.get_patch_by_id("011").set_color("#01DF3A")
#Move the numbers in the circles
vd.get_label_by_id("100").set_x(-0.55)
vd.get_label_by_id("011").set_x(0.1)
#Strength of color, 2.0 is very strong.
vd.get_patch_by_id("100").set_alpha(0.8)
vd.get_patch_by_id("001").set_alpha(0.6)
vd.get_patch_by_id("011").set_alpha(0.8)
plt.title("Venn Diagram",fontsize=14)
plt.savefig("output",format="pdf")
What is the code to move the circle labels (i.e."Set1","Set2","Set3") because right now one is too far away from the circle.
Something like that:
lbl = vd.get_label_by_id("A")
x, y = lbl.get_position()
lbl.set_position((x+0.1, y-0.2)) # Or whatever
The "A", "B", and "C" are predefined identifiers, denoting the three sets.
What is the code to make the circles be three equal sizes/change the circle size?
If you do not want the circle/region sizes to correspond to your data (not necessarily a good idea), you can get an unweighted ("classical") Venn diagram using the function venn3_unweighted:
from matplotlib_venn import venn3_unweighted
venn3_unweighted(...same parameters you used in venn3...)
You can further cheat and tune the result by providing a subset_areas parameter to venn3_unweighted - this is a seven-element vector specifying the desired relative size of each region. In this case the diagram will be drawn as if the region areas were subset_areas, yet the numbers will be shown from the actual subsets. Try, for example:
venn3_unweighted(...., subset_areas=(10,1,1,1,1,1,1))
What is the code to move the circles around the plot.
The need to "move the circles around" is somewhat unusual - normally you would either want the circles to be positioned so that their intersection sizes correspond to your data, or use the "default" positioning. The functions venn3 and venn3_unweighted cater to those two requirements. Moving circles around arbitrarily is possible, but would require some lower-level coding and I'd advice against that.
I found it difficult to find what the commands such as "set_x", "set_alpha" should be
The object you get when you call v.get_label_by_id is a Matplotlib Text object. You can read about its methods and properties here. The object returned by v.get_patch_by_id is a PathPatch, look here and here for reference.
I am looking for a way to set a black border on the errorbars in my plot,
The following code:
ax.errorbar(x, y, yerr, fmt='o', label='label',color="#8da0cb",capthick=2, elinewidth=2,zorder=10)
produces:
I find it more aesthetically pleasing if there was a black border around the errorbar like there is on the marker.
Thanks for any help you can provide
Not a great solution, but you could get close by plotting the errorbars again behind your original ones, with a wider line and cap thinkness, and setting the colour of those ones to black. We can make use of the zorder kwarg to put them behind the others.
Heres a MWE:
import matplotlib.pyplot as plt
import numpy as np
# Fake data
x=np.arange(0,5,1)
y=np.ones(x.shape)
yerr = np.ones(x.shape)/4.
# Create figure
fig,ax = plt.subplots(1)
# Set some limits
ax.set_xlim(-1,5)
ax.set_ylim(-2,4)
# Plot errorbars with the line color you want
ax.errorbar(x,y,yerr, fmt='o',color='r',capthick=2,elinewidth=2,capsize=3,zorder=10)
# Plot black errorbars behind (lower zorder) with a wider line and cap thinkness
ax.errorbar(x,y,yerr, fmt='o',color='k',capthick=4,elinewidth=4,capsize=4,zorder=5)
plt.show()
Again, not a perfect solution, but at least it allows you to include it in the legend. This time, rather than plot the errorbars twice, we will use the matplotlib.patheffects module to add a Stroke to the errorbars.
errorbar returns several Line2D and LineCollection objects, so we need to apply the stroke to each of the relevant ones.
import matplotlib.patheffects as path_effects
e = ax.errorbar(x,y,yerr, fmt='o',color='r',capthick=2,elinewidth=2, label='path effects')
e[1][0].set_path_effects([path_effects.Stroke(linewidth=4, foreground='black'),
path_effects.Normal()])
e[1][1].set_path_effects([path_effects.Stroke(linewidth=4, foreground='black'),
path_effects.Normal()])
e[2][0].set_path_effects([path_effects.Stroke(linewidth=4, foreground='black'),
path_effects.Normal()])
ax.legend(loc=0)
As far as I can see from the information provided in the webpage of pyplot I do not see a valid kwargs that exists for what you are asking.
There exists mfc, mec, ms and mew which are markerfacecolor, markeredgecolor, markersize and markeredgewith. It can probably be asked in GitHub so that people take this into consideration and add it in the next version of matplotlib.
Also taking a look at the answer for this question asked in Stackoverflow, I don't believe it can be done.
How can I flip the origin of a matplotlib plot to be in the upper-left corner - as opposed to the default lower-left? I'm using matplotlib.pylab.plot to produce the plot (though if there is another plotting routine that is more flexible, please let me know).
I'm looking for the equivalent of the matlab command: axis ij;
Also, I've spent a couple hours surfing matplotlib help and google but haven't come up with an answer. Some info on where I could have looked up the answer would be helpful as well.
The easiest way is to use:
plt.gca().invert_yaxis()
After you plotted the image. Origin works only for imshow.
axis ij just makes the y-axis increase downward instead of upward, right? If so, then matplotlib.axes.invert_yaxis() might be all you need -- but I can't test that right now.
If that doesn't work, I found a mailing post suggesting that
setp(gca(), 'ylim', reversed(getp(gca(), 'ylim')))
might do what you want to resemble axis ij.
For an image or contour plot, you can use the keyword origin = None | 'lower' | 'upper' and for a line plot, you can set the ylimits high to low.
from pylab import *
A = arange(25)/25.
A = A.reshape((5,5))
figure()
imshow(A, interpolation='nearest', origin='lower')
figure()
imshow(A, interpolation='nearest')
d = arange(5)
figure()
plot(d)
ylim(5, 0)
show()
The following is a basic way to achieve this
ax=pylab.gca()
ax.set_ylim(ax.get_ylim()[::-1])
This
plt.ylim(max(plt.ylim()), min(plt.ylim()))
has an advantage over this
plt.gca().invert_yaxis()
and is that if you are in interactive mode and you repeatedly plot the same plot (maybe with updated data and having a breakpoint after the plot) the y axis won't keep inverting every time.
How can I flip the origin of a matplotlib plot to be in the upper-left corner - as opposed to the default lower-left? I'm using matplotlib.pylab.plot to produce the plot (though if there is another plotting routine that is more flexible, please let me know).
I'm looking for the equivalent of the matlab command: axis ij;
Also, I've spent a couple hours surfing matplotlib help and google but haven't come up with an answer. Some info on where I could have looked up the answer would be helpful as well.
The easiest way is to use:
plt.gca().invert_yaxis()
After you plotted the image. Origin works only for imshow.
axis ij just makes the y-axis increase downward instead of upward, right? If so, then matplotlib.axes.invert_yaxis() might be all you need -- but I can't test that right now.
If that doesn't work, I found a mailing post suggesting that
setp(gca(), 'ylim', reversed(getp(gca(), 'ylim')))
might do what you want to resemble axis ij.
For an image or contour plot, you can use the keyword origin = None | 'lower' | 'upper' and for a line plot, you can set the ylimits high to low.
from pylab import *
A = arange(25)/25.
A = A.reshape((5,5))
figure()
imshow(A, interpolation='nearest', origin='lower')
figure()
imshow(A, interpolation='nearest')
d = arange(5)
figure()
plot(d)
ylim(5, 0)
show()
The following is a basic way to achieve this
ax=pylab.gca()
ax.set_ylim(ax.get_ylim()[::-1])
This
plt.ylim(max(plt.ylim()), min(plt.ylim()))
has an advantage over this
plt.gca().invert_yaxis()
and is that if you are in interactive mode and you repeatedly plot the same plot (maybe with updated data and having a breakpoint after the plot) the y axis won't keep inverting every time.