Matplotlib: Change color of individual grid lines - python

I've only been using Python for about a month now, so I'm sorry if there's some simple solution to this that I overlooked.
Basically I have a figure with 4 subplots, the 2 on the left show longitudinal plots and the ones on the right show scatter plots at certain points of the longitudinal plots. You can click through the scatter plots at different points of the longitudinal plot with buttons, and the tick label of the longitudinal plot you're currently at will be highlighted in blue.
Coloring a certain tick label already works with this:
xlabels = []
labelcolors = []
for i, item in enumerate(mr.segmentlst):
if re.search('SFX|MC|MQ|MS|MKC', item):
xlabels.append(mr.segmentlst[i])
else:
xlabels.append('')
for i, item in enumerate(mr.segmentlst):
if re.search('SFX', item):
labelcolors.append('black')
else:
labelcolors.append('gray')
labelcolors[self.ind]='blue'
[t.set_color(i) for (i,t) in zip(labelcolors, ax1.xaxis.get_ticklabels())]
[t.set_color(i) for (i,t) in zip(labelcolors, ax2.xaxis.get_ticklabels())]
It only shows certain tick labels and changes their colors accordingly (I don't know if there is another solution for this, it's the only one I could find). Don't mind the mr.segmentlist, I've currently hardcoded the plot to use an attribute from another method so I can easily keep testing it in Spyder.
I'd like to also change the grid line color of the currently highlighted tick label (only xgridlines are visible) in the longitudinal plots, is there some kind of similar way of doing this? I've searched the internet for a solution for about 2 hours now and didn't really find anything helpful.
I thought something like ax1.get_xgridlines() might be used, but I have no idea how I could transform it into a useful list.
Thanks,
Tamara

get_xgridlines() returns a list of Line2D objects, so if you can locate which line you want to modify, you can modify any of their properties
x = np.random.random_sample((10,))
fig = plt.figure()
ax = fig.add_subplot(111)
ax.scatter(x,x)
ax.grid()
a = ax.get_xgridlines()
b = a[2]
b.set_color('red')
b.set_linewidth(3)

since the above solution only works with major gridlines
(since get_gridlines() is currently hardcoded to use only the major ones),
here's how you can also access the minor gridlines by adapting
the get_gridlines() function (from here):
from matplotlib import cbook
def get_gridlines(ax, which):
'''
Parameters:
ax : ax.xaxis or ax.yaxis instance
which : 'major' or 'minor'
Returns:
The grid lines as a list of Line2D instance
'''
if which == 'major':
ticks = ax.get_major_ticks()
if which == 'minor':
ticks = ax.get_minor_ticks()
return cbook.silent_list('Line2D gridline',
[tick.gridline for tick in ticks])

Related

Managing ticks for multiple lines on the same subplot in Matplotlib

I want to plot multiple lines in the same plot, like in the picture below:
The problem with the picture is that if the Y values of the graphs aren't similar the y ticks get jumbled, it's unclear which tick is related to the first graph and which one isn't.
Is there a way for me to colour the ticks of each graph differently (preferably to the colour of the graph)? or maybe separate it into different columns?
Also, I wouldn't mind using more than one subplot, as long as the graphs' space overlaps.
The code I use to create the new lines:
def generate_graph():
colors = "rgbmcmyk"
subplot_recent.clear()
lines_drawn = []
mat_figure.legends = []
for i in range(n):
lines_drawn.append(["A Name", subplot_recent.plot(values[i][0], values[i][1], colors[i])[0]])
mat_figure.legend((i[1] for i in lines_drawn), (i[0] for i in lines_drawn), 'upper right')
subplot_recent.yaxis.set_major_locator(plt.MaxNLocator(10))
mat_canvas.draw()
The error actually was that I forgot to cast the values to int/float, and so matplotlib didn't really know what to do with them all to well.
It's fixed now. Thanks!

How to reproduce this legend with multiple curves?

I've been working hard on a package of functions for my work, and I'm stuck on a layout problem. Sometimes I need to work with a lot of columns subplots (1 row x N columns) and the standard matplotlib legend sometimes is not helpful and makes it hard to visualize all the data.
I've been trying to create something like the picture below. I already tried to create a subplot for the curves and another one for the legends (and display the x-axis scale as a horizontal plot). Also, I tried to spine the x-axis, but when I have a lot of curves plotted inside the same subplots the legend becomes huge.
The following image is from a software. I'd like to create a similar look. Notice that these legends are "static": it remains fixed independent of the zooming. Another observation is, I don't need all the ticks or anything like that.
What I'm already have is the following (the code is a mess, becouse I'm trying many different solutions and it is not organized nor pythonic yet.
import matplotlib.pyplot as plt
fig, ax = plt.subplots(1,2, sharey = True)
ax[0].semilogx(np.zeros_like(dados.Depth)+0.02, dados.Depth)
ax[0].semilogx(dados.AHT90, dados.Depth, label = 'aht90')
ax[0].set_xlim(0.2,2000)
ax[0].grid(True, which = 'both', axis = 'both')
axres1 = ax[0].twiny()
axres1.semilogx(dados.AHT90, dados.Depth, label = 'aht90')
axres1.set_xlim(0.2 , 2000)
axres1.set_xticks(np.logspace(np.log10(0.2),np.log10(2000),2))
axres1.spines["top"].set_position(("axes", 1.02))
axres1.get_xaxis().set_major_formatter(matplotlib.ticker.ScalarFormatter())
axres1.tick_params(axis='both', which='both', labelsize=6)
axres1.set_xlabel('sss')#, labelsize = 5)
axres2 = ax[0].twiny()
axres2.semilogx(dados.AHT10, dados.Depth, label = 'aht90')
axres2.set_xlim(0.2 , 2000)
axres2.set_xticks(np.logspace(np.log10(0.2),np.log10(2000),2))
axres2.spines["top"].set_position(("axes", 1.1))
axres2.get_xaxis().set_major_formatter(matplotlib.ticker.ScalarFormatter())
axres2.tick_params(axis='both', which='both', labelsize=6)
axres2.set_xlabel('aht10')#, labelsize = 5)
fig.show()
and the result is:
But well, I'm facing some issues on make a kind of make it automatic. If I add more curves, the prameter "set position" it is not practical to keep setting the position "by hand"
set_position(("axes", 1.02))
and another problem is, more curves I add, that kind of "legend" keep growing upward, and I have to adjust the subplot size with
fig.subplots_adjust(top=0.75)
And I'm also want to make the adjustment automatic, without keeping updating that parameter whenever I add more curves

Highlight a label in a legend, matplotlib

As of now I am using Matplotlib to generate plots.
The legend on the plot can be tweaked using some parameters (as mentioned in this guide). But I would like to have something specific in the legend, as attached in this image below.
I would like to highlight one of the labels in the legend like shown (as of now done using MS paint).
If there are other ways of highlighting a specific label, that would also suffice.
The answer by FLab is actually quite reasonable given how painful it can be to backtrace the coordinates of the plotted items. However, the demands of publication-grade figures are quite often unreasonable, and seeing matplotlib challenged by MS Paint is a enough good motivation for answering this.
Lets consider this example from the matplotlib gallery as a starting point:
N = 100
x = np.arange(N)
fig = plt.figure()
ax = fig.add_subplot(111)
xx = x - (N/2.0)
plt.plot(xx, (xx*xx)-1225, label='$y=x^2$')
plt.plot(xx, 25*xx, label='$y=25x$')
plt.plot(xx, -25*xx, label='$y=-25x$')
legend = plt.legend()
plt.show()
Once an image has been drawn, we can backtrace the elements in the legend instance to find out their coordinates. There are two difficulties associated with this:
The coordinates we'll get through the get_window_extent method are in pixels, not "data" coordinates, so we'll need to use a transform function. A great overview of the transforms is given here.
Finding a proper boundary is tricky. The legend instance above has two useful attributes, legend.legendHandles and legend.texts - two lists with a list of line artists and text labels respectively. One would need to get a bounding box for both elements, while keeping in mind that the implementation might not be perfect and is backend-specific (c.f. this SO question). This is a proper way to do this, but it's not the one in this answer, because...
.. because luckily in your case the legend items seem to be uniformly separated, so we could just get the legend box, split it into a number of rectangles equal to the number of rows in your legend, and draw one of the rectangles on-screen. Below we'll define two functions, one to get the data coordinates of the legend box, and another one to split them into chunks and draw a rectangle according to an index:
from matplotlib.patches import Rectangle
def get_legend_box_coord(ax, legend):
""" Returns coordinates of the legend box """
disp2data = ax.transData.inverted().transform
box = legend.legendPatch
# taken from here:
# https://stackoverflow.com/a/28728709/4118756
box_pixcoords = box.get_window_extent(ax)
box_xycoords = [disp2data(box_pixcoords.p0), disp2data(box_pixcoords.p1)]
box_xx, box_yy = np.array(box_xycoords).T
return box_xx, box_yy
def draw_sublegend_box(ax, legend, idx):
nitems = len(legend.legendHandles)
xx, yy = get_legend_box_coord(ax, legend)
# assuming equal spacing between legend items:
y_divisors = np.linspace(*yy, num=nitems+1)
height = y_divisors[idx]-y_divisors[idx+1]
width = np.diff(xx)
lower_left_xy = [xx[0], y_divisors[idx+1]]
legend_box = Rectangle(
xy = lower_left_xy,
width = width,
height = height,
fill = False,
zorder = 10)
ax.add_patch(legend_box)
Now, calling draw_sublegend_box(ax, legend, 1) produces the following plot:
Note that annotating the legend in such is way is only possible once the figure has been drawn.
In order to highlight a specific label, you could have it in bold.
Here's the link to another SO answer that suggest how to use Latex to format entries of a legend:
Styling part of label in legend in matplotlib

Matplotlib: Hiding specific y-tick labels not working when ticks on the right side of plot

I'm creating a subplot figure with 2 columns and a number of rows. I'm using the following code to move my tick labels and axis label to the right side for the right column (but still keeping the tick marks on both sides):
fig, ax = plt.subplots(4, 2, sharex=False, sharey=False)
fig.subplots_adjust(wspace=0, hspace=0)
for a in ax[:,1]:
a.yaxis.tick_right()
a.yaxis.set_ticks_position('both')
a.yaxis.set_label_position('right')
Then, because the subplots are close together (which is what I want, I don't want any padding in between the plots), the top and bottom y-tick labels overlap between plots. I have attempted to fix this using the method described here (this selects only those ticks that are inside the view interval - check the link for more info):
import matplotlib.transforms as mtransforms
def get_major_ticks_within_view_interval(axis):
interval = axis.get_view_interval()
ticks_in_view_interval = []
for tick, loc in zip(axis.get_major_ticks(), axis.get_major_locator()()):
if mtransforms.interval_contains(interval, loc):
ticks_in_view_interval.append(tick)
return ticks_in_view_interval
for i,a in enumerate(ax.ravel()):
nplots = len(ax.ravel())
yticks = get_major_ticks_within_view_interval(a.yaxis)
if i != 0 and i != 1:
yticks[-1].label.set_visible(False)
if i != nplots-2 and i != nplots-1:
yticks[0].label.set_visible(False)
This seems to work fine for the left column, but in the right column the overlapping ticks are still visible. Does anyone know why this happens, and how to fix it? I just can't seem to figure it out.
I have finally found the solution, so I figured I'd put it here as well in case someone ever has the same problem (or if I forget what I did, haha). I found out when I happened upon the following page: http://matplotlib.org/1.3.1/users/artists.html
What I didn't realize is that the labels on the left and the right of the y-axis can be modified independently of each other. When using yticks[0].label.set_visible(False), the label refers only to the left side labels, so the right side labels stay unchanged. To fix it, I replaced
yticks[0].label.set_visible(False)
by
yticks[0].label1.set_visible(False)
yticks[0].label2.set_visible(False)
(and the same for yticks[-1]). Now it works like a charm!
Generally I've found that problems with overlap in matplotlib can be solved by using
plt.tight_layout()
have you tried that?

're-sort' / adapt ticks of matshow matrix plot

I tried hard, but I'm stuck with matplotlib here. Please overlook, that the mpl docs are a bit confusing to me . My question concerns the following:
I draw a symmetrical n*n matrix D with matshow function. That works.
I want to do the same thing, just with different order of (the n) items in D
D = [:,neworder]
D = [neworder,:]
Now, how do I make the ticks reproduce this neworder, preferably using additionally MaxNLocator?
As far as I understand...
set_xticklabels assigns labels to the ticks by order, independently of where the ticks are set?!
set_xticks (mpl docs: 'Set the x ticks with list of ticks') here I'm really not sure what it does. Can somebody explain it precisely? I don't know, whether these functions are helpful in my case at all. Maybe even things are different between using a common xy plot and matshow.
import numpy as np
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.gca()
D = np.arange(100).reshape(10,10)
neworder = np.arange(10)
np.random.shuffle(neworder)
D = D[:,neworder]
D = D[neworder, :]
# modify ticks somehow...
ax.matshow(D)
plt.show()
Referring to Paul's answer, think I tried smth like this. Using the neworder to define positions and using it for the labels, I added plt.xticks(neworder, neworder) as tick-modifier. For example with neworder = [9 8 4 7 2 6 3 0 1 5] I get is this
The order of the labels is correct, but the ticks are not. The labels should be independently show the correct element independently of where the ticks are set. So where is the mistake?
I think what you want to do is set the labels on the new plot to show the rearranged order of the values. Is that right? If so, you want to keep the tick locations the same, but change the labels:
plt.xticks(np.arange(0,10), neworder)
plt.yticks(np.arange(0,10), neworder)
Edit: Note that these commands must be issued after matshow. This seems to be a quirk of matshow (plot does not show this behaviour, for example). Perhaps it's related to this line from the plt.matshow documentation:
Because of how :func:matshow tries to set the figure aspect ratio to be the
one of the array, if you provide the number of an already
existing figure, strange things may happen.
Perhaps the safest way to go is to issue plt.matshow(D) without first creating a figure, then use plt.xticks and plt.yticks to make adjustments.
Your question also asks about the set_ticks and related axis methods. The same thing can be accomplished using those tools, again after issuing matshow:
ax = plt.gca()
ax.xaxis.set_ticks(np.arange(0,10)) # turn on all tick locations
ax.xaxis.set_ticklabels(neworder) # use neworder for labels
Edit2: The next part of your question is related to setting a max number of ticks. 20 would require a new example. For our example I'll set the max no. of ticks at 2:
ax = plt.gca()
ax.xaxis.set_major_locator(plt.MaxNLocator(nbins=3)) # one less tick than 'bin'
tl = ax.xaxis.get_ticklocs() # get current tick locations
tl[1:-1] = [neworder[idx] for idx in tl[1:-1]] # find what the labels should be at those locs
ax.xaxis.set_ticklabels(tl) # set the labels
plt.draw()
You are on the right track. The plt.xticks command is what you need.
You can specify the xtick locations and the label at each position with the following command.
labelPositions = arange(len(D))
newLabels = ['z','y','x','w','v','u','t','s','q','r']
plt.xticks(labelPositions,newLabels)
You could also specify an arbitrary order for labelPositions, as they will be assigned based on the values in the vector.
labelPositions = [0,9,1,8,2,7,3,6,4,5]
newLabels = ['z','y','x','w','v','u','t','s','q','r']
plt.xticks(labelPositions,newLabels)

Categories