I need to add a checkbox to a python chart, to check the lines to be displayed or hidden.
I found the code below that works just fine, except one detail, that is annoyingly difficult for me to find out, I already have a few weeks since I am trying to do that: besides the checkboxes and the series names, I would like to also have the line colors along to the text, to be able to visually identify which line is which series.
Initially I tried to show the legend over the text, next to the checkboxes, but the legend would move if the window is resized, and that would not be feasible to implement.
My feeling is that there should be a way to add the line colors display within the function that creates the checkboxes, can anyone give me some suggestions on how to do that? The function is:
def func(label):
index = labels.index(label)
lines[index].set_visible(not lines[index].get_visible())
plt.draw()
The link with the full code is:
https://matplotlib.org/3.1.0/gallery/widgets/check_buttons.html
for idx, text in enumerate(check.labels):
text.set_color(lines[idx].get_color())
Add this bit of code after instantiating CheckButtons. It will color the text like you want it to.
Old, worse solution:
labels = [str(line.get_label()) + ", " + str(line.get_color()) for line in lines]
Related
I have been trying to make a figure using plotly that combines multiple figures together. In order to do this, I have been trying to use the make_subplots function, but I have found it very difficult to have the plots added in such a way that they are properly formatted. I can currently make singular plots (as seen directly below):
However, whenever I try to combine these singular plots using make_subplots, I end up with this:
This figure has the subplots set up completely wrong, since I need each of the four subplots to contain data pertaining to the four methods (A, B, C, and D). In other words, I would like to have four subplots that look like my singular plot example above.
I have set up the code in the following way:
for sequence in sequences:
#process for making sequence profile is done here
sequence_df = pd.DataFrame(sequence_profile)
row_number=1
grand_figure = make_subplots(rows=4, cols=1)
#there are four groups per sequence, so the grand figure should have four subplots in total
for group in sequence_df["group"].unique():
figure_df_group = sequence_df[(sequence_df["group"]==group)]
figure_df_group.sort_values("sample", ascending=True, inplace=True)
figure = px.line(figure_df_group, x = figure_df_group["sample"], y = figure_df_group["intensity"], color= figure_df_group["method"])
figure.update_xaxes(title= "sample")
figure.update_traces(mode='markers+lines')
#note: the next line fails, since data must be extracted from the figure, hence why it is commented out
#grand_figure.append_trace(figure, row = row_number, col=1)
figure.update_layout(title_text="{} Profile Plot".format(sequence))
grand_figure.append_trace(figure.data[0], row = row_number, col=1)
row_number+=1
figure.write_image(os.path.join(output_directory+"{}_profile_plot_subplots_in_{}.jpg".format(sequence, group)))
grand_figure.write_image(os.path.join(output_directory+"grand_figure_{}_profile_plot_subplots.jpg".format(sequence)))
I have tried following directions (like for example, here: ValueError: Invalid element(s) received for the 'data' property) but I was unable to get my figures added as is as subplots. At first it seemed like I needed to use the graph object (go) module in plotly (https://plotly.com/python/subplots/), but I would really like to keep the formatting/design of my current singular plot. I just want the plots to be conglomerated in groups of four. However, when I try to add the subplots like I currently do, I need to use the data property of the figure, which causes the design of my scatter plot to be completely messed up. Any help for how I can ameliorate this problem would be great.
Ok, so I found a solution here. Rather than using the make_subplots function, I just instead exported all the figures onto an .html file (Plotly saving multiple plots into a single html) and then converted it into an image (HTML to IMAGE using Python). This isn't exactly the approach I would have preferred to have, but it does work.
UPDATE
I have found that plotly express offers another solution, as the px.line object has the parameter of facet that allows one to set up multiple subplots within their plot. My code is set up like this, and is different from the code above in that the dataframe does not need to be iterated in a for loop based on its groups:
sequence_df = pd.DataFrame(sequence_profile)
figure = px.line(sequence_df, x = sequence_df["sample"], y = sequence_df["intensity"], color= sequence_df["method"], facet_col= sequence_df["group"])
Although it still needs more formatting, my plot now looks like this, which is works much better for my purposes:
I'm developing a map visualization program in Python using several modules from qtpy. There is a main window interface which displays a background map containing several geolocated points on the screen. The location of each point is determined by an external .csv file that has information regarding the latitude, longitude, and other text attribution. This file gets read-in by the program each time the map window is instantiated. The color of each point defaults to red when the map window is opened, but I would like to have each point change to a different color based on its metadata stored in the .csv file. For instance, there is a header in the file called "color", and each point has the text string "red", "green" or "blue" encoded. Here is the section of code I've been working on so far...
# Initialize all points to default color.
color = QtCore.Qt.red
for i, p in zip(range(len(self.points)), self.points):
if lb_lat <= stn_lat and stn_lat <= ub_lat and window_rect.contains(*self.transform.map(stn_x, stn_y)):
if p['color'] == 'green':
color = QtCore.Qt.green
elif p['color'] == 'blue':
color = QtCore.Qt.blue
elif p['color'] == 'red':
color = QtCore.Qt.red
else:
color = QtCore.Qt.white
qp.setPen(QtGui.QPen(color, self.scale))
qp.setBrush(QtGui.QBrush(color))
qp.drawEllipse(QtCore.QPointF(stn_x, stn_y), size, size)
The list of points is stored in the variable self.points and I'm trying to iterate through this list and apply the correct color to each point using QtGui.QPen and QBrush. What is happening is that if the color attribute in the .csv file for point 1 has the text string "green", then the entire array of points changes to green instead of just that one point. Looking at the code after the if...else statements, I haven't been able to find a way to "index" the setPen and setBrush commands for just the point in question. The coloring methods are acting on the entire array of points as one indivisible unit instead of working on each point separately as intended. Would anyone perhaps know of a way to do this using the Qt framework? Please let me know if supplying additional code might help clarify the problem or give better context as I'd be happy to do that.
I was able to solve the issue I had by removing the looping construct where I was iterating through the items in self.points. I had a higher-level "for" loop already in place and this was causing the incorrect array index to be referenced each time the points were being drawn to the screen. Each point is now changing to the appropriate color.
I'm printing a title in python and I want it on the center of the screen.
I know I can do it by using
"{:^50}".format("Title")
But the thing with this command is it only utilizes the width I give in (in this case, 50). But it isn't perfect and is sometimes way off. Even if I approximate the width by observing/guessing, it would go out of format if I re-size the terminal. I always want to align it on the middle of the screen, even when the terminal is re-sized(say, in fullscreen mode). Any ways I can achieve this?
EDIT:
I have did this:
Well, I figured out the way to find the window size,
import os
columns = os.popen('stty size', 'r').read().split()[0]
"{:^"+columns+"}".format("Title")
but the last line shows error. I finally have the window size, but I cannot format it correctly. Any help is appreciated!
As zondo pointed out, the title won't reposition when the window is resized.
The correct way to do this: "{:^"+columns+"}".format("Title") is like so:
"{:^{}}".format("Title", width)
#^---------------^ first argument goes with first brace
# ^--------------------^ second argument goes with second brace and sets the width
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'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()