matplolib arrow is creating a weird vertical line at the arrow head - python

I am trying to place an axis arrow.
For some reason, when I place an arrow at my plot it also creates a huge vertical line orders of magnitude bigger.
I am instantiating the arrow like this:
#examples of what would be found within x_length, set, y_length, and ax on the anomalous case
x_length=[30000000000.0]
y_length=[[7.7e-09, 1.613e-08]]
set=0
ax=plt.subplot(1,2,1)
#The problematic statement by itself
arrow=ax.arrow(x_length[set], 0, 0.04*x_length[set], 0, shape='full',head_width=max(y_length[set])*0.04,head_length=0.04*x_length[set],length_includes_head=True,color='black', zorder=2)
It works properly when y values are big (let's say "t_values>1"). Although, when the y values are small (let's say "y_values<1e-6"), this problem emerges.
The figures below show a case that what is expected happen and another with the anomalous behavior:
Based on this Figure, I think the lines always is drawn, but only noticed when y values are small
With large values it works as expected
Note: Using the zoom feature, it's possible to verify that the arrow is placed as expected although, this weird line is also placed at the arrow's head.
I have already tried to modify every single parameter, also applying constanst values instead of variables. Although, nothing worked. Moreover, even if a inclined arrow is placed, the unpleasant line is always vertical.

I solved the problem.
This weird line was infinitesimal arrow tail width. So, replacing the arrow method with the width arg included solved the problem. Since the default width is 1e-3, that was the reason why the issue only happened for plot in some orders of magnitude greater then this default width.
ax.arrow(x_max[set], 0, 0.04*x_length[set], 0,shape='full',head_width=y_length[set][i]*0.04,head_length=0.04*x_length[set],length_includes_head=True,color='black', zorder=2,width=max(y_length[set])*0.04)

Related

Label text position in Bokeh

I would like to stick MyText Label to the bottom right part of my figure
for a given text and a given font size (as shown on the picture for 'this is super fun', font size of '20px' and with tiny characters. I found the good position by dichotomy ).
What is the function position I need to pass to x ?
This should depends on len(MyText), text_font_size and figure width ...
from bokeh.models import ColumnDataSource, Label, LabelSet, Range1d
from bokeh.plotting import figure, output_file, show
width,height=400,300
p = figure(plot_width=width, plot_height=height)
MyText='this is super fun'
my_font_size = "20px"
labels = Label(x=width/2+25, y=0,x_units='screen', y_units='screen', text=MyText,text_font_size=my_font_size)
p.add_layout(labels)
show(p)
I don't think there is any 100% robust way to do this, actually.
You can set the text_align to "right" which helps:
p = figure(plot_width=width, plot_height=height)
labels = Label(x=width-50, y=0,
x_units="screen", y_units='screen', text_align="right",
text=MyText,text_font_size=my_font_size)
Note the -50 above is to account (roughly) for the width of the space to the right of the "plot area" (i.e where the toolbar is). However if you add a y-axis on the left side, you'd need to account for that too, and if you allow zooming, then left space can grow and shrink to accommodate bigger or smaller axis labels, which means you can't reliably account for that space with a single constant up front. You could set min_border values to be larger, which might mitigate the problem for some range of zooming/panning.
Also the above assumes the plot sizing mode is not "responsive". If the plot itself can resize then no constant value in screen units will ever work.
If you can fix your x range start/end (or add an "extra" range), then you could right-align to the range end value using "data" units. But if you allow zooming or panning then the label will move to stay fixed at that data position.
The main issue is that the "inner_width" is only computed in the browser. It's not available to the Python code because it doesn't exist outside the browser. What's really needed is some special convention or confguration to designate "inner_width" as a symbolic concept that updates to whatever is necessary, regardless of panning or zooming or resizing. I'd suggest making a GitHub issue to propose this feature.
In the mean time, I think any solution will involve some trial and error with a fixed font size in "px" and also ideally limiting panning/zooming if possible.

Painting numbers used as markers in different colors in matplotlib

I want to represent a dataset of dots, each dot having an x and y coordinate and a single-digit value, and each of the values should be represented by a particular color. What I managed to do is creating sth like that, which looks nice enough:
# x, y and digits are one-dimensional np.arrays of the same shape
plt.scatter(x, y, marker='o', c=digits)
#random colormap
plt.viridis()
Now I want to represent digits themselves instead of just colored dots. As I understand it, marker-argument cannot be an array so I decided that sth like that would work:
for i in range(len(digits)):
plt.scatter(x[i], y[i], c=digits[i], marker=('${}$'.format(digits[i])))
Which almost did, but 'c=digits[i]' doesn't seem to work because a digit doesn't actually encode any particular color. I think (correct me if I'm wrong) that the first code works, because python somehow automatically understands that by c=digits I didn't mean any actual color but wanted to differentiate between two dots.
So the question is:
What is the easiest thing I can do in the second case to indicate the colors of the digits without stating them directly but using a default colormap? (ideally I would like to get sth identical to what first code does, but with digits instead of dots)
It seems I have found a solution, so in case sb has a similar question:
cmap = plt.cm.get_cmap('name_of_demanded_colormap', neededColorsNum).colors
for i in range(len(digits)):
plt.scatter(x[i], y[i], \\
c=np.array([cmap[digits[i]]]), \\
marker=('${}$'.format(digits[i])))
Some "explanations":
- cm is an object needed for gods know what reason;
get_cmap is the method which creates the specified color map, which is unexpectedly not an array but another gods know what object;
colors is the method of color map which finally creates sth to work with, a matrix with rows representing colors;
the code is so natural, jupyter demands to make the color-row two-dimensional, because some misinterpretation possibility.
Whatever complaints, works as intended, so good enough.

Python boxplot fails at automatic plot boundaries/limits

I am manually putting a bunch of boxplots in a plot.
The code I am using is this (I am computing mean_, iqr, CL, etc. elsewhere):
A = np.random.random(2)
D = plt.boxplot(A, positions=np.atleast_1d(dist_val), widths=np.min(unique_dists_vals) / 10.) # a simple case with just one variable to boxplot
D['medians'][0].set_ydata(median_)
D['boxes'][0]._xy[[0,1,4], 1] = iqr[0]
D['boxes'][0]._xy[[2,3],1] = iqr[1]
D['whiskers'][0].set_ydata(np.array([iqr[0], CL[0]]))
D['whiskers'][1].set_ydata(np.array([iqr[1], CL[1]]))
D['caps'][0].set_ydata(np.array([CL[0], CL[0]]))
D['caps'][1].set_ydata(np.array([CL[1], CL[1]]))
I do this in a loop, putting one box plot per some location x.
I am not making any changes to the axis limits. The resulting figure looks like this:
what is going on with 1 x-tick?
the limits are just off on both x and y.
This appears to be a bug?
And no, I cannot just manually set the limits etc. since this has to be a completely general code.
What I have tried so far is:
During the loop when I compute the box plots, try keeping track of the largest y value seen so far and the largest x value etc. and then at the end manually set the bound to this. Other issues come up here, however, such as boxes extending beyond the plot etc. and then I manually have to adjust the limits to extend beyond the box width etc.
I have used both "ax.axis('auto')" and "ax.set_autoscale_on(True)" after plotting right before plt.show(), does not work:
While the first item in the list above does technically work (not ideal) I would like to know if there is a generic way to simply say: "done plotting, fix limits" (should automatically be done while plotting I guess?).
Thank you.

Modifying bar-width and bar-position in matplotlib bar-plot (looping over containers)

I'm trying to adapt the following strategy (taken from here) to adjust the sizes of bars in matplotlib barplots
# Iterate over bars
for container in ax.containers:
# Each bar has a Rectangle element as child
for i,child in enumerate(container.get_children()):
# Reset the lower left point of each bar so that bar is centered
child.set_y(child.get_y()- 0.125 + 0.5-hs[i]/2)
# Attribute height to each Recatangle according to country's size
plt.setp(child, height=hs[i])
but have encountered a strange behaviour when using this on a plot based on a two-columns DataFrame. The relevant part of the code is almost identical:
for container in axes.containers:
for size, child in zip(sizes, container.get_children()):
child.set_x(child.get_x()- 0.50 + 0.5-size/2)
plt.setp(child, width=size)
The effect I get is that the size of the width of the bars (I'm using in in bar-chart; not an hbar) is changed as intended, but that the re-centering is only applied to the bars that correspond to the second column of the DataFrame (I've swapped them to check), which corresponds to the lighter blue in the figure below.
I don't quite see how this could happen, since both changes seem to be applied as part of the same loop. I also find it difficult to trouble-shoot since in my case the outer-loop goes through two containers, and the inner-loop goes through as many children as there are bars (and this for each container).
How could I start troubleshooting this? And how could I find out what I'm actually looping through? (I know each child is a rectangle-object, but this doesn't yet tell me the difference between the rectangles in both containers)
Apparently the following approach works better when modifying vertical bar-plots:
for container in axes.containers:
for i, child in enumerate(container.get_children()):
child.set_x(df.index[i] - sizes[i]/2)
plt.setp(child, width=sizes[i])
So the main difference with the original approach I was adapting is that I do not get the current x_position of the container, but re-use the index of the DataFrame to set the x_position of the container at the index minus half of its new width.

Matplotlib adding overlay labels to an axis

In matplotlib I wish to know the cleanest and most robust means of overlaying labels onto an axis. This is probably best demonstrated with an example:
While normal axis labels/ticks are placed every 5.00 units additional labels without ticks have been overlayed onto the axis (this can be seen at 1113.75 which partially covers 1114.00 and 1105.00 which is covered entirely). The labels also have the same font and size as their normal, ticked, counterparts with the background (if any) going right up to the axis (as a tick mark would).
What is the simplest way of obtaining this effect in matplotlib?
Edit
Following on from #Ken's suggestion I have managed to obtain the effect for an existing tick/label by using ax.yaxis.get_ticklines and ax.yaxis.get_ticklabels to both remove the tick marker and change the background/font/zorder of a label. However, I am unsure how best to add a new tick/label to an axis.
In other words I am looking for a function add_tick(ax.yaxis, loc) that adds a tick at location loc and returns the tickline and ticklabel objects for me to operate on.
I haven't ever tried to do that, but I think that the Artist tutorial might be helpful for you. In particular, the last section has the following code:
for line in ax1.yaxis.get_ticklines():
# line is a Line2D instance
line.set_color('green')
line.set_markersize(25)
line.set_markeredgewidth(3)
I think that using something like line.set_markersize(0) might make the markers have size zero. The difficult part might be finding the ones that need that done. It is possible that the line.xdata or line.ydata arrays might contain enough information to isolate the ones you need. Of course, if you are manually adding the tick marks, it is possible that as you do that the instance gets returned, so you can just modify them as you create them.
The best solution I have been able to devise:
# main: axis; olocs: locations list; ocols: location colours
def overlay_labels(main, olocs, ocols):
# Append the overlay labels as ticks
main.yaxis.set_ticks(np.append(main.yaxis.get_ticklocs(), olocs))
# Perform generic formatting to /all/ ticks
# [...]
labels = reversed(main.yaxis.get_ticklabels())
markers = reversed(main.yaxis.get_ticklines()[1::2]) # RHS ticks only
glines = reversed(main.yaxis.get_gridlines())
rocols = reversed(ocols)
# Suitably format each overlay tick (colours and lines)
for label,marker,grid,colour in izip(labels, markers, glines, rocols):
label.set_color('white')
label.set_backgroundcolor(colour)
marker.set_visible(False)
grid.set_visible(False)
It is not particularly elegant but does appear to work.

Categories