diff --git a/.flake8 b/.flake8 index 21e44dfea9d2..546c5f39d628 100644 --- a/.flake8 +++ b/.flake8 @@ -145,6 +145,7 @@ per-file-ignores = examples/lines_bars_and_markers/span_regions.py: E402 examples/lines_bars_and_markers/stem_plot.py: E402 examples/lines_bars_and_markers/step_demo.py: E402 + examples/lines_bars_and_markers/timeline.py: E402 examples/misc/agg_buffer.py: E402 examples/misc/anchored_artists.py: E501 examples/misc/contour_manual.py: E501 diff --git a/examples/lines_bars_and_markers/timeline.py b/examples/lines_bars_and_markers/timeline.py index 6b508ddc5e46..3ef964defe48 100644 --- a/examples/lines_bars_and_markers/timeline.py +++ b/examples/lines_bars_and_markers/timeline.py @@ -15,57 +15,105 @@ import matplotlib.dates as mdates from datetime import datetime -# A list of Matplotlib releases and their dates -# Taken from https://api.github.com/repos/matplotlib/matplotlib/releases -names = ['v2.2.2', 'v2.2.1', 'v2.2.0', 'v2.1.2', 'v2.1.1', 'v2.1.0', 'v2.0.2', - 'v2.0.1', 'v2.0.0', 'v1.5.3', 'v1.5.2', 'v1.5.1', 'v1.5.0', 'v1.4.3', - 'v1.4.2', 'v1.4.1', 'v1.4.0'] - -dates = ['2018-03-17T03:00:07Z', '2018-03-16T22:06:39Z', - '2018-03-06T12:53:32Z', '2018-01-18T04:56:47Z', - '2017-12-10T04:47:38Z', '2017-10-07T22:35:12Z', - '2017-05-10T02:11:15Z', '2017-05-02T01:59:49Z', - '2017-01-17T02:59:36Z', '2016-09-09T03:00:52Z', - '2016-07-03T15:52:01Z', '2016-01-10T22:38:50Z', - '2015-10-29T21:40:23Z', '2015-02-16T04:22:54Z', - '2014-10-26T03:24:13Z', '2014-10-18T18:56:23Z', - '2014-08-26T21:06:04Z'] -dates = [datetime.strptime(ii, "%Y-%m-%dT%H:%M:%SZ") for ii in dates] +try: + # Try to fetch a list of Matplotlib releases and their dates + # from https://api.github.com/repos/matplotlib/matplotlib/releases + import urllib.request + import json + + url = 'https://api.github.com/repos/matplotlib/matplotlib/releases' + url += '?per_page=100' + data = json.loads(urllib.request.urlopen(url, timeout=.4).read().decode()) + + dates = [] + names = [] + for item in data: + if 'rc' not in item['tag_name'] and 'b' not in item['tag_name']: + dates.append(item['published_at'].split("T")[0]) + names.append(item['tag_name']) + # Convert date strings (e.g. 2014-10-18) to datetime + dates = [datetime.strptime(d, "%Y-%m-%d") for d in dates] + +except Exception: + # In case the above fails, e.g. because of missing internet connection + # use the following lists as fallback. + names = ['v2.2.4', 'v3.0.3', 'v3.0.2', 'v3.0.1', 'v3.0.0', 'v2.2.3', + 'v2.2.2', 'v2.2.1', 'v2.2.0', 'v2.1.2', 'v2.1.1', 'v2.1.0', + 'v2.0.2', 'v2.0.1', 'v2.0.0', 'v1.5.3', 'v1.5.2', 'v1.5.1', + 'v1.5.0', 'v1.4.3', 'v1.4.2', 'v1.4.1', 'v1.4.0'] + + dates = ['2019-02-26', '2019-02-26', '2018-11-10', '2018-11-10', + '2018-09-18', '2018-08-10', '2018-03-17', '2018-03-16', + '2018-03-06', '2018-01-18', '2017-12-10', '2017-10-07', + '2017-05-10', '2017-05-02', '2017-01-17', '2016-09-09', + '2016-07-03', '2016-01-10', '2015-10-29', '2015-02-16', + '2014-10-26', '2014-10-18', '2014-08-26'] + + # Convert date strings (e.g. 2014-10-18) to datetime + dates = [datetime.strptime(d, "%Y-%m-%d") for d in dates] ############################################################################## -# Next, we'll iterate through each date and plot it on a horizontal line. -# We'll add some styling to the text so that overlaps aren't as strong. +# Next, we'll create a `~.Axes.stem` plot with some variation in levels as to +# distinguish even close-by events. In contrast to a usual stem plot, we will +# shift the markers to the baseline for visual emphasis on the one-dimensional +# nature of the time line. +# For each event, we add a text label via `~.Axes.annotate`, which is offset +# in units of points from the tip of the event line. # # Note that Matplotlib will automatically plot datetime inputs. -levels = np.array([-5, 5, -3, 3, -1, 1]) -fig, ax = plt.subplots(figsize=(8, 5)) - -# Create the base line -start = min(dates) -stop = max(dates) -ax.plot((start, stop), (0, 0), 'k', alpha=.5) - -# Iterate through releases annotating each one -for ii, (iname, idate) in enumerate(zip(names, dates)): - level = levels[ii % 6] - vert = 'top' if level < 0 else 'bottom' - - ax.scatter(idate, 0, s=100, facecolor='w', edgecolor='k', zorder=9999) - # Plot a line up to the text - ax.plot((idate, idate), (0, level), c='r', alpha=.7) - # Give the text a faint background and align it properly - ax.text(idate, level, iname, - horizontalalignment='right', verticalalignment=vert, fontsize=14, - backgroundcolor=(1., 1., 1., .3)) + +# Choose some nice levels +levels = np.tile([-5, 5, -3, 3, -1, 1], + int(np.ceil(len(dates)/6)))[:len(dates)] + +# Create figure and plot a stem plot with the date +fig, ax = plt.subplots(figsize=(8.8, 4), constrained_layout=True) ax.set(title="Matplotlib release dates") -# Set the xticks formatting -# format xaxis with 3 month intervals -ax.get_xaxis().set_major_locator(mdates.MonthLocator(interval=3)) + +markerline, stemline, baseline = ax.stem(dates, levels, + linefmt="C3-", basefmt="k-", + use_line_collection=True) + +plt.setp(markerline, mec="k", mfc="w", zorder=3) + +# Shift the markers to the baseline by replacing the y-data by zeros. +markerline.set_ydata(np.zeros(len(dates))) + +# annotate lines +vert = np.array(['top', 'bottom'])[(levels > 0).astype(int)] +for d, l, r, va in zip(dates, levels, names, vert): + ax.annotate(r, xy=(d, l), xytext=(-3, np.sign(l)*3), + textcoords="offset points", va=va, ha="right") + +# format xaxis with 4 month intervals +ax.get_xaxis().set_major_locator(mdates.MonthLocator(interval=4)) ax.get_xaxis().set_major_formatter(mdates.DateFormatter("%b %Y")) -fig.autofmt_xdate() +plt.setp(ax.get_xticklabels(), rotation=30, ha="right") + +# remove y axis and spines +ax.get_yaxis().set_visible(False) +for spine in ["left", "top", "right"]: + ax.spines[spine].set_visible(False) -# Remove components for a cleaner look -plt.setp((ax.get_yticklabels() + ax.get_yticklines() + - list(ax.spines.values())), visible=False) +ax.margins(y=0.1) plt.show() + + +############################################################################# +# +# ------------ +# +# References +# """""""""" +# +# The use of the following functions, methods and classes is shown +# in this example: + +import matplotlib +matplotlib.axes.Axes.stem +matplotlib.axes.Axes.annotate +matplotlib.axis.Axis.set_major_locator +matplotlib.axis.Axis.set_major_formatter +matplotlib.dates.MonthLocator +matplotlib.dates.DateFormatter