I'm trying to find a peak of an fft of a signal to be used for a further analysis of the signal. I'm using a SpanSelect of data and doing an fft, represented as a frequency spectrum. I really wanted to have the plot be interactive and the user click a point to be further analyzed, but I don't see a way to do that so would like a way to find local frequency peaks. The frequency spectrum may look like this:
So I would want a way to return the frequency that has a peak at 38 hz for example. Is there a way to do this?
use argrelextrema for finding local maxima:
import numpy as np
from scipy.signal import argrelextrema
from matplotlib.pyplot import *
np.random.seed()
x = np.random.random(50)
m = argrelextrema(x, np.greater) #array of indexes of the locals maxima
y = [x[m] for i in m]
plot(x)
plot(m, y, 'rs')
show()
You can do something like that using matplotlib widgets, for example check out the lasso method of selecting points.
You can then use the selected point in any form of analysis you need.
EDIT: Combined lasso and SpanSelect widget from matplotlib examples
#!/usr/bin/env python
from __future__ import print_function
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import SpanSelector, LassoSelector
from matplotlib.path import Path
import matplotlib.pyplot as plt
try:
raw_input
except NameError:
# Python 3
raw_input = input
class SelectFromCollection(object):
"""Select indices from a matplotlib collection using `LassoSelector`.
Selected indices are saved in the `ind` attribute. This tool highlights
selected points by fading them out (i.e., reducing their alpha values).
If your collection has alpha < 1, this tool will permanently alter them.
Note that this tool selects collection objects based on their *origins*
(i.e., `offsets`).
Parameters
----------
ax : :class:`~matplotlib.axes.Axes`
Axes to interact with.
collection : :class:`matplotlib.collections.Collection` subclass
Collection you want to select from.
alpha_other : 0 <= float <= 1
To highlight a selection, this tool sets all selected points to an
alpha value of 1 and non-selected points to `alpha_other`.
"""
def __init__(self, ax, collection, alpha_other=0.3):
self.canvas = ax.figure.canvas
self.collection = collection
self.alpha_other = alpha_other
self.xys = collection.get_offsets()
self.Npts = len(self.xys)
# Ensure that we have separate colors for each object
self.fc = collection.get_facecolors()
if len(self.fc) == 0:
raise ValueError('Collection must have a facecolor')
elif len(self.fc) == 1:
self.fc = np.tile(self.fc, self.Npts).reshape(self.Npts, -1)
self.lasso = LassoSelector(ax, onselect=self.onselect)
self.ind = []
def onselect(self, verts):
path = Path(verts)
self.ind = np.nonzero([path.contains_point(xy) for xy in self.xys])[0]
self.fc[:, -1] = self.alpha_other
self.fc[self.ind, -1] = 1
self.collection.set_facecolors(self.fc)
self.canvas.draw_idle()
def disconnect(self):
self.lasso.disconnect_events()
self.fc[:, -1] = 1
self.collection.set_facecolors(self.fc)
self.canvas.draw_idle()
def onselect(xmin, xmax):
indmin, indmax = np.searchsorted(x, (xmin, xmax))
indmax = min(len(x)-1, indmax)
thisx = x[indmin:indmax]
thisy = y[indmin:indmax]
line2.set_data(thisx, thisy)
ax2.set_xlim(thisx[0], thisx[-1])
ax2.set_ylim(thisy.min(), thisy.max())
fig.canvas.draw()
if __name__ == '__main__':
plt.ion()
fig = plt.figure(figsize=(8,6))
ax = fig.add_subplot(211, axisbg='#FFFFCC')
x = np.arange(0.0, 5.0, 0.01)
y = np.sin(2*np.pi*x) + 0.5*np.random.randn(len(x))
ax.plot(x, y, '-')
ax.set_ylim(-2,2)
ax.set_title('Press left mouse button and drag to test')
ax2 = fig.add_subplot(212, axisbg='#FFFFCC')
line2, = ax2.plot(x, y, '-')
pts = ax2.scatter(x, y)
# set useblit True on gtkagg for enhanced performance
span = SpanSelector(ax, onselect, 'horizontal', useblit=True,
rectprops=dict(alpha=0.5, facecolor='red') )
selector = SelectFromCollection(ax2, pts)
plt.draw()
raw_input('Press any key to accept selected points')
print("Selected points:")
print(selector.xys[selector.ind])
selector.disconnect()
# Block end of script so you can check that the lasso is disconnected.
raw_input('Press any key to quit')
Related
I am using Multicursor to get a cursor on every graph.
I want to show the value of the datapoint, which is hit by the cursor, inside a legend during hovering over the graphs, like this
Actually I have thought that this is a standard feature of matplotlib respectively Multicursor, but it seems not. Did someone already something like this or do I have to implement it by my own.
I already found this post matplotlib multiple values under cursor, but this could be just the beginning for the implementation I want.
I have developed a solution.
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.widgets import MultiCursor
from bisect import bisect_left
fig = plt.figure(figsize=(15, 8))
# create random graph with 60 datapoints, 0 till 59
x = list(range(0,60))
axes_list = []
def createRandomGraph(ax,x):
y = np.random.randint(low=0, high=15, size=60)
data.append(y)
ax.plot(x,y, marker='.')
def take_closest(myList, myNumber):
"""
Assumes myList is sorted. Returns closest value to myNumber.
If two numbers are equally close, return the smallest number.
"""
pos = bisect_left(myList, myNumber)
if pos == 0:
return myList[0]
if pos == len(myList):
return myList[-1]
before = myList[pos - 1]
after = myList[pos]
if after - myNumber < myNumber - before:
return after, pos
else:
return before, pos-1
def show_Legend(event):
#get mouse coordinates
mouseXdata = event.xdata
# the value of the closest data point to the current mouse position shall be shown
closestXValue, posClosestXvalue = take_closest(data[0], mouseXdata)
i = 1
for ax in axes_list:
datalegend = ax.text(1.05, 0.5, data[i][posClosestXvalue], fontsize=7,
verticalalignment='top', bbox=props, transform=ax.transAxes)
ax.draw_artist(datalegend)
# this remove is required because otherwise after a resizing of the window there is
# an artifact of the last label, which lies behind the new one
datalegend.remove()
i +=1
fig.canvas.update()
# store the x value of the graph in the first element of the list
data = [x]
# properties of the legend labels
props = dict(boxstyle='round', edgecolor='black', facecolor='wheat', alpha=1.5)
for i in range(5):
if(i>0):
# all plots share the same x axes, thus during zooming and panning
# we will see always the same x section of each graph
ax = plt.subplot(5, 1, i+1, sharex=ax)
else:
ax = plt.subplot(5, 1, i+1)
axes_list.append(ax)
createRandomGraph(ax,x)
multi = MultiCursor(fig.canvas, axes_list, color='r', lw=1)
# function show_Legend is called while hovering over the graphs
fig.canvas.mpl_connect('motion_notify_event', show_Legend)
plt.show()
The output looks like this
Maybe you like it and find it useful
I am trying to select points using a lasso tool from a matplotlib scatter plot. A demo is shown here:
https://matplotlib.org/3.1.0/gallery/widgets/lasso_selector_demo_sgskip.html
In the example, all the scatter points have the same color. I have a scatter plot with varying colour, although that should not make a difference. Here is my code:
xvals = [pt[0] for pt in list(positions.values())]
yvals = [pt[1] for pt in list(positions.values())]
nr_colors=10
subplot_kw = dict(xlim=(min(xvals), max(xvals)), ylim=(min(yvals), max(yvals)), autoscale_on=False)
fig,ax = plt.subplots(subplot_kw=subplot_kw)
pts = ax.scatter(x=xvals,y=yvals,c=coloring,s=sizing/15000,cmap=plt.cm.get_cmap("RdYlGn"))
selector = lassoselector.SelectFromCollection(ax, pts)
def accept(event):
if event.key == "enter":
selector.pointslist.append(selector.ind)
print("Appended a cluster!")
elif event.key == "1":
selector.disconnect()
ax.set_title("")
fig.canvas.draw()
plt.close()
print("Analyzing clusters...")
vals=[]
for selected_rows in selector.pointslist:
vals.append(np.mean(whereUsed.T.values[selected_rows],axis=0))
diff = vals[0]-vals[1]
df = pd.DataFrame({"Difference":diff})
df.index = dimension_value_names
df.sort_values(by="Difference",ascending=True).plot(kind="barh",legend=False,title="Cluster 1 - Cluster 2",figsize=(10,15))
fig.canvas.mpl_connect("key_press_event", accept)
ax.set_title("Press enter to accept selected points, press 1 to close")
plt.show()
As you can see the code is quite similar to the example. Only difference is that my pts object contains many colors instead of just one. So, when accessing the colours in the class through:
self.fc = collection.get_facecolors()
I should get my colour list. For some reason, this is not working and self.fc only contains one colour (blue) when I start drawing. Turning the whole image to blue and a lighter shaded blue, as in the example. How can I keep my original colours and just fade out the selection I'm not using?
I think it is a bug.
If you run it in interactive mode it does not change the facecolors to blue. In the sense of first creating the figure with the scatter, and then initialising the selector.
It seams that the facecolors from the collection are not populated until the figure is actually drawn, if the selector is called before the figure is drawn, then the get_facecolors() method will return the default color (blue).
I did a workaround that may not be pretty but it does the trick. Basically I included a optional parameter to the SelectFromCollection class such that instead of getting the facecolors from the collection, it uses the parameter. I included the whole code below for reference.
The main change to the class is the line:
if facecolors is not None: self.fc = facecolors
'''
import numpy as np
from matplotlib.widgets import LassoSelector
from matplotlib.path import Path
class SelectFromCollection(object):
# Select indices from a matplotlib collection using `LassoSelector`.
#
# Selected indices are saved in the `ind` attribute. This tool fades out the
# points that are not part of the selection (i.e., reduces their alpha
# values). If your collection has alpha < 1, this tool will permanently
# alter the alpha values.
#
# Note that this tool selects collection objects based on their *origins*
# (i.e., `offsets`).
#
# Parameters
# ----------
# ax : :class:`~matplotlib.axes.Axes`
# Axes to interact with.
#
# collection : :class:`matplotlib.collections.Collection` subclass
# Collection you want to select from.
#
# alpha_other : 0 <= float <= 1
# To highlight a selection, this tool sets all selected points to an
# alpha value of 1 and non-selected points to `alpha_other`.
#
def __init__(self, ax, collection, alpha_other=0.3, facecolors=None):
self.canvas = ax.figure.canvas
self.collection = collection
self.alpha_other = alpha_other
self.xys = collection.get_offsets()
self.Npts = len(self.xys)
# Ensure that we have separate colors for each object
self.fc = collection.get_facecolors()
if len(self.fc) == 0:
raise ValueError('Collection must have a facecolor')
elif len(self.fc) == 1:
self.fc = np.tile(self.fc, (self.Npts, 1))
if facecolors is not None: self.fc = facecolors
self.lasso = LassoSelector(ax, onselect=self.onselect)
self.ind = []
def onselect(self, verts):
path = Path(verts)
self.ind = np.nonzero(path.contains_points(self.xys))[0]
self.fc[:, -1] = self.alpha_other
self.fc[self.ind, -1] = 1
self.collection.set_facecolors(self.fc)
self.canvas.draw_idle()
def disconnect(self):
self.lasso.disconnect_events()
self.fc[:, -1] = 1
self.collection.set_facecolors(self.fc)
self.canvas.draw_idle()
if __name__ == '__main__':
import matplotlib.pyplot as plt
# Fixing random state for reproducibility
np.random.seed(19680801)
data = np.random.rand(100, 100)
x_2d = np.zeros((100,100))
y_2d = np.zeros((100,100))
for r_ in range(100):
x_2d[r_,:] = np.arange(100)
y_2d[:,r_] = np.arange(100)
fig, ax = plt.subplots()
pts = ax.scatter(x_2d.flatten(), y_2d.flatten(), c=data.flatten(), cmap=plt.cm.jet)
facecolors = plt.cm.jet(data.flatten())
selector = SelectFromCollection(ax, pts, facecolors=facecolors)
def accept(event):
if event.key == "enter":
print("Selected points:")
print(selector.xys[selector.ind])
selector.disconnect()
ax.set_title("")
fig.canvas.draw()
fig.canvas.mpl_connect("key_press_event", accept)
ax.set_title("Press enter to accept selected points.")
plt.show()
'''
I am stuck again with interactive plotting with matplotlib.
Everything else works like a charm (hovering and clicking of objects in a figure) but if I zoom the shown figure and it will be updated, zooming rectangle will remain in the new figure. Probably I have to reset zooming settings somehow but I couldn't find out the correct method to do it from other StackOverflow questions (clearing the figure is not obviously enough).
I built a toy example to illustrate the problem. Four points are attached to four images and they are plotted to the figure. With interactive-mode by inserting cursor on top of chosen point, it shows related image in a imagebox. After one point is clicked, program waits 2 seconds and updates the view by rotating all the samples 15 degrees.
The problem occurs when current view is zoomed and then its updated. Zoom-to-rectangle will start automatically and after clicking once anywhere in the figure, the rectangle is gone without doing anything. This is shown in below image. I just want to have normal cursor after figure is updated.
Here is the code for the toy example:
import matplotlib.pyplot as plt
import matplotlib as mpl
from matplotlib.offsetbox import OffsetImage, AnnotationBbox
import numpy as np
import copy
def initialize_figure(fignum):
plt.figure(fignum)
plt.clf()
def draw_interactive_figures(new_samples, images):
global new_samples_tmp, images_tmp, offset_image_tmp, image_box_tmp, fig_tmp, x_tmp, y_tmp
initialize_figure(1)
plt.ion()
fig_tmp = plt.gcf()
images_tmp = copy.deepcopy(images)
offset_image_tmp = OffsetImage(images_tmp[0,:,:,:])
image_box_tmp = (40., 40.)
x_tmp = new_samples[:,0]
y_tmp = new_samples[:,1]
new_samples_tmp = copy.deepcopy(new_samples)
update_plot()
fig_tmp.canvas.mpl_connect('motion_notify_event', hover)
fig_tmp.canvas.mpl_connect('button_press_event', click)
plt.show()
fig_tmp.canvas.start_event_loop()
plt.ioff()
def update_plot():
global points_tmp, annotationbox_tmp
ax = plt.gca()
points_tmp = plt.scatter(*new_samples_tmp.T, s=14, c='b', edgecolor='k')
annotationbox_tmp = AnnotationBbox(offset_image_tmp, (0,0), xybox=image_box_tmp, xycoords='data', boxcoords='offset points', pad=0.3, arrowprops=dict(arrowstyle='->'))
ax.add_artist(annotationbox_tmp)
annotationbox_tmp.set_visible(False)
def hover(event):
if points_tmp.contains(event)[0]:
inds = points_tmp.contains(event)[1]['ind']
ind = inds[0]
w,h = fig_tmp.get_size_inches()*fig_tmp.dpi
ws = (event.x > w/2.)*-1 + (event.x <= w/2.)
hs = (event.y > h/2.)*-1 + (event.y <= h/2.)
annotationbox_tmp.xybox = (image_box_tmp[0]*ws, image_box_tmp[1]*hs)
annotationbox_tmp.set_visible(True)
annotationbox_tmp.xy =(x_tmp[ind], y_tmp[ind])
offset_image_tmp.set_data(images_tmp[ind,:,:])
else:
annotationbox_tmp.set_visible(False)
fig_tmp.canvas.draw_idle()
def click(event):
if points_tmp.contains(event)[0]:
inds = points_tmp.contains(event)[1]['ind']
ind = inds[0]
initialize_figure(1)
update_plot()
plt.scatter(x_tmp[ind], y_tmp[ind], s=20, marker='*', c='y')
plt.pause(2)
fig_tmp.canvas.stop_event_loop()
fig_tmp.canvas.draw_idle()
def main():
fig, ax = plt.subplots(1, figsize=(7, 7))
points = np.array([[1,1],[1,-1],[-1,1],[-1,-1]])
zero_layer = np.zeros([28,28])
one_layer = np.ones([28,28])*255
images = np.array([np.array([zero_layer, zero_layer, one_layer]).astype(np.uint8),np.array([zero_layer, one_layer, zero_layer]).astype(np.uint8),np.array([one_layer, zero_layer, zero_layer]).astype(np.uint8),np.array([one_layer, zero_layer, one_layer]).astype(np.uint8)])
images = np.transpose(images, (0,3,2,1))
theta = 0
delta = 15 * (np.pi/180)
rotation_matrix = np.array([[np.cos(theta),-np.sin(theta)],[np.sin(theta),np.cos(theta)]])
while True:
rotated_points = np.matmul(points, rotation_matrix)
draw_interactive_figures(rotated_points, images)
theta += delta
rotation_matrix = np.array([[np.cos(theta),-np.sin(theta)],[np.sin(theta),np.cos(theta)]])
if __name__== "__main__":
main()
Thanks in advance!
I'm providing you with a starting point here. The following is a script that creates a plot and allows you to add new points by clicking on the axes. For each point one may mouse hover and show a respective image.
import matplotlib.pyplot as plt
from matplotlib.offsetbox import OffsetImage, AnnotationBbox
import numpy as np
class MyInteractivePlotter():
def __init__(self):
self.fig, self.ax = plt.subplots()
self.ax.set(xlim=(0,1), ylim=(0,1))
self.points = np.array([[0.5,0.5]]) # will become N x 2 array
self.images = [np.random.rand(10,10)]
self.scatter = self.ax.scatter(*self.points.T)
self.im = OffsetImage(self.images[0], zoom=5)
self.ab = AnnotationBbox(self.im, (0,0), xybox=(50., 50.), xycoords='data',
boxcoords="offset points", pad=0.3,
arrowprops=dict(arrowstyle="->"))
# add it to the axes and make it invisible
self.ax.add_artist(self.ab)
self.ab.set_visible(False)
self.cid = self.fig.canvas.mpl_connect("button_press_event", self.onclick)
self.hid = self.fig.canvas.mpl_connect("motion_notify_event", self.onhover)
def add_point(self):
# Update points (here, we just add a new random point)
self.points = np.concatenate((self.points, np.random.rand(1,2)), axis=0)
# For each points there is an image. (Here, we just add a random one)
self.images.append(np.random.rand(10,10))
# Update the scatter plot to show the new point
self.scatter.set_offsets(self.points)
def onclick(self, event):
self.add_point()
self.fig.canvas.draw_idle()
def onhover(self, event):
# if the mouse is over the scatter points
if self.scatter.contains(event)[0]:
# find out the index within the array from the event
ind, = self.scatter.contains(event)[1]["ind"]
# make annotation box visible
self.ab.set_visible(True)
# place it at the position of the hovered scatter point
self.ab.xy = self.points[ind,:]
# set the image corresponding to that point
self.im.set_data(self.images[ind])
else:
#if the mouse is not over a scatter point
self.ab.set_visible(False)
self.fig.canvas.draw_idle()
m = MyInteractivePlotter()
plt.show()
I would suggest you take this and add your functionality into it. Once you stumble upon a problem you can use it to ask for clarifications.
I am trying to create a plot using matplotlib but I get an error, and after hours of searching, I do not see an alternative or something that works. Here's the code that's giving me trouble:
import matplotlib.transforms as transforms
self.transDataToAxes = self.transScale + (self.transLimits +
transforms.Affine2D().skew_deg(rot, 0))
Which gives me the error: AttributeError: 'Affine2D' object has no attribute 'skew_deg'. This error happens with both python 2.7 and python 3.
If anyone has any suggestions on what I can try, it would be greatly appreciated.
Edit: Here is the entire script which I am trying to run, it should also be noted that I've tried this on Windows, Linux, and Mac with no success:
import matplotlib
spc_file = open('1OUN.txt', 'r').read()
import sharppy
import sharppy.sharptab.profile as profile
import sharppy.sharptab.interp as interp
import sharppy.sharptab.winds as winds
import sharppy.sharptab.utils as utils
import sharppy.sharptab.params as params
import sharppy.sharptab.thermo as thermo
import numpy as np
from StringIO import StringIO
def parseSPC(spc_file):
## read in the file
data = np.array([l.strip() for l in spc_file.split('\n')])
## necessary index points
title_idx = np.where( data == '%TITLE%')[0][0]
start_idx = np.where( data == '%RAW%' )[0] + 1
finish_idx = np.where( data == '%END%')[0]
## create the plot title
data_header = data[title_idx + 1].split()
location = data_header[0]
time = data_header[1][:11]
## put it all together for StringIO
full_data = '\n'.join(data[start_idx : finish_idx][:])
sound_data = StringIO( full_data )
## read the data into arrays
p, h, T, Td, wdir, wspd = np.genfromtxt( sound_data, delimiter=',', comments="%", unpack=True )
return p, h, T, Td, wdir, wspd
pres, hght, tmpc, dwpc, wdir, wspd = parseSPC(spc_file)
prof = profile.create_profile(profile='default', pres=pres, hght=hght, tmpc=tmpc, \
dwpc=dwpc, wspd=wspd, wdir=wdir, missing=-9999, strictQC=True)
import matplotlib.pyplot as plt
plt.plot(prof.tmpc, prof.hght, 'r-')
plt.plot(prof.dwpc, prof.hght, 'g-')
#plt.barbs(40*np.ones(len(prof.hght)), prof.hght, prof.u, prof.v)
plt.xlabel("Temperature [C]")
plt.ylabel("Height [m above MSL]")
plt.grid()
plt.show()
msl_hght = prof.hght[prof.sfc] # Grab the surface height value
agl_hght = interp.to_agl(prof, msl_hght) # Converts to AGL
msl_hght = interp.to_msl(prof, agl_hght) # Converts to MSL
plt.plot(thermo.ktoc(prof.thetae), prof.hght, 'r-', label='Theta-E')
plt.plot(prof.wetbulb, prof.hght, 'c-', label='Wetbulb')
plt.xlabel("Temperature [C]")
plt.ylabel("Height [m above MSL]")
plt.legend()
plt.grid()
plt.show()
sfcpcl = params.parcelx( prof, flag=1 ) # Surface Parcel
#fcstpcl = params.parcelx( prof, flag=2 ) # Forecast Parcel
#mupcl = params.parcelx( prof, flag=3 ) # Most-Unstable Parcel
#mlpcl = params.parcelx( prof, flag=4 ) # 100 mb Mean Layer Parcel
# This serves as an intensive exercise of matplotlib's transforms
# and custom projection API. This example produces a so-called
# SkewT-logP diagram, which is a common plot in meteorology for
# displaying vertical profiles of temperature. As far as matplotlib is
# concerned, the complexity comes from having X and Y axes that are
# not orthogonal. This is handled by including a skew component to the
# basic Axes transforms. Additional complexity comes in handling the
# fact that the upper and lower X-axes have different data ranges, which
# necessitates a bunch of custom classes for ticks,spines, and the axis
# to handle this.
from matplotlib.axes import Axes
import matplotlib.transforms as transforms
import matplotlib.axis as maxis
import matplotlib.spines as mspines
import matplotlib.path as mpath
from matplotlib.projections import register_projection
# The sole purpose of this class is to look at the upper, lower, or total
# interval as appropriate and see what parts of the tick to draw, if any.
class SkewXTick(maxis.XTick):
def draw(self, renderer):
if not self.get_visible(): return
renderer.open_group(self.__name__)
lower_interval = self.axes.xaxis.lower_interval
upper_interval = self.axes.xaxis.upper_interval
if self.gridOn and transforms.interval_contains(
self.axes.xaxis.get_view_interval(), self.get_loc()):
self.gridline.draw(renderer)
if transforms.interval_contains(lower_interval, self.get_loc()):
if self.tick1On:
self.tick1line.draw(renderer)
if self.label1On:
self.label1.draw(renderer)
if transforms.interval_contains(upper_interval, self.get_loc()):
if self.tick2On:
self.tick2line.draw(renderer)
if self.label2On:
self.label2.draw(renderer)
renderer.close_group(self.__name__)
# This class exists to provide two separate sets of intervals to the tick,
# as well as create instances of the custom tick
class SkewXAxis(maxis.XAxis):
def __init__(self, *args, **kwargs):
maxis.XAxis.__init__(self, *args, **kwargs)
self.upper_interval = 0.0, 1.0
def _get_tick(self, major):
return SkewXTick(self.axes, 0, '', major=major)
#property
def lower_interval(self):
return self.axes.viewLim.intervalx
def get_view_interval(self):
return self.upper_interval[0], self.axes.viewLim.intervalx[1]
# This class exists to calculate the separate data range of the
# upper X-axis and draw the spine there. It also provides this range
# to the X-axis artist for ticking and gridlines
class SkewSpine(mspines.Spine):
def _adjust_location(self):
trans = self.axes.transDataToAxes.inverted()
if self.spine_type == 'top':
yloc = 1.0
else:
yloc = 0.0
left = trans.transform_point((0.0, yloc))[0]
right = trans.transform_point((1.0, yloc))[0]
pts = self._path.vertices
pts[0, 0] = left
pts[1, 0] = right
self.axis.upper_interval = (left, right)
# This class handles registration of the skew-xaxes as a projection as well
# as setting up the appropriate transformations. It also overrides standard
# spines and axes instances as appropriate.
class SkewXAxes(Axes):
# The projection must specify a name. This will be used be the
# user to select the projection, i.e. ``subplot(111,
# projection='skewx')``.
name = 'skewx'
def _init_axis(self):
#Taken from Axes and modified to use our modified X-axis
self.xaxis = SkewXAxis(self)
self.spines['top'].register_axis(self.xaxis)
self.spines['bottom'].register_axis(self.xaxis)
self.yaxis = maxis.YAxis(self)
self.spines['left'].register_axis(self.yaxis)
self.spines['right'].register_axis(self.yaxis)
def _gen_axes_spines(self):
spines = {'top':SkewSpine.linear_spine(self, 'top'),
'bottom':mspines.Spine.linear_spine(self, 'bottom'),
'left':mspines.Spine.linear_spine(self, 'left'),
'right':mspines.Spine.linear_spine(self, 'right')}
return spines
def _set_lim_and_transforms(self):
"""
This is called once when the plot is created to set up all the
transforms for the data, text and grids.
"""
rot = 30
#Get the standard transform setup from the Axes base class
Axes._set_lim_and_transforms(self)
# Need to put the skew in the middle, after the scale and limits,
# but before the transAxes. This way, the skew is done in Axes
# coordinates thus performing the transform around the proper origin
# We keep the pre-transAxes transform around for other users, like the
# spines for finding bounds
self.transDataToAxes = self.transScale + (self.transLimits +
transforms.Affine2D().skew_deg(rot, 0))
# Create the full transform from Data to Pixels
self.transData = self.transDataToAxes + self.transAxes
# Blended transforms like this need to have the skewing applied using
# both axes, in axes coords like before.
self._xaxis_transform = (transforms.blended_transform_factory(
self.transScale + self.transLimits,
transforms.IdentityTransform()) +
transforms.Affine2D().skew_deg(rot, 0)) + self.transAxes
# Now register the projection with matplotlib so the user can select
# it.
register_projection(SkewXAxes)
pcl = sfcpcl
# Create a new figure. The dimensions here give a good aspect ratio
fig = plt.figure(figsize=(6.5875, 6.2125))
ax = fig.add_subplot(111, projection='skewx')
ax.grid(True)
pmax = 1000
pmin = 10
dp = -10
presvals = np.arange(int(pmax), int(pmin)+dp, dp)
# plot the moist-adiabats
for t in np.arange(-10,45,5):
tw = []
for p in presvals:
tw.append(thermo.wetlift(1000., t, p))
ax.semilogy(tw, presvals, 'k-', alpha=.2)
def thetas(theta, presvals):
return ((theta + thermo.ZEROCNK) / (np.power((1000. / presvals),thermo.ROCP))) - thermo.ZEROCNK
# plot the dry adiabats
for t in np.arange(-50,110,10):
ax.semilogy(thetas(t, presvals), presvals, 'r-', alpha=.2)
plt.title(' OAX 140616/1900 (Observed)', fontsize=14, loc='left')
# Plot the data using normal plotting functions, in this case using
# log scaling in Y, as dicatated by the typical meteorological plot
ax.semilogy(prof.tmpc, prof.pres, 'r', lw=2)
ax.semilogy(prof.dwpc, prof.pres, 'g', lw=2)
ax.semilogy(pcl.ttrace, pcl.ptrace, 'k-.', lw=2)
# An example of a slanted line at constant X
l = ax.axvline(0, color='b', linestyle='--')
l = ax.axvline(-20, color='b', linestyle='--')
# Disables the log-formatting that comes with semilogy
ax.yaxis.set_major_formatter(plt.ScalarFormatter())
ax.set_yticks(np.linspace(100,1000,10))
ax.set_ylim(1050,100)
ax.xaxis.set_major_locator(plt.MultipleLocator(10))
ax.set_xlim(-50,50)
plt.show()
And the text file can be found here, before renaming it: OUN_Sounding
So I'm modifying someone else's library to setup a cbar with log (values). I thought I could use LogFormatterExponent() ... But it seemingly randomly adds and 'e' to the exponents that it uses for the cbar. What's going on? How can I suppress/fix this?
if show_cbar:
if log:
l_f = LogFormatterExponent()
else:
l_f = ScalarFormatter()
if qtytitle is not None:
plt.colorbar(ims,format=l_f).set_label(qtytitle)
else:
plt.colorbar(ims,format=l_f).set_label(units)
Here's what I'm seeing for log=True:
And another plot where log = False:
At first, I thought the 'e's were being cut-off by the label at right... but over several plots this doesn't appear to be the case. I usually get 1-2 'e's ... But on a plot with only 3 color bar ticks, I see none!
A minimal example is
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors as cm
import matplotlib.ticker as ct
data = np.exp(np.random.rand(20, 20) * 100)
fig, ax = plt.subplots()
log_norm = cm.LogNorm()
im = ax.imshow(data, interpolation='nearest', cmap='viridis', norm=log_norm)
fig.colorbar(im, format=ct.LogFormatterExponent())
This looks like a bug in mpl. If you already have a large library, I would just include a fixed version of the formatter.
class LogFormatterExponentFixed(LogFormatter):
"""
Format values for log axis; using ``exponent = log_base(value)``
"""
def __call__(self, x, pos=None):
"""Return the format for tick val *x* at position *pos*"""
vmin, vmax = self.axis.get_view_interval()
vmin, vmax = mtransforms.nonsingular(vmin, vmax, expander=0.05)
d = abs(vmax - vmin)
b = self._base
if x == 0:
return '0'
sign = np.sign(x)
# only label the decades
fx = math.log(abs(x)) / math.log(b)
isDecade = is_close_to_int(fx)
if not isDecade and self.labelOnlyBase:
s = ''
elif abs(fx) > 10000:
s = '%1.0g' % fx
elif abs(fx) < 1:
s = '%1.0g' % fx
else:
# this is the added line
fd = math.log(abs(d)) / math.log(b)
s = self.pprint_val(fx, fd)
if sign == -1:
s = '-%s' % s
return self.fix_minus(s)
Working on a fix for upstream.