text: """
  Name: plot.py
  Author: Lena Hoersting
  Description: Plot the log files
  """
  
  """ import packages """
  import logging 
  from importlib import import_module
  import matplotlib.pyplot as plt
  from matplotlib import rc
  import os
  import pandas as pd
  import numpy as np
  import copy as cp
  import datetime as dt
  
  
  """ import project libraries """
  import modules.data.log as log
  import modules.data.datamgm as dtm
  
  
  """ import project configurations """
  import configurations.settings_global as config_global
  import configurations.settings_visualisation as config 
  
  """ Logging """
  logger = dtm.initialise_logger(__name__)
  
  """ 
  Global variables
  """
  global_design = ""
  
  """
  Auxiliary functions
  """
  
  ### Set the plot design
  def set_plot_design(family='serif', usetex=True): 
      # rc('font', **{'family': family, 'serif': ['Computer Modern']})
      rc('text', usetex=usetex)
  
  ### find the correct label for the x or y axis
  def find_label(x,y,more_info=""): 
  
      """ y: optimization """
      if y == "runtime": 
          y_label = "runtime [min]"
      elif y == "runtime1":
          y_label = "runtime (1st objective)"
      elif y == "Gap1": 
          y_label = "relative gap (1st objective)"
      elif y == "Gap2": 
          y_label = "relative gap (2nd objective)"
      elif y == "ObjVal1":
          y_label = "mean passengers waiting [per stop and min]"
      elif y == "ObjVal2":
          y_label = "mean cargo delay [min]"
      elif "Gap" in y:
          y_label = "relative gap"
    
      """ y: prefix """
      y_label = ""
      
      if "max" in y: 
          y_label += "maximal "
      elif "min" in y: 
          y_label += "minimal "
      elif "avg" in y: 
          y_label += "mean "
             
      # passengers
      if "Passengers-SystemLoad" in y:
          y_label += "system load by passengers"
      elif "Load:Passengers" in y:
          y_label += "utilisation rate by passengers"
      elif "Passengers:Rejected" in y: 
          y_label += "number of rejected passengers"
      elif "Stop-Passengers" in y:
          y_label += "number [per stop and min]"
      elif "Tram-Passengers" in y: 
          y_label += "number [per tram]"
      elif "Passengers" in y: 
          y_label += "number of passengers"        
      elif "Cargo-Delay" in y: 
          y_label += "shipment delay [min]"
      elif "Rejected-Cargo" in y: 
          y_label += "number of rejected shipments"
      elif "Load:Cargo" in y:
          y_label += "utilisation rate by shipments"
      elif "Cargo-SystemLoad" in y:
          y_label += "system load by shipments"
      elif "numTramsCargo" in y: 
          y_label += "number of cargo trams"
      elif "Tram-Cargo" in y: 
          y_label += "number [per tram and min]"    
      elif "Cargo" in y and "percent" in more_info: 
          y_label += "percentage"
      elif "Cargo" in y: 
          y_label += "number of shipments"
      elif "Tram-Delay" in y: 
          y_label += "tram delay [min]"
      elif "Tram-Load" in y:  
          y_label += "tram load ratio"
      elif "Tram-ServiceTime" in y: 
          y_label += "service time per stop [min]"
      else: 
          y_label = y
        
      if not y_label: 
          y_label = y
      
      if "final" in y: 
          y_label += " in last period"
      if "percent" in more_info:
          y_label += " (\%)"
      if "avg" in more_info: 
          y_label = "mean " + y_label
          
      """ x label """
      if x == "numPeriods":
          x_label = "time horizon [min]"
      elif x == "numCargo":
          x_label = "number of shipments"
      elif x == "numPassengers": 
          x_label = "average passenger arrival intensity [per stop and period]"
      elif x == "Time": 
          x_label = "time"
      else: 
          x_label = x
      # logger.debug(f"{x} -> {x_label},")
      # logger.debug(f"{y} -> {y_label}")
      return x_label, y_label
      
  ### Filter the name of the csv file
  def get_file_name(file, df, filter_value={},title_instance="",plots=[]): 
      
      folder = dtm.get_folder_to_file(file)
  
      try: 
          instance = list(df["instance"])[0]
          configs = instance.split("_")
      except IndexError:
          logger.debug("Empty file!")
          return "", ""
  
      plot_name = ""
      for p in range(len(plots)): 
          plot_name += plots[p]
          if p + 1 < len(plots): 
              plot_name += "-"
              
      
      if title_instance: 
          instance = "_" + title_instance
      else: 
          instance = ""
          
      prefix = ""
      
      for key in filter_value: 
          val = filter_value[key]
          if isinstance(val,str): 
              if not "pas" in val:
                  prefix += "_" + val 
          elif key in ["Time"]:
              prefix += "_final"
  
      title = f"{plot_name}{instance}{prefix}"
          
  
      
      
      return folder, title
  
  def get_legend_name(y,prefix=""):
      name = ""
      if "avg" in y: 
          name += "mean "
      elif "max" in y: 
          name += "maximal "
                 
      if not name: 
          name = y
      if prefix: 
          name += f" ({prefix})"
          
      if not name: 
          name = y
      logger.debug(f"{y}, {prefix} -> {name}")
      return name
  
  def get_plot_name(y,prefix="",information=""):    
      name = ""
      
      if "Passengers:Waiting" in y: 
          name += "waiting passengers"
      elif "Passengers:Boarded" in y: 
          name += "travelling passengers"
      elif "Passengers:Alighted" in y: 
          name += "alighted passengers"
      elif "Tram-Cargo:Loaded" in y: 
          name += "loaded"
      elif "Tram-Cargo:Unloaded" in y: 
          name += "delivered"        
      elif "Cargo:Waiting" in y: 
          name += "waiting shipments"
      elif "Cargo:Loaded" in y: 
          name += "travelling shipments"
      elif "Cargo:Unloaded" in y: 
          name += "delivered shipments"  
      elif "Cargo:RejectedPlanned" in y: 
          name += "rejected shipments"
      elif "Cargo:Rejected" in y: 
          name += "denied shipments"        
      elif "Load:Cargo" in y:
          name += "cargo load"
      elif "Load:Passengers" in y: 
          name += "passenger load"
      elif "ServiceTime:Cargo" in y:
          name += "cargo service"
      elif "ServiceTime:Passengers" in y: 
          name += "passenger service"
      else: 
          name = y
      
      logger.debug(f"{y} -> {name}")
      
      return name
      
           
  ### return the correct method name
  def get_method_name(met):
      if met == "timetabling_closed":
          return "solver"
      elif met == "timetabling_open":
          return "heuristic"
      elif met == "timetabling_benchmark":
          return "benchmark"
      else: 
          return met
         
  ### Iterate over different colors
  # def color_for_method(met="",i=0, colors = ["orange","royalblue", "green", "grey", "red"]): 
  def color_for_method(met="",i=0, colors = ['#ff7f00','#377eb8', '#4daf4a','#f781bf','#984ea3', '#a65628', '#999999', '#e41a1c', '#dede00']): 
      if met == "timetabling_benchmark": 
          return colors[0+i]
      elif met == "timetabling_closed": 
          return colors[1+i]
      elif met == "timetabling_open": 
          return colors[2+i]
      elif met: 
          return colors[3+i]
      else: 
          return colors[i]
  
  ### Iterate over different colors
  def marker_for_method(met="",i=0): 
      if met == "timetabling_benchmark": 
          return ""#"^"
      elif met == "timetabling_closed": 
          return "*"
      elif met == "timetabling_open": 
          return ""#"o"
      else: 
          return "+"
  
  ### Iterate over different colors
  def pattern_for_method(met,i=0): 
      if met == "timetabling_benchmark": 
          return 5*"\\"
      elif met == "timetabling_closed": 
          return ""
      elif met == "timetabling_open": 
          return 5*"//"
      else: 
          return "+"
  
  ### function for setting the colors of the box plots pairs
  def set_box_color(bp, color):
      plt.setp(bp['boxes'], color=color)
      plt.setp(bp['whiskers'], color=color)
      plt.setp(bp['caps'], color=color)
      plt.setp(bp['medians'], color=color)
      plt.setp(bp['fliers'], markeredgecolor=color)
      
  def strIntersection(s1, s2):
      word = ""
      out = ""
      
      for c1 in range(len(s1)):
          for c2 in range(len(s2)): 
              if str(s1[c1]) == str(s2[c2]):
                  out = ""
                  for i in range(min(len(s1[c1:]),len(s2[c2:]))):
                      if s1[c1+i] == s2[c2+i]:
                          # logger.debug(out)
                          out += s1[c1+i]                         
                      else: 
                          if len(word) < len(out):
                              word = cp.copy(out)
                          break
              if len(word) < len(out):
                  word = cp.copy(out)
                
      word = word.strip()
      word = word.strip("-")
      # logger.ebug(f"{s1}, {s2} -> {word}")
      if not s1: 
          word = s2
      if not s2: 
          word = s1
      return word
      
  def strDifference(s1, s2):
      word = ""
      out = ""
      
      for c1 in range(len(s1)):
          for c2 in range(len(s2)): 
              if str(s1[c1]) != str(s2[c2]):
                  out = ""
                  for i in range(min(len(s1[c1:]),len(s2[c2:]))):
                      if s1[c1+i] != s2[c2+i]:
                          # logger.debug(out)
                          out += s1[c1+i]                         
                      else: 
                          if len(word) < len(out):
                              word = cp.copy(out)
                          break
              if len(word) < len(out):
                  word = cp.copy(out)
                
      word = word.strip()
      word = word.strip("-")
      # logger.ebug(f"{s1}, {s2} -> {word}")
      if not s1: 
          word = s2
      if not s2: 
          word = s1
      return word
      
  """
  Auxiliary functions: Filtering and preparing data
  """
  
  ### Initiliase all data
  def initialise_data(file,x="",y="",compare="",filter_value={}):
              
      df, prefix = filter_all_values(file, filter_value)
                  
      methods = list(set(df[compare]))
      methodName = [get_method_name(met) for met in methods]
      zipped_pairs = zip(methodName, methods)
      methods = [x for _, x in sorted(zipped_pairs)]
      
      iterator = list(set(df[x]))
      iterator.sort()
      
      prefix += f"_{x}_{y}"
      
      return df, iterator, methods, prefix
  
  ### Filter the undesired values from the dataframe
  def filter_all_values(file, filter_value): 
      
      df = dtm.get_data_from_csv(file)
      
      prefix = ""
      if filter_value: 
          for key in filter_value: 
              if isinstance(filter_value[key],list): 
                  alle = set(df[key])
                  for entry in alle:
                      if not entry in filter_value[key]:  
                          df = df[df[key]!=entry]
                      else: 
                          prefix += f"_{entry}"
              else: 
                  df = df[df[key]==filter_value[key]]
                  prefix += f"_{filter_value[key]}"
      #df = df[df["instance"].str.contains("pas4e-02")] 
      #prefix += "pas4e-02"
      return df, prefix 
      
      
  def get_positions(data,plot="boxplot", level = 2): 
  
      positions = {}
      
      numMethds = len(data)
      for met in data: 
          if level == 3: 
              logger.debug(data)
              if len(data[met]) > 0:
                  numMethds *= len(data[met])
              for met2 in data[met]:
                  numIt = len(data[met][met2])
          else: 
              numIt = len(data[met])
              break
          
      if numMethds == 0: 
          return [], [], []
      
      scale = 0.8/numMethds
      buffer = 0.2 # left and right
   
      if plot == "boxplot": 
          scale = 0.6/numMethds
          buffer = 0.4
      counter = 0   
      
      length_one_xtick = numMethds * scale + buffer
      # logger.debug(length_one_xtick)
      length_between_first_and_last_tick = numMethds * scale
      
      if numMethds > 1: 
          center = length_between_first_and_last_tick / 2
      else: 
          center = 0
          
      # logger.debug(center)
          
      xticks_position = [center+length_one_xtick*i for i in range(numIt)]
      # logger.debug(xticks_position)
      
      # logger.debug(numIt)
      
      for met in data:
          # logger.debug(met)
          positions[met] = []
          if plot == "barchart": 
              pos = counter * scale
              # logger.debug(pos)        
          elif plot == "boxplot": 
              pos = counter * scale + 0.5*scale
              
              # logger.debug(pos)
          else: 
              counter = 0
              pos = xticks_position[counter]
          xticklabels = []
          xticks = []
          if level == 3: 
              for met2 in data[met]: 
                  iterator = data[met][met2]
                  break
          else: 
              iterator = data[met]
              
          for it in iterator:
              
              if int(it) == float(it): 
                  label = it
              else: 
                  label = round(it,2)
              
              if numIt*numMethds * len(str(label))<= 100:
                  xticks.append(pos)   
                  xticklabels.append(label) 
              elif int(it) % 10 == 0:
                  xticks.append(pos)   
                  xticklabels.append(label)  
              elif numIt * numMethds * len(str(label)) >= 1000: 
                  if int(it) % 120 == 0:
                      xticks.append(pos)   
                      xticklabels.append(label)      
                          
              if plot == "boxplot" or plot == "barchart":
                  # logger.debug(it)
                  # logger.debug(pos)
                  positions[met].append(pos)
                  pos += length_one_xtick
              else: 
                  pos = xticks_position[counter]
                  positions[met].append(pos)
                  counter += 1
          counter += 1
      
      # logger.debug(positions)
      # logger.debug(xticks)
      logger.debug(xticklabels)
      return positions, xticks, xticklabels
  
      
  def compute_average(df, iterator, x): 
      """
      Function to compute the average value of all indices
      :param config - configurations file
  
      """  
      average_intensity = {}    
      for it in iterator: 
          minimal = it[0]
          maximal = it[1]
          filtered_df = df[(df[x] >= minimal) & (df[x]<maximal)]
          average_intensity[it] = filtered_df[x].mean()
          
          # divide by number of periods to compute the intensity per period
          if x == "numPassengers":        
              average_intensity[it] = filtered_df[filtered_df["pasDemand"]=="0-rep"][x].mean()
              average_intensity[it] = average_intensity[it]/filtered_df["numPeriods"].mean()
              average_intensity[it] = average_intensity[it]/filtered_df["numStops"].mean()
      
      logger.debug(average_intensity)
      return average_intensity
      
  def analyse_entities_log(file,x="",ys="",compare="", aggregate_x=False,filter_value={},compute=""): 
      
      df, iterator, methods, prefix = initialise_data(file,x,ys,compare,filter_value)
      
      iterator_plot = False
      
      if aggregate_x: 
          minimal = min(iterator)
          maximal = max(iterator)
          if aggregate_x == "passenger_set": 
              aggregate_x = len(list(set(df[aggregate_x])))
              
          logger.debug(aggregate_x)
          if aggregate_x >= 2:
              
              iterator_new = [] 
              
              # minimal is NaN
              try: 
                  int(minimal)
              except: 
                  minimal = 0
                  maximal = 0
                  
              stepwidt = (maximal-minimal)/aggregate_x
                  
                  
              for num in range(aggregate_x): 
                  iterator_new.append((int(minimal+num*stepwidt), int(minimal+(1+num)*stepwidt)))
              logger.debug(iterator_new)
              iterator = iterator_new 
          else: 
              iterator_plot = cp.copy(iterator)
              iterator = [(minimal,maximal) for it in iterator_plot]
              
          average_values = compute_average(df, iterator, x)
          
  
      # Filter data into the dictionary "data"
      data_global = {}
      
      if isinstance(ys,str):
          data_global[ys] = {}
      else:
          for y in ys:
              # logger.debug(y)
              data_global[y] = {}
              
      # logger.debug(data_global)
      for y in data_global: 
          counter = 0
          data = dict()
          iterator_key = {}
          # logger.debug(y)
          
          for met in methods:
              pos = counter
              data[met] = {}
              counter += 1
              counter_it = 0
              for it in iterator: 
                  counter_it += 1
                  
                  if isinstance(it,(list,tuple)):
                      row = df[(df[compare]==met) & (df[x] >= it[0]) & (df[x] <= it[1])]                   
                      if iterator_plot:
                          if not it in iterator_key: 
                              iterator_key[it]= [iterator_plot[counter_it-1],iterator_plot[counter_it-1]]
                          it_label = int(iterator_plot[counter_it-1])
                      
                      else: 
                          if it in iterator_key: 
                              iterator_key[it][0] = min(np.min(row[x]),iterator_key[it][0])
                              iterator_key[it][1] = max(np.max(row[x]),iterator_key[it][0])
                          else: 
                              iterator_key[it] = [np.min(row[x]),np.max(row[x])]
                          it_label = average_values[it]
                      
                  else:
                      row = df[(df[compare]==met) & (df[x]==it)]
                      if not it in iterator_key: 
                          iterator_key[it] = it              
                      it_label = int(it)
  
                  if "avEntity" in compute: 
                      row = row.groupby(["run","passengerName"]).mean() 
                  if "maEntity" in compute: 
                      row = row.groupby(["run","passengerName"]).max() 
                  
                  if "+" in y: 
                      y_list = y.split("+")
                      for i in range(len(y_list)): 
                          key = y_list[i]
                          if i > 0: 
                              data[met][it_label] += row[key].astype(np.float).replace(np.nan, 0)   
                          else: 
                              data[met][it_label] = row[key].astype(np.float).replace(np.nan, 0)
                      prefix = key.split("-")[0]
                                             
                  else:
                      data[met][it_label] = np.copy(row[y].astype(np.float).replace(np.nan, 0))
                      prefix = y.split("-")[0]
  
      
                        
                  
                  # logger.debug(f"prefix: {prefix}")
                  if "numPassengers" in compute: 
                      data[met][it_label] = np.divide(data[met][it_label],row["numPassengers"])
                  if "cargo" in compute:
                      data[met][it_label] = np.divide(data[met][it_label],row["numCargo"])
                  if "numTram" in compute: 
                      data[met][it_label] = np.divide(data[met][it_label],row[f"numTramsOperating"])
                          
                  if "Passengers-Passengers" in compute: 
                      if len(y.split("-")) > 1: 
                          data[met][it_label] = np.divide(data[met][it_label],row[f"{prefix}-Passengers-Passengers"]) 
                  if "Cargo-Cargo" in compute: 
                      if len(y.split("-")) > 1: 
                          data[met][it_label] = np.divide(data[met][it_label],row[f"{prefix}-Cargo-Cargo"])                      
                  if "ServiceTime" in compute:                
                      if len(y.split("-")) > 1: 
                          data[met][it_label] = np.divide(data[met][it_label],row[f"{prefix}-Tram-ServiceTime"])
                  if "StandingTime" in compute: 
                      data[met][it_label] = np.divide(data[met][it_label],row[f"{prefix}-Tram-StandingTime"])
                  if "Capacity" in compute:
                      if len(y.split("-")) > 1: 
                          # logger.debug(f"{prefix}-Tram-Capacity")
                          # logger.debug(row[f"{prefix}-Tram-Capacity"])
                          # logger.debug(data[met][it_label])
                          
                          data[met][it_label] = np.divide(data[met][it_label],row[f"{prefix}-Tram-Capacity"])
                      else: 
                          data[met][it_label] = np.divide(data[met][it_label],row[f"Capacity"])
                                                 
                  if "Load" in compute: 
                      data[met][it_label] = np.divide(data[met][it_label],row[f"{prefix}-Tram-Load"])
  
                  
                   
                  
                  # logger.debug(f"{met}, {it}, data: {data[met][it_label]}")
                  if list(data[met][it_label]):
                      if "div" in compute:
                          data[met][it_label] = data[met][it_label]/float(compute.split("-")[-1])
                      if "avg" in compute:
                          # logger.debug(data[met][it_label])
                          data[met][it_label] = float(np.mean(data[met][it_label]))
                      elif "percent" in compute: 
                          data[met][it_label] = float(np.mean(data[met][it_label])/len(data[met][it_label]))
                      elif "total" in compute: 
                          data[met][it_label] = float(sum(data[met][it_label]))
                      elif  "max" in compute: 
                          data[met][it_label] = float(np.max(data[met][it_label]))
                  else: 
                      data[met][it_label] = 0
                      
                      
                      
                  
                  
          # logger.debug(data)
              
              # logger.debug(f"{met}, {it}, data: {data[met][it_label]}")
          data_global[y] = cp.deepcopy(data)
      # logger.debug(f"data: {data}")
  
      xlabels = []
      # for it in iterator: 
          # label = iterator_key[it]
          # if isinstance(label,(list,tuple)): 
              # xlabels.append(average_values[it])  
          # else: 
              # xlabels.append(int(it))
      # logger.debug(data_global)
      return data_global, df, xlabels
      
      
  def get_xticklabel_hour(ticks, start=0): 
      xticklabels1 = [f"{int(start+float(tick)/60)}:{int(float(tick)%60):02}" for tick in ticks if start + int(float(tick)/60) <=24]
      xticklabels2 = [f"{int(start+float(tick)/60)-24}:{int(float(tick)%60):02}" for tick in ticks if start + int(float(tick)/60) > 24]
      
      return xticklabels1+xticklabels2
      
  
  
  
  """ Functions to define and create the plots """
  
  # Create actual plot
  def merge_and_save_final_plot(fig,ax,plots, xticklabels,x="",y="",compute="",folder="",title="",pos_legend=[0.00,1.01],show=True,FORMAT="pdf"):
  
      x_label, y_label = find_label(x,y,compute)
      
      
      plt.xlabel(x_label)
      plt.ylabel(y_label)
      ax.yaxis.grid(True)
      ax.set_ylim(ymin=0)
      
      start, end = ax.get_ylim()
      start_x, end_x = ax.get_xlim()    
      
      # x axis 
      if "Time" in x: 
          if config.bool_consider_warm_up:
              plt.axvspan(0,config.time_warm_up, facecolor='grey', alpha=0.2)        
          if config.bool_consider_cool_down:
              plt.axvspan(config.time_cool_down,config.time_cool_down+config.time_warm_up, facecolor='grey', alpha=0.2)        
          
          if plots[0]["xticks"][-1]/60 < np.ceil(plots[0]["xticks"][-1]/60): 
              plots[0]["xticks"].append(np.ceil(plots[0]["xticks"][-1]/60)*60)
              plots[0]["xticklabel"].append(np.ceil(plots[0]["xticks"][-1]/60)*60)
          plots[0]["xticklabel"] = get_xticklabel_hour(plots[0]["xticklabel"], start=config.start_hour)
          
      plt.xticks(plots[0]["xticks"], plots[0]["xticklabel"])
      #else: 
          
      #    logger.debug(f"xticks: {int(plots[0]['xticks'][-1])}")
      #    plt.xticks([i for i in range(0,int(plots[0]["xticks"][-1])-1,10)], [i for i in range(0,int(plots[0]["xticks"][-1])-1,10)])
  
      # y axis
  
      if compute == "percent": 
          if int(end) <= 1:
              ax.set_ylim(0,1)
              plt.yticks([i*0.1 for i in range(11)], [i*10 for i in range(11)])
          else:         
              ax.hlines(1,start_x,end_x,colors="red",alpha=0.5,zorder=2)   
              end_new = int(end*10)+1
              plt.yticks([i*0.1 for i in range(end_new)], [i*10 for i in range(end_new)]) 
          
      else: 
          
          #logger.debug(f"y-axis: start: {start}: end: {end}")
          if end <= 1: 
              ax.yaxis.grid(False)
              plt.yticks([i*0.1 for i in range(11)])
          elif end <= 7: 
              plt.yticks([i for i in range(8)])
          elif end <= 15:  
              plt.yticks([i for i in range(16)])
          elif end <= 60:
              plt.yticks([i for i in range(0,60+6,5)])
              # ax.hlines(50,start_x,end_x,colors="red",alpha=0.5,zorder=2)  
          elif end <= 200: 
              plt.yticks([i for i in range(0,int(end)+11,10)])
  
      if "ServiceTime" in y:
          ax.hlines(10,0,plots[0]["xticks"][-1],colors="red",alpha=0.5,zorder=2)  
              
      # plt.axvspan(end_x-25,end_x, facecolor='grey', alpha=0.2)        
      # plt.vlines(108,0,250, facecolor='orange', alpha=0.9) 
      # plt.vlines(73,0,250, facecolor='orange', alpha=0.9) 
      # create legends 
      pos = cp.copy(pos_legend)
      width = 1/sum([len(plot["legend"]) for plot in plots])
      for plot in plots:          
              
          if plot["print_legend"] or len(plots)>1:
              
              #legend = ax.legend(plot["legend"],labels=plots["legend_label"], loc='upper left',bbox_to_anchor=pos, borderaxespad=0.,framealpha=1)
              for i in range(len(plot["legend"])):
                  
                  if pos[1] >= 1:
                      loc = 'lower left'
                  else: 
                      loc = 'upper left'
                  
                  if len(plots)>1 or len(plot["legend_title"])>1:
                      legend_title = plot["legend_title"][i]
                  else: 
                      legend_title = None
                      
                  legend = ax.legend(plot["legend"][i],plot["legend_label"][i],title=legend_title,loc=loc,bbox_to_anchor=pos)
  
                  ax.add_artist(legend)   
                  if pos[1] >= 1:
                      pos[0] += width
                  else: 
                      pos[1] -= 0.1*plot["legend_height"]   
      
      title = f"{x}_{y}_" + title
      title = title.replace(":","")
      title = title.replace("+","-")
      
      logger.info(f"plot: {title}")    
      
      if show:       
          plt.show()
      else:
  
          if folder: 
              folder = f"{folder}/"
          
          logger.debug(f"{folder}/{title}.{FORMAT}")
          plt.savefig(f"{folder}{title}.{FORMAT}",bbox_inches='tight')
          
      plt.close("all") 
  
  
      
  
  
  ### Return boxplots
  def create_plots(file,x="",y={},compare="", aggregate_x=False,filter_value={},output="",y_axis="",show=True,FORMAT="pdf",title_instance="",pos_legend=[0.00,0.99], path = ""):
      
      set_plot_design()
      
      data = {}
      
      fig, ax = plt.subplots()
      
      y_global = False
      plottypes = []
      
      for plottype in y: 
          plottypes.append(plottype)
          y_value = y[plottype][0]
          if isinstance(y_value,str):
              if isinstance(y_global,str):             
                  y_global = strIntersection(y_value,y_global)
              else: 
                  y_global = y_value
                         
          else: 
              for val in y_value:
                  if isinstance(y_global,str):             
                      y_global = strIntersection(val,y_global)
                  else: 
                      y_global = val
      
      # logger.debug(f"y_global: {y_global}")    
      plots = []
      
      for plottype in y: 
          y_value = y[plottype][0] 
          
          if len(y[plottype]) > 2:
              design = y[plottype][2]
              compute = y[plottype][1]
          elif len(y[plottype]) > 1: 
              compute = y[plottype][1]
              design = "bot"
          else: 
              compute = ""
          
          if isinstance(y_value,str):
              label_legend = strDifference(y_value,y_global)
          else: 
              label_legend = []
              for val in y_value:
                  label_legend.append(strDifference(val,y_global))
          
          if  len(plottypes) > 1: 
              print_legend = True
          else: 
              print_legend = False
          if plottype == "boxplot":
              data[plottype], df, iterator_key = analyse_entities_log(file,x=x,ys=y_value,compare=compare, aggregate_x=aggregate_x,filter_value=filter_value,compute=compute)
              plot = create_boxplot(fig,ax,data[plottype],x=x,y=y[plottype], label_legend = print_legend)
          
          elif plottype == "barchart":
              data[plottype], df, iterator_key = analyse_entities_log(file,x=x,ys=y_value,compare=compare, aggregate_x=aggregate_x,filter_value=filter_value,compute=compute)
              plot = create_chart_plot(fig,ax,data[plottype],x=x,y=y[plottype], label_legend = label_legend, design=design)          
          
          elif plottype == "line":  
              data[plottype], df, iterator_key = analyse_entities_log(file,x=x,ys=y_value,compare=compare, aggregate_x=aggregate_x,filter_value=filter_value,compute=compute)
              plot = create_line_plot(fig,ax,data[plottype],x=x,y=y[plottype], label_legend = print_legend)
              
          elif plottype == "level":  
              data[plottype], df, iterator_key = analyse_entities_log(file,x=x,ys=y_value,compare=compare, aggregate_x=1,filter_value=filter_value,compute=compute)
              plot = create_line_plot(fig,ax,data[plottype],x=x,y=y[plottype], label_legend = print_legend)
          plots.append(plot)
          
      folder, title = get_file_name(file, df,filter_value,title_instance,plottypes)
      if path: 
          folder = path
      
      if title: 
          merge_and_save_final_plot(fig,ax,plots, iterator_key,x=x,y=y_global,compute=y_axis,show=show,folder=folder,title=title,FORMAT=FORMAT,pos_legend=pos_legend)    
  
  """
  Prepare a boxplot
  """    
  def create_boxplot(fig,ax,data,x="", y="",label_legend=False):
      
      plot = {}
      
      boxes = []
      legend_titles = {}
      title = ""
      for d in data:
  
          i_met =0
          
          for met in data[d]:
              if label_legend: 
                  if not title: 
                      title = d
                  elif met in legend_titles: 
                      title = get_legend_name(legend_titles[met], prefix="boxplot")
                  else: 
                      title = strIntersection(title,d)
                      title = get_legend_name(title, prefix="boxplot")
              else: 
                  title = None
              legend_titles[met] = title
          i = 1
          positions, xticks, xticklabels = get_positions(data[d],plot="boxplot")
          
          legend_label = []
          
          for met in data[d]:   
              legend_label.append(get_method_name(met))
              # Creating plot 
              width = 0.6
              if len(data[d]) > 1:
                  pos = positions[met]
                  width = width/len(data[d])
              else: 
                  pos = None
              box = plt.boxplot(data[d][met].values(),sym=".", positions=pos, widths=width)
              boxes.append(box["boxes"][0])
              i = -1
              set_box_color(box, color_for_method(met))
              
      plot["print_legend"] = False
      if len(data[d]) > 1: 
          plot["print_legend"] = True
          
      plot["legend_height"] = len(data[d])
      plot["legend"] = [boxes]
      plot["legend_title"] = [legend_titles[met] for met in legend_titles]
      plot["xticks"] = xticks
      plot["xticklabel"] = xticklabels
      plot["legend_label"] = [legend_label]
      return plot
  
                        
  """
  Prepare a bar plot
  """
  def create_chart_plot(fig,ax,data,x="", y="",label_legend="",pattern_density=1, design="bot"):
      global global_design
      global_design = design
      plot = {}
      
      
      #logger.debug(f"Create bar chart for: \n {data}")
      boxes = {}
      
      legend_labels = {}
      legend_titles = {}
      legends = []
      bottom = {}
      alpha = 0.8
      for d in data:
          for met in data[d]:
              bottom[met] = None
              boxes[met] = []
              legend_labels[met] = []
              
      i_y = 0
  
      title = ""
      logger.debug(data)
      for d in data:
          width = 0.8
          align = "center"
  
          if design == "seperate": 
              positions, xticks, xticklabels = get_positions(data,plot="barchart",level=3)
              if len(data) > 1: 
                  width = width/len(data)
                  align = "edge"
          else: 
              positions, xticks, xticklabels = get_positions(data[d],plot="barchart")
              # logger.debug(positions)
              # logger.debug(xticks)
              # logger.debug(xticklabels)
          i_met =0
          
          for met in data[d]:
              if len(data[d]) > 1: 
                  width = 0.8
                  title = get_legend_name(get_method_name(met))
                  width = width/len(data[d])
                  align = "edge"
              else: 
                  if not title: 
                      title = d
                  elif met in legend_titles: 
                      title = get_legend_name(legend_titles[met])
                  else: 
                      title = strIntersection(title,d)
                      title = get_legend_name(title)
              # logger.debug(width)
                  
              if design == "seperate": 
                  pos = positions[d]
              else: 
                  pos = positions[met]
              legend_titles[met] = title
              legend_labels[met].append(get_plot_name(d))
              
              edge_color = "black"
              if len(data[d]) > 100:
                  edgecolor=color_for_method(i=i_y)
                          
              # Creating plot
              bar = plt.bar(pos, data[d][met].values(), align=align, label=f"{d} ({get_method_name(met)})", edgecolor=edge_color,color = color_for_method(i=i_y),alpha = alpha, bottom=bottom[met], width=width,hatch=pattern_density*pattern_for_method(met))
              # logger.debug(f"data for y={y}; met={met}:\n {data[y][met].values()}")
              if "bot" in design:
                  new_values = list(data[d][met].values())
                  # replace NaN: 
                  new_values = [0 if np.isnan(x) else x for x in new_values]
                  if bottom[met] != None:
                      bottom[met] = np.array(bottom[met])
                      bottom[met] += np.array(new_values)
                  else:
                      bottom[met] = np.array(new_values)  
                  bottom[met] = list(bottom[met])
              
              
              boxes[met].append(bar)
              i_met += 1
              if len(data) <= 1: 
                  i_y += 1
          i_y += 1
          #alpha -= 1/(len(data))
          
      plot["print_legend"] = False
      if len(data) > 1: 
          plot["print_legend"] = True
          
      plot["legend_height"] = len(data) + 1
      plot["legend"] = [boxes[met] for met in boxes]
      plot["legend_title"] = [legend_titles[met] for met in legend_titles]
      plot["xticks"] = xticks
      plot["xticklabel"] = xticklabels
      plot["legend_label"] = [legend_labels[met] for met in legend_labels]
      return plot
      
  """
  Prepare a basic plot
  """
  def create_line_plot(fig,ax,data,x="", y="",label_legend=""):
      global global_design
      
      plot = {}
      labels = []
      legends = []
      lines = []
      i = 0
      
      legend_name = ""
      for d in data:
          if label_legend:
              if not legend_name: 
                  legend_name = d
              else: 
                  legend_name = strIntersection(legend_name,d)
          if global_design == "seperate": 
              positions, xticks, xticklabels = get_positions(data,plot="line",level=3)
          else: 
              positions, xticks, xticklabels = get_positions(data[d],plot="line")
          for met in data[d]: 
              if len(data[d]) > 1: 
                  color = color_for_method(met)
                  label = get_method_name(met)
                  marker = marker_for_method(met)
              else: 
                  color = color_for_method(i=i)
                  method_name = get_method_name(met)
                  label = get_plot_name(d)
                  marker = marker_for_method(i=i)
  
              if global_design == "seperate": 
                  pos = positions[d]
              else: 
                  pos = positions[met]               
              
              labels.append(label)
              lines += ax.plot(pos,data[d][met].values(),label=label,color=color,marker=marker)
          legends.append(lines)
          i += 1
          
          plot["print_legend"] = False    
          if len(data[d]) > 1: 
              plot["print_legend"] = True
          
      plot["legend_height"] = len(data)
      plot["legend"] = [lines]
      plot["legend_title"] = [get_legend_name(legend_name)]
      plot["xticks"] = xticks
      plot["xticklabel"] = xticklabels
      plot["legend_label"] = [labels]
      
      return plot
      
  def create_bin_plot(fig,ax,data,x="", y="",label_legend=""):
  
      global global_design
      
      plot = {}
      labels = []
      legends = []
      lines = []
      i = 0
      
      legend_name = ""
      for d in data:
          if label_legend:
              if not legend_name: 
                  legend_name = d
              else: 
                  legend_name = strIntersection(legend_name,d)
          if global_design == "seperate": 
              positions, xticks, xticklabels = get_positions(data,plot="line",level=3)
          else: 
              positions, xticks, xticklabels = get_positions(data[d],plot="line")
          for met in data[d]: 
              if len(data[d]) > 1: 
                  color = color_for_method(met)
                  label = get_method_name(met)
                  marker = marker_for_method(met)
              else: 
                  color = color_for_method(i=i)
                  method_name = get_method_name(met)
                  label = get_plot_name(d)
                  marker = marker_for_method(i=i)
  
              if global_design == "seperate": 
                  pos = positions[d]
              else: 
                  pos = positions[met]               
              
              labels.append(label)
              lines += ax.plot(pos,data[d][met].values(),label=label,color=color,marker=marker)
          legends.append(lines)
          i += 1
          
          plot["print_legend"] = False    
          if len(data[d]) > 1: 
              plot["print_legend"] = True
          
      plot["legend_height"] = len(data)
      plot["legend"] = [lines]
      plot["legend_title"] = [get_legend_name(legend_name)]
      plot["xticks"] = xticks
      plot["xticklabel"] = xticklabels
      plot["legend_label"] = [labels]
      
      return plot
  
  
  def plot_period_value_dependent(global_log,files,x="Time",y="Load", compare="instance",output="",compute="percent",show=True,filter_value = False,filetype="pdf"): 
  
      # get data
      entity_type = {}
      key = {}
          
      df, prefix = filter_all_values(global_log,filter_value)       
      instances = set(df[compare])
      
      data = {}
      dataCum = {}
      error = {}
      colors = {}
      counter = 0
      file_names = []
      
      for file in files: 
          file_name = file.split("/")[-1].split(".")[0].split("-")[-1]
          log = pd.read_csv(file,sep=";")
          data[file_name] = [[] for i in range(len(log.columns)-1)]
          iterator = np.arange(0,len(log.columns)-1,1)
          counter += 1
          colors[file_name] = color_for_method(file_name,counter)
          for instance in instances: 
              df = log[log[compare]==instance]
              for p in range(len(data[file_name])): 
                  data[file_name][p] += list(df[str(p)])
          
          error[file_name] = np.std(data[file_name],axis=1)
          data[file_name] = np.average(data[file_name],axis=1)
          try: 
              cum_data += np.copy(data[file_name])
          except: 
              cum_data = np.copy(data[file_name])
          dataCum[file_name] = np.copy(cum_data)
          file_names.insert(0,file_name)
          
      for file in files: 
          file_name = file.split("/")[-1]
          entity_type = file_name.split("-")[0]
          key = file_name.split("-")[-1].split(".")[0]
      
      """Plot """
      fig, ax = plt.subplots()
      ax.set_yticks(np.arange(0,len(log.columns)-1,1))
      ax.yaxis.grid(True)
       
      
      for file_name in file_names: 
          ax.bar(iterator,dataCum[file_name],label=file_name,color=colors[file_name])
      
      set_plot_design()
      if compute == "percent": 
          plt.yticks([i*0.1 for i in range(11)], [i*10 for i in range(11)])
      x_label, y_label = find_label(x,y)
      plt.xlabel(x_label)
      ax.set_ylabel(y_label) 
      
      #ax.legend(loc='upper left',bbox_to_anchor=(0, 1.2), borderaxespad=0.) 
      ax.legend(loc='upper left') 
      
      if show:         
          plt.show()
      else: 
          plt.savefig(f"time_{entity_type}_{key}.{filetype}",bbox_inches='tight')
  
  """ Descriptive Plots """ 
  
  ### Plotting as Time Space Network: 
  	# x-axis: Simulation time
  	# y-axis: Different Locations 
  	# One plot per vehicle 
  def plot_time_space_network(LogFile,path="",FORMAT="pdf"): 
      if isinstance(LogFile,str):
          data = log.read_entities_log(LogFile)
          data = pd.read_csv(LogFile,sep=";")
          #logger.debug(f"data:{data}")
      else: 
          data = LogFile.read_log()
          #logger.debug(f"data:{data}")
          
      tram = {}
      time = []
      numStop = 0
      print(data)
      path = path + "figs"
      if not os.path.exists(path):
          os.makedirs(path)
      entities = list(set(data["Entity"]))
      entities.sort()
      for key in entities: 
          if "Tram" in key: 
              tram[key] = data[data["Entity"] == key]
              print(tram)
              time += [float(t) for t in tram[key]["Time"]]
          elif "Stop" in key: 
              numStop += 1
      all_time = sorted(list(set(time)))
      print(numStop)
      # New figure and layout 
      width = len(all_time)
      plt.figure("{}-Time-Space-Network".format(path),figsize=(0.15*width, 6))
      all_locations = [l for l in range(numStop)]
      plt.yticks(all_locations)
      plt.tight_layout(pad=2)
      if width > 100: 
          plt.margins(x=0.001)
      else: 
          plt.margins(x=0.01)
  
      # Axis labels
      plt.xlabel('Time')
      plt.ylabel('Stop')
      #plt.title("Space-Time-Network")		
  
      # area around location
      for l in range(numStop): 
          plt.plot([all_time[0],all_time[-1]],[l,l],color='whitesmoke',linewidth=20.0)
  
      # Each tram has is own plot
      for key in tram:
          t = tram[key]
          time = [float(i) for i in t["Time"]]
          location = [i for i in t["Location"]]
          status = [i for i in t["Status"]]
          
          # Convert location strings to integers
          for l in reversed(range(len(location))): 
              try: 
                  location[l] = int(location[l])
              except: 
                  # When the tram is between two stations, the first station marks the start point
                  location[l] = int(location[l].split("-")[0])
          
          # Shift the trams which are waiting slightly up- or downwards
          for l in range(len(location)):	
  
              # Shift the lines slightly up- or downwards, to symbolise the pathection	 
              if location[l] <= location[l-1]:
                  location[l] += 0.02
              else: 
                  location[l] -= 0.02
                      
              if status[l] == "Waiting":
                  if location[l] <= location[l-1]:
                      location[l] += 0.05
                  else: 
                      location[l] -= 0.05
          if key == "Tram-4":           
              plt.plot(time,location,marker=".",color="red",label=key.replace("-"," "))
          else:
              plt.plot(time,location,marker=".",color="purple",label=key.replace("-"," "))
      #plt.legend()	
      #plt.show()
      try:
          plt.savefig("%s/Time-Space-Network.%s"%(path,FORMAT))
          plt.close("all")
          logger.info("Saved Time-Space-Network.%s in %s"%(FORMAT,path))
      except: 
          logger.info("Plot for Time-Space-Network could not be saved.")
  
  
  ### Plot 
  def plot_time_series(LogFile,entity,y_keyword,x_keyword="Time",path="",FORMAT="svg"):
      if isinstance(LogFile,str):
          data = log.read_entities_log(LogFile)
      else: 
          data = LogFile.read_log()
      title = "%s_%s"%(entity,y_keyword)
      entities = []
      x = []
      path = path + "/figs"
  
      for key in data: 
          if entity in key: 
              entities.append(data[key])
              entities[-1]["Name"] = key
              x += [float(t) for t in data[key][x_keyword]]
              
              entities[-1][x_keyword] = [float(t) for t in entities[-1][x_keyword]]
              entities[-1][y_keyword] = [float(t) for t in entities[-1][y_keyword]]
      x_all = sorted(list(set(x)))	
  
      # New figure and layout 
      width = 80#len(x_all)
      plt.figure("{}-{}".format(path,title),figsize=(0.1*width, 6))
  
  
      # Axis labels
      plt.xlabel(x_keyword)
      plt.ylabel(y_keyword)
      plt.title(title.replace("_"," "))	
          
      for e in entities: 
          x = e[x_keyword]
          y = e[y_keyword]
          plt.plot(x,y,label=e["Name"].replace("-"," "))
  
      plt.tight_layout(pad=1)
      plt.legend()
      try:
          plt.savefig("%s/%s.%s"%(path,title,FORMAT))
          plt.close("all")
          logger.info("Saved %s.%s in %s"%(title,FORMAT,path))
      except: 
          logger.info("Plot for %s could not be saved."%(y_keyword))
          
  def plot_box_plot_performance(file,path="",FORMAT="pdf"): 
      df = pd.read_csv(file,delimiter=";")
      
      return 0
      
  def plot_box_plot(logfile,path="",FORMAT="pdf"): 
     
      # Read the entities log
      data = log.read_entities_log(logfile)
      
      resultdict = {}
      resultdict["Tram"] = {}
      resultdict["Stop"] = {}
      
      for key1 in data:
          sucessful = True
          for key2 in data[key1]:
              
              if key2 in ["Passengers", "Cargo"]:
                  array = []
                  counter = 0
                  
                  
                  for index in range(len(data[key1]["Time"])): 
                      
                      while counter <= float(data[key1]["Time"][index]):
                          
                          try: 
                              array.append(float(data[key1][key2][index]))
                          except: 
                              sucessful = False
                              break
                          counter += 1
              
                  if sucessful: 
                      if "Tram" in key1: 
                          try:    
                              resultdict["Tram"][key2][key1] = array
                          except:
                              resultdict["Tram"][key2] = {key1: array}
                      if "Stop" in key1: 
                          try:    
                              resultdict["Stop"][key2][key1] = array
                          except:
                              resultdict["Stop"][key2] = {key1: array}
                      
      for entity in resultdict:
          for key in resultdict[entity]: 
          
              fig = plt.figure(figsize =(10, 7)) 
              labels = resultdict[entity][key].keys()
              # Creating plot 
              plt.boxplot(resultdict[entity][key].values(),labels = labels) 
              
            
              # save plot
              title = f"bp_{entity}_{key}"            
              plt.savefig(f"{path}/figs/{title}.{FORMAT}")
              plt.close("all") 
      
  
  ### General plot
  def plot_log_files(path="",format="svg"):
  	logger.info('Starting analysis ...')
  	
  	data = []
  	numFigure = 1
  	for filename in os.listdir("logs"):
  		
  		logger.info('***\nReading %s ...' %(filename))
  		with open("%s/%s"%("logs",filename),'r') as file:
  			titles = file.readline().split()
  			data = file.read().split("\n")
  			data = [row.split() for row in data]
  			for row in data: 
  				if not row: 
  					data.remove(row)
  					
  			# x values: 
  			x = [float(row[0]) for row in data]
  			
  			y = []
  			for i in range(1,len(titles)):	
  				try: 
  					float(data[0][i])	
  				except: 	
  					continue
  				
  				directory = path + "/figs"
  				if not os.path.exists(directory):
  					os.mkdir(directory)
  				directory += "/" + filename.strip("log")	
  				if not os.path.exists(directory):
  					os.mkdir(directory)
  				
  				numFigure += 1
  				
  				try: 
  					y = [float(row[i]) for row in data]
  					
  					plt.figure(numFigure)
  					plt.plot(x,y,drawstyle='steps-mid')
  					plt.xlabel('Time')
  					plt.ylabel(titles[i])
  					plt.savefig("%s/%s.%s"%(directory,titles[i],format))
  					
  					plt.close("all")
  					logger.info("Saved %s.%s in %s"%(titles[i],format,directory))
  				except: 
  					logger.info("Plot for %s could not be saved."%(titles[i]))
                      
  # function to visualise the passenger arrivals
  def plot_passenger_arrival_intensity(passengerData,station=False,path="",FORMAT="pdf",name="",show=True):
      passengerData = passengerData.replace("/",".")
      pas = import_module(passengerData)
      
      intensity = np.transpose(pas.station_arriving_intensity)
      
      
      if not isinstance(station,int) :
      
          xs = [i for i in range(len(intensity[0]))]
          ys = [i for i in range(len(intensity))]
          
          fig, ax = plt.subplots(figsize=(8,4.8))
          # fig, ax = plt.subplots()
  
          ax.yaxis.grid(True)        
          
          for y in ys: 
              # plt.step(xs,intensity[y], label=f"station {y}")
              plt.plot(xs,intensity[y], label=f"station {y}")
              
          plt.legend()
          xticks = [i for i in range(0,xs[-1]+2,120)]
          xticklabels = get_xticklabel_hour(xticks, start=4)
          # plt.xticks(xticks)
          plt.xticks(xticks, xticklabels)
          set_plot_design()
          plt.yticks([i for i in range(11)]) 
          # plt.axvspan(4*60,5*60, facecolor='grey', alpha=0.2)
          # plt.text(4*60+30, 0.45, 'A', fontsize=25, ha = "center", family="sans-serif")
          # plt.axvspan(10*60,13*60, facecolor='grey', alpha=0.2)
          # plt.text(11*60+30, 0.45, 'B', fontsize=25, ha = "center", family="sans-serif")
          plt.xlabel("time")
          plt.ylabel("passenger arriving intensity")        
          
          # heat map
          # Setting the labels of x axis.
          # set the xticks as student-names
          # rotate the labels by 90 degree to fit the names
          
          # y = [i for i in range(len(intensity[0]))]
          # plt.xticks(ticks=x,labels=x,rotation=90)
          # Setting the labels of y axis.
          # set the xticks as subject-names
          #plt.yticks(ticks=np.arange(len(subjects)),labels=subjects)
          # use the imshow function to generate a heatmap
          # cmap parameter gives color to the graph
          # setting the interpolation will lead to different types of graphs
          # plt.imshow(intensity, cmap='hot',interpolation="nearest")
      else: 
          """Plot """
          fig, ax = plt.subplots()
          ax.yaxis.grid(True)
          
          xs = [i for i in range(len(intensity[0]))][:numPeriods]
          ys = [i for i in range(len(intensity[:station+1]))]
          
          ax.bar(x,intensity[station],color="royalblue")
          
          set_plot_design()
          plt.xlabel("time (min)")
          plt.ylabel("passenger arriving intensity")
          #plt.yticks([i*0.1 for i in range(11)])
          xticks = [i for i in range(0,x[-1]+2,30)]
          plt.xticks(xticks)
      if show:         
          plt.show()
      else: 
          plt.savefig(f"{path}/time_passenger_intensity{name}.{FORMAT}",bbox_inches='tight')