I'm searching for a way to extract all text elements from a matplotlibfigure including their position, style, alignment etc. Calling the findobj(matplotlib.text.Text) method of a figure does that job exactly. However, I get some weird duplicates for all the tick labels and I don't know how to handle them.
For example, use findobj for printing all Text elements of an axis:
import matplotlib
import pylab as p
p.plot([1,2,3])
p.xticks([1],["tick"])
ax = p.gca()
fig = p.gcf()
p.draw()
def print_texts(artist):
for t in artist.findobj(matplotlib.text.Text):
if t.get_visible() and t.get_text():
print " %r # %s" % (t.get_text(), t.get_position())
print "X-Axis Text Elements:"
print_texts(ax.xaxis)
print "Y-Axis Text Elements:"
print_texts(ax.yaxis)
Result:
X-Axis Text Elements:
'tick' # (1.0, 0.0)
'tick' # (0.0, 1.0)
Y-Axis Text Elements:
u'1.0' # (0.0, 1.0)
u'1.0' # (1.0, 0.0)
u'1.5' # (0.0, 1.5)
u'1.5' # (1.0, 0.0)
u'2.0' # (0.0, 2.0)
u'2.0' # (1.0, 0.0)
u'2.5' # (0.0, 2.5)
u'2.5' # (1.0, 0.0)
u'3.0' # (0.0, 3.0)
u'3.0' # (1.0, 0.0)
Note that all tick labels have duplicates positioned at the end of the axis. Why? How to filter them out from a list of Text elements? Their get_visible() attribute is True.
Another thing is that I first have to do call draw() in order to update/generate the ticks. How do I force an update of the tick labels? matplotlib.colorbar.Colorbar seems to have a update_ticks() method, but I can't find something similar for ticks on the axes.
I also tried writing a custum backend and fetch all the texts from the draw_text()
method of the renderer. In contrast to the documentation draw_text() does
not receive a matplotlib.text.Text instance with all the necessary
information but only a simple string and a pre-layouted position.
The answer to this problem was given in the matplotlib mailing list. The Tick object always creates two text labels, one for the left/bottom and one for the right/top. When a Tick artist is drawn its label1On and label2On attributes define which of the two child text labels receive the draw() call. Both of them remain in the visible state however.
So before iterating through all the text elements of a figure, I hide those labels that are not supposed to be seen:
for tick in fig.findobj(matplotlib.axis.Tick):
tick.label1.set_visible(tick.label1On)
tick.label2.set_visible(tick.label2On)
for text in fig.findobj(match=Text, include_self=False):
s = text.get_text()
if not s or not text.get_visible(): continue
# do something with the texts
Related
I am trying to achieve if two objects (let's say two cubes) with their locations and dimensions.
for example we have a function:
def isOverlapped(locationCube1, dimensionCube1, locationCube2, dimensionCube2)
It should return true if they overlapped and false otherwise. The parameters should be a tuple of x, y, z coordination.
For example: we have two cubes:
Cube 1: location = (2, 2, 2) , dimension = (1.0, 5.0, 1.0)
Cube 2: location = (1.0, -1.0, 1.0) , dimension = (2.0, 2.0, 2.0)
So, after I put this in simulation I found out that they overlapped each others.
Now, I am wondering how to program such a thing. Thank you!
The idea is that you check each axis separately. If ANY axis does not overlap, then the objects do not intersect. Assuming the lines are sorted, if the end of line 1 > start of line 2 and start of line 1 < end of line 2, then they overlap.
https://gamedevelopment.tutsplus.com/tutorials/collision-detection-using-the-separating-axis-theorem--gamedev-169
I have 1min 20s long video record of 23.813 FPS. More precisely, I have 1923 frames in which I've been scanning desired features. I've detected some specific behavior via neural network and using chosen metric I calculated a value for each frame.
So, now, I have X-Y values to plot a graph:
X: time (each step of size 0,041993869s)
Y: a value measured by neural network
In the default state, the plot looks like this:
So, I've tried to limit the number of bins in the faith that the bins will be spread over all my values. But they are not. As you can see, only first fifteen x-values are rendered:
pyplot.locator_params(axis='x', nbins=15)
But neither one is desired state. The desired state should render the labels of such x-bins with y-value higher than e.g. 1.2. So, it should look like this:
Is possible to achieve such result?
Code:
# draw plot
from pandas import read_csv
from matplotlib import pyplot
test_video_fps = 23.813
df = read_csv('/path/to/csv/file/file.csv', header=None)
df.columns = ['anomaly']
df['time'] = [round((i + 1) / test_video_fps, 2) for i in range(df.shape[0])]
axes = df.plot.bar(x='time', y='anomaly', rot='0')
# pyplot.locator_params(axis='x', nbins=15)
# axes.get_xaxis().set_visible(False)
fig = pyplot.gcf()
fig.set_size_inches(16, 10)
fig.savefig('/path/to/output/plot.png', dpi=100)
# pyplot.show()
Example:
Simple example with a subset of original data.
0.379799
0.383786
0.345488
0.433286
0.469474
0.431993
0.474253
0.418843
0.491070
0.447778
0.384890
0.410994
0.898229
1.872756
2.907009
3.691382
4.685749
4.599612
3.738768
8.043357
7.660785
2.311198
1.956096
2.877326
3.467511
3.896339
4.250552
6.485533
7.452986
7.103761
2.684189
2.516134
1.512196
1.435303
0.852047
0.842551
0.957888
0.983085
0.990608
1.046679
1.082040
1.119655
0.962391
1.263255
1.371034
1.652812
2.160451
2.646674
1.460051
1.163745
0.938030
0.862976
0.734119
0.567076
0.417270
Desired plot:
Your question has become a two-part problem, but it is interesting enough that I will answer both.
I will answer this in Matplotlib object oriented notation with numpy data rather than pandas. This will make things easier to explain, and can be easily generalized to pandas.
I will assume that you have the following two data arrays:
dt = 0.041993869
x = np.arange(0.0, 15 * dt, dt)
y = np.array([1., 1.1, 1.3, 7.6, 2.4, 0.8, 0.7, 0.8, 1.0, 1.5, 10.0, 4.5, 3.2, 0.9, 0.7])
Part 1: Identifying the locations where you want labels
The data can be masked to get the locations of the peaks:
mask = y > 1.2
Consecutive peaks can be easily eliminated by computing the diff. A diff of a boolean mask will be True at the locations where the mask changes sense. You will then have to take every other element to get the locations where it goes from False to True. The following code will capture all the corner cases where you start with a peak or end in the middle of a peak:
d = np.flatnonzero(np.diff(mask))
if mask[d[0]]: # First diff is end of peak: True to False
d = np.concatenate(([0], d[1::2] + 1))
else:
d = d[::2] + 1
d is now an array indices into x and y that represent the first element of each run of peaks. You can get the last element by swapping the indices [1::2] and [::2] in the if-else statement, and removing the + 1 in both cases.
The locations of the labels are now simply x[d].
Part 2: Locating and formatting the labels
For this part, you will need to access Matplotlib's object oriented API via the Axes object you are plotting on. You already have this in the pandas form, making the transfer easy. Here is a sample in raw Matplotlib:
fig, axes = plt.subplots()
axes.plot(x, y)
Now use the ticker API to easily set the locations and labels. You actually set the locations directly (not with a Locator) since you have a very fixed list of ticks:
axes.set_xticks(x[d])
axes.xaxis.set_major_formatter(ticker.StrMethodFormatter('{x:0.01g}s'))
For the sample data show here, you get
I am trying to plot a histogram in python, and add text on the right upper corner.
here I am creating and plotting the histogram:
sample = stats.poisson.rvs(loc = 0,mu = lamda, size = 10001)
plt.hist(sample)
pd.DataFrame(sample).hist(bins=58,
figsize=(9,9),
edgecolor="k", linewidth=1)
Now, I am trying to plot the mean and median in the right upper corner:
plt.text(0.8, 0.9, s = 'mean = {0}'.format(round(np.mean(sample), 2)))
plt.text(0.8, 0.8, s = 'median = {0}'.format(np.median(sample)))
and here is the screenshot of the output:
As you can see, the x and y values of the text are coordinate values.
How can I pass relative x and y values (to place the text in the upper right corner)?
You need to specify which coordinate system you want to use, otherwise it will automatically use the data coordinate system.
In your case you want to use ax.transax.
plt.text(0.8, 0.9, s = 'mean = {0}'.format(round(np.mean(sample), 2)),transform=ax.transAxes)
plt.text(0.8, 0.8, s = 'median = {0}'.format(np.median(sample)),transform=ax.transAxes)
I suggest you to read this
You can also find an example in the matplotlib text documentation
axes=plt.subplot(111)
axes.invert_xaxis() # not inverted
ts.plot(ax=axes)
# axes.invert_xaxis() # inverted
plt.show()
Why can not it invert the axis before calling pandas.Series.plot, but it can do it after calling? since my program is complicated, the figure is embedded in tkinter, lines on the axes are drawn via pressing buttons, so to invert the axis before calling plot is optimal. What is convenient for it?
I just did some research on matplotlib.plot
when you first define axes and reverse x-axis, it did reverse the x-axis.
axes=plt.subplot(111)
print(axes.get_xlim())
# (0.0, 1.0)
axes.invert_xaxis()
print(axes.get_xlim())
# (1.0, 0.0)
But when you call ts.plot(ax=axes), the function overwrites the x_axis value in object axes with new ts's axis value:
ts = pd.DataFrame([1,2,3])
ts.plot(ax=axes)
print(axes.get_xlim())
#(0.0, 2.0)
what you have done to axes before have been overwritten.
It just likes the example below:
x = [1,2,3]
x[0] = 4
print(x)
#[4, 2, 3]
def func(l):
l[0] = 1
func(x)
print(x)
#[1, 2, 3]
you did do some change on the list, but the func also did the some changes on the list and covered your changes.
I would like to create a graph like this one using gnuplot (or matplotlib, if need be), but I don't know if/how it can be done:
This, of course, is just a rough sketch. What is important is that I need to plot pairs of values (in this example, each pair consists of a red and a blue dot). One item from each pair is a single value, the other one is supposed to show a range of values (my idea was to plot the mean value with error bars to indicate the range's max and min, but I'm open to better ideas). The x-axis has no purpose other than giving the names of the various categories–all that matters are the y-values.
I am pretty sure I could create something like this (value pairs and x-categories) using histograms, but boxes just strike me as wrong in this case.
What I have got so far: I have this gnuplot command:
plot 'TEST.out' using 0:2:3:xticlabel(1) w errorbars pt 7 notitle
Used with this data file (category name, y-value, error bar value):
cat1 15 0
cat1 18 3
cat2 13 0
cat2 10 4
it yields below plot, which goes in the right direction, but which is not yet ideal (all data points have the same colour, and for the single values you can still see that error bars were used; also the grouping isn't very nice–if the two points making up one pair were closer to each other that would make the plot easier on the eye).
If anyone has any suggestions (even for creating a graph that does not look exactly like the example I gave in the beginning). I would be very grateful.
Thanks a lot for your time!
In Matplotlib, you can set ticks individually. The method is explained in one of Matplotlib's examples.
Here is a start:
# Initializations:
from matplotlib import pyplot as pp
import numpy as np
pp.ion() # "Interactive mode on": pyplot.* commands draw immediately
# Data:
series_red_y = [1.3, 1.4, 2.2]
series_blue_y = [1.6, 1.8, 1.8]
series_blue_err = [0.25, 0.25, 0.5]
names = ('Category 1', 'Category 2', 'Category 3')
# Where on the x-axis the data will go:
series_red_x = np.arange(0, 3*len(series_red_y), 3) # Step of 3: red dot, blue dot, empty space
series_blue_x = np.arange(1, 3*len(series_blue_y)+1, 3) # Step of 3: red dot, blue dot, empty space
# Plotting:
pp.scatter(series_red_x, series_red_y, c='r', s=100)
pp.scatter(series_blue_x, series_blue_y, s=100)
pp.errorbar(series_blue_x, series_blue_y, yerr=series_blue_err, fmt=None,
capsize=0)
pp.xticks((series_red_x+series_blue_x)/2., names)
# We wait until the user is ready to close the program:
raw_input('Press enter...')
Here is the result, which you can customize to your specific needs:
Here is a gnuplot solution:
set xrange [-.25:2.75]
set xtics ('cat0' .25, 'cat1' 1.25, 'cat2' 2.25)
plot '< sed 1~2!d TEST.out' using 0:2:(.1) with circles, \
'< sed 1~2d TEST.out' using (.5+$0):2:3 with errorbars
You might need to generate the first two lines of the script if the number of categories is not known beforehand. It should be rather easy, e.g. in bash:
cat_count=$(wc -l < TEST.out)
let cat_count/=2
let cat_count--
echo xrange "[-.25:$cat_count.75]"
echo -n 'set xtics('
for i in $(seq 0 $cat_count) ; do
echo -n "'cat$i'" $i.25
((i==cat_count)) || echo -n ,
done
echo ')'