I have a program that uses pandas to read csv files and then generates and saves graphical charts. I have been trying to follow the SOLID principles so I have tried to seperate responsibilities. So far the project has the structure of
GUI
├─ Service
│ ├─ Business Logic
│ │ ├─ DataReader
│ │ ├─ ChartGenerator
The GUI makes calls to the Service which holds instances of the DataReader and ChartGenerator
ChartService.py
from matplotlib.pyplot import title
from ChartGenerator import ChartGenerator
import DataReader
from enum import Enum
class Chart(Enum):
kW = 'kW'
kWH = 'kWH'
Hum = 'Humidity'
Temp = 'Temperature'
lineColours = [
'blue',
'green',
'red',
'black',
'cyan',
'magenta',
'yellow'
]
class ChartService:
def __init__(self, dataPath) -> None:
self.dataPath = dataPath
def GetClients(self) -> list:
self.clientDict = DataReader.GetCustomerRacks(self.dataPath)
return list(self.clientDict.keys())
def GenClient(self,
client,
savePath,
combineCharts,
charts={
Chart.kW: True,
Chart.kWH: True,
Chart.Hum: True,
Chart.Temp: True
},
options={
'chartFileNames': {
'kW': 'kW.png',
'kWH': 'kWH.png',
'Hum': 'Humidity.png',
'Temp': 'Temperature.png'
},
'chartNames': {
'kW': 'kW',
'kWH': 'kWH',
'Hum': 'Humidity',
'Temp': 'Temperature'
},
"chartGen":{
"chartSizeX": 5,
"chartSizeY": 5,
"subPlotSizeX": 2,
"subPlotSizeY": 2
}
},
):
if(not combineCharts):
if charts[Chart.kW]:
chartGenerator = ChartGenerator(options['chartGen'])
axis = chartGenerator.PlotChart(None, '', '')
for rack, colour in zip(self.clientDict[client], lineColours):
data = DataReader.GetRackkWData(self.dataPath, rack)
chartGenerator.PlotChart(data, 'Date', 'kW', xAxisLabel='Date', yAxisLabel='kW', lineColour=colour, axis=axis)
fileName = options['chartNames']['kW']
chartGenerator.SaveChart(
f"{savePath}/{client}{fileName}")
if charts[Chart.kWH]:
chartGenerator = ChartGenerator(options['chartGen'])
axis = chartGenerator.PlotChart(None, '', '')
for rack, colour in zip(self.clientDict[client], lineColours):
data = DataReader.GetRackkWHourData(self.dataPath, rack)
chartGenerator.PlotChart(data, 'Date', 'kWH', xAxisLabel='Date', yAxisLabel='kWH', lineColour=colour, axis=axis)
fileName = options['chartNames']['kWH']
chartGenerator.SaveChart(
f"{savePath}/{client}{fileName}")
if charts[Chart.Hum]:
data = DataReader.GetHumidityData(self.dataPath)
chartGenerator = ChartGenerator(options['chartGen'])
chartGenerator.PlotChart(data, 'DateTime', 'Hum', xAxisLabel='Date', yAxisLabel='Humidity')
fileName = options['chartNames']['Hum']
chartGenerator.SaveChart(f"{savePath}/{client}{fileName}")
if charts[Chart.Temp]:
data = DataReader.GetTemperatureData(self.dataPath)
chartGenerator = ChartGenerator(options['chartGen'])
chartGenerator.PlotChart(data, 'DateTime', 'Temp', xAxisLabel='Date', yAxisLabel='Temperature')
fileName = options['chartNames']['Temp']
chartGenerator.SaveChart(f"{savePath}/{client}{fileName}")
else:
chartGenerator = ChartGenerator()
if charts[Chart.kW]:
# Get empty axis
axis = chartGenerator.PlotChart(None, '', '')
#Loop over axis with each rack
for rack, colour in zip(self.clientDict[client], lineColours):
data = DataReader.GetRackkWData(self.dataPath, rack)
chartGenerator.PlotChart(data, 'Date', 'kW', xAxisLabel='Date', yAxisLabel='kW', lineColour=colour, axis=axis)
fileName = options['chartNames']['kW']
chartGenerator.SaveChart(
f"{savePath}/{client}{fileName}")
if charts[Chart.kWH]:
axis = chartGenerator.PlotChart(None, '', '')
for rack, colour in zip(self.clientDict[client], lineColours):
data = DataReader.GetRackkWHourData(self.dataPath, rack, axis=axis)
chartGenerator.PlotChart(data, 'Date', 'kWH', xAxisLabel='Date', yAxisLabel='kWH', lineColour=colour, axis=axis)
fileName = options['chartNames']['kWH']
chartGenerator.SaveChart(
f"{savePath}/{client}{fileName}")
if charts[Chart.Hum]:
data = DataReader.GetHumidityData(self.dataPath)
chartGenerator.PlotChart(data, 'DateTime', 'Hum', xAxisLabel='Date', yAxisLabel='Humidity')
if charts[Chart.Temp]:
data = DataReader.GetTemperatureData(self.dataPath)
chartGenerator.PlotChart(data, 'DateTime', 'Temp', xAxisLabel='Date', yAxisLabel='Temperature')
chartGenerator.SaveChart(f"{savePath}/{client}.png")
This service is initiated by the GUI with a path of where to read the data from. It makes calls to the ChartGenerator, I have designed ChartGenerator so that any "plots" that are done on an instance of ChartGenerator will generate on the same image (or "figure").
ChartGenerator.py
import matplotlib.pyplot as plt
import numpy as np
class ChartGenerator:
def __init__(
self,
options={
"chartSizeX": 5,
"chartSizeY": 5,
"subPlotSizeX": 2,
"subPlotSizeY": 2
}
) -> None:
self.fig = plt.figure(figsize=(options['chartSizeX'],
options['chartSizeY']))
self.subPlotSizeX = options["subPlotSizeX"]
self.subPlotSizeY = options["subPlotSizeY"]
self.axes = []
def PlotChart(
self,
data,
xAxis,
yAxis,
axis=None,
xAxisLabel=None,
yAxisLabel=None,
lineStyle='--',
lineColour='blue',
chartTitle=None,
) -> plt.axis:
'''
Plots a Line chart with data from Pandas Dataframe.
Returns axis which plot was drawn on
Arguments
----------
data is Dataframe to read from.
xAxis, yAxis are names of columns to plot onto graph.
axis (Default = None) can be given if wanting to plot onto an existing graph. If no axis is given then a new axis is created as a subplot.
xAxisLabel, yAxisLabel are labels that can be printed onto the sides of the graph
linestyle is the style of the line on the graph
chartTitle is text above the chart
'''
### NOTE: Should be implemented with function as argument to call
if (not axis):
axis = self.fig.add_subplot(self.subPlotSizeX, self.subPlotSizeY,
len(self.axes) + 1)
self.axes.append(axis)
axis.set_title(chartTitle)
axis.set_ylabel(yAxisLabel)
axis.set_xlabel(xAxisLabel)
axis.plot(data[xAxis], data[yAxis], linestyle=lineStyle, figure=self.fig)
return axis
def SaveChart(self, path, title=None, showChart=False) -> None:
'''
Saves generated graph as image to path provided
Arguments
----------
path is the path of the file to be saved
title (Default=None) is text that will be printed at the top of the image
showChart (Default = False) when True will display the graph before finishing
'''
self.fig.suptitle(title, fontsize=40)
self.fig.autofmt_xdate()
self.fig.tight_layout()
if showChart:
self.fig.show()
self.fig.savefig(path)
plt.close(self.fig)
Problems arise when trying to implement an option to let the charts be all generated on one image or all seperately.
The current process for generating all charts on one image is:
Create ChartGenerator instance
[
Plot all of the charts
]
Save Image
vs the process for generating them seperately:
[
Create ChartGenerator instance
Plot one chart
Save Image
] Repeat for all charts
1 Answer 1
I managed to find a solution I found that worked well for this case, I'm sure it's not the cleanest or most optimal but it worked for my case.
Since the main problem was that generating seperate graphs required a new instance of the ChartGenerator class I made a class which would return a new instance if the option was enabled.
def ReturnGenerator(combine, generator) -> ChartGenerator:
'''
If combine is false returns a new ChartGenerator
'''
if (not combine):
generator = ChartGenerator()
That way I would be able to assign generator for each graph.
combineChartGenerator = ChartGenerator()
if charts[Chart.kW]:
kWGenerator = ReturnGenerator(combineCharts, combineChartGenerator)
axis = kWGenerator.InitialiseAxis(Axis(yAxisLabel='kW'))
# Loop over each rack
for rack in self.clientDict[client]:
data = DataReader.GetRackkWData(self.dataPath, rack)
kWGenerator.PlotChart(data['Date'], data['kW'], xAxisLabel='Date', yAxisLabel='kW', axis=axis, plotName=rack)
# Save Image
if (not combineCharts):
fileName = options['chartFileNames']['kW']
kWGenerator.SaveChart(
f"{savePath}/{client}{fileName}"
)
Also notice in that example I manually created the axis since I need to reference it multiple times over the loop. Since the ChartGenerator needs to be aware of all the axes I pass it to the InitialiseAxis
function which adds the axis to the axes
array in ChartGenerator
.
Explore related questions
See similar questions with these tags.
DataReader
. \$\endgroup\$