pythonmatplotliblinechart

Within a function, add dynamic ytick labels to a linechart that are evenly distributed when data value range in df parameter varies


I have a series of datasets that I have to create a line graph for. I created a function that takes a df containing the data, a date to indicate the vertical line for "Inception date", and the dir where to save the created graph.

The issue I am having is that for the line graph, I can't seem to properly add yticks to display evenly and beyond the last data point. I also want the ytick values to be rounded to the tens value.

This is what I have tried:

  1. Use np.arange() but it didn't work well. The range of the data varies based on the dataset and I couldn't figure this one out.
  2. Use a condition to check if the max value is above or below 1000 to help with the bins distribution. I am not sure if this is a good approach as I made the assumption of having 1000 as a threshold. Then, I set nbins=7 to see how it looks. I can't figure out how to make the nbins value dynamic so that the bins are evenly distributed over the y axis.

Here is my function:

def build_multiple_line_graphs(df, inception_date, line_graph_path):
  """Builds line graphs for market data."""
     # adjust this line to your local folder path
     os.makedirs(line_graph_path, exist_ok=True)
  
     inception_date = datetime.strptime(inception_date, '%Y-%m-%d')
  
     # Plot the time series
     fig, ax = plt.subplots()
 
     # setting the size of the line graph
     dpi=150
     desired_width_pixels = 563
     desired_height_pixels = 169
     fig.set_size_inches(desired_width_pixels / dpi, desired_height_pixels / dpi)
     fig.tight_layout()
     df.plot(kind = 'line', ax=ax, y=complex, color='#0092BC')
      
     # Add a horizontal line at y=0
     ax.axhline(y=0, color='#005871', linestyle='-', label='Horizontal Line')
 
     # Set the background color
     ax.set_facecolor('#F0F8FF')
     fig.patch.set_facecolor('#e4f2ff')
 
     # removing frame from the graph
     ax.spines['top'].set_visible(False)
     ax.spines['right'].set_visible(False)
     ax.spines['left'].set_visible(False)
 
     # set custom y-axis label
     max_y = max(df)
     if max_y < 1000:
        max_y = 100*math.ceil(round(max_y/100)) if max_y > 0 else 1
     else:
        max_y = round((1000*math.ceil(max_y/1000))) if max_y > 0 else 1
    
     new_max_y = max_y + (max_y//3)
     increment = round(max_y/5)
     ax.set_yticks(np.arange(0, max_y, increment))
     plt.locator_params(axis='y', nbins=7)
        
     # here is where I tried np.arange with no success
     #ax.yaxis.set_major_locator(ticker.MultipleLocator(increment))
     #ax.set_yticks(np.arange(0,df+1, increment))
     #plt.yticks(np.arange(0,max(df)+increment, increment))
     #start, end = ax.get_ylim()
     #stepsize = end/5
     #ax.yaxis.set_ticks(np.arange(0, end+1, 100))
     ax.set_ylim(ymin=0)
 
     # set custom x-axis label every 5 years
     ax.xaxis.set_major_locator(mdates.YearLocator(5))
     ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y'))
     plt.xticks(rotation=0, ha='center')
     ax.grid(axis='y', color='gray')
      
     # Customize the plot
     ax.legend().remove()
     ax.tick_params(axis='both', labelsize=8, left = False, bottom = False)
     ax.set_xlabel(None)
     ax.fill_between(df.index, df, 0, color='#FFFFFF', alpha=0.8)
     ax.set_xlim(df.index[0], df.index[-1])
            
     # formating the inception line
     ax.axvline(inception_date, color = '#0092BC', linestyle='dotted', label='Inception Date')
     ax.text(inception_date, 25, 'Inception Date', rotation=90, rotation_mode='anchor', transform_rotates_text= True, fontsize=6)
     inception_date_text = inception_date.strftime("%m/%d/%y")
     ax.annotate(inception_date_text, xy=(inception_date, -1), xytext=(-15, -18), fontsize=8,
     textcoords='offset points', ha='center', va='bottom')
 
     # saving the plot
     plt.gcf() # get current figure
     fig_size = plt.gcf().get_size_inches()
     plt.gcf().set_size_inches(fig_size[0]*2, fig_size[1]*2)
     plt.savefig(os.path.join(line_graph_path, 'name_linegraph.png'), bbox_inches='tight',dpi=300)
        
      # no display of the plot needed
      plt.close()

Here is how it looks now: enter image description here

The Inception date in this example is 2024-08-23.

Here are the libraries I used:

import matplotlib.pyplot as plt
import matplotlib.dates as mdates
import matplotlib.ticker as ticker
import os
from datetime import datetime, timedelta
from datetime import *
import math

Solution

  • You were almost there. Just add +1 on this line:

    ax.set_yticks(np.arange(0, max_y + 1, increment))