{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "\n# Custom tick formatter for time series\n\n.. redirect-from:: /gallery/text_labels_and_annotations/date_index_formatter\n.. redirect-from:: /gallery/ticks/date_index_formatter2\n\nWhen plotting daily data, e.g., financial time series, one often wants\nto leave out days on which there is no data, for instance weekends, so that\nthe data are plotted at regular intervals without extra spaces for the days\nwith no data.\nThe example shows how to use an 'index formatter' to achieve the desired plot.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "import matplotlib.pyplot as plt\nimport numpy as np\n\nimport matplotlib.cbook as cbook\nfrom matplotlib.dates import DateFormatter, DayLocator\nimport matplotlib.lines as ml\nfrom matplotlib.ticker import Formatter\n\n# Load a structured numpy array from yahoo csv data with fields date, open, high,\n# low, close, volume, adj_close from the mpl-data/sample_data directory. The\n# record array stores the date as an np.datetime64 with a day unit ('D') in\n# the date column (``r['date']``).\nr = cbook.get_sample_data('goog.npz')['price_data']\nr = r[:9]  # get the first 9 days\n\nfig, (ax1, ax2) = plt.subplots(nrows=2, figsize=(6, 6), layout='constrained')\nfig.get_layout_engine().set(hspace=0.15)\n\n# First we'll do it the default way, with gaps on weekends\nax1.plot(r[\"date\"], r[\"adj_close\"], 'o-')\n\n# Highlight gaps in daily data\ngaps = np.flatnonzero(np.diff(r[\"date\"]) > np.timedelta64(1, 'D'))\nfor gap in r[['date', 'adj_close']][np.stack((gaps, gaps + 1)).T]:\n    ax1.plot(gap['date'], gap['adj_close'], 'w--', lw=2)\nax1.legend(handles=[ml.Line2D([], [], ls='--', label='Gaps in daily data')])\n\nax1.set_title(\"Plot y at x Coordinates\")\nax1.xaxis.set_major_locator(DayLocator())\nax1.xaxis.set_major_formatter(DateFormatter('%a'))\n\n\n# Next we'll write a custom index formatter. Below we will plot\n# the data against an index that goes from 0, 1,  ... len(data).  Instead of\n# formatting the tick marks as integers, we format as times.\ndef format_date(x, _):\n    try:\n        # convert datetime64 to datetime, and use datetime's strftime:\n        return r[\"date\"][round(x)].item().strftime('%a')\n    except IndexError:\n        pass\n\n# Create an index plot (x defaults to range(len(y)) if omitted)\nax2.plot(r[\"adj_close\"], 'o-')\n\nax2.set_title(\"Plot y at Index Coordinates Using Custom Formatter\")\nax2.xaxis.set_major_formatter(format_date)  # internally creates FuncFormatter"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Instead of passing a function into `.Axis.set_major_formatter` you can use\nany other callable, e.g. an instance of a class that implements __call__:\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "class MyFormatter(Formatter):\n    def __init__(self, dates, fmt='%a'):\n        self.dates = dates\n        self.fmt = fmt\n\n    def __call__(self, x, pos=0):\n        \"\"\"Return the label for time x at position pos.\"\"\"\n        try:\n            return self.dates[round(x)].item().strftime(self.fmt)\n        except IndexError:\n            pass\n\n\nax2.xaxis.set_major_formatter(MyFormatter(r[\"date\"], '%a'))\n\nplt.show()"
      ]
    }
  ],
  "metadata": {
    "kernelspec": {
      "display_name": "Python 3",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "codemirror_mode": {
        "name": "ipython",
        "version": 3
      },
      "file_extension": ".py",
      "mimetype": "text/x-python",
      "name": "python",
      "nbconvert_exporter": "python",
      "pygments_lexer": "ipython3",
      "version": "3.14.6"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}