|
15 | 15 | import matplotlib.dates as mdates
|
16 | 16 | from datetime import datetime
|
17 | 17 |
|
18 |
| -# A list of Matplotlib releases and their dates |
19 |
| -# Taken from https://api.github.com/repos/matplotlib/matplotlib/releases |
20 |
| -names = ['v2.2.2', 'v2.2.1', 'v2.2.0', 'v2.1.2', 'v2.1.1', 'v2.1.0', 'v2.0.2', |
21 |
| - 'v2.0.1', 'v2.0.0', 'v1.5.3', 'v1.5.2', 'v1.5.1', 'v1.5.0', 'v1.4.3', |
22 |
| - 'v1.4.2', 'v1.4.1', 'v1.4.0'] |
23 |
| - |
24 |
| -dates = ['2018-03-17T03:00:07Z', '2018-03-16T22:06:39Z', |
25 |
| - '2018-03-06T12:53:32Z', '2018-01-18T04:56:47Z', |
26 |
| - '2017-12-10T04:47:38Z', '2017-10-07T22:35:12Z', |
27 |
| - '2017-05-10T02:11:15Z', '2017-05-02T01:59:49Z', |
28 |
| - '2017-01-17T02:59:36Z', '2016-09-09T03:00:52Z', |
29 |
| - '2016-07-03T15:52:01Z', '2016-01-10T22:38:50Z', |
30 |
| - '2015-10-29T21:40:23Z', '2015-02-16T04:22:54Z', |
31 |
| - '2014-10-26T03:24:13Z', '2014-10-18T18:56:23Z', |
32 |
| - '2014-08-26T21:06:04Z'] |
33 |
| -dates = [datetime.strptime(ii, "%Y-%m-%dT%H:%M:%SZ") for ii in dates] |
| 18 | +try: |
| 19 | + # Try to fetch a list of Matplotlib releases and their dates |
| 20 | + # from https://api.github.com/repos/matplotlib/matplotlib/releases |
| 21 | + import urllib.request |
| 22 | + import json |
| 23 | + |
| 24 | + url = 'https://api.github.com/repos/matplotlib/matplotlib/releases' |
| 25 | + url += '?per_page=100' |
| 26 | + data = json.loads(urllib.request.urlopen(url, timeout=.4).read().decode()) |
| 27 | + |
| 28 | + dates = [] |
| 29 | + names = [] |
| 30 | + for item in data: |
| 31 | + if 'rc' not in item['tag_name'] and 'b' not in item['tag_name']: |
| 32 | + dates.append(item['published_at'].split("T")[0]) |
| 33 | + names.append(item['tag_name']) |
| 34 | + # Convert date strings (e.g. 2014-10-18) to datetime |
| 35 | + dates = [datetime.strptime(d, "%Y-%m-%d") for d in dates] |
| 36 | + |
| 37 | +except Exception: |
| 38 | + # In case the above fails, e.g. because of missing internet connection |
| 39 | + # use the following lists as fallback. |
| 40 | + names = ['v2.2.4', 'v3.0.3', 'v3.0.2', 'v3.0.1', 'v3.0.0', 'v2.2.3', |
| 41 | + 'v2.2.2', 'v2.2.1', 'v2.2.0', 'v2.1.2', 'v2.1.1', 'v2.1.0', |
| 42 | + 'v2.0.2', 'v2.0.1', 'v2.0.0', 'v1.5.3', 'v1.5.2', 'v1.5.1', |
| 43 | + 'v1.5.0', 'v1.4.3', 'v1.4.2', 'v1.4.1', 'v1.4.0'] |
| 44 | + |
| 45 | + dates = ['2019-02-26', '2019-02-26', '2018-11-10', '2018-11-10', |
| 46 | + '2018-09-18', '2018-08-10', '2018-03-17', '2018-03-16', |
| 47 | + '2018-03-06', '2018-01-18', '2017-12-10', '2017-10-07', |
| 48 | + '2017-05-10', '2017-05-02', '2017-01-17', '2016-09-09', |
| 49 | + '2016-07-03', '2016-01-10', '2015-10-29', '2015-02-16', |
| 50 | + '2014-10-26', '2014-10-18', '2014-08-26'] |
| 51 | + |
| 52 | + # Convert date strings (e.g. 2014-10-18) to datetime |
| 53 | + dates = [datetime.strptime(d, "%Y-%m-%d") for d in dates] |
34 | 54 |
|
35 | 55 | ##############################################################################
|
36 |
| -# Next, we'll iterate through each date and plot it on a horizontal line. |
37 |
| -# We'll add some styling to the text so that overlaps aren't as strong. |
| 56 | +# Next, we'll create a `~.Axes.stem` plot with some variation in levels as to |
| 57 | +# distinguish even close-by events. In contrast to a usual stem plot, we will |
| 58 | +# shift the markers to the baseline for visual emphasis on the one-dimensional |
| 59 | +# nature of the time line. |
| 60 | +# For each event, we add a text label via `~.Axes.annotate`, which is offset |
| 61 | +# in units of points from the tip of the event line. |
38 | 62 | #
|
39 | 63 | # Note that Matplotlib will automatically plot datetime inputs.
|
40 | 64 |
|
41 |
| -levels = np.array([-5, 5, -3, 3, -1, 1]) |
42 |
| -fig, ax = plt.subplots(figsize=(8, 5)) |
43 |
| - |
44 |
| -# Create the base line |
45 |
| -start = min(dates) |
46 |
| -stop = max(dates) |
47 |
| -ax.plot((start, stop), (0, 0), 'k', alpha=.5) |
48 |
| - |
49 |
| -# Iterate through releases annotating each one |
50 |
| -for ii, (iname, idate) in enumerate(zip(names, dates)): |
51 |
| - level = levels[ii % 6] |
52 |
| - vert = 'top' if level < 0 else 'bottom' |
53 |
| - |
54 |
| - ax.scatter(idate, 0, s=100, facecolor='w', edgecolor='k', zorder=9999) |
55 |
| - # Plot a line up to the text |
56 |
| - ax.plot((idate, idate), (0, level), c='r', alpha=.7) |
57 |
| - # Give the text a faint background and align it properly |
58 |
| - ax.text(idate, level, iname, |
59 |
| - horizontalalignment='right', verticalalignment=vert, fontsize=14, |
60 |
| - backgroundcolor=(1., 1., 1., .3)) |
| 65 | + |
| 66 | +# Choose some nice levels |
| 67 | +levels = np.tile([-5, 5, -3, 3, -1, 1], |
| 68 | + int(np.ceil(len(dates)/6)))[:len(dates)] |
| 69 | + |
| 70 | +# Create figure and plot a stem plot with the date |
| 71 | +fig, ax = plt.subplots(figsize=(8.8, 4), constrained_layout=True) |
61 | 72 | ax.set(title="Matplotlib release dates")
|
62 |
| -# Set the xticks formatting |
63 |
| -# format xaxis with 3 month intervals |
64 |
| -ax.get_xaxis().set_major_locator(mdates.MonthLocator(interval=3)) |
| 73 | + |
| 74 | +markerline, stemline, baseline = ax.stem(dates, levels, |
| 75 | + linefmt="C3-", basefmt="k-", |
| 76 | + use_line_collection=True) |
| 77 | + |
| 78 | +plt.setp(markerline, mec="k", mfc="w", zorder=3) |
| 79 | + |
| 80 | +# Shift the markers to the baseline by replacing the y-data by zeros. |
| 81 | +markerline.set_ydata(np.zeros(len(dates))) |
| 82 | + |
| 83 | +# annotate lines |
| 84 | +vert = np.array(['top', 'bottom'])[(levels > 0).astype(int)] |
| 85 | +for d, l, r, va in zip(dates, levels, names, vert): |
| 86 | + ax.annotate(r, xy=(d, l), xytext=(-3, np.sign(l)*3), |
| 87 | + textcoords="offset points", va=va, ha="right") |
| 88 | + |
| 89 | +# format xaxis with 4 month intervals |
| 90 | +ax.get_xaxis().set_major_locator(mdates.MonthLocator(interval=4)) |
65 | 91 | ax.get_xaxis().set_major_formatter(mdates.DateFormatter("%b %Y"))
|
66 |
| -fig.autofmt_xdate() |
| 92 | +plt.setp(ax.get_xticklabels(), rotation=30, ha="right") |
| 93 | + |
| 94 | +# remove y axis and spines |
| 95 | +ax.get_yaxis().set_visible(False) |
| 96 | +for spine in ["left", "top", "right"]: |
| 97 | + ax.spines[spine].set_visible(False) |
67 | 98 |
|
68 |
| -# Remove components for a cleaner look |
69 |
| -plt.setp((ax.get_yticklabels() + ax.get_yticklines() + |
70 |
| - list(ax.spines.values())), visible=False) |
| 99 | +ax.margins(y=0.1) |
71 | 100 | plt.show()
|
| 101 | + |
| 102 | + |
| 103 | +############################################################################# |
| 104 | +# |
| 105 | +# ------------ |
| 106 | +# |
| 107 | +# References |
| 108 | +# """""""""" |
| 109 | +# |
| 110 | +# The use of the following functions, methods and classes is shown |
| 111 | +# in this example: |
| 112 | + |
| 113 | +import matplotlib |
| 114 | +matplotlib.axes.Axes.stem |
| 115 | +matplotlib.axes.Axes.annotate |
| 116 | +matplotlib.axis.Axis.set_major_locator |
| 117 | +matplotlib.axis.Axis.set_major_formatter |
| 118 | +matplotlib.dates.MonthLocator |
| 119 | +matplotlib.dates.DateFormatter |
0 commit comments