{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "\n# Timeline with lines, dates, and text\n\nHow to create a simple timeline using Matplotlib release dates.\n\nTimelines can be created with a collection of dates and text. In this example,\nwe show how to create a simple timeline using the dates for recent releases\nof Matplotlib. First, we'll pull the data from GitHub.\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "from datetime import datetime\n\nimport matplotlib.pyplot as plt\nimport numpy as np\n\nimport matplotlib.dates as mdates\n\ntry:\n # Try to fetch a list of Matplotlib releases and their dates\n # from https://api.github.com/repos/matplotlib/matplotlib/releases\n import json\n import urllib.request\n\n url = 'https://api.github.com/repos/matplotlib/matplotlib/releases'\n url += '?per_page=100'\n data = json.loads(urllib.request.urlopen(url, timeout=1).read().decode())\n\n dates = []\n releases = []\n for item in data:\n if 'rc' not in item['tag_name'] and 'b' not in item['tag_name']:\n dates.append(item['published_at'].split(\"T\")[0])\n releases.append(item['tag_name'].lstrip(\"v\"))\n\nexcept Exception:\n # In case the above fails, e.g. because of missing internet connection\n # use the following lists as fallback.\n releases = ['2.2.4', '3.0.3', '3.0.2', '3.0.1', '3.0.0', '2.2.3',\n '2.2.2', '2.2.1', '2.2.0', '2.1.2', '2.1.1', '2.1.0',\n '2.0.2', '2.0.1', '2.0.0', '1.5.3', '1.5.2', '1.5.1',\n '1.5.0', '1.4.3', '1.4.2', '1.4.1', '1.4.0']\n dates = ['2019-02-26', '2019-02-26', '2018-11-10', '2018-11-10',\n '2018-09-18', '2018-08-10', '2018-03-17', '2018-03-16',\n '2018-03-06', '2018-01-18', '2017-12-10', '2017-10-07',\n '2017-05-10', '2017-05-02', '2017-01-17', '2016-09-09',\n '2016-07-03', '2016-01-10', '2015-10-29', '2015-02-16',\n '2014-10-26', '2014-10-18', '2014-08-26']\n\ndates = [datetime.strptime(d, \"%Y-%m-%d\") for d in dates] # Convert strs to dates.\nreleases = [tuple(release.split('.')) for release in releases] # Split by component.\ndates, releases = zip(*sorted(zip(dates, releases))) # Sort by increasing date." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Next, we'll create a stem plot with some variation in levels as to\ndistinguish even close-by events. We add markers on the baseline for visual\nemphasis on the one-dimensional nature of the timeline.\n\nFor each event, we add a text label via `~.Axes.annotate`, which is offset\nin units of points from the tip of the event line.\n\nNote that Matplotlib will automatically plot datetime inputs.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# Choose some nice levels: alternate meso releases between top and bottom, and\n# progressively shorten the stems for micro releases.\nlevels = []\nmacro_meso_releases = sorted({release[:2] for release in releases})\nfor release in releases:\n macro_meso = release[:2]\n micro = int(release[2])\n h = 1 + 0.8 * (5 - micro)\n level = h if macro_meso_releases.index(macro_meso) % 2 == 0 else -h\n levels.append(level)\n\n\ndef is_feature(release):\n \"\"\"Return whether a version (split into components) is a feature release.\"\"\"\n return release[-1] == '0'\n\n\n# The figure and the axes.\nfig, ax = plt.subplots(figsize=(8.8, 4), layout=\"constrained\")\nax.set(title=\"Matplotlib release dates\")\n\n# The vertical stems.\nax.vlines(dates, 0, levels,\n color=[(\"tab:red\", 1 if is_feature(release) else .5) for release in releases])\n# The baseline.\nax.axhline(0, c=\"black\")\n# The markers on the baseline.\nmeso_dates = [date for date, release in zip(dates, releases) if is_feature(release)]\nmicro_dates = [date for date, release in zip(dates, releases)\n if not is_feature(release)]\nax.plot(micro_dates, np.zeros_like(micro_dates), \"ko\", mfc=\"white\")\nax.plot(meso_dates, np.zeros_like(meso_dates), \"ko\", mfc=\"tab:red\")\n\n# Annotate the lines.\nfor date, level, release in zip(dates, levels, releases):\n version_str = '.'.join(release)\n ax.annotate(version_str, xy=(date, level),\n xytext=(-3, np.sign(level)*3), textcoords=\"offset points\",\n verticalalignment=\"bottom\" if level > 0 else \"top\",\n weight=\"bold\" if is_feature(release) else \"normal\",\n bbox=dict(boxstyle='square', pad=0, lw=0, fc=(1, 1, 1, 0.7)))\n\nax.xaxis.set(major_locator=mdates.YearLocator(),\n major_formatter=mdates.DateFormatter(\"%Y\"))\n\n# Remove the y-axis and some spines.\nax.yaxis.set_visible(False)\nax.spines[[\"left\", \"top\", \"right\"]].set_visible(False)\n\nax.margins(y=0.1)\nplt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ ".. admonition:: References\n\n The use of the following functions, methods, classes and modules is shown\n in this example:\n\n - `matplotlib.axes.Axes.annotate`\n - `matplotlib.axes.Axes.vlines`\n - `matplotlib.axis.Axis.set_major_locator`\n - `matplotlib.axis.Axis.set_major_formatter`\n - `matplotlib.dates.MonthLocator`\n - `matplotlib.dates.DateFormatter`\n\n.. tags::\n\n component: annotate\n plot-type: line\n level: intermediate\n\n" ] } ], "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.13.2" } }, "nbformat": 4, "nbformat_minor": 0 }