How can the line width of the error bar caps in Matplotlib be changed?
I tried the following code:
(_, caplines, _) = matplotlib.pyplot.errorbar(
data['distance'], data['energy'], yerr=data['energy sigma'],
capsize=10, elinewidth=3)
for capline in caplines:
capline.set_linewidth(10)
capline.set_color('red')
pp.draw()
Unfortunately, this updates the color of the caps, but does not update the line width of the caps!
The resulting effect is similar to the "fat error bar lines / thin caps" in the following image:
It would be nice to have "fat" bar caps, in the case; how can this be done, in Matplotlib? Drawing the bar caps "manually", one by one with plot() would work, but a simpler alternative would be best.
EOL, you were very close..,
distance = [1,3,7,9]
energy = [10,20,30,40]
sigma = [1,3,2,5]
(_, caps, _) = plt.errorbar(distance, energy, sigma, capsize=20, elinewidth=3)
for cap in caps:
cap.set_color('red')
cap.set_markeredgewidth(10)
plt.show
set_markeredgewidth sets the width of the cap lines.
Matplotlib objects have so many attributes that often it is difficult to remember the right ones for a given object. IPython is a very useful tool for introspecting matplotlib. I used it to analyze the properties of the 2Dlines correponding to the error cap lines and I found that and other marker properties.
Cheers
This is based on #joaquin's answer, but a little more concise (if you just want plain error caps with no special styling):
distance = [1,3,7,9]
energy = [10,20,30,40]
sigma = [1,3,2,5]
plt.errorbar(distance,
energy,
sigma,
capsize=5,
elinewidth=2,
markeredgewidth=2)
Related
When creating 2d-images with pyplot by using pcolor/pcolormesh I am using custom labels in my project. Unfortunately, some of those labels are too long to fit into their allocated space, and therefore spill over the next label, as shown below for a 2x2-matrix:
and a 4x4-matrix:
I tried to find solutions for that issue, and until now have found two possible approaches: Replacing all spaces with newlines (as proposed by https://stackoverflow.com/a/62521738/2546099), or using textwrap.wrap(), which needs a fixed text width (as proposed by https://stackoverflow.com/a/15740730/2546099 or https://medium.com/dunder-data/automatically-wrap-graph-labels-in-matplotlib-and-seaborn-a48740bc9ce)
I tested both approaches for both test matrices shown above, resulting in (when replacing space with newlines) for the 2x2-matrix:
and for the 4x4-matrix:
Similarly, I tested the second approach for both matrices, the 2x2-matrix, resulting in
and the 4x4-matrix, resulting in
Still, both approaches share the same issues:
When wrapping the label, a part of the label will extend into the image. Setting a fixed pad size will only work if the label size is known beforehand, to adjust the distance properly. Else, some labels might be spaced too far apart from the image, while others are still inside the image.
Wrapping the label with a fixed size might leave too much white space around. The best case might be figure 5 and 6: I used a wrap at 10 characters for both figures, but while the text placement works out nicely (from my point of view) for four columns, there is enough space to increase the wrap limit to 20 characters for two columns. For even more columns 10 characters might even be too much.
Therefore, are there other solutions which might solve my problem dynamically, depending on the size of the figure and the available space?
The code I used for generating the pictures above is:
import textwrap
import numpy as np
import matplotlib.pyplot as plt
imshow_size = 4
x_vec, y_vec = (
np.linspace(0, imshow_size - 1, imshow_size),
np.linspace(0, imshow_size - 1, imshow_size),
)
labels_to_plot = np.linspace(0, imshow_size, imshow_size + 1)
categories = ["This is a very long long label serving its job as place holder"] * (
imshow_size
)
## Idea 1: Replace space with \n, similar to https://stackoverflow.com/a/62521738/2546099
# categories = [category.replace(" ", "\n") for category in categories]
## Idea 2: Wrap labels at certain text length, similar to https://stackoverflow.com/a/15740730/2546099 or
## https://medium.com/dunder-data/automatically-wrap-graph-labels-in-matplotlib-and-seaborn-a48740bc9ce
categories = [textwrap.fill(category, 10) for category in categories]
X, Y = np.meshgrid(x_vec, y_vec)
Z = np.random.rand(imshow_size, imshow_size)
fig, ax = plt.subplots()
ax.pcolormesh(X, Y, Z)
ax.set_xticks(labels_to_plot[:-1])
ax.set_xticklabels(categories, va="center")
ax.set_yticks(labels_to_plot[:-1])
ax.set_yticklabels(categories, rotation=90, va="center")
fig.tight_layout()
plt.savefig("with_wrap_at_10_with_four_labels.png", bbox_inches="tight")
# plt.show()
I'm using Shady to write some text on screen, and I'm wondering what would be the simplest way to control the alignment of the string. From my understanding, the align parameter in a Shady text object controls the paragraph alignment, but I'm interested in controlling the alignment of a single line of text.
Essentially I'd like to replicate the behavior of the horizontalalignment, verticalalignment and rotation parameters of the matplotlib text function. But to do that I need to estimate the area (in pixels) that will be occupied by the string once rendered. Can I get that out of Shady somehow? In the manual it says that the rendering is done on the CPU and the rendered String is then pushed to the GPU, so it should be doable.
You're correct that the .text.align and .text.wrapping properties are to do with "alignment" only at the logical level of the text flow—i.e., how the lines of a multi-line text stimulus are aligned relative to each other in the coordinate-frame in which they're read (independent of which way up the whole stimulus is physically).
The properties you're talking about—rotation, "vertical alignment", and even what you call "horizontal alignment" if there's only one line of text in play—are not text-specific properties: they could apply equally well to any rectangular patch. For this reason, the properties you want to manipulate are stim.* level properties, not stim.text.*. Specifically, they are .anchor and .rotation as demonstrated here:
#!/usr/bin/env python -m Shady shell
import Shady, Shady.Text
w = Shady.World(fullScreenMode=False)
axes = w.Stimulus(Shady.PixelRuler(1000), anchor=Shady.LOWER_LEFT, size=600)
xlabel = w.Stimulus(text='x axis label', x=300, y=0, anchor=Shady.TOP)
ylabel = w.Stimulus(text='y axis label', x=0, y=300, anchor=Shady.BOTTOM, rotation=90)
speed = 30
msg = w.Stimulus(
xy = 300,
rotation = Shady.Integral( lambda t: speed ),
text = 'Change msg.anchor to anything\nbetween [-1,-1] and [+1,+1]\nand see what happens',
text_blockbg = [0, 0, 0, 0.5],
)
Shady.AutoFinish(w)
Somewhere in the undocumented functions of Shady.Text there is probably some way of estimating, in advance, what the size of a rendered text stimulus is going to be. In fact, on closer examination, it looks like the least annoying way to do it would be to actually make the texture array:
img = Shady.Text.MakeTextImage('hello world')
heightInPixels, widthInPixels, _ = img.shape
But hopefully with the appropriate usage of .anchor you should no longer need this.
I am not sure on how to plot a dotted line from a shapefile in Python. It appears that readshapefile() does not have any linestyle for me to set. Below I have a working code where I take a shapefile and plot it, but it only plots a solid line. Any ideas to set me in the right direction? Thanks!
The shapefile can be found here: http://www.natice.noaa.gov/products/daily_products.html, where the Start Date is Feb 15th, end date is Feb 17th, and the Date Types is Ice Edge. It should be the first link.
#!/awips2/python/bin/python
from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt
map = Basemap(llcrnrlon=-84.37,llcrnrlat=42.11,urcrnrlon=-20.93,urcrnrlat=66.48,
resolution='i', projection='tmerc', lat_0 = 55., lon_0 = -50.)
map.drawmapboundary(fill_color='aqua')
map.fillcontinents(color='#ddaa66',lake_color='aqua')
map.drawcoastlines(zorder = 3)
map.readshapefile('nic_autoc2018046n_pl_a', 'IceEdge', zorder = 2, color = 'blue')
plt.show()
From the Basemap documentation:
A tuple (num_shapes, type, min, max) containing shape file info is
returned. num_shapes is the number of shapes, type is the type code
(one of the SHPT* constants defined in the shapelib module, see
http://shapelib.maptools.org/shp_api.html) and min and max are
4-element lists with the minimum and maximum values of the vertices.
If drawbounds=True a matplotlib.patches.LineCollection object is
appended to the tuple.
drawbounds is True by default, so all you have to do is collect the return value of readshapefile and alter the linestyle of the returned LineCollection object, which can be done with LineCollection.set_linestyle(). So in principle you can change the linestyle of your plotted shape file with something like this:
result = m.readshapefile('shapefiles/nic_autoc2018046n_pl_a', 'IceEdge', zorder = 10, color = 'blue')#, drawbounds = False)
col = result[-1]
col.set_linestyle('dotted')
plt.show()
However, your shapefile contains 5429 separate line segments of different length and somehow matplotlib does not seem to be able to deal with this large amount of non-continuous lines. At least on my machine the plotting did not finish within one hour, so I interrupted the process. I played a bit with your file and it seems like many of the lines are broken into segments unnecessarily (I'm guessing this is because the ice sheet outlines are somehow determined on tiles and then pieced together afterwards, but only the providers will really know). Maybe it would help to piece together adjacent pieces, but I'm not sure.
I was also wondering whether the result would even look that great with a dotted line, because there are so many sharp bends. Below I show a picture where I only plot the 100 longest line segments (leaving out drawcoastlines and with thicker lines) using this code:
import numpy as np
result = m.readshapefile('shapefiles/nic_autoc2018046n_pl_a', 'IceEdge', zorder = 10, color = 'blue')#, drawbounds = False)
col = result[-1]
segments = col.get_segments()
seglens = [len(seg) for seg in col.get_segments()]
segments = np.array(segments)
seglens = np.array(seglens)
idx = np.argsort(seglens)
seglens = seglens[idx]
segments = segments[idx]
col.remove()
new_col = LineCollection(segments[-100:],linewidths = 2, linestyles='dotted', colors='b')
ax.add_collection(new_col)
plt.show()
And the result looks like this:
I am using Reportlab to create some graphs in my PDF reports. I was creating an Area Line Plot and got stuck at a point where I am not able to understand why am I not getting the output I would like to see.
Here is the code I had written for my output:
def standardLinePlot(data, width=200, height=200):
d = Drawing(width, height)
lp = AreaLinePlot()
lp.data=data
lp.width, lp.height = width, height
lp.xValueAxis.valueMin = 0
lp.xValueAxis.valueMax =36
lp.xValueAxis.valueSteps = [0,6,12,18,24,30,36]
lp.yValueAxis.valueMin = 0
lp.yValueAxis.valueMax =100
lp.strokeColor=colors.black
lp.fillColor=colors.grey
lp.reversePlotOrder = False
lp.joinedLines=1
d.add(lp)
return d
The output I am getting is:
My intended output is that grey color should be in place of red color which is the area under the line plot. The other problem is how can I add the axis title to this chart. For example, I need “Months” to be my X axis and “% of NAV” to be my Y axis.
To define the color for the lines it seems you need to access... well, the lines :). So, lp.lines[0].strokeColor = colors.grey instead of lp.strokeColor = colors.grey, as that one goes for the plot background color!
The question about the labels is a bit more tricky, though... ScatterPlot includes functionality to set labels for X and Y axis, but that's not the case for AreaLinePlot. Of course, you could derive a class from AreaLinePlot copying that functionality, if you're going to use it often.
Change
lp = AreaLinePlot()
to
lp = LinePlot()
and try that
lp.lines[0].strokeColor = colors.red
lp.lines[0].inFill = True
but the fill color will be the same as the line color.
credit goes to #Ricardo Cárdenes
In matplotlib, what is a way of converting the text box size into data coordinates?
For example, in this toy script I'm fine-tuning the coordinates of the text box so that it's next to a data point.
#!/usr/bin/python
import matplotlib.pyplot as plt
xx=[1,2,3]
yy=[2,3,4]
dy=[0.1,0.2,0.05]
fig=plt.figure()
ax=fig.add_subplot(111)
ax.errorbar(xx,yy,dy,fmt='ro-',ms=6,elinewidth=4)
# HERE: can one get the text bbox size?
txt=ax.text(xx[1]-0.1,yy[1]-0.4,r'$S=0$',fontsize=16)
ax.set_xlim([0.,3.4])
ax.set_ylim([0.,4.4])
plt.show()
Is there a way of doing something like this pseudocode instead?
x = xx[1] - text_height
y = yy[1] - text_width/2
ax.text(x,y,text)
Generally speaking, you can't get the size of the text until after it's drawn (thus the hacks in #DSM's answer).
For what you're wanting to do, you'd be far better off using annotate.
E.g. ax.annotate('Your text string', xy=(x, y), xytext=(x-0.1, y-0.4))
Note that you can specify the offset in points as well, and thus offset the text by it's height (just specify textcoords='offset points')
If you're wanting to adjust vertical alignment, horizontal alignment, etc, just add those as arguments to annotate (e.g. horizontalalignment='right' or equivalently ha='right')
I'm not happy with it at all, but the following works; I was getting frustrated until I found this code for a similar problem, which suggested a way to get at the renderer.
import matplotlib.pyplot as plt
xx=[1,2,3]
yy=[2,3,4]
dy=[0.1,0.2,0.05]
fig=plt.figure()
figname = "out.png"
ax=fig.add_subplot(111)
ax.errorbar(xx,yy,dy,fmt='ro-',ms=6,elinewidth=4)
# start of hack to get renderer
fig.savefig(figname)
renderer = plt.gca().get_renderer_cache()
# end of hack
txt = ax.text(xx[1], yy[1],r'$S=0$',fontsize=16)
tbox = txt.get_window_extent(renderer)
dbox = tbox.transformed(ax.transData.inverted())
text_width = dbox.x1-dbox.x0
text_height = dbox.y1-dbox.y0
x = xx[1] - text_height
y = yy[1] - text_width/2
txt.set_position((x,y))
ax.set_xlim([0.,3.4])
ax.set_ylim([0.,4.4])
fig.savefig(figname)
OTOH, while this might get the text box out of the actual data point, it doesn't necessarily get the box out of the way of the marker, or the error bar. So I don't know how useful it'll be in practice, but I guess it wouldn't be that hard to loop over all the drawn objects and move the text until it's out of the way. I think the linked code tries something similar.
Edit: Please note that this was clearly a courtesy accept; I would use Joe Kington's solution if I actually wanted to do this, and so should everyone else. :^)