I'm using Shady to write some text on screen, and I'm wondering what would be the simplest way to control the alignment of the string. From my understanding, the align parameter in a Shady text object controls the paragraph alignment, but I'm interested in controlling the alignment of a single line of text.
Essentially I'd like to replicate the behavior of the horizontalalignment, verticalalignment and rotation parameters of the matplotlib text function. But to do that I need to estimate the area (in pixels) that will be occupied by the string once rendered. Can I get that out of Shady somehow? In the manual it says that the rendering is done on the CPU and the rendered String is then pushed to the GPU, so it should be doable.
You're correct that the .text.align and .text.wrapping properties are to do with "alignment" only at the logical level of the text flow—i.e., how the lines of a multi-line text stimulus are aligned relative to each other in the coordinate-frame in which they're read (independent of which way up the whole stimulus is physically).
The properties you're talking about—rotation, "vertical alignment", and even what you call "horizontal alignment" if there's only one line of text in play—are not text-specific properties: they could apply equally well to any rectangular patch. For this reason, the properties you want to manipulate are stim.* level properties, not stim.text.*. Specifically, they are .anchor and .rotation as demonstrated here:
#!/usr/bin/env python -m Shady shell
import Shady, Shady.Text
w = Shady.World(fullScreenMode=False)
axes = w.Stimulus(Shady.PixelRuler(1000), anchor=Shady.LOWER_LEFT, size=600)
xlabel = w.Stimulus(text='x axis label', x=300, y=0, anchor=Shady.TOP)
ylabel = w.Stimulus(text='y axis label', x=0, y=300, anchor=Shady.BOTTOM, rotation=90)
speed = 30
msg = w.Stimulus(
xy = 300,
rotation = Shady.Integral( lambda t: speed ),
text = 'Change msg.anchor to anything\nbetween [-1,-1] and [+1,+1]\nand see what happens',
text_blockbg = [0, 0, 0, 0.5],
)
Shady.AutoFinish(w)
Somewhere in the undocumented functions of Shady.Text there is probably some way of estimating, in advance, what the size of a rendered text stimulus is going to be. In fact, on closer examination, it looks like the least annoying way to do it would be to actually make the texture array:
img = Shady.Text.MakeTextImage('hello world')
heightInPixels, widthInPixels, _ = img.shape
But hopefully with the appropriate usage of .anchor you should no longer need this.
Related
When creating 2d-images with pyplot by using pcolor/pcolormesh I am using custom labels in my project. Unfortunately, some of those labels are too long to fit into their allocated space, and therefore spill over the next label, as shown below for a 2x2-matrix:
and a 4x4-matrix:
I tried to find solutions for that issue, and until now have found two possible approaches: Replacing all spaces with newlines (as proposed by https://stackoverflow.com/a/62521738/2546099), or using textwrap.wrap(), which needs a fixed text width (as proposed by https://stackoverflow.com/a/15740730/2546099 or https://medium.com/dunder-data/automatically-wrap-graph-labels-in-matplotlib-and-seaborn-a48740bc9ce)
I tested both approaches for both test matrices shown above, resulting in (when replacing space with newlines) for the 2x2-matrix:
and for the 4x4-matrix:
Similarly, I tested the second approach for both matrices, the 2x2-matrix, resulting in
and the 4x4-matrix, resulting in
Still, both approaches share the same issues:
When wrapping the label, a part of the label will extend into the image. Setting a fixed pad size will only work if the label size is known beforehand, to adjust the distance properly. Else, some labels might be spaced too far apart from the image, while others are still inside the image.
Wrapping the label with a fixed size might leave too much white space around. The best case might be figure 5 and 6: I used a wrap at 10 characters for both figures, but while the text placement works out nicely (from my point of view) for four columns, there is enough space to increase the wrap limit to 20 characters for two columns. For even more columns 10 characters might even be too much.
Therefore, are there other solutions which might solve my problem dynamically, depending on the size of the figure and the available space?
The code I used for generating the pictures above is:
import textwrap
import numpy as np
import matplotlib.pyplot as plt
imshow_size = 4
x_vec, y_vec = (
np.linspace(0, imshow_size - 1, imshow_size),
np.linspace(0, imshow_size - 1, imshow_size),
)
labels_to_plot = np.linspace(0, imshow_size, imshow_size + 1)
categories = ["This is a very long long label serving its job as place holder"] * (
imshow_size
)
## Idea 1: Replace space with \n, similar to https://stackoverflow.com/a/62521738/2546099
# categories = [category.replace(" ", "\n") for category in categories]
## Idea 2: Wrap labels at certain text length, similar to https://stackoverflow.com/a/15740730/2546099 or
## https://medium.com/dunder-data/automatically-wrap-graph-labels-in-matplotlib-and-seaborn-a48740bc9ce
categories = [textwrap.fill(category, 10) for category in categories]
X, Y = np.meshgrid(x_vec, y_vec)
Z = np.random.rand(imshow_size, imshow_size)
fig, ax = plt.subplots()
ax.pcolormesh(X, Y, Z)
ax.set_xticks(labels_to_plot[:-1])
ax.set_xticklabels(categories, va="center")
ax.set_yticks(labels_to_plot[:-1])
ax.set_yticklabels(categories, rotation=90, va="center")
fig.tight_layout()
plt.savefig("with_wrap_at_10_with_four_labels.png", bbox_inches="tight")
# plt.show()
I have a suptitle object that will sometimes be wrapped, using the built in wrapping functionality of Matplotlib. However, when trying to get the height of the suptitle, I seem to always get the height corresponding to one line. Where am I going wrong? This is what I'm trying with:
from matplotlib.figure import Figure
from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas
fig = Figure((4, 4))
FigureCanvas(fig)
text_1 = "I'm a short text"
text_2 = "I'm a longer text that will be wrapped autoamtically by Matplotlib, using wrap=True"
title = fig.suptitle(text_1, wrap=True)
fig.canvas.draw() # Draw text to find out how big it is
bbox = title.get_window_extent()
print(bbox.width) # 105
print(bbox.height) # 14
title = fig.suptitle(text_2, wrap=True)
fig.canvas.draw() # Draw text to find out how big it is
bbox = title.get_window_extent()
print(bbox.width) # 585 <-- This looks about right
print(bbox.height) # Still 14 even though this time the text is wrapped!
The same thing happens with Text objects (using something like fig.text(0.5, 0.5, text_1, wrap=True).
Thank you #ImportanceOfBeingErnest for pointing out that this is not really possible. Here is one workaround, that kind of works, by checking the number of lines the text is broken up into, and multiplying by the approximate line-height. This works when automatically inserted breaks are mixed with manually (i.e. there is an "\n" in the text), but will be off by a number of pixels. Any more precise suggestions welcome.
def get_text_height(fig, obj):
""" Get the approximate height of a text object.
"""
fig.canvas.draw() # Draw text to find out how big it is
t = obj.get_text()
r = fig.canvas.renderer
w, h, d = r.get_text_width_height_descent(t, obj._fontproperties,
ismath=obj.is_math_text(t))
num_lines = len(obj._get_wrapped_text().split("\n"))
return (h * num_lines)
text = "I'm a long text that will be wrapped automatically by Matplotlib, using wrap=True"
obj = fig.suptitle(text, wrap=True)
height = get_text_height(fig, obj)
print(height) # 28 <-- Close enough! (In reality 30)
How can the line width of the error bar caps in Matplotlib be changed?
I tried the following code:
(_, caplines, _) = matplotlib.pyplot.errorbar(
data['distance'], data['energy'], yerr=data['energy sigma'],
capsize=10, elinewidth=3)
for capline in caplines:
capline.set_linewidth(10)
capline.set_color('red')
pp.draw()
Unfortunately, this updates the color of the caps, but does not update the line width of the caps!
The resulting effect is similar to the "fat error bar lines / thin caps" in the following image:
It would be nice to have "fat" bar caps, in the case; how can this be done, in Matplotlib? Drawing the bar caps "manually", one by one with plot() would work, but a simpler alternative would be best.
EOL, you were very close..,
distance = [1,3,7,9]
energy = [10,20,30,40]
sigma = [1,3,2,5]
(_, caps, _) = plt.errorbar(distance, energy, sigma, capsize=20, elinewidth=3)
for cap in caps:
cap.set_color('red')
cap.set_markeredgewidth(10)
plt.show
set_markeredgewidth sets the width of the cap lines.
Matplotlib objects have so many attributes that often it is difficult to remember the right ones for a given object. IPython is a very useful tool for introspecting matplotlib. I used it to analyze the properties of the 2Dlines correponding to the error cap lines and I found that and other marker properties.
Cheers
This is based on #joaquin's answer, but a little more concise (if you just want plain error caps with no special styling):
distance = [1,3,7,9]
energy = [10,20,30,40]
sigma = [1,3,2,5]
plt.errorbar(distance,
energy,
sigma,
capsize=5,
elinewidth=2,
markeredgewidth=2)
In matplotlib, what is a way of converting the text box size into data coordinates?
For example, in this toy script I'm fine-tuning the coordinates of the text box so that it's next to a data point.
#!/usr/bin/python
import matplotlib.pyplot as plt
xx=[1,2,3]
yy=[2,3,4]
dy=[0.1,0.2,0.05]
fig=plt.figure()
ax=fig.add_subplot(111)
ax.errorbar(xx,yy,dy,fmt='ro-',ms=6,elinewidth=4)
# HERE: can one get the text bbox size?
txt=ax.text(xx[1]-0.1,yy[1]-0.4,r'$S=0$',fontsize=16)
ax.set_xlim([0.,3.4])
ax.set_ylim([0.,4.4])
plt.show()
Is there a way of doing something like this pseudocode instead?
x = xx[1] - text_height
y = yy[1] - text_width/2
ax.text(x,y,text)
Generally speaking, you can't get the size of the text until after it's drawn (thus the hacks in #DSM's answer).
For what you're wanting to do, you'd be far better off using annotate.
E.g. ax.annotate('Your text string', xy=(x, y), xytext=(x-0.1, y-0.4))
Note that you can specify the offset in points as well, and thus offset the text by it's height (just specify textcoords='offset points')
If you're wanting to adjust vertical alignment, horizontal alignment, etc, just add those as arguments to annotate (e.g. horizontalalignment='right' or equivalently ha='right')
I'm not happy with it at all, but the following works; I was getting frustrated until I found this code for a similar problem, which suggested a way to get at the renderer.
import matplotlib.pyplot as plt
xx=[1,2,3]
yy=[2,3,4]
dy=[0.1,0.2,0.05]
fig=plt.figure()
figname = "out.png"
ax=fig.add_subplot(111)
ax.errorbar(xx,yy,dy,fmt='ro-',ms=6,elinewidth=4)
# start of hack to get renderer
fig.savefig(figname)
renderer = plt.gca().get_renderer_cache()
# end of hack
txt = ax.text(xx[1], yy[1],r'$S=0$',fontsize=16)
tbox = txt.get_window_extent(renderer)
dbox = tbox.transformed(ax.transData.inverted())
text_width = dbox.x1-dbox.x0
text_height = dbox.y1-dbox.y0
x = xx[1] - text_height
y = yy[1] - text_width/2
txt.set_position((x,y))
ax.set_xlim([0.,3.4])
ax.set_ylim([0.,4.4])
fig.savefig(figname)
OTOH, while this might get the text box out of the actual data point, it doesn't necessarily get the box out of the way of the marker, or the error bar. So I don't know how useful it'll be in practice, but I guess it wouldn't be that hard to loop over all the drawn objects and move the text until it's out of the way. I think the linked code tries something similar.
Edit: Please note that this was clearly a courtesy accept; I would use Joe Kington's solution if I actually wanted to do this, and so should everyone else. :^)
I want my widget to look exactly like it does now, except to be smaller. It includes buttons, labels, text, images, etc. Is there any way to just say "scale this to be half the size", and have GTK do all the image processing, widget resizing, etc., necessary? If not, what's the easiest way to accomplish this?
Resolution independence has been worked on by some gtk devs, and here is an update with a very big patch to introduce it into GTK. The patch is however a year old now and it is still unclear how/when/if it is going to be included: (screenshots at the end)
http://mail.gnome.org/archives/gtk-devel-list/2008-August/msg00044.html
Change the theme from the user interface is not something that I recommend, but you can do it if you require it, using a custom gtkrc may help you to change the font and the way the buttons are drawed, mostly because of the xthickness and ythickness.
import gtk
file = "/path/to/the/gtkrc"
gtk.rc_parse(file)
gtk.rc_add_default_file(file)
gtk.rc_reparse_all()
And the custom gtkrc may look like this:
gtk_color_scheme = "fg_color:#ECE9E9;bg_color:#ECE9E9;base_color:#FFFFFF;text_color:#000000;selected_bg_color:#008DD7;selected_fg_color:#FFFFFF;tooltip_bg_color:#000000;tooltip_fg_color:#F5F5B5"
style "theme-fixes" {
fg[NORMAL] = #fg_color
fg[PRELIGHT] = #fg_color
fg[SELECTED] = #selected_fg_color
fg[ACTIVE] = #fg_color
fg[INSENSITIVE] = darker (#bg_color)
bg[NORMAL] = #bg_color
bg[PRELIGHT] = shade (1.02, #bg_color)
bg[SELECTED] = #selected_bg_color
bg[INSENSITIVE] = #bg_color
bg[ACTIVE] = shade (0.9, #bg_color)
base[NORMAL] = #base_color
base[PRELIGHT] = shade (0.95, #bg_color)
base[ACTIVE] = shade (0.9, #selected_bg_color)
base[SELECTED] = #selected_bg_color
base[INSENSITIVE] = #bg_color
text[NORMAL] = #text_color
text[PRELIGHT] = #text_color
text[ACTIVE] = #selected_fg_color
text[SELECTED] = #selected_fg_color
text[INSENSITIVE] = darker (#bg_color)
GtkTreeView::odd_row_color = shade (0.929458256, #base_color)
GtkTreeView::even_row_color = #base_color
GtkTreeView::horizontal-separator = 12
font_name = "Helvetica World 7"
}
class "*" style "theme-fixes"
There is no built in way to do this. To do this, you'll have to consider what is "taking up space" in your ui, and how to reduce it.
If your UI is mostly text and images, you can use a smaller font size, then scale all images down by an appropriate percentage. The widget sizing will shrink automatically once the text and images that they are displaying shrinks (unless you've done Bad Things like hardcode heights/widths, use GtkFixed, etc).
The tricky part will be determining the relationship between font point size and image scale.
EDIT:
Here's a post about the pygtk syntax to change the font size.
Having written a 100% scalable gtk app, what I did was limit myself to gdk_draw_line, and gdk_draw_rectangle, which were easy to then scale myself. Text was "done" via gdk_draw_line. (for certain low values of "done.") See: http://wordwarvi.sourceforge.net
Not that it helps you any, I'm guessing.