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.
Related
I am using Altair to create a mark_point plot with the slightly strange combination (as far as I can tell via searching) of clip=False and .interactive(). This allows me to pan and zoom the axes while the points themselves are allowed to leave the axes bounding box and stay visible.
An unintuitive side effect of this is that the plot title moves its position to get out of the way of the points as they leave the axes - to the point where the title will in fact leave the screen altogether if I pan down far enough. I would like to have the title stay in a fixed position regardless of the contents of a mark moving around outside the axes.
It seems like this should be possible via configure_title but I can't figure it out. The frame keyword controls the reference for the anchor, but there is no option for using screen pixels instead of the data. I checked the Vega documentation for Title and it does not appear like there are any relevant properties that Altair is not controlling. Weirdly, the axis labels don't have this problem: they stay in their place even as the points move over them. I would like the title to behave the same way.
Here is a very simple MRE that creates a plot with this problem:
import altair as alt
import pandas as pd
import numpy as np
df = pd.DataFrame({'x': np.linspace(0, 4, 100)})
df['y'] = np.sin(2 * np.pi * df['x'])
chart = alt.Chart(df, title='TITLE HERE').mark_point(filled=True, size=100, clip=False).encode(
x='x:Q', y='y:Q').interactive()
chart = chart.configure_title(frame='group')
The obvious workaround is to not use a chart title and instead use a mark_text with clip=False and fixed pixel coordinates at the top of the window to manually make my own title, but I would rather not have to jump through that hoop every time I make a plot.
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
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
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.
I am trying to right-align the entries in a matplotlib axes legend (by default they are left-aligned), but can't seem to find any way of doing this. The setup I have is below:
(I have added data and labels to my_fig axes using the ax.plot() command)
ax = my_fig.get_axes()[0]
legend_font = FontProperties(size=10)
ax.legend(prop=legend_font, num_points=1, markerscale=0.5)
There is a list of legend keyword arguments in the docs for matplotlib Axes, but there doesn't seem to be any straighforward way to set the alignment of the legend entries there. Anybody know of a backdoor way of doing this? Thanks.
EDIT:
To clarify what I am trying to achieve, right now my legend looks like:
Maneuver: 12-OCT-2011 12:00 UTC
Bias: 14-OCT-2011 06:00 UTC
I want it to look like:
Maneuver: 12-OCT-2011 12:00 UTC
Bias: 14-OCT-2011 06:00 UTC
The backdoor you're looking for is the following:
# get the width of your widest label, since every label will need
# to shift by this amount after we align to the right
shift = max([t.get_window_extent().width for t in legend.get_texts()])
for t in legend.get_texts():
t.set_ha('right') # ha is alias for horizontalalignment
t.set_position((shift,0))
I tried to get the example work, but I couldn't.
At least since matplotlib version 1.1.1 (maybe earlier) we need a dedicated renderer instance.
Take care of your backend which defines the renderer. Depending on backend the output may look fine on screen but dismal as PDF.
# get the width of your widest label, since every label will need
#to shift by this amount after we align to the right
renderer = figure.canvas.get_renderer()
shift = max([t.get_window_extent(renderer).width for t in legend.get_texts()])
for t in legend.get_texts():
t.set_ha('right') # ha is alias for horizontalalignment
t.set_position((shift,0))
The answer of #Paul Ivanov took me into the right direction. But it needed a slight adaptation for me:
max_shift = max([t.get_window_extent().width for t in legend_obj.get_texts()])
for t in legend_obj.get_texts():
t.set_ha('right') # ha is alias for horizontalalignment
temp_shift = max_shift - t.get_window_extent().width
t.set_position((temp_shift, 0))
The change means that we set a different shift for each object based on its own width and the max width of the legend text.
For those that get the Cannot get window extent w/o renderer error, add a plt.pause(0.1) :)