I am combining all defined function into a class and use if, elif to operate.
I will explain in the following.
First, I have a 3 types of plot, combo, line, and bar.
I know how to define function separately for these three plot.
Second, I want to combine these 3 plots together within a package using if.
The code I tried is:
import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
class AP(object):
def __init__(self, dt, date, group, value, value2, value3, value4, value5, value6, TYPE):
self.dt = dt
self.date = date
self.group= carrier
self.value = value
self.col1 = col1
self.col2 = col2
self.col3 = col3
self.col4 = col4
self.TYPE = TYPE
if self.TYPE == "combo":
def ComboChart(self, dt, date, group, value, TYPE):
dataset = pd.read_csv(dt)
dataset['date'] = pd.to_datetime(dataset[date])
dataset['yq'] = pd.PeriodIndex(dataset['date'], freq='Q')
dataset['qtr'] = dataset['date'].dt.quarter
dataset = dataset.groupby([carrier, 'yq', 'qtr'])[value].sum().reset_index()
dataset['total.YQGR'] = dataset[value] / dataset.groupby(['qtr', carrier])[value].transform('shift') - 1
dataset = dataset[np.isfinite(dataset['total.YQGR'])]
dataset['total.R'] = dataset[value] / dataset.groupby(group)[value].transform('first')
dataset.yq = dataset.yq.astype(str)
fig, ax1 = plt.subplots(figsize=(12,7))
ax2=ax1.twinx()
sns.lineplot(x='yq',y='total.R', data=dataset, hue=group, ax=ax1, legend = None, palette = ('navy', 'r'), linewidth=5)
ax1.set_xticklabels(ax1.get_xticks(), rotation=45, fontsize=15, weight = 'heavy')
ax1.set_xlabel("", fontsize=15)
ax1.set_ylabel("")
ax1.set_ylim((0, max(dataset['total.R']) + 0.05))
sns.barplot(x='yq', y='total.YQGR', data=dataset, hue=group, ax=ax2, palette = ('navy', 'r'))
ax2.set_yticklabels(['{:.1f}%'.format(a*100) for a in ax2.get_yticks()])
ax2.set_ylabel("")
ax2.set_ylim((min(dataset['total.YQGR']) - 0.01, max(dataset['total.YQGR']) + 0.2))
ax2.get_legend().remove()
ax2.legend(bbox_to_anchor=(-0.35, 0.5), loc=2, borderaxespad=0., fontsize = 'xx-large')
for groups in ax2.containers:
for bar in groups:
if bar.get_height() >= 0:
ax2.text(
bar.get_xy()[0] + bar.get_width()/1.5,
bar.get_height() + 0.003,
'{:.1f}%'.format(round(100*bar.get_height(),2)),
color='black',
horizontalalignment='center',
fontsize = 12, weight = 'heavy'
)
else:
ax2.text(
bar.get_xy()[0] + bar.get_width()/1.5,
bar.get_height() - 0.008,
'{:.1f}%'.format(round(100*bar.get_height(),2)),
color='black',
horizontalalignment='center',
fontsize = 12, weight = 'heavy'
)
ax1.yaxis.set_visible(False)
ax2.yaxis.set_visible(False)
ax2.xaxis.set_visible(False)
ax1.spines["right"].set_visible(False)
ax1.spines["left"].set_visible(False)
ax1.spines["top"].set_visible(False)
ax1.spines["bottom"].set_visible(False)
ax2.spines["right"].set_visible(False)
ax2.spines["left"].set_visible(False)
ax2.spines["top"].set_visible(False)
ax2.spines["bottom"].set_visible(False)
ax1.set_title(TYPE, fontsize=20)
plt.show()
fig.savefig(TYPE, bbox_inches='tight', dpi=600)
elif self.TYPE == "line":
def line(self, dt, date, carrier, value, value2, TYPE):
dataset = pd.read_csv(dt)
dataset['date'] = pd.to_datetime(dataset[date])
dataset['yq'] = pd.PeriodIndex(dataset['date'], freq='Q')
dataset = dataset.groupby([group, 'yq'])[value, value2].sum().reset_index()
dataset['Arate'] = dataset[value2] / dataset[value]
dataset.yq = dataset.yq.astype(str)
fig, ax1 = plt.subplots(figsize=(12,7))
sns.lineplot(x='yq', y='Arate', data=dataset, hue=group, ax=ax1, linewidth=5)
ax1.set_xticklabels(dataset['yq'], rotation=45, fontsize = 15)
ax1.set_xlabel("")
ax1.set_ylabel("")
ax1.set_ylim((min(dataset['Arate']) - 0.05, max(dataset['Arate']) + 0.05))
ax1.set_yticklabels(['{:.1f}%'.format(a*100) for a in ax1.get_yticks()], fontsize = 18, weight = 'heavy')
ax1.legend(bbox_to_anchor=(0., 1.02, 1., .102), loc=2, borderaxespad=0., ncol = 6)
ax1.yaxis.grid(True)
ax1.spines["right"].set_visible(False)
ax1.spines["left"].set_visible(False)
ax1.spines["top"].set_visible(False)
ax1.spines["bottom"].set_visible(False)
ax1.set_title(TYPE, fontsize = 20)
plt.show()
fig.savefig(TYPE, bbox_inches='tight', dpi=600)
elif self.TYPE == "bar":
def Bar(self, dt, date, group, value3, value4, value5, value6, TYPE):
dataset = pd.read_csv(dt, sep = '|')
dataset['date'] = pd.to_datetime(dataset[date])
dataset['yq'] = pd.PeriodIndex(dataset['date'], freq='Q')
dataset = dataset.groupby([group, 'yq'])[value3, value4, value5, value6].sum().reset_index()
dataset = dataset.groupby([group]).tail(4)
dataset.yq = dataset.yq.astype(str)
dataset = pd.melt(dataset, id_vars = [group, 'yq'], value_vars = [value3, value4, value5, value6])
dataset = dataset.groupby(['variable', group]).value.sum().reset_index()
dataset['L4Qtr'] = dataset.value / dataset.groupby([group]).value.transform('sum')
fig, ax1 = plt.subplots(figsize=(12,7))
sns.barplot(x='variable', y='L4Qtr', data=dataset, hue=group, ax=ax1)
ax1.set_xticklabels(ax1.get_xticklabels(), fontsize=17.5, weight = 'heavy')
ax1.set_xlabel("", fontsize=15)
ax1.set_ylabel("")
ax1.yaxis.set_ticks(np.arange(0, max(dataset['L4Qtr']) + 0.1, 0.05), False)
ax1.set_yticklabels(['{:.1f}%'.format(a*100) for a in ax1.get_yticks()], fontsize = 18, weight = 'heavy')
ax1.legend(bbox_to_anchor=(0., 1.02, 1., .102), loc=2, borderaxespad=0., ncol = 6)
for groups in ax1.containers:
for bar in groups:
ax1.text(
bar.get_xy()[0] + bar.get_width()/2,
bar.get_height() + 0.005,
'{:.1f}%'.format(round(100*bar.get_height(),2)),
color=bar.get_facecolor(),
horizontalalignment='center',
fontsize = 16, weight = 'heavy'
)
ax1.spines["right"].set_visible(False)
ax1.spines["left"].set_visible(False)
ax1.spines["top"].set_visible(False)
ax1.spines["bottom"].set_visible(False)
ax1.set_title(TYPE, fontsize=20)
plt.show()
fig.savefig(TYPE, bbox_inches='tight', dpi=600)
Third, I hope others can simply use this module as below:
import sys
sys.path.append(r'\\users\desktop\module')
from AP import AP as ap
Finally, when someone assign TYPE, it will automatically plot and save it.
# This will plot combo chart
ap(r'\\users\desktop\dataset.csv', date = 'DATEVALUE', group = 'GRPS', value = 'total', TYPE = 'combo')
Above is the ideal thought. I do not need to pass value2 ~ value6 in it since combo does not use them.
When I want bar:
# This will plot bar chart
ap(r'\\users\desktop\dataset.csv', date = 'DATEVALUE', group = 'GRPS', value3 = 'col1', value4 = 'col2', value5 = 'col3', value6 = 'col4', TYPE = 'combo')
My code is incorrect since error happened. It seems that I need to pass all parameters in it.
However, even I passed all parameters in it. No error but no output.
Any suggestion?
could you explain, why you don't just create subclasses for the types? Wouldn't that be more straight-forward?
1.) One way would be to make the subclasses visible to the user and if you don't like this,
2.) you could just create a kind of interface class (eg AP that hides the class that is used behind the scenes and for example instanciates as soon as the type is set.
3.) you can work as you began, but then I guess you would have to make the methods visible to the user, because I guess the way you implemented it, the functions are only visible in the init method (maybe your indentaion is not quite correct). For example if your if statements are executed by the init method, then you could assign the methods to instance variables like self.ComboChart= ComboChart to be able to call the method from outside. But imho that would not be very pythonic and a bit more hacky/less object oriented.
So I'd suggest 1.) and if that is not possible for some reason, then I'd go for solution 2. Both solutions also allow you to form a clean class structure and reuse code that way, while you are still able to build your simplified interface class if you like.
An example (pseudo code) for method 1 would look like below. Please note, that I haven't tested it, it is only meant to give you an idea, about splitting logic in an object oriented way. I didn't check your whole solution and so I don't know for example, if you always group your data in the same way. I'd proabably also separate the presentation logic from the data logic. That would especially be a good idea if you plan to display the same data in more ways, because with the current logic, you would reread the csv file and reporcess the data each time you want another represenatiation. So not to make it more complicated while I just want to explain the basic principle I ignored this and gave an example for a base class "Chart" and a subclass "ComboChart". The "ComboChart" class knows how to read/group the data, because it inherits the methods from "Chart", so you only have to implement it once and thus if you find a bug or want to enhance it later, you only need to do it in one place. The draw_chart method then only needs to do what's different according to the chosen representation. A user would have to create the instance of the subclass according the chart type they want to display and call display_chart().
import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
class Chart(object):
def __init__(self, dt, date, group, value, value2, value3, value4, value5, value6):
self.dt = dt
self.date = date
self.group= carrier
self.value = value
self.col1 = col1
self.col2 = col2
self.col3 = col3
self.col4 = col4
self.TYPE = TYPE
self.dataset= None
def _read_data_(self)
dataset = pd.read_csv(dt)
dataset['date'] = pd.to_datetime(dataset[self.date])
dataset['yq'] = pd.PeriodIndex(dataset['date'], freq='Q')
dataset['qtr'] = dataset['date'].dt.quarter
dataset = dataset.groupby([carrier, 'yq', 'qtr'])[value].sum().reset_index()
dataset['total.YQGR'] = dataset[value] / dataset.groupby(['qtr', carrier])[value].transform('shift') - 1
dataset = dataset[np.isfinite(dataset['total.YQGR'])]
dataset['total.R'] = dataset[value] / dataset.groupby(group)[value].transform('first')
dataset.yq = dataset.yq.astype(str)
self.dataset= dataset
return dataset
def get_data(self):
if self.dataset is None:
self._read_data_()
return self.dataset
def group_data(self):
dataset= self.get_data()
dataset = dataset.groupby([carrier, 'yq', 'qtr'])[value].sum().reset_index()
dataset['total.YQGR'] = dataset[value] / dataset.groupby(['qtr', carrier])[value].transform('shift') - 1
dataset = dataset[np.isfinite(dataset['total.YQGR'])]
dataset['total.R'] = dataset[value] / dataset.groupby(group)[value].transform('first')
dataset.yq = dataset.yq.astype(str)
return dataset
def draw_chart(self):
pass
class ComboChart(Chart):
def draw_chart(self):
dataset = self.group_data()
fig, ax1 = plt.subplots(figsize=(12,7))
ax2=ax1.twinx()
sns.lineplot(x='yq',y='total.R', data=dataset, hue=group, ax=ax1, legend = None, palette = ('navy', 'r'), linewidth=5)
ax1.set_xticklabels(ax1.get_xticks(), rotation=45, fontsize=15, weight = 'heavy')
ax1.set_xlabel("", fontsize=15)
ax1.set_ylabel("")
ax1.set_ylim((0, max(dataset['total.R']) + 0.05))
sns.barplot(x='yq', y='total.YQGR', data=dataset, hue=group, ax=ax2, palette = ('navy', 'r'))
ax2.set_yticklabels(['{:.1f}%'.format(a*100) for a in ax2.get_yticks()])
ax2.set_ylabel("")
ax2.set_ylim((min(dataset['total.YQGR']) - 0.01, max(dataset['total.YQGR']) + 0.2))
ax2.get_legend().remove()
ax2.legend(bbox_to_anchor=(-0.35, 0.5), loc=2, borderaxespad=0., fontsize = 'xx-large')
for groups in ax2.containers:
for bar in groups:
if bar.get_height() >= 0:
ax2.text(
bar.get_xy()[0] + bar.get_width()/1.5,
bar.get_height() + 0.003,
'{:.1f}%'.format(round(100*bar.get_height(),2)),
color='black',
horizontalalignment='center',
fontsize = 12, weight = 'heavy'
)
else:
ax2.text(
bar.get_xy()[0] + bar.get_width()/1.5,
bar.get_height() - 0.008,
'{:.1f}%'.format(round(100*bar.get_height(),2)),
color='black',
horizontalalignment='center',
fontsize = 12, weight = 'heavy'
)
ax1.yaxis.set_visible(False)
ax2.yaxis.set_visible(False)
ax2.xaxis.set_visible(False)
ax1.spines["right"].set_visible(False)
ax1.spines["left"].set_visible(False)
ax1.spines["top"].set_visible(False)
ax1.spines["bottom"].set_visible(False)
ax2.spines["right"].set_visible(False)
ax2.spines["left"].set_visible(False)
ax2.spines["top"].set_visible(False)
ax2.spines["bottom"].set_visible(False)
ax1.set_title(TYPE, fontsize=20)
plt.show()
fig.savefig(TYPE, bbox_inches='tight', dpi=600)
The second method (with the interface class) would just look the same, only that you have a forth class that is known to the user and knows how to call the real implementation. Like this:
class YourInterface:
def __init__(self, your_arguments, TYPE):
if TYPE == __ 'ComboChart':
self.client= ComboChart(your_arguments)
elif TYPE == ....
def display_chart(self):
self.client.display_chart()
But it's a pretty boring class, isnt't it?
I'd only do this if your class hierarchy is very technical and could change over time if you want to avoid that the users of your library build up dependencies on the real class hierarchy that would probably be broken as soon as you change your hierarchy. For most cases I guess, class hierarchies stay relatively stable, so you don't need such an extra level of abstraction created by an interface class.
Related
for i in range(5):
for j in range(5):
sub_image = self.image[i*8:i*8+8, j*8:j*8+8]
ax = plt.subplot(gs[4 - i, j], picker = True, label = self.iD)
c = ax.pcolormesh(sub_image, vmin=0, vmax=maxZ, cmap="viridis")
ax.axis("off")
ax.set_aspect("equal")
self.grid[4-i,j] = self.iD
self.iD += 1
fig.subplots_adjust(right=0.71, left=.285, top=0.9, bottom=0.1)
self.cbar_ax = fig.add_axes([0.85, 0.15, 0.05, 0.7])
self.cbar = fig.colorbar(c, cax=self.cbar_ax)
self.cbar.set_label("Charge (Photoelectrons)", rotation=270, size=24, labelpad=24)
self.cbar_ax.tick_params(labelsize=15)
fig.suptitle(f"Run {self.run} Event {self.ev}", fontsize=30)
fig.canvas.mpl_connect("pick_event",self.nextWindow)
self.layout.addWidget(self.static_canvas,1,1)#,1,2)
self.toolBar = NavigationToolbar(self.static_canvas, self)
self.layout.addWidget(self.toolBar,2,1,1,2)
snapshot of plot
I am trying to create the functionality for when a pick_event is processed, the value that corresponds with the colorbar is printed. When the cursor is over any part of the plot, the NavigationToolbar2QT displays the value I want on the bottom right, but I dont know how to access it otherwise.
This is how I was able to access the data. I then formatted the string to get the data I want.
data = self.toolBar._mouse_event_to_message(event.mouseevent)
I'm making a real-time application that graphs
sensor data in a real-time fashion.
The data is saved to a .csv file and graphed using matplotlib in the following fashion:
class Scope:
def __init__(self, ax, ax2, f, maxt=2000, dt=0.02):
self.ax = ax
self.ax2 = ax2
self.maxt = maxt
self.f = f
self.tdata = []
self.ydata = []
self.humdata = []
self.tempdata = []
self.TVOCdata = []
self.ax.set_ylim(0, 1023)
self.ax.set_xlim(0, self.maxt)
self.ax2.set_ylim(0, 100)
self.ax2.set_xlim(0, self.maxt)
self.line, = ax.plot(self.tdata, self.ydata)
self.line2, = ax2.plot(self.tdata, self.humdata)
self.line3, = ax2.plot(self.tdata, self.tempdata, 'g-')
self.line4, = ax.plot(self.tdata, self.TVOCdata)
def animate(self, y):
if path.exists(self.f):
data = pd.read_csv(self.f)
self.tdata.append(data['dur'].iloc[-1])
self.ydata.append(data['CO2'].iloc[-1])
self.line.set_data(self.tdata, self.ydata)
self.line.set_label('CO2')
I'd like to change the representation of the time axis from seconds to hours since the measurements that i'm doing might last for days at a time. Is there a handy way of doing this? Truly thankful for any kind of help.
Found a solution to this problem. The key is to use the ticker FuncFormatter-subclass to customize the ticks in the x-axis in the following way:
formatter = matplotlib.ticker.FuncFormatter(lambda s, x: time.strftime('%H:%M',time.gmtime(s // 60)))
self.ax.xaxis.set_major_formatter(mticker.FuncFormatter(formatter))
I'm attempting to subclass matplotlib.lines.Line2D to create lines whose width and dash spacings are in the same coordinates as the data being plotted. I've expanded on this answer to try to support dashed lines (with the dash spacings also being in data units) and have the following code currently:
import matplotlib.pyplot as plt
from matplotlib.lines import Line2D
class LineDataUnits(Line2D):
"""
A Line2D object, but with the linewidth and dash properties defined in data coordinates.
"""
def __init__(self, *args, **kwargs):
_lw_data = kwargs.pop("linewidth", 1)
_dashes_data = kwargs.pop("dashes", (1,))
super().__init__(*args, **kwargs)
self._lw_data = _lw_data
self._dashes_data = _dashes_data
self._dashOffset = 0
def _get_lw(self):
if self.axes is not None:
ppd = 72./self.axes.figure.dpi
trans = self.axes.transData.transform
return ((trans((1, self._lw_data))-trans((0, 0)))*ppd)[1]
else:
return 1
def _set_lw(self, lw):
self._lw_data = lw
def _get_dashes(self):
if self.axes is not None:
ppd = 72./self.axes.figure.dpi
trans = self.axes.transData.transform
return tuple([((trans((1, dash_data))-trans((0, 0)))*ppd)[1] for dash_data in self._dashes_data])
else:
return tuple((1, 0))
def _set_dashes(self, dashes):
self._dashes_data = dashes
_linewidth = property(_get_lw, _set_lw)
_dashSeq = property(_get_dashes, _set_dashes)
if __name__ == "__main__":
fig, ax = plt.subplots()
line1 = Line2D([0, 10], [3, 3], linewidth=10, color="blue", dashes=(3, 3), dash_capstyle="round")
line2 = LineDataUnits([0, 10], [6, 6], linewidth=0.5, color="red", dashes=(1, 1), dash_capstyle="round")
print("dash_capstyle:", line2.get_dash_capstyle())
ax.add_line(line1)
ax.add_line(line2)
ax.set_xticks(range(10))
ax.grid()
ax.set_xlim(0, 10)
ax.set_ylim(0, 10)
plt.show()
Which yields the following graph:
When I zoom in and out, the dash spacings do seem to remain constant in the data units, however as you can see in the graph, their values are not calculated correctly. Furthermore, it appears setting the dash_capstyle parameter in this class seems to have no effect.
Any help would be greatly appreciated.
Edit: Solution
After much more experimentation, I managed to get it working properly. Firstly, even though the line was being plotted with dashes, the Line2D still had its _linestyle parameter set to "-" (solid), which caused the cap style argument to be ignored. Secondly, there was an error in my conversion from data units to points. With the changes below, it now works perfectly.
# in __init__()
super().__init__(*args, **kwargs):
+ self.set_linestyle("--")
self._lw_data = _lw_data
# in _get_dashes()
- return tuple([((trans((1, dash_data))-trans((0, 0)))*ppd)[1] for dash_data in self._dashes_data])
+ dpu = (trans((1, 1)) - trans((0, 0)))[0]
+ return tuple([u*dpu*ppd for u in self._dashes_data])
# in __init__()
super().__init__(*args, **kwargs):
+ self.set_linestyle("--")
self._lw_data = _lw_data
# in _get_dashes()
- return tuple([((trans((1, dash_data))-trans((0, 0)))*ppd)[1] for dash_data in self._dashes_data])
+ dpu = (trans((1, 1)) - trans((0, 0)))[0]
+ return tuple([u*dpu*ppd for u in self._dashes_data])
today I developed a simple class in python in order to get a plot style suitable for my purpose ...
here the class :
this is the base class in which I define the colors ... in function colors , my particular interest is for the colors cycle named 'soft'
from abc import ABCMeta, abstractmethod
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.ticker import MultipleLocator, FormatStrFormatter
from matplotlib.axes import Axes
import matplotlib.pylab as pylab
import matplotlib
from cycler import cycler
class PlotBase(metaclass=ABCMeta):
def __init__(self, title , filename : str = ' '):
self.title = title
self.filename = filename
#----------------------------------------------------------------------------------------------
def findLimits(self):
'''
found the vectors limits in order to set the figure limits x,y
'''
_minX = 10E20
_minY = 10E20
_maxX = 0.000
_maxY = 0.000
for i in range(0,len(self.variable)-1,3):
if _minX >= min(self.variable[i]):
_minX = min(self.variable[i])
if _maxX <= max(self.variable[i]):
_maxX = max(self.variable[i])
if _minY >= min(self.variable[i+1]):
_minY = min(self.variable[i+1])
if _maxY <= max(self.variable[i+1]):
_maxY = max(self.variable[i+1])
return [round(_minX,2), round(_maxX,2) , round(_minY,2) , round(_maxY,2)]
#------------------------------------------------------------------------------------------------
def save(self, filename : 'str -> name of the file with extension'):
plt.savefig(filename)
#------------------------------------------------------------------------------------------------
def colors(self, style : 'str (name of the pallette of line colors)'):
if style == 'vega':
style = "cycler('color', ['1F77B4', 'FF7F0E', '2CA02C', 'D62728', '9467BD', '8C564B', 'E377C2', '7F7F7F', 'BCBD22', '17BECF'] )"
elif style == 'gg':
style = "cycler('color', ['E24A33', '348ABD', '988ED5', '777777', 'FBC15E', '8EBA42', 'FFB5B8'])"
elif style == 'brewer':
style = "cycler('color', ['66C2A5', 'FC8D62', '8DA0CB', 'E78AC3', 'A6D854', 'FFD92F', 'E5C494', 'B3B3B3'] )"
elif style == 'soft1':
style = "cycler('color', ['8DA0CB', 'E78AC3', 'A6D854', 'FFD92F', 'E5C494', 'B3B3B3', '66C2A5', 'FC8D62'] )"
elif style == 'tthmod':
style = "cycler('color', ['30a2da', 'fc4f30', 'e5ae38', '6d904f', '8b8b8b'])"
elif style == 'grayscale':
style = "cycler('color', ['252525', '525252', '737373', '969696', 'BDBDBD', 'D9D9D9', 'F0F0F0', 'F0F0FF' ])"
elif style == 'grayscale2':
style = "cycler('color', ['525252', '969696', 'BDBDBD' ,'D9D9D9', 'F0F0F0', 'F0F0FF' ])"
return style
#------------------------------------------------------------------------------------------------
def linestyle(self , linestyle : str):
if linestyle == 'linetype1':
linestyle = "cycler('linestyle', ['-', '--', ':', '-.'])"
if linestyle == 'linetype2':
linestyle = "cycler('linestyle', ['-', ':', '-.', '--'])"
return linestyle
#------------------------------------------------------------------------------------------------
#abstractmethod
def plot(self,*args,**kwargs):
"""
Abstract method!
the derived class must implement its self method
"""
pass
then I define the usable callable class :
class TimesPlot(PlotBase):
'''
mathpazo style (LaTeX-class) plot suitable for:
- palatino fonts template / beamer
- classic thesis style
- mathpazo package
'''
def __init__(self,title,filename: str= ' '):
super().__init__(title,filename)
#------------------------------------------------------------------------------------------------------------
def plot(self, *args,**kwargs): #
self.variable = [*args]
if len(self.variable) % 3 != 0:
print('Error variable must be coupled (even number)')
raise AttributeError('you must give 2 array x,y followed by string label for each plot')
'''
plot method, define all the parameter for the plot
the rendering of the figure is setting to beh "light"
--> TODO : define parameter in order to set the size of figure/font/linewidth
'''
#plt.rc('text', usetex=True )
#plt.rcParams['text.latex.preamble']=[r"\usepackage{times}"]
#plt.rcParams['text.latex.preamble']=[r"\usepackage{mathpazo}"]
plt.rcParams['font.family'] = 'serif' #'serif'
#plt.rcParams['font.sans-serif'] = ''#'DejaVu Sans' #'Tahoma' #, , 'Lucida Grande', 'Verdana']
plt.rcParams['font.size'] = 14
#plt.rcParams['font.name'] = 'Helvetica'
plt.rcParams['font.style'] = 'italic'
#plt.rc('font',family='' ,size=16, weight='normal')
plt.rc_context({'axes.edgecolor':'#999999' }) # BOX colors
plt.rc_context({'axes.linewidth':'1' }) # BOX width
plt.rc_context({'axes.xmargin':'0' })
plt.rc_context({'axes.ymargin':'0' })
plt.rc_context({'axes.labelcolor':'#555555' })
plt.rc_context({'axes.edgecolor':'999999' })
plt.rc_context({'axes.axisbelow':'True' })
plt.rc_context({'xtick.color':'#555555' }) # doesn't affect the text
plt.rc_context({'ytick.color':'#555555' }) # doesn't affect the text
plt.rc_context({ 'axes.prop_cycle': self.colors('soft1')})
#plt.rc('lines', linewidth=3)
fig,ax = plt.subplots(1,figsize=(10,6))
plt.title(self.title,color='#555555',fontsize=18)
plt.xlabel('time [s]',fontsize=16)
plt.ylabel('y(t)',fontsize=16)
#plt.grid(linestyle='dotted')
plt.grid(linestyle='--')
#plt.figure(1)
#ax.edgecolor('gray')
for i in range(0,len(self.variable)-1,3):
plt.plot(self.variable[i],self.variable[i+1], linewidth=3, label= self.variable[i+2])
ax.set_xlim( self.findLimits()[0] , self.findLimits()[1] )
ax.set_ylim( self.findLimits()[2] , self.findLimits()[3] + 0.02 )
majorLocator = MultipleLocator(20)
majorFormatter = FormatStrFormatter('%f')
minorXLocator = MultipleLocator(0.05)
minorYLocator = MultipleLocator(0.05)
ax.xaxis.set_minor_locator(minorXLocator)
ax.yaxis.set_minor_locator(minorYLocator)
ax.yaxis.set_ticks_position('both')
ax.xaxis.set_ticks_position('both')
#axes.xmargin: 0
#axes.ymargin: 0
#plt.legend(fontsize=10)
#handles, labels = ax.get_legend_handles_labels()
#ax.legend(handles, labels)
#ax.legend(frameon=True, fontsize=12)
#text.set_color('gray')
#leg = plt.legend(framealpha = 0, loc = 'best')
#ax.legend(borderpad=1)
legend = leg = plt.legend(framealpha = 1, loc = 'best', fontsize=14,fancybox=False, borderpad =0.4)
leg.get_frame().set_edgecolor('#dddddd')
leg.get_frame().set_linewidth(1.2)
plt.setp(legend.get_texts(), color='#555555')
plt.tight_layout(0.5)
if self.filename != ' ':
super().save(self.filename)
plt.show()
The falls happens because in my university pc this class give me a plot using the soft1 scheme of colors ... at home with same version of python It use the default set of colors (corresponding to the set that I defined named 'vega') ... not only the line colors change ... also the box of the plot have different colors contrast ... could somebody help me ??
I call this class simply passing them 6 vector (in order to obtain 3 curve) as follow :
fig1 = makeplot.TimesPlot('Solution of Differential Equations','p1.pdf')
fig1.plot(fet,feu,'Explicit Euler',bet,beu,'Implicit Euler',x,y,'Analytical')
May you please help me to understand the reason why this happens ? I've tried in my home from archlinux and gentoo .... while in my desktop pc in which the class works correctly I use the last debian
Matplotlib axes have Major and Minor ticks. How do I add a third level of tick below Minor?
For example
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.ticker
t = np.arange(0.0, 100.0, 0.1)
s = np.sin(0.1*np.pi*t)*np.exp(-t*0.01)
fig, ax = plt.subplots()
plt.plot(t, s)
ax1 = ax.twiny()
ax1.plot(t, s)
ax1.xaxis.set_ticks_position('bottom')
majors = np.linspace(0, 100, 6)
minors = np.linspace(0, 100, 11)
thirds = np.linspace(0, 100, 101)
ax.xaxis.set_major_locator(matplotlib.ticker.FixedLocator(majors))
ax.xaxis.set_minor_locator(matplotlib.ticker.FixedLocator(minors))
ax1.xaxis.set_major_locator(matplotlib.ticker.FixedLocator([]))
ax1.xaxis.set_minor_locator(matplotlib.ticker.FixedLocator(thirds))
ax1.tick_params(which='minor', length=2)
ax.tick_params(which='minor', length=4)
ax.tick_params(which='major', length=6)
ax.grid(which='both',axis='x',linestyle='--')
plt.axhline(color='gray')
plt.show()
produces the effect I want using twinned x-axes.
Is there a better way?
As I stated that you can achieve what you want by deriving from some key classes, I decided to do so (but as I said, it's probably not worth the effort). Anyway, here is what I've got:
from matplotlib import pyplot as plt
from matplotlib import axes as maxes
from matplotlib import axis as maxis
import matplotlib.ticker as mticker
import matplotlib.cbook as cbook
from matplotlib.projections import register_projection
from matplotlib import ticker
import numpy as np
class SubMinorXAxis(maxis.XAxis):
def __init__(self,*args,**kwargs):
self.subminor = maxis.Ticker()
self.subminorTicks = []
self._subminor_tick_kw = dict()
super(SubMinorXAxis,self).__init__(*args,**kwargs)
def reset_ticks(self):
cbook.popall(self.subminorTicks)
##self.subminorTicks.extend([self._get_tick(major=False)])
self.subminorTicks.extend([maxis.XTick(self.axes, 0, '', major=False, **self._subminor_tick_kw)])
self._lastNumSubminorTicks = 1
super(SubMinorXAxis,self).reset_ticks()
def set_subminor_locator(self, locator):
"""
Set the locator of the subminor ticker
ACCEPTS: a :class:`~matplotlib.ticker.Locator` instance
"""
self.isDefault_minloc = False
self.subminor.locator = locator
locator.set_axis(self)
self.stale = True
def set_subminor_formatter(self, formatter):
"""
Set the formatter of the subminor ticker
ACCEPTS: A :class:`~matplotlib.ticker.Formatter` instance
"""
self.isDefault_minfmt = False
self.subminor.formatter = formatter
formatter.set_axis(self)
self.stale = True
def get_subminor_ticks(self, numticks=None):
'get the subminor tick instances; grow as necessary'
if numticks is None:
numticks = len(self.get_subminor_locator()())
if len(self.subminorTicks) < numticks:
# update the new tick label properties from the old
for i in range(numticks - len(self.subminorTicks)):
##tick = self._get_tick(major=False)
tick = maxis.XTick(self.axes, 0, '', major=False, **self._subminor_tick_kw)
self.subminorTicks.append(tick)
if self._lastNumSubminorTicks < numticks:
protoTick = self.subminorTicks[0]
for i in range(self._lastNumSubminorTicks, len(self.subminorTicks)):
tick = self.subminorTicks[i]
tick.gridOn = False
self._copy_tick_props(protoTick, tick)
self._lastNumSubminorTicks = numticks
ticks = self.subminorTicks[:numticks]
return ticks
def set_tick_params(self, which='major', reset=False, **kwargs):
if which == 'subminor':
kwtrans = self._translate_tick_kw(kwargs, to_init_kw=True)
if reset:
self.reset_ticks()
self._subminor_tick_kw.clear()
self._subminor_tick_kw.update(kwtrans)
for tick in self.subminorTicks:
tick._apply_params(**self._subminor_tick_kw)
else:
super(SubMinorXAxis, self).set_tick_params(which=which, reset=reset, **kwargs)
def cla(self):
'clear the current axis'
self.set_subminor_locator(mticker.NullLocator())
self.set_subminor_formatter(mticker.NullFormatter())
super(SubMinorXAxis,self).cla()
def iter_ticks(self):
"""
Iterate through all of the major and minor ticks.
...and through the subminors
"""
majorLocs = self.major.locator()
majorTicks = self.get_major_ticks(len(majorLocs))
self.major.formatter.set_locs(majorLocs)
majorLabels = [self.major.formatter(val, i)
for i, val in enumerate(majorLocs)]
minorLocs = self.minor.locator()
minorTicks = self.get_minor_ticks(len(minorLocs))
self.minor.formatter.set_locs(minorLocs)
minorLabels = [self.minor.formatter(val, i)
for i, val in enumerate(minorLocs)]
subminorLocs = self.subminor.locator()
subminorTicks = self.get_subminor_ticks(len(subminorLocs))
self.subminor.formatter.set_locs(subminorLocs)
subminorLabels = [self.subminor.formatter(val, i)
for i, val in enumerate(subminorLocs)]
major_minor = [
(majorTicks, majorLocs, majorLabels),
(minorTicks, minorLocs, minorLabels),
(subminorTicks, subminorLocs, subminorLabels),
]
for group in major_minor:
for tick in zip(*group):
yield tick
class SubMinorAxes(maxes.Axes):
name = 'subminor'
def _init_axis(self):
self.xaxis = SubMinorXAxis(self)
self.spines['top'].register_axis(self.xaxis)
self.spines['bottom'].register_axis(self.xaxis)
self.yaxis = maxis.YAxis(self)
self.spines['left'].register_axis(self.yaxis)
self.spines['right'].register_axis(self.yaxis)
register_projection(SubMinorAxes)
if __name__ == '__main__':
fig = plt.figure()
ax = fig.add_subplot(111,projection = 'subminor')
t = np.arange(0.0, 100.0, 0.1)
s = np.sin(0.1*np.pi*t)*np.exp(-t*0.01)
majors = np.linspace(0, 100, 6)
minors = np.linspace(0, 100, 11)
thirds = np.linspace(0, 100, 101)
ax.plot(t, s)
ax.xaxis.set_ticks_position('bottom')
ax.xaxis.set_major_locator(ticker.FixedLocator(majors))
ax.xaxis.set_minor_locator(ticker.FixedLocator(minors))
ax.xaxis.set_subminor_locator(ticker.FixedLocator(thirds))
##some things in set_tick_params are not being set correctly
##by default. For instance 'top=False' must be stated
##explicitly
ax.tick_params(which='subminor', length=2, top=False)
ax.tick_params(which='minor', length=4)
ax.tick_params(which='major', length=6)
ax.grid(which='both',axis='x',linestyle='--')
plt.show()
It's not perfect, but for the use case you provided it's working fine. I drew some ideas from this matplotlib example and by going through the source codes directly. The result looks like this:
I tested the code on both Python 2.7 and Python 3.5.
EDIT:
I noticed that the subminor gridlines would always be drawn if the grid is turned on (while I had intended for it not to be drawn at all). I rectified this in the code above, i.e. the subminor ticks should never produce grid lines. If gridlines should be implemented properly, some more work will be needed.