I am fairly new to Matplotlib. This idea behind this figure is to graph temperature highs and lows. I've run into trouble with the xaxis and right yaxis.
For the xaxis, the color of the font doesn't want to change even though I call tick_params(labelcolor='#b6b6b6'). Also, the dates should only span from Jan - Dec. For unknown reasons, Matplotlib is prepending an extra Dec and appending an extra Jan, causing the text to flow outside of the graph's spine bounds. I want to remove these extra months.
For the right yaxis, I'm not sure I understand the use of subplots properly. I want to convert the ˚C temperatures in the left yaxis to ˚F and use the converted temps for the secondary yaxis.
Here's some code to reproduce something similar to what I've got.
import numpy as np
import pandas as pd
import matplotlib as mpl
import matplotlib.pyplot as plt
import matplotlib.dates as dates
import matplotlib.ticker as ticker
# generate some data to plot
highs = np.linspace(0, 40, 365) # these numbers will escalate instead of fluctuate, but the problem with the axes will still be the same.
lows = np.linspace(-40, 0, 365)
date_rng = pd.date_range('1/1/2015', '12/31/2015', freq='D')
data = {'highs': highs, 'lows': lows}
to_plot = pd.DataFrame(data, index=date_rng)
fig, ax = plt.subplots()
# plot the basic data
lines = ax.plot(date_rng, to_plot['lows'], '-',
date_rng, to_plot['highs'], '-')
# get the axes reference
ax1 = plt.gca()
# fill in between the lines
ax1.fill_between(date_rng,
to_plot['lows'], to_plot['highs'],
facecolor='#b6b6b6', # gradient fillbetween
alpha=.2)
# set the xaxis to only 12 months and space the names.
ax1.xaxis.set_major_locator(dates.MonthLocator())
ax1.xaxis.set_minor_locator(dates.MonthLocator(bymonthday=15, interval=1))
ax1.xaxis.set_major_formatter(ticker.NullFormatter())
ax1.xaxis.set_minor_formatter(dates.DateFormatter('%b'))
for tick in ax1.xaxis.get_minor_ticks():
tick.tick1line.set_markersize(0)
tick.tick2line.set_markersize(0)
tick.label1.set_horizontalalignment('center')
# add a right y axis and set all yaxis properties
ax1.set_ylim([-50, 50])
# change the color and sizes scheme
info_colors = '#b6b6b6'
bold_colors = '#777777'
# graph lines
ax1.lines[0].set_color('#e93c00') # top redish orange
ax1.lines[1].set_color('#009ae9') # btm blue
plt.setp(lines, lw=.8, alpha=1)
# spines
ax.spines['top'].set_visible(False)
for pos in ['bottom', 'right', 'left']:
ax.spines[pos].set_edgecolor(info_colors)
# set the title
plt.title('Record Temps, 2005-15: Ann Arbour, MI', fontsize=10, color=bold_colors)
# ticks
ax1.tick_params(axis='both', color=info_colors, labelcolor=info_colors, length=5, direction='out', pad=7, labelsize=8)
# add a legend and edit its properties
leg = plt.legend(['Highs','Lows'], frameon=False, loc=0, fontsize='small')
for text in leg.get_texts():
text.set_color(info_colors)
plt.ylabel('˚C', color=info_colors)
# set extra yaxis label
ax2 = ax.twinx()
ax2.set_ylabel('˚F', color=info_colors)
ax2.tick_params('y', colors=info_colors)
To change the color of the minor labels, put the following code after ax1.xaxis.set_minor_formatter(dates.DateFormatter('%b')):
ax1.xaxis.set_tick_params(which='minor', colors=info_colors)
Related
I'm making a weather display graph using GNUplot with a weather API. I'm currently plotting the next 48 hours of temperature and rainfall.
As you can see in the above image, the temperature is the line with the axis defined on the left; while the rainfall is depicted by the bar graph (bottom left) and its axis is defined on the right. (0, 0.5, 1).
I would however like to include other data in the graph as well. The first thing I want to include is cloud cover at the top of the graph. Again as a bar graph.
I'm including a mockup that I made is a graphic editor:
Is there a way to do this with gnuplot, or will I have to use another program to accomplish it?
You have y1-axis on the left and y2-axis on the right. If you want to have a 3rd y-axis you have to shift it somehow. One way to achieve this is with multiplot, basically several plots on top of each other.
You have to make sure that all plots are using the same (fixed) margins on the canvas (automargin probably won't work) and the same xrange (the second plot takes it from the first plot). Check the following example with some random data. Certainly, some fine tuning could be done. Adapt it to your needs.
Code:
### Three y-axes
reset session
# create some test data
myTimeFmt = "%d.%m.%Y %H:%M:%S"
set print $Data
do for [i=1:48] {
myTime(i) = strftime(myTimeFmt, time(0)+i*3600)
myTemp(i) = sin(i/5.)*5 + 20 + rand(0)
myRain(i) = int(rand(0)+0.3) * rand(0)*20
myCloud(i) = rand(0)*50
print sprintf("%s %g %g %g",myTime(i),myTemp(i),myRain(i),myCloud(i))
}
set print
set key off
set margins screen 0.1, screen 0.8, screen 0.1, screen 0.94
set multiplot
set format x "%H:%M" timedate
set xtics 3600*6
set grid xtics, mxtics, ytics, mytics
##### first plot
set ylabel "Temperature °C" tc "red"
set yrange[10:30]
set ytics nomirror tc "red"
set y2label "Rain / mm" offset -1,0 textcolor rgb "blue"
set y2range[0:40]
set y2tics nomirror tc "blue"
set style fill solid 1.0
plot $Data u (timecolumn(1,myTimeFmt)):3 axes x1y1 w l lc "red", \
'' using (timecolumn(1,myTimeFmt)):4 axes x1y2 w boxes lc "blue"
unset xlabel
unset ylabel
unset y2label
unset tics
##### Second plot
set bmargin screen 0.73
set border 4
set xrange[GPVAL_X_MIN:GPVAL_X_MAX] # identical xrange like 1st plot
set y2range[100:0] reverse
plot $Data u (timecolumn(1,myTimeFmt)):5 axes x1y2 w boxes lc rgbcolor "grey"
##### Third plot (just for 3rd y-axis)
set rmargin at screen 0.9
set border 8 # only right border visible
set y2label "Cloud coverage" offset -1,0 textcolor rgb "black"
set y2tics nomirror offset 0,0
plot NaN # plot some dummy
unset multiplot
### end of code
Result:
I am using basemap on Python 2.7 but would like to go for Python 3, and therefor, moving to cartopy. It would be fantastic if you would give me some advises how to change my code from basemap to cartopy:
This is the basemap code:
from mpl_toolkits.basemap import Basemap
# plot map without continents and coastlines
m = Basemap(projection='kav7',lon_0=0)
# draw map boundary, transparent
m.drawmapboundary()
m.drawcoastlines()
# draw paralells and medians, no labels
if (TheLatInfo[1] == len(TheLatList)) & (TheLonInfo[1] == len(TheLonList)):
m.drawparallels(np.arange(-90,90.,30.))
m.drawmeridians(np.arange(-180,180.,60.))
grids = m.pcolor(LngArrLons,LngArrLats,MSKTheCandData,cmap=cmap,norm=norm,latlon='TRUE')
This is the cartopy example I found and have changed some bits:
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
import cartopy.feature as cpf
ax = plt.axes(projection=ccrs.Robinson())
ax.coastlines()
ax.set_boundary
ax.gridlines(draw_labels=False)
plt.show()
I am not sure about how to set the gridlines in the exact positions and how to color them black instead of grey. Furthermore, I wonder how to insert/overlay my actual map with data then. Is "ax.pcolor" well enough supported by cartopy?
Thank you!
To color your gridlines black, you can use a color= keyword:
ax.gridlines(color='black')
To specify lat/lon gridline placement, you really only need a few extra lines, if you don't care about labels:
import matplotlib.ticker as mticker
gl = ax.gridlines(color='black')
gl.xlocator = mticker.FixedLocator([-180, -90, 0, 90, 180])
gl.ylocator = mticker.FixedLocator([-90,-45,0,45,90])
(As of writing this, Robinson projections don't support gridline labels.)
To overlay your data on the map,pcolor should work, but it's famously slow. I would recommend pcolormesh, though you can substitute one for another in this syntax:
ax.pcolormesh(lon_values, lat_values, data)
Note that if your data come on a different projection than the map projection you're plotting (typically true), you need to specify the data's projection in the plotting syntax using the transform= keyword. That tells cartopy to transform your data from their original projection to that of the map. Plate Carrée is the same as cylindrical equidistant (typical for climate model output, for example):
ax.pcolormesh(lon_values, lat_values, data, transform=ccrs.PlateCarree())
I'm trying plot two panels in a plot.
The first one (left) is a data with latitude values in its y-axis. The second panel is a map.
I wanna that the latitude values of both panels coinciding, but I don't know how get it.
I have a code like this:
fig_mapa= plt.figure()
'''Mapa'''
ax1=fig_mapa.add_subplot(122)
map = Basemap(llcrnrlon=-90,llcrnrlat=-58.1,urcrnrlon=-32,urcrnrlat=12.6,
resolution='f',projection='merc',lon_0=-58,lat_0=-25, ax=ax1)
map.drawparallels(np.arange(-90,90.,5), labels=[0,1,0,0], linewidth=0.5)
map.drawmeridians(np.arange(-180.,180.,5), labels=[0,0,0,1], linewidth=0.5)
map.readshapefile("./Fases_tectonicas/Shapefiles/Unidades_Fi", 'Unidades_Fi', linewidth=0.1)
#map.warpimage(image='./Geotiffs/NE1_HR_LC_SR_W_DR/NE1_HR_LC_SR_W_DR.tif', zorder=1)
map.drawcoastlines(linewidth=0.5, color='k')
Nombre_Unidad= []
for elemento in map.Unidades_Fi_info:
Nombre_Unidad.append(elemento['NAME'])
for i in range(len(Nombre_Unidad)):
draw=map.Unidades_Fi[i]
poly=Polygon(draw, facecolor=color[Nombre_Unidad[i]],edgecolor='k', alpha=0.5,linewidth=0.1, zorder=2)
plt.gca().add_patch(poly)
'''Gráfico Eventos Compresivos'''
ax2= fig_mapa.add_subplot(121)
ax2.set_ylim(-58.1,12.6)
ax2.set_xlim(120,0)
ax2.set_xlabel('Tiempo [Ma]')
ax2.set_ylabel('Latitud[°]')
ax2.grid()
The simplest way to align two axes is with the sharex or sharey keyword for plt.subplots. However, the coordinates that Basemap shows and the coordinates that it uses for the Axes instance are two different things, so you will have to convert between the two if you want to have understandable ytick labels and some meaningful graph in your second Axes instance. Below I show how you can align the two y-axes, set the yticks properly and transform your data to the data coordinates of your Basemap. I left the creation of the Basemap untouched.
from matplotlib import pyplot as plt
from mpl_toolkits.basemap import Basemap
import numpy as np
##figure with two subplots and shared y-axis
fig,(ax2,ax1) = plt.subplots(nrows=1, ncols=2, sharey='row')
m1 = Basemap(llcrnrlon=-90,llcrnrlat=-58.1,urcrnrlon=-32,urcrnrlat=12.6,
#resolution='f',
projection='merc',lon_0=-58,lat_0=-25, ax=ax1)
m1.drawparallels(np.arange(-90,90.,5), labels=[0,1,0,0], linewidth=0.5)
m1.drawmeridians(np.arange(-180.,180.,5), labels=[0,0,0,1], linewidth=0.5)
m1.drawcoastlines(linewidth=0.5, color='k')
##turning off yticks at basemap
ax1.yaxis.set_ticks_position('none')
##setting yticks:
yticks = np.arange(-55,12.6,5)
##transform yticks:
_,yticks_data = m1(0*yticks,yticks)
ax2.set_yticks(yticks_data)
ax2.set_yticklabels(['{: >3}$^\circ${}'.format(
abs(int(y)), 'N' if y>0 else 'S' if y<0 else ' '
) for y in yticks])
ax2.set_xlim(120,0)
ax2.set_xlabel('Tiempo [Ma]')
ax2.set_ylabel('Latitud[$^\circ$]')
ax2.grid()
#some fake data for testing plotting
yrange = np.linspace(-60,20,100)
temp = (np.sin(10*np.deg2rad(yrange))+1)*50
##transform yrange
_,yrange_data = m1(0*yrange, yrange)
ax2.plot(temp,yrange_data)
plt.show()
The result of the above code looks like this:
Hope this helps.
As an example, in chaco/examples/demo/basic/image_inspector.py, how to set the zoom factor such that 1 array point corresponds to 1 screen pixel (100% zoom). It seems that the ZoomTool methods (zoom_in, zoom_out, ...) only deal with zoom factor changes, not with absolute factor setting.
I would try something with plot.range2d.low, plot.range2d.high and plot.outer_bounds. The first two relate to data space, while the latter relates to the size of the picture area. By setting the limits of the data space using the picture area, you can map 1 pixel to 1 data unit. Here's an example, the interesting bit is in the _zoom_100_percent method:
import numpy as np
from chaco.api import Plot, ArrayPlotData
from chaco.tools.api import PanTool, ZoomTool
from enable.api import ComponentEditor
from traits.api import Button, HasTraits, Instance, on_trait_change
from traitsui.api import Item, View
class HundredPercentZoom(HasTraits):
plot = Instance(Plot)
zoom_button = Button('100% Zoom')
traits_view = View(
Item('plot', editor=ComponentEditor(), show_label=False),
'zoom_button',
width=800,
height=600,
)
def _plot_default(self):
t = np.linspace(0, 1000, 200)
y = 400 * (np.sin(t) + 0.1 * np.sin(t * 100))
plot = Plot(ArrayPlotData(t=t, y=y))
plot.plot(('t', 'y'))
plot.tools.append(PanTool(plot))
plot.tools.append(ZoomTool(plot))
return plot
#on_trait_change('zoom_button')
def _zoom_100_percent(self):
low = self.plot.range2d.low
bounds = self.plot.outer_bounds
print(bounds)
self.plot.range2d.high = (low[0] + bounds[0], low[1] + bounds[1])
if __name__ == "__main__":
hpz = HundredPercentZoom()
hpz.configure_traits()
I added a print statement in there so you can see that the plot area is different than the window area, which is 800x600. I also added a PanTool and ZoomTool, so you can pan around once zoomed in. You can go back to the orignal zoom state using the Escape key, as long as your plot has a ZoomTool.
The solution I have arrived to, starting from the original example image_inspector.py . A button allows to have a 100 % zoom factor around a point chosen as the zoom center.
All is in the _btn_fired method in class Demo.
There may still be a problem of 1 not being subtracted or added to some bounds or limits, as the button operation is not strictly involutive (a second press should not do anything) as it should.
Anything simpler?
#!/usr/bin/env python
"""
Demonstrates the ImageInspectorTool and overlay on a colormapped image
plot. The underlying plot is similar to the one in cmap_image_plot.py.
- Left-drag pans the plot.
- Mousewheel up and down zooms the plot in and out.
- Pressing "z" brings up the Zoom Box, and you can click-drag a rectangular
region to zoom. If you use a sequence of zoom boxes, pressing alt-left-arrow
and alt-right-arrow moves you forwards and backwards through the "zoom
history".
- Pressing "p" will toggle the display of the image inspector overlay.
"""
# Major library imports
from numpy import linspace, meshgrid, pi, sin, divide, multiply
# Enthought library imports
from enable.api import Component, ComponentEditor
from traits.api import HasTraits, Instance, Button, Float
from traitsui.api import Item, Group, View, HGroup
# Chaco imports
from chaco.api import ArrayPlotData, jet, Plot
from chaco.tools.api import PanTool, ZoomTool
from chaco.tools.image_inspector_tool import ImageInspectorTool, \
ImageInspectorOverlay
#===============================================================================
# # Create the Chaco plot.
#===============================================================================
def _create_plot_component():# Create a scalar field to colormap
xbounds = (-2*pi, 2*pi, 600)
ybounds = (-1.5*pi, 1.5*pi, 300)
xs = linspace(*xbounds)
ys = linspace(*ybounds)
x, y = meshgrid(xs,ys)
z = sin(x)*y
# Create a plot data obect and give it this data
pd = ArrayPlotData()
pd.set_data("imagedata", z)
# Create the plot
plot = Plot(pd)
img_plot = plot.img_plot("imagedata",
xbounds = xbounds[:2],
ybounds = ybounds[:2],
colormap=jet)[0]
# Tweak some of the plot properties
plot.title = "My First Image Plot"
plot.padding = 50
# Attach some tools to the plot
plot.tools.append(PanTool(plot))
zoom = ZoomTool(component=plot, tool_mode="box", always_on=False)
plot.overlays.append(zoom)
imgtool = ImageInspectorTool(img_plot)
img_plot.tools.append(imgtool)
overlay = ImageInspectorOverlay(component=img_plot, image_inspector=imgtool,
bgcolor="white", border_visible=True)
img_plot.overlays.append(overlay)
return plot
#===============================================================================
# Attributes to use for the plot view.
size = (800, 600)
title="Inspecting a Colormapped Image Plot"
#===============================================================================
# # Demo class that is used by the demo.py application.
#===============================================================================
class Demo(HasTraits):
plot = Instance(Component)
center_x = Float
center_y = Float
btn = Button('100 %')
def _btn_fired(self):
img_plot, = self.plot.plots['plot0']
zoom_center = (self.center_x, self.center_y)
# Size of plot in screen pixels
plot_size = img_plot.bounds
# Zoom center in screen space
zoom_center_screen, = img_plot.map_screen(zoom_center)
# Get actual bounds in data space
low, high = (img_plot.index_mapper.range.low,
img_plot.index_mapper.range.high)
# Get data space x and y units in terms of x and y array indices
sizes = [item.get_size() for item in img_plot.index.get_data()]
(min_x, min_y), (max_x, max_y) = img_plot.index.get_bounds()
unit = divide((max_x - min_x, max_y - min_y), sizes)
# Calculate new bounds
new_low = zoom_center - multiply(zoom_center_screen, unit)
new_high = new_low + multiply(plot_size, unit)
# Set new bounds
img_plot.index_mapper.range.set_bounds(new_low,new_high)
traits_view = View(
Group(
Item('plot', editor=ComponentEditor(size=size),
show_label=False),
HGroup('center_x', 'center_y', 'btn'),
orientation = "vertical"
),
resizable=True, title=title
)
def _plot_default(self):
return _create_plot_component()
demo = Demo()
if __name__ == "__main__":
demo.configure_traits()
I'd like to relabel the radial tick markers in the following polar log plot:
f = figure ;
t = 0:0.01: pi/2 ;
polar(t, 10 * log10(cos(t))/(50) + 1)
from 1, 0.8, 0.6, 0.4, 0.2 to 0, -10, -20, -30, -40 (i.e. radial dB ticks).
Trying some of the methods from Fixing the Radial Axis on MATLAB Polar Plots, I was able to relabel the markers provided my tick markers were positive and increasing.
I tried the following based on How to remove Rho labels from Matlab polar plot?
rho_labels = {'1' '0.8' '0.6' '0.4' '0.2'};
rho_labels2 = {'0' '-10' '-20' '-30' '-40'};
for r=1:length(rho_labels)
ff = findall(f, 'string', rho_labels{r}) ;
ff = rho_labels2{r} ;
end
but it also didn't work (seems to do nothing, so I suspect I'm operating on a copy of the find results not handled).
How can these tick markers be adjusted? Also, if I wanted a different number of concentric circles than 5, how can that be done (for example, 4 subdivisions with -40 dB at the "origin".)
Here is a way to rename the radial tick labels. Be warned that if there is a match between any radial and angular tick mark labels, both will be replaced and the angular labels will be wrong. But the angular tick labels are fixed as 0, 30, 60, ..., 330, so as long as the radial tick labels don't include these values, you should be fine.
What the code does is find all the text in the plot using findall, trim the blank spaces at the front of each string, then, for each entry in rho_labels, set the string entry corresponding to the tick label with that identifier to the corresponding entry in rho_labels2.
f = figure ;
t = 0:0.01: pi/2 ;
polar(t, 10 * log10(cos(t))/(50) + 1)
rho_labels = {'1' '0.8' '0.6' '0.4' '0.2'};
rho_labels2 = {'0' '-10' '-20' '-30' '-40'};
ff = findall(f,'type','text');
t=strtrim(get(ff,'String'));
for r=1:length(rho_labels)
set(ff(strcmp(t,rho_labels{r})),'String',rho_labels2{r})
end
To modify the number of rings, I think think of anything better than modifying polar.m and creating your own custom polarMOD.m function. If you do this you can also do the radial labels as well. In fact, there might be such a function on the MathWorks File Exchange.