Matplotlib - Several lines on the same plot - python

I am converting some old Python 2.7 code to 3.6.
My routine plots the first line OK but subsequent lines seem to start where the previous line left off. (Running on-line at www.pythonanywhere.com)
My code:
import matplotlib
from matplotlib import pyplot;
k = 0
while k < len(Stations):
# Draw the graph
fig.patch.set_facecolor('black') # Outside border
pyplot.rcParams['axes.facecolor'] = 'black' # Graph background
pyplot.rcParams['axes.edgecolor'] = 'red'
pyplot.tick_params(axis='x', colors='yellow')
pyplot.tick_params(axis='y', colors='yellow')
pyplot.ylim(float(BtmLimit),float(TopLimit))
pyplot.ylabel("Percent of normal range.", size=10, color = "yellow")
pyplot.xticks([]) # Hide X axis
pyplot.title("Plotted at %sGMT, %s %s %s" % (thour, tday, tdate, tmonth), color = "yellow")
if Error == 'False': pyplot.plot(Epoch, Scaled, color = (Color), linewidth=1.9)
pyplot.plot(Epoch, Top, color = [0,0.5,0]) # Green lines
pyplot.plot(Epoch, Btm, color = [0,0.5,0])
k = k + 1
pyplot.savefig(SD+'RiverLevels.png', facecolor='black', bbox_inches='tight')
pyplot.show()
pyplot.close()
The data looks like this:
Epoch
['1638046800', '1638047700', '1638048600', '1638049500', '1638050400', '1638051300', '1638052200', '1638053100', '1638054000', '1638054900', '1638
055800', '1638056700', '1638057600', '1638058500', '1638059400', '1638060300', '1638061200', '1638062100', '1638063000', '1638063900', '1638064800
', '1638065700', '1638066600', '1638067500', '1638068400', '1638069300', '1638070200', '1638071100', '1638072000', '1638072900', '1638073800', '16
38074700', '1638075600', '1638076500', '1638077400', '1638078300', '1638079200', '1638080100', '1638081000', '1638081900', '1638082800', '16380837
00', '1638084600', '1638085500', '1638086400', '1638087300', '1638088200', '1638089100', '1638090000', '1638090900', '1638091800', '1638092700', '
1638093600', '1638094500', '1638095400']
Scaled
['32.475247524752476', '33.069306930693074', '33.76237623762376', '33.56435643564357', '33.56435643564357', '33.86138613861387', '34.1584158415841
6', '34.35643564356436', '34.554455445544555', '34.554455445544555', '34.75247524752476', '34.95049504950495', '35.049504950495056', '35.148514851
48515', '35.049504950495056', '35.14851485148515', '35.44554455445545', '35.54455445544555', '35.54455445544555', '35.34653465346535', '35.5445544
5544555', '35.64356435643565', '35.84158415841585', '35.742574257425744', '35.54455445544555', '35.44554455445545', '35.44554455445545', '35.34653
465346535', '35.24752475247525', '35.049504950495056', '34.95049504950495', '34.95049504950495', '34.851485148514854', '34.65346534653466', '34.35
643564356436', '34.15841584158416', '34.35643564356436', '34.35643564356436', '34.25742574257426', '34.05940594059406', '33.86138613861387', '33.6
63366336633665', '33.86138613861387', '33.663366336633665', '33.663366336633665', '33.46534653465347', '33.366336633663366', '33.56435643564357',
'33.663366336633665', '33.663366336633665', '33.663366336633665', '33.663366336633665', '33.960396039603964', '34.05940594059406', '34.05940594059
406']
Output image

I guess this may be due to using strings instead of numbers. When you use strings, the x values are taken as categories and not ordered numerically but in the order they appear in the list (unless a category is exactly repeated). I understand that the snippet is not complete, but the values of Epoch and Scaled actually change on each iteration.
After plotting the first set of data, any values not present in the first set will be positioned "afterwards" those of the first set (ie: to the right of first set's last point in x, and higher than the last point in y). When the second set of data is plotted, the first x values have not appeared in the previous set, so they are plotted afterwards (beginning of light blue line in the plot), regardless of their numeric value. Then, the final values are the same of those that had appeared in the first set, so the line goes back to the left of the figure.
You can try using [float(x) for x in Epoch] and [float(y) for y in Scaled] in the plots. As I see that there are spaces in the strings representing the numbers, you could use a function like this:
def flist_from_slist(data):
return [float(x.replace(' ', '')) for x in data]
And replace the pyplot.plot call by:
pyplot.plot(flist_from_slist(Epoch), flist_from_slist(Scaled), linewidth=1.9)
Moreover, there is a lot of code inside the loop that could be moved outside (setting the ticks, labels, etc).

Related

Pyqtgraph: How do I update the X-Axis values without it moving

So I have found this code that creates a python graph that is updated a plot in real time that does everything i need, but i would like if instead of the x-axis moving the values are updated. I have been searching for examples but i only find a static version where the values of the x values stay the same.
What i have right now:
what i want:
Here is the code:
# Create Plot Widget
self.scrolling_timestamp_plot_widget = pg.PlotWidget(axisItems={'bottom': TimeAxisItem(orientation='bottom')})
# Enable/disable plot squeeze (Fixed axis movement)
self.scrolling_timestamp_plot_widget.plotItem.setMouseEnabled(x=False, y=False)
self.scrolling_timestamp_plot_widget.setTitle('Signal 1 ')
self.scrolling_timestamp_plot_widget.setLabel('left', 'Value')
self.scrolling_timestamp_plot_widget.setLabel('bottom', 'Time (s)')
self.scrolling_timestamp_plot = self.scrolling_timestamp_plot_widget.plot()
self.scrolling_timestamp_plot.setPen("r")
def plot_updater(self):
self.data_point = float(self.current_position_value)
self.data.append({'x': self.timestamp.elapsed(), 'y': self.data_point })
print("List Values:",self.data)
self.scrolling_timestamp_plot.setData(x=[item['x'] for item in self.data], y=[item['y'] for item in self.data])

Altair: two independent sliders for a layered plot

I have a dataframe like this one (code to generate the data):
I want to compare two lines - l1 and l2, both depend on the parameter t. Each line has five values of t sampled that are numbered with t_i. I want to plot both lines, with one of the sampled points highlighted for each line. The points to highlight should be set with two sliders - one for each line.
I can get it working without the sliders:
base = alt.Chart(df).encode(x='x', y='y', color='line_name')
for line_name in df.line_name.unique():
line = base.transform_filter(datum.line_name == line_name)
plots += [line.mark_line(), line.mark_point().transform_filter(datum.t_i == int(line_name[1]))]
alt.layer(*plots)
Or with 1 slider:
for line_name in df.line_name.unique():
line = base.transform_filter(datum.line_name == line_name)
slider = alt.binding_range(min=0, max=4, step=1, name='t_i:')
select_t_i = alt.selection_single(name="t_i", fields=['t_i'], bind=slider, init={'t_i': 0})
plots += [line.mark_line(),
line.mark_point().add_selection(select_t_i).transform_filter(select_t_i)]
alt.layer(*plots[:-1])
I get the expected result:
But if I change the last line to actually add the second slider:
alt.layer(*plots[:-1]) -> alt.layer(*plots)
I get nothing - the plot does not show up and calling display does not help. How should I do that instead?
Also, I would like to see the value of t for the selected point, not the t_i. I actually added t_i because I couldn't define the slider with arbitrary values - all examples I saw, have min, max, step. How can I display the value of t, so it is updated with the slider?
Thanks!
EDIT (working code):
for line_name in df.line_name.unique():
line = base.transform_filter(datum.line_name == line_name)
slider = alt.binding_range(min=0, max=4, step=1, name='t_%s:' % line_name[1:])
select_t_i = alt.selection_single(fields=['t_i'], bind=slider, init={'t_i': 0})
plots += [line.mark_line(),
line.mark_point().add_selection(select_t_i).transform_filter(select_t_i)]
alt.layer(*plots[:-1])
Two selections cannot have the same name. Remove name="t_i" from your selection definition (so that each one will have a unique automatically-generated name), and it will work.

How to plot dotted lines from a shapefile in python?

I am not sure on how to plot a dotted line from a shapefile in Python. It appears that readshapefile() does not have any linestyle for me to set. Below I have a working code where I take a shapefile and plot it, but it only plots a solid line. Any ideas to set me in the right direction? Thanks!
The shapefile can be found here: http://www.natice.noaa.gov/products/daily_products.html, where the Start Date is Feb 15th, end date is Feb 17th, and the Date Types is Ice Edge. It should be the first link.
#!/awips2/python/bin/python
from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt
map = Basemap(llcrnrlon=-84.37,llcrnrlat=42.11,urcrnrlon=-20.93,urcrnrlat=66.48,
resolution='i', projection='tmerc', lat_0 = 55., lon_0 = -50.)
map.drawmapboundary(fill_color='aqua')
map.fillcontinents(color='#ddaa66',lake_color='aqua')
map.drawcoastlines(zorder = 3)
map.readshapefile('nic_autoc2018046n_pl_a', 'IceEdge', zorder = 2, color = 'blue')
plt.show()
From the Basemap documentation:
A tuple (num_shapes, type, min, max) containing shape file info is
returned. num_shapes is the number of shapes, type is the type code
(one of the SHPT* constants defined in the shapelib module, see
http://shapelib.maptools.org/shp_api.html) and min and max are
4-element lists with the minimum and maximum values of the vertices.
If drawbounds=True a matplotlib.patches.LineCollection object is
appended to the tuple.
drawbounds is True by default, so all you have to do is collect the return value of readshapefile and alter the linestyle of the returned LineCollection object, which can be done with LineCollection.set_linestyle(). So in principle you can change the linestyle of your plotted shape file with something like this:
result = m.readshapefile('shapefiles/nic_autoc2018046n_pl_a', 'IceEdge', zorder = 10, color = 'blue')#, drawbounds = False)
col = result[-1]
col.set_linestyle('dotted')
plt.show()
However, your shapefile contains 5429 separate line segments of different length and somehow matplotlib does not seem to be able to deal with this large amount of non-continuous lines. At least on my machine the plotting did not finish within one hour, so I interrupted the process. I played a bit with your file and it seems like many of the lines are broken into segments unnecessarily (I'm guessing this is because the ice sheet outlines are somehow determined on tiles and then pieced together afterwards, but only the providers will really know). Maybe it would help to piece together adjacent pieces, but I'm not sure.
I was also wondering whether the result would even look that great with a dotted line, because there are so many sharp bends. Below I show a picture where I only plot the 100 longest line segments (leaving out drawcoastlines and with thicker lines) using this code:
import numpy as np
result = m.readshapefile('shapefiles/nic_autoc2018046n_pl_a', 'IceEdge', zorder = 10, color = 'blue')#, drawbounds = False)
col = result[-1]
segments = col.get_segments()
seglens = [len(seg) for seg in col.get_segments()]
segments = np.array(segments)
seglens = np.array(seglens)
idx = np.argsort(seglens)
seglens = seglens[idx]
segments = segments[idx]
col.remove()
new_col = LineCollection(segments[-100:],linewidths = 2, linestyles='dotted', colors='b')
ax.add_collection(new_col)
plt.show()
And the result looks like this:

How to avoid keys with zero percentage in pie plot matplotlib

I have to plot pie chart with %age values, I am facing a problem that some value are very small and their %age is about zero, when I plot using matplotlib in python, therir labels overlab and they are not readable. I think its one solution is to avoid values with zero %age and second is to seprate labels to overlap (with some arrow etc.) Here is my simple code
def show_pi_chart(plot_title,keys,values,save_file):
size = len(keys)
#Get Colors list
color_list = make_color_list(size)
pyplot.axis("equal")
pyplot.pie(values,
labels=keys,
colors=color_list,
autopct="%1.1f%%"
)
pyplot.title(plot_title)
pyplot.show()
And my chart is
What is the solution to make labels dictant or remove small %age keys
The following code should work as intended:
from matplotlib import pyplot
from collections import Counter
import numpy as np
def fixOverLappingText(text):
# if undetected overlaps reduce sigFigures to 1
sigFigures = 2
positions = [(round(item.get_position()[1],sigFigures), item) for item in text]
overLapping = Counter((item[0] for item in positions))
overLapping = [key for key, value in overLapping.items() if value >= 2]
for key in overLapping:
textObjects = [text for position, text in positions if position == key]
if textObjects:
# If bigger font size scale will need increasing
scale = 0.05
spacings = np.linspace(0,scale*len(textObjects),len(textObjects))
for shift, textObject in zip(spacings,textObjects):
textObject.set_y(key + shift)
def show_pi_chart(plot_title,keys,values):
pyplot.axis("equal")
# make sure to assign text variable to index [1] of return values
text = pyplot.pie(values, labels=keys, autopct="%1.1f%%")[1]
fixOverLappingText(text)
pyplot.title(plot_title)
pyplot.show()
show_pi_chart("TITLE",("One","Two","Three","Four","Five","Six","Seven", "Eight"),(20,0,0,10,44,0,0,44))

Custom labels in Chaco Legend

I'd like to change the line labels on a chaco Legend because my labels need to be ascending floats:
1,2,3,4
But it is string sorting, so I'm getting:
1, 10, 11, 2, 21 etc...
I noticed the documentation seems unfinished in regard to this:
http://chaco.readthedocs.org/en/latest/user_manual/basic_elements/overlays.html#legend
I've tried setting the legends labels manually:
self.plot.legend.labels = list([i for i in self.mylist])
I'm using a colormap, so this is very noticeable as the legend shows blue lines and red lines mixed seemingly randomly due to the string sorting.
Below is a minimal working example
This example does not use the same colormap I'm using, but shows how the line ordering in the legend is not sorted. It's not important which colormap is used, what's important is the string sorting in the legend gives unwanted aesthetics.
from traits.api import *
from chaco.api import *
from traitsui.api import *
from chaco.example_support import COLOR_PALETTE
from enable.api import ComponentEditor
import numpy as np
class TestPlot(HasTraits):
plot = Instance(Plot)
traits_view = View( Item('plot', editor=ComponentEditor(), show_label=False) )
def _plot_default(self):
data = ArrayPlotData()
plot = Plot(data)
x = np.linspace(0,10,100)
data.set_data('x', x)
for i, freq in enumerate(range(1,20,3)):
y = 'line_%s' % freq
color = tuple(COLOR_PALETTE[i])
data.set_data(y, i*x)
plot.plot(('x', y), name=y, color=color)
plot.legend.visible = True
return plot
if __name__ == '__main__':
TestPlot().configure_traits()
See screenshot:
To sort your labels properly you need just to apply natural sorting. Install "natsort" library and insert two lines in your code:
from natsort import natsorted
...
plot.legend.labels = natsorted(plot.plots.keys())
This will do the trick.
You can add leading zeros for one digit numbers by changing the line
y = 'line_%s' % freq
to
y = 'line_%02d' % freq
I assume you have no more than 99 graphs otherwise you need to change the 02 to 03. Then your legend should be correctly sorted.
See https://docs.python.org/3.4/library/string.html#format-specification-mini-language for more information on the string format specifiers.
The format 0# where # is a number, means that in the string the number uses # positions and if the number is smaller than the given width it is filled with trailing zeros. If you want floating point numbers with one digit as fractional part use %04.1f

Categories

Resources