5
\$\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
\$\endgroup\$
1
  • \$\begingroup\$ Please show your code for DataReader. \$\endgroup\$
    – Reinderien
    Jan 1 at 16:08

1 Answer 1

0
\$\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.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.