Matplotlib: plt.text with user-defined circle radii - matplotlib

Dear stackoverflow users,
I want to plot some data labels with its coordinates in a x,y-plot. Around the labels I want to put a circle with a user-defined radius as I want to symbolize the magnitude of the data property by the radius of the circle.
An example dataset could look like the following:
point1 = ["label1", 0.5, 0.25, 1e0] # equals [label, x, y, radius]
point2 = ["label2", 0.5, 0.75, 1e1] # equals [label, x, y, radius]
I want to use a code silimar to the following one:
import matplotlib.pyplot as plt
plt.text(point1[1], point1[2], point1[0], bbox = dict(boxstyle="circle")) # here I want to alter the radius by passing point1[3]
plt.text(point2[1], point2[2], point2[0], bbox = dict(boxstyle="circle")) # here I want to alter the radius by passing point2[3]
plt.show()
Is this possible somehow or is the plt.add_patch variant the only possible way?
Regards

In principle, you can use the boxes' pad parameter to define the circle size. However this is then relative to the label. I.e. a small label would have a smaller circle around it for the same value of pad than a larger label. Also the units of pad are fontsize (i.e. if you have a fontsize of 10pt, a padding of 1 would correspond to 10pt).
import numpy as np
import matplotlib.pyplot as plt
points = [["A", 0.2, 0.25, 0], # zero radius
["long label", 0.4, 0.25, 0], # zero radius
["label1", 0.6, 0.25, 1]] # one radius
for point in points:
plt.text(point[1], point[2], point[0], ha="center", va="center",
bbox = dict(boxstyle=f"circle,pad={point[3]}", fc="lightgrey"))
plt.show()
I don't know in how far this is desired.
I guess usually you would rather create a scatterplot at the same positions as the text
import numpy as np
import matplotlib.pyplot as plt
points = [["A", 0.2, 0.25, 100], # 5 pt radius
["long label", 0.4, 0.25, 100], # 5 pt radius
["label1", 0.6, 0.25, 1600]] # 20 pt radius
data = np.array([l[1:] for l in points])
plt.scatter(data[:,0], data[:,1], s=data[:,2], facecolor="gold")
for point in points:
plt.text(point[1], point[2], point[0], ha="center", va="center")
plt.show()

Related

How to avoid overlapping bars in plt.bar when x-values aren't spaced evenly?

I have
x = array([0., 0.08, 0.12, 0.18, 0.27, 0.42, 0.65])
y = array([0., 0.03758546, 0.06577713, 0.48786205, 0.28553257, 0.09909356, 0.02414922])
I then write
plt.ticklabel_format(useOffset=False)
plt.figure(figsize=(20,10))
plt.xlabel('D/Dmax')
plt.bar(x, y), align = 'edge', tick_label = x, color = 'red', edgecolor = "black")
And I get the following chart. Why is it like this, and how can I make the bars not overlap and distinct like every other bar chart?
As your bars don't have a constant width, you can calculate these widths as the difference between the x-values: np.diff(x). Note that there is one less difference than there are elements in x. To get a width for the last bar (which in theory could be infinite), you can either repeat the next-to-last width, or add an extra x-value to set the rightmost boundary.
from matplotlib import pyplot as plt
import numpy as np
x = np.array([0., 0.08, 0.12, 0.18, 0.27, 0.42, 0.65])
y = np.array([0., 0.03758546, 0.06577713, 0.48786205, 0.28553257, 0.09909356, 0.02414922])
widths = np.pad(np.diff(x), (0, 1), 'edge')
plt.figure(figsize=(20, 10))
plt.xlabel('D/Dmax')
plt.bar(x, y, width=widths, align='edge', tick_label=x, color='red', edgecolor="black")
plt.show()
In this case, a logical extension for x could be to include 1:
from matplotlib import pyplot as plt
import numpy as np
x = np.array([0., 0.08, 0.12, 0.18, 0.27, 0.42, 0.65])
x = np.concatenate([x, [1]])
y = np.array([0., 0.03758546, 0.06577713, 0.48786205, 0.28553257, 0.09909356, 0.02414922])
widths = np.diff(x)
plt.figure(figsize=(20, 10))
plt.xlabel('D/Dmax')
plt.bar(x[:-1], y, width=widths, align='edge', color='red', edgecolor="black")
plt.xticks(x)
plt.show()
Your real x-values are much smaller than the default bar width which makes the bars overlap. You need to use a smaller bar width, for ex. 0.02 which is of the order of your smaller x-value.
plt.bar(x, y, align='edge', tick_label=x, color='red', edgecolor="black",
width=0.02)

Compact horizontal guage Matplotlib

How to create a compact horizontal gauge like for example a thermometer for temperature, barometer for pressure using Matplotlib. The scale of the gauge will be split into ranges; each range denoting high-high, high. low and low-low and a pointer reading the value? Is it possible to create such a gauge in matplotlib?
You could use a colorbar.
For example:
import matplotlib.pyplot as plt
import matplotlib as mpl
fig = plt.figure(figsize=(8, 2))
ax = fig.add_axes([0.1, 0.4, 0.8, 0.2])
bounds = [-20, -10, 0, 10, 20]
labels = ('low-low', 'low', 'high', 'high-high')
cmap = mpl.cm.coolwarm
norm = mpl.colors.Normalize(vmin=bounds[0], vmax=bounds[-1])
cb = mpl.colorbar.ColorbarBase(
ax,
cmap=cmap,
norm=norm,
orientation='horizontal',
boundaries=bounds,
label='temperature (degrees celcius)',
)
for i, label in enumerate(labels):
xpos = float((2*i + 1))/(2*len(labels))
ax.annotate(label, xy=(xpos, 0.5), xycoords='axes fraction', ha='center', va='center')
plt.show()
Which produces something like this:
For more info see these examples in the matplotlib docs.

How to hide contour lines / data from a specific area on Basemap

I am working some meteorological data to plot contour lines on a basemap. The full working example code I have done earlier is here How to remove/omit smaller contour lines using matplotlib. All works fine and I don’t complain with the contour plot. However there is a special case that I have to hide all contour lines over a specific region (irregular lat & lon) on a Basemap.
The only possible solution I can think of is to draw a ploygon lines over a desired region and fill with the color of same as Basemap. After lot of search I found this link How to draw rectangles on a Basemap (code below)
from mpl_toolkits.basemap import Basemap
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Polygon
def draw_screen_poly( lats, lons, m):
x, y = m( lons, lats )
xy = zip(x,y)
poly = Polygon( xy, facecolor='red', alpha=0.4 )
plt.gca().add_patch(poly)
lats = [ -30, 30, 30, -30 ]
lons = [ -50, -50, 50, 50 ]
m = Basemap(projection='sinu',lon_0=0)
m.drawcoastlines()
m.drawmapboundary()
draw_screen_poly( lats, lons, m )
plt.show()
It seems to work partially. However, I want to draw a region which is irregular.
Any solution is appreciated.
Edit: 1
I have understood where the problem is. It seems that any colour (facecolor) filled within the polygon region does not make it hide anything below. Always it is transparent only, irrespective of alpha value used or not. To illustrate the problem, I have cropped the image which has all three regions ie. contour, basemap region and polygon region. Polygon region is filled with red colour but as you can see, the contour lines are always visible. The particular line I have used in the above code is :-
poly = Polygon(xy, facecolor='red', edgecolor='b')
Therefore the problem is not with the code above. It seem the problem with the polygon fill. But still no solution for this issue. The resulting image (cropped image) is below (See my 2nd edit below the attached image):-
Edit 2:
Taking clue from this http://matplotlib.1069221.n5.nabble.com/Clipping-a-plot-inside-a-polygon-td41950.html which has the similar requirement of mine, I am able to remove some the data. However, the removed data is only from outside of polygon region instead of within. Here is the code I have taken clue from:-
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import RegularPolygon
data = np.arange(100).reshape(10, 10)
fig = plt.figure()
ax = fig.add_subplot(111)
ax.contourf(data)
poly = RegularPolygon([ 0.5, 0.5], 6, 0.4, fc='none',
ec='k', transform=ax.transAxes)
for artist in ax.get_children():
artist.set_clip_path(poly)
Now my question is that what command is used for removing the data within the polygon region?
Didn't noticed there was a claim on this so I might just give the solution already proposed here. You can tinker with the zorder to hide stuff behind your polygon:
import matplotlib
import matplotlib.mlab as mlab
import matplotlib.pyplot as plt
matplotlib.rcParams['xtick.direction'] = 'out'
matplotlib.rcParams['ytick.direction'] = 'out'
delta = 0.025
x = np.arange(-3.0, 3.0, delta)
y = np.arange(-2.0, 2.0, delta)
X, Y = np.meshgrid(x, y)
Z1 = mlab.bivariate_normal(X, Y, 1.0, 1.0, 0.0, 0.0)
Z2 = mlab.bivariate_normal(X, Y, 1.5, 0.5, 1, 1)
# difference of Gaussians
Z = 10.0 * (Z2 - Z1)
# Create a simple contour plot with labels using default colors. The
# inline argument to clabel will control whether the labels are draw
# over the line segments of the contour, removing the lines beneath
# the label
fig = plt.figure()
ax = fig.add_subplot(111)
CS = plt.contour(X, Y, Z,zorder=3)
plt.clabel(CS, inline=1, fontsize=10)
plt.title('Simplest default with labels')
rect1 = matplotlib.patches.Rectangle((0,0), 2, 1, color='white',zorder=5)
ax.add_patch(rect1)
plt.show()
, the result is:

Logarithmic multi-sequenz plot with equal bar widths

I have something like
import matplotlib.pyplot as plt
import numpy as np
a=[0.05, 0.1, 0.2, 1, 2, 3]
plt.hist((a*2, a*3), bins=[0, 0.1, 1, 10])
plt.gca().set_xscale("symlog", linthreshx=0.1)
plt.show()
which gives me the following plot:
As one can see, the bar width is not equal. In the linear part (from 0 to 0.1), everything is find, but after this, the bar width is still in linear scale, while the axis is in logarithmic scale, giving me uneven widths for bars and spaces in between (the tick is not in the middle of the bars).
Is there any way to correct this?
Inspired by https://stackoverflow.com/a/30555229/635387 I came up with the following solution:
import matplotlib.pyplot as plt
import numpy as np
d=[0.05, 0.1, 0.2, 1, 2, 3]
def LogHistPlot(data, bins):
totalWidth=0.8
colors=("b", "r", "g")
for i, d in enumerate(data):
heights = np.histogram(d, bins)[0]
width=1/len(data)*totalWidth
left=np.array(range(len(heights))) + i*width
plt.bar(left, heights, width, color=colors[i], label=i)
plt.xticks(range(len(bins)), bins)
plt.legend(loc='best')
LogHistPlot((d*2, d*3, d*4), [0, 0.1, 1, 10])
plt.show()
Which produces this plot:
The basic idea is to drop the plt.hist function, compute the histogram by numpy and plot it with plt.bar. Than, you can easily use a linear x-axis, which makes the bar width calculation trivial. Lastly, the ticks are replaced by the bin edges, resulting in the logarithmic scale. And you don't even have to deal with the symlog linear/logarithmic botchery anymore.
You could use histtype='stepfilled' if you are okay with a plot where the data sets are plotted one behind the other. Of course, you'll need to carefully choose colors with alpha values, so that all your data can still be seen...
a = [0.05, 0.1, 0.2, 1, 2, 3] * 2
b = [0.05, 0.05, 0.05, 0.15, 0.15, 2]
colors = [(0.2, 0.2, 0.9, 0.5), (0.9, 0.2, 0.2, 0.5)] # RGBA tuples
plt.hist((a, b), bins=[0, 0.1, 1, 10], histtype='stepfilled', color=colors)
plt.gca().set_xscale("symlog", linthreshx=0.1)
plt.show()
I've changed your data slightly for a better illustration. This gives me:
For some reason the overlap color seems to be going wrong (matplotlib 1.3.1 with Python 3.4.0; Is this a bug?), but it's one possible solution/alternative to your problem.
Okay, I found out the real problem: when you create the histogram with those bin-edge settings, the histogram creates bars which have equal size, and equal outside-spacing on the non-log scale.
To demonstrate, here's a zoomed-in version of the plot in the question, but in non-log scale:
Notice how the first two bars are centered around (0 + 0.1) / 2 = 0.05, with a gap of 0.1 / 10 = 0.01 at the edges, while the next two bars are centered around (0.1 + 1.0) / 2 = 0.55, with a gap of 1.1 / 10 = 0.11 at either edge.
When converting things to log scale, bar widths and edge widths all go for a huge toss. This is compounded further by the fact that you have a linear scale from 0 to 0.1, after which things become log-scale.
I know no way of fixing this, other than to do everything manually. I've used the geometric means of the bin-edges in order to compute what the bar edges and bar widths should be. Note that this piece of code will work only for two datasets. If you have more datasets, you'll need to have some function that fills in the bin-edges with a geometric series appropriately.
import numpy as np
import matplotlib.pyplot as plt
def geometric_means(a):
"""Return pairwise geometric means of adjacent elements."""
return np.sqrt(a[1:] * a[:-1])
a = [0.05, 0.1, 0.2, 1, 2, 3] * 2
b = [0.05, 0.1, 0.2, 1, 2, 3] * 3
# Find frequencies
bins = np.array([0, 0.1, 1, 10])
a_hist = np.histogram(a, bins=bins)[0]
b_hist = np.histogram(b, bins=bins)[0]
# Find log-scale mid-points for bar-edges
mid_vals = np.hstack((np.array([0.05,]), geometric_means(bins[1:])))
# Compute bar left-edges, and bar widths
a_x = np.empty(mid_vals.size * 2)
a_x = bins[:-1]
a_widths = mid_vals - bins[:-1]
b_x = np.empty(mid_vals.size * 2)
b_x = mid_vals
b_widths = bins[1:] - mid_vals
plt.bar(a_x, a_hist, width=a_widths, color='b')
plt.bar(b_x, b_hist, width=b_widths, color='g')
plt.gca().set_xscale("symlog", linthreshx=0.1)
plt.show()
And the final result:
Sorry, but the neat gaps between the bars get killed. Again, this can be fixed by doing the appropriate geometric interpolation, so that everything is linear on log-scale.
Just in case someone stumbles upon this problem:
This solution looks much more like the way it should be
plotting a histogram on a Log scale with Matplotlib

Axis labels on invisible axes in matplotlib

Is it possible to have a visible axis label on an invisble axis? I would like to plot 2 axes that have, apart from their own ylabels, also a common one:
import matplotlib
from matplotlib.pyplot import *
figure()
ax1 = axes([0.3, 0.2, 0.4, 0.2]); ylabel("Label 1")
ax2 = axes([0.3, 0.5, 0.4, 0.2]); ylabel("Label 2")
ax_common = axes([0.2, 0.2, 0.5, 0.5], zorder=-10)
xticks([]); yticks([])
ylabel("Common", fontsize="x-large")
savefig("out.png")
The code above produces this plot:
out.png
Is there a way to remove axis lines? If I add ax_common.set_axis_off(), the axes and the ylabel is removed. Do I have to create a text label instead, without create the additional axes?
Do this:
ax_common.set_frame_on(False)