How to change the size of individual legend label size? - python

As shown in the figure, the same font size for Greek letters seems smaller than normal characters. I want to make them looks the same size, how to achieve this?
The code of the graph is as follows:
import numpy as np
import math
import matplotlib.pyplot as plt
alpha=np.arange(0,1,0.01)
gamma=np.sin(2*np.pi*alpha)
x=alpha
y=np.cos(2*np.pi*x)
plt.plot(x,y,label=r'cosine function')
plt.plot(alpha,gamma,label=r'$\gamma=\sin(\alpha)$')
plt.legend(loc=0,fontsize=20)
plt.show()

There's a little bit of a trick to this. Scroll down to the end if you're just interested in the solution.
plt.legend returns a Legend object with methods that allow you to modify the appearance of the legend. So first we'll save the Legend object:
legend = plt.legend(loc=0, fontsize=20)
The method we are looking for is Legend.get_texts(). This will return a list of Text objects whose methods control the size, color, font, etc. of the legend text. We only want the second Text object:
text = legend.get_texts()[1]
The Text object has a method called Text.set_fontsize. So let's try that. Altogether, the end of your code should look like:
legend = plt.legend(loc=0,fontsize=20)
text = legend.get_texts()[1]
text.set_fontsize(40)
And this is what we get:
Hm. It looks like both of the legend entries have been made bigger. This certainly isn't what we want. What is going on here, and how do we fix it?
The short of it is that the size, color, etc. of each of the legend entries are managed by an instance of a FontProperties class. The problem is that the two entries share the same instance. So setting the size of one instance also changes the size of the other.
The workaround is to create a new, independent instance of the font properties, as follows. First, we get our text, just as before:
text = legend.get_texts()[1]
Now, instead of setting the size immediately, we get the font properties object, but then make sure to copy it:
props = text.get_font_properties().copy()
Now we make this new, independent font properties instance our text's properties:
text.set_fontproperties(props)
And we can now try setting this legend entry's size:
text.set_size(40)
Solution
The end of your code should now look like:
legend = plt.legend(loc=0,fontsize=20)
text = legend.get_texts()[1]
props = text.get_font_properties().copy()
text.set_fontproperties(props)
text.set_size(40)
Producing a plot looking like

Related

Label text position in Bokeh

I would like to stick MyText Label to the bottom right part of my figure
for a given text and a given font size (as shown on the picture for 'this is super fun', font size of '20px' and with tiny characters. I found the good position by dichotomy ).
What is the function position I need to pass to x ?
This should depends on len(MyText), text_font_size and figure width ...
from bokeh.models import ColumnDataSource, Label, LabelSet, Range1d
from bokeh.plotting import figure, output_file, show
width,height=400,300
p = figure(plot_width=width, plot_height=height)
MyText='this is super fun'
my_font_size = "20px"
labels = Label(x=width/2+25, y=0,x_units='screen', y_units='screen', text=MyText,text_font_size=my_font_size)
p.add_layout(labels)
show(p)
I don't think there is any 100% robust way to do this, actually.
You can set the text_align to "right" which helps:
p = figure(plot_width=width, plot_height=height)
labels = Label(x=width-50, y=0,
x_units="screen", y_units='screen', text_align="right",
text=MyText,text_font_size=my_font_size)
Note the -50 above is to account (roughly) for the width of the space to the right of the "plot area" (i.e where the toolbar is). However if you add a y-axis on the left side, you'd need to account for that too, and if you allow zooming, then left space can grow and shrink to accommodate bigger or smaller axis labels, which means you can't reliably account for that space with a single constant up front. You could set min_border values to be larger, which might mitigate the problem for some range of zooming/panning.
Also the above assumes the plot sizing mode is not "responsive". If the plot itself can resize then no constant value in screen units will ever work.
If you can fix your x range start/end (or add an "extra" range), then you could right-align to the range end value using "data" units. But if you allow zooming or panning then the label will move to stay fixed at that data position.
The main issue is that the "inner_width" is only computed in the browser. It's not available to the Python code because it doesn't exist outside the browser. What's really needed is some special convention or confguration to designate "inner_width" as a symbolic concept that updates to whatever is necessary, regardless of panning or zooming or resizing. I'd suggest making a GitHub issue to propose this feature.
In the mean time, I think any solution will involve some trial and error with a fixed font size in "px" and also ideally limiting panning/zooming if possible.

Get all objects (artists) drawn on a figure

I'm hoping to setup a method which can convert a normal figure (dark lines, white/transparent background) to a pseudo-inverted figure (light lines, black/transparent background). I could just post-process invert the image, but directly inverted colors look awful, so I'd like to instead (try to) create a mapping from one set of colors to another, and then apply this to all artists which have been added to (all axes on) a figure.
Is there a way to access all objects (e.g. text, scatter, lines, ticklabels, etc) that have been added to a figure?
Edit: my motivation is to automatically create white-background and black-background versions of figures. White-background figures will always (I think) be required for publications (for example), while black-background figures may be better for presentations (i.e. talk slides). While it wouldn't be that much trouble to setup a flag, and change each color based on that, e.g.
if dark:
col_line = 'cyan'
col_bg = 'black'
else:
col_line = 'red'
col_bg = 'white'
# ... plot ...
It would be much cooler and more convenient (despite overhead) to do something like,
fig.savefig('dark.pdf')
invert(fig)
fig.savefig('light.pdf')
Recursively call .get_children(), stop when the returned list is empty.
You can use a different style or change an existing style to your needs instead of changing all properties of all possible artists yourself.
E.g. you might start with the "dark_background" style and then adjust some further parameters using rcParams.
import numpy as np
import matplotlib.pyplot as plt
plt.style.use("dark_background")
style = {"lines.linewidth" : 2,
"axes.facecolor" : "#410448"}
plt.rcParams.update(style)
plt.plot(np.sin(np.linspace(0, 2 * np.pi)))
plt.show()

How can i set the location of an axis label in terms of locations on said axis?

I would like to move my axis label Number of States to the left, so that it is actually over the numbers i have. Other similar questions/answers have suggested using labelpad, but this shifts the text up or down, not left/right. How can i move my title to the right?
i've also tried the horizontalalignment kwarg, which a. seems to have the right and left alignments reversed, and also does not move the title far enough, nor offer any actual control on where exactly it goes.
i see that i can set the _x and _y properties of the Text instance, using set_[xy](), but it seems a bit hacky. Is there a convenient way i can set the location of hte title relative to a value on the xaxis?
You can grap the title object and set its respective position property to whatever you like.
A simple example could be:
%matplotlib inline
import matplotlib.pyplot as plt
plt.plot([1,2,3])
ti=plt.title('foo')
ti.set_position((0.2,1))
which creates a plot like
Note that the position is set in relative coordinates.
The position argument suggested by #ThePredator (loc : {‘center’, ‘left’, ‘right’}, see docs also works in a similar fashion.
Update
To set the position of the text in the data coordinate system instead of the axis coordinates, a simple transformation can be used. For details have a look at the Transformation Documentation. A minimal example could look like:
%matplotlib inline
import matplotlib.pyplot as plt
f,ax = plt.subplots(1)
ax.plot([1,2,3])
ti=ax.set_title('foo')
ti.set_position(ax.transLimits.transform((0.5,3)))
This places the title centered at (0.5, 3) as shown in the following plot

how to remove generic content of matplotlib regardless of type plotted

I've looked around for a while and found a number of clever ways to remove content from a plot.
e.g. lines = blah, del lines[0], line gone.
but this obviously doesn't work for histograms, or imshow for instance.
Is there a way to clear the plotting area (i.e. not using clf()) without having to regenerate the entire figure. This requires you to have no strict knowledge of what is currently plotted but still be able to remove it. I am developing an application which uses the same area for plotting and I want a generic way to remove the content for different types of graphs I want to display. Some lines, some scatter, some hist etc.
Thanks.
Everything you can display on an axes is a subclass of Artist and have a member function remove (doc). All of the plotting functions (should) return the artist (or list of artists) that they add to the axes. Ex:
ln, = ax.plot(...) # adds a line
img = ax.imshow( ) # adds an image
ln.remove() # removes the line
img.remove() # removes the image
You will have to re-draw the canvas (plt.draw() or ax.figure.canvas.draw()) before the changes are visible.
You can get a list of all the artists in a given axes with
artist_list = ax.get_children()
If you want to remove everything from an axis,
ax.cla()

Matplotlib adding overlay labels to an axis

In matplotlib I wish to know the cleanest and most robust means of overlaying labels onto an axis. This is probably best demonstrated with an example:
While normal axis labels/ticks are placed every 5.00 units additional labels without ticks have been overlayed onto the axis (this can be seen at 1113.75 which partially covers 1114.00 and 1105.00 which is covered entirely). The labels also have the same font and size as their normal, ticked, counterparts with the background (if any) going right up to the axis (as a tick mark would).
What is the simplest way of obtaining this effect in matplotlib?
Edit
Following on from #Ken's suggestion I have managed to obtain the effect for an existing tick/label by using ax.yaxis.get_ticklines and ax.yaxis.get_ticklabels to both remove the tick marker and change the background/font/zorder of a label. However, I am unsure how best to add a new tick/label to an axis.
In other words I am looking for a function add_tick(ax.yaxis, loc) that adds a tick at location loc and returns the tickline and ticklabel objects for me to operate on.
I haven't ever tried to do that, but I think that the Artist tutorial might be helpful for you. In particular, the last section has the following code:
for line in ax1.yaxis.get_ticklines():
# line is a Line2D instance
line.set_color('green')
line.set_markersize(25)
line.set_markeredgewidth(3)
I think that using something like line.set_markersize(0) might make the markers have size zero. The difficult part might be finding the ones that need that done. It is possible that the line.xdata or line.ydata arrays might contain enough information to isolate the ones you need. Of course, if you are manually adding the tick marks, it is possible that as you do that the instance gets returned, so you can just modify them as you create them.
The best solution I have been able to devise:
# main: axis; olocs: locations list; ocols: location colours
def overlay_labels(main, olocs, ocols):
# Append the overlay labels as ticks
main.yaxis.set_ticks(np.append(main.yaxis.get_ticklocs(), olocs))
# Perform generic formatting to /all/ ticks
# [...]
labels = reversed(main.yaxis.get_ticklabels())
markers = reversed(main.yaxis.get_ticklines()[1::2]) # RHS ticks only
glines = reversed(main.yaxis.get_gridlines())
rocols = reversed(ocols)
# Suitably format each overlay tick (colours and lines)
for label,marker,grid,colour in izip(labels, markers, glines, rocols):
label.set_color('white')
label.set_backgroundcolor(colour)
marker.set_visible(False)
grid.set_visible(False)
It is not particularly elegant but does appear to work.

Categories