7
\$\begingroup\$

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
Alex Waygood
1,0376 silver badges12 bronze badges
asked Jul 28, 2021 at 9:33
\$\endgroup\$
1
  • \$\begingroup\$ Please show your code for DataReader. \$\endgroup\$ Commented Jan 1, 2022 at 16:08

1 Answer 1

1
\$\begingroup\$

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.

answered Aug 4, 2021 at 3:50
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.