Source code for GINCCO_lib.modules.time_series_plot

__all__ = ["plot_point", "plot_point_monthly",]
import os
from datetime import datetime
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
import random

def _choose_locator(tstart, tend):
    duration_days = (tend - tstart).days

    if duration_days <= 100:  # short range
        locator = mdates.AutoDateLocator(minticks=5, maxticks=10)
        fmt = mdates.DateFormatter("%b-%d\n%Y")
    elif duration_days <= 3650:  # up to ~10 years
        months = max(1, duration_days / 30.44)
        interval = max(1, round(months / 8))
        locator = mdates.MonthLocator(bymonthday=1, interval=interval)
        fmt = mdates.DateFormatter("%b-%d\n%Y")
    else:  # very long range
        years = max(1, duration_days / 365)
        interval = max(1, round(years / 8))
        locator = mdates.YearLocator(base=interval)
        fmt = mdates.DateFormatter("%Y")
    return locator, fmt



[docs] def plot_point( title, tstart, # datetime.datetime tend, # datetime.datetime data_point, # numpy array, shape (n_point, n_time) path_save=".", name_save="figure", point_labels=None # optional list of names for each point ): """ Plot daily time series for one or multiple points over a given time range. Parameters ---------- title : str Title of the plot. tstart : datetime.datetime Start date of the time series. tend : datetime.datetime End date of the time series. data_point : np.ndarray Array of time series values. Shape can be: * (n_time,) — single time series. * (n_point, n_time) — multiple time series. path_save : str, optional Directory where the figure will be saved. Default is the current directory ``"."``. name_save : str, optional Base filename (without extension) for the saved image. Default is ``"figure"``. point_labels : list of str, optional Optional list of labels for each time series. If not provided, default names like “Point 1”, “Point 2”, etc. are used. Returns ------- str Full path to the saved PNG image. """ # Build daily time axis if data_point.ndim == 2: n_time = data_point.shape[1] elif data_point.ndim == 1: n_time = data_point.shape[0] else: raise ValueError("data_point must be 1D or 2D") times = [tstart + (tend - tstart) * i/(n_time-1) for i in range(n_time)] # Duration in days duration_days = (tend - tstart).days + 1 # Plot fig, ax = plt.subplots(figsize=(9, 3)) if data_point.ndim == 1: label = point_labels[0] if point_labels else "Point 1" ax.plot(times, data_point, lw=1.5, label=label) elif data_point.ndim == 2: for i in range(data_point.shape[0]): label = point_labels[i] if (point_labels and i < len(point_labels)) else f"Point {i+1}" ax.plot(times, data_point[i, :], lw=1.5, label=label) else: raise ValueError("data_point must be 1D or 2D") ax.set_title(title) ax.set_xlim(times[0], times[-1]) ax.set_ylabel("Value") ax.grid(True, alpha=0.3) ax.legend(ncol=1, fontsize=9, frameon=True) # Apply formatter locator, fmt = _choose_locator(tstart, tend) ax.xaxis.set_major_locator(locator) ax.xaxis.set_major_formatter(fmt) plt.setp(ax.get_xticklabels(), rotation=0, ha="center") fig.tight_layout() os.makedirs(path_save, exist_ok=True) session_id = random.randint(10000, 99999) out_path = os.path.join(path_save, f"{name_save}_{session_id}.png") fig.savefig(out_path, dpi=300) plt.close(fig) return out_path
#--------#--------#--------#--------#--------#--------# import os, random import numpy as np import matplotlib.pyplot as plt def _choose_monthly_tick_indices(n_time: int, n_xticks_desired: int): """ Pick visually nice tick indices for monthly data of length n_time. Strategy: 1) Try k in [3..12] 'regular ticks' where (n_time % k == 0) for nice integer step = n_time // k. Use the k that minimizes |k - n_xticks_desired|. Regular ticks are [0, step, 2*step, ...], then we ALWAYS append the last index (n_time-1) if it is not already included. 2) If nothing divides nicely, fall back to a rounded step from desired. Always deduplicate and keep indices sorted and within [0, n_time-1]. """ n_time = int(n_time) desired = max(3, int(n_xticks_desired)) best_k = None best_gap = 1e9 # Prefer tidy segment counts from 3 to 12 for k in range(3, 13): if n_time % k == 0: # integer step gap = abs(k - desired) if gap < best_gap: best_gap = gap best_k = k if best_k is not None: step = n_time // best_k idx = list(range(0, n_time, step)) # Ensure last index is present if idx[-1] != n_time - 1: idx.append(n_time - 1) # Keep ticks roughly 4..9 when possible if len(idx) < 4 and step > 1: # add more ticks by halving step if possible half = max(1, step // 2) idx = list(range(0, n_time, half)) if idx[-1] != n_time - 1: idx.append(n_time - 1) return sorted(set(idx)) # Fallback: pick a step based on desired count # We want about desired ticks including the last, so aim for desired-1 intervals if desired <= 1: desired = 2 base_step = max(1, int(round((n_time - 1) / (desired - 1)))) idx = list(range(0, n_time - 1, base_step)) idx.append(n_time - 1) idx = sorted(set(i for i in idx if 0 <= i < n_time)) return idx
[docs] def plot_point_monthly( title, time_label, # sequence of str or anything convertible to str, len = n_time data_point, # numpy array, shape (n_point, n_time) or (n_time,) n_xticks_desired=6, # user suggestion; function will adjust for nice spacing path_save=".", name_save="figure", point_labels=None # optional list of names for each point ): """ Plot monthly time series for one or multiple points using custom tick labels. Parameters ---------- title : str Title of the figure. time_label : sequence of str Sequence of labels for the x-axis, typically months or years. Length must match the time dimension of ``data_point``. data_point : np.ndarray Time series data array. Shape can be: * (n_time,) — single time series. * (n_point, n_time) — multiple time series. n_xticks_desired : int, optional Desired number of x-axis ticks. The function adjusts this automatically to achieve clean and evenly spaced labels. Default is 6. path_save : str, optional Directory path where the output image will be saved. Default is ``"."``. name_save : str, optional Base name (without extension) for the saved image. Default is ``"figure"``. point_labels : list of str, optional Optional list of names for each time series. If not provided, default names like “Point 1”, “Point 2”, etc. are used. Returns ------- str Full path to the saved PNG image. """ # Validate shapes time_label = [str(x) for x in time_label] if np.ndim(data_point) == 1: n_time = data_point.shape[0] n_series = 1 elif np.ndim(data_point) == 2: n_series, n_time = data_point.shape else: raise ValueError("data_point must be 1D or 2D") if len(time_label) != n_time: raise ValueError(f"len(time_label)={len(time_label)} does not match time axis n_time={n_time}") # Indices for plotting x = np.arange(n_time, dtype=int) # Choose tick indices tick_idx = _choose_monthly_tick_indices(n_time, n_xticks_desired) tick_labels = [time_label[i] for i in tick_idx] # Plot fig, ax = plt.subplots(figsize=(9, 3)) if np.ndim(data_point) == 1: label = point_labels[0] if point_labels else "Point 1" ax.plot(x, data_point, lw=1.5, label=label) else: for i in range(n_series): label = point_labels[i] if (point_labels and i < len(point_labels)) else f"Point {i+1}" ax.plot(x, data_point[i, :], lw=1.5, label=label) ax.set_title(title) ax.set_xlim(0, n_time - 1) ax.set_ylabel("Value") ax.grid(True, alpha=0.3) ax.legend(ncol=1, fontsize=9, frameon=True) # Apply ticks ax.set_xticks(tick_idx) ax.set_xticklabels(tick_labels, rotation=0, ha="center") fig.tight_layout() os.makedirs(path_save, exist_ok=True) session_id = random.randint(10000, 99999) out_path = os.path.join(path_save, f"{name_save}_{session_id}.png") fig.savefig(out_path, dpi=300) plt.close(fig) return out_path