labours.py 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. import argparse
  2. from datetime import datetime, timedelta
  3. import sys
  4. import warnings
  5. import numpy
  6. if sys.version_info[0] < 3:
  7. # OK, ancients, I will support Python 2, but you owe me a beer
  8. input = raw_input
  9. def parse_args():
  10. parser = argparse.ArgumentParser()
  11. parser.add_argument("--output", default="",
  12. help="Path to the output file (empty for display).")
  13. parser.add_argument("--text-size", default=12,
  14. help="Size of the labels and legend.")
  15. parser.add_argument("--backend", help="Matplotlib backend to use.")
  16. parser.add_argument("--style", choices=["black", "white"], default="black",
  17. help="Plot's general color scheme.")
  18. parser.add_argument(
  19. "--resample", default="year",
  20. help="The way to resample the time series. Possible values are: "
  21. "\"month\", \"year\", \"no\", \"raw\" and pandas offset aliases ("
  22. "http://pandas.pydata.org/pandas-docs/stable/timeseries.html"
  23. "#offset-aliases).")
  24. args = parser.parse_args()
  25. return args
  26. def main():
  27. args = parse_args()
  28. import matplotlib
  29. if args.backend:
  30. matplotlib.use(args.backend)
  31. import matplotlib.pyplot as pyplot
  32. import pandas
  33. start, granularity, sampling = input().split()
  34. start = datetime.fromtimestamp(int(start))
  35. granularity = int(granularity)
  36. sampling = int(sampling)
  37. matrix = numpy.array([numpy.fromstring(line, dtype=int, sep=" ")
  38. for line in sys.stdin.read().split("\n")[:-1]]).T
  39. date_range_sampling = pandas.date_range(
  40. start + timedelta(days=sampling), periods=matrix.shape[1],
  41. freq="%dD" % sampling)
  42. if args.resample not in ("no", "raw"):
  43. aliases = {
  44. "year": "A",
  45. "month": "M"
  46. }
  47. daily_matrix = numpy.zeros(
  48. (matrix.shape[0] * granularity, matrix.shape[1]),
  49. dtype=numpy.float32)
  50. for i in range(1, matrix.shape[0]):
  51. daily_matrix[i * granularity:(i + 1) * granularity] = \
  52. matrix[i] / granularity
  53. date_range_granularity = pandas.date_range(
  54. start, periods=daily_matrix.shape[0], freq="1D")
  55. df = pandas.DataFrame({
  56. dr: pandas.Series(row, index=date_range_sampling)
  57. for dr, row in zip(date_range_granularity, daily_matrix)
  58. }).T
  59. df = df.resample(aliases.get(args.resample, args.resample)).sum()
  60. row0 = matrix[0]
  61. matrix = df.as_matrix()
  62. matrix[0] = row0
  63. for i in range(1, matrix.shape[0]):
  64. matrix[i, i] += matrix[i, :i].sum()
  65. matrix[i, :i] = 0
  66. if args.resample in ("year", "A"):
  67. labels = [dt.year for dt in df.index]
  68. elif args.resample in ("month", "M"):
  69. labels = [dt.strftime("%Y %B") for dt in df.index]
  70. else:
  71. labels = [dt.date() for dt in df.index]
  72. else:
  73. labels = [
  74. "%s - %s" % ((start + timedelta(days=i * granularity)).date(),
  75. (start + timedelta(days=(i + 1) * granularity)).date())
  76. for i in range(matrix.shape[0])]
  77. if len(labels) > 18:
  78. warnings.warn("Too many labels - consider resampling.")
  79. if args.style == "white":
  80. pyplot.gca().spines["bottom"].set_color("white")
  81. pyplot.gca().spines["top"].set_color("white")
  82. pyplot.gca().spines["left"].set_color("white")
  83. pyplot.gca().spines["right"].set_color("white")
  84. pyplot.gca().xaxis.label.set_color("white")
  85. pyplot.gca().yaxis.label.set_color("white")
  86. pyplot.gca().tick_params(axis="x", colors="white")
  87. pyplot.gca().tick_params(axis="y", colors="white")
  88. pyplot.stackplot(date_range_sampling, matrix, labels=labels)
  89. legend = pyplot.legend(loc=2, fontsize=args.text_size)
  90. frame = legend.get_frame()
  91. frame.set_facecolor("black" if args.style == "white" else "white")
  92. frame.set_edgecolor("black" if args.style == "white" else "white")
  93. for text in legend.get_texts():
  94. text.set_color(args.style)
  95. pyplot.ylabel("Lines of code", fontsize=args.text_size)
  96. pyplot.xlabel("Time", fontsize=args.text_size)
  97. pyplot.tick_params(labelsize=args.text_size)
  98. pyplot.xlim(date_range_sampling[0], date_range_sampling[-1])
  99. pyplot.gcf().set_size_inches(12, 9)
  100. # add border ticks
  101. locs = pyplot.gca().get_xticks().tolist()
  102. locs.extend(pyplot.xlim())
  103. pyplot.gca().set_xticks(locs)
  104. # hacking time!
  105. labels = pyplot.gca().get_xticklabels()
  106. labels[-2].set_text(date_range_sampling[0].date())
  107. labels[-2].set_text = lambda _: None
  108. labels[-1].set_text(date_range_sampling[-1].date())
  109. labels[-1].set_text = lambda _: None
  110. if not args.output:
  111. pyplot.gcf().canvas.set_window_title(
  112. "Hercules %d x %d (granularity %d, sampling %d)" %
  113. (matrix.shape + (granularity, sampling)))
  114. pyplot.show()
  115. else:
  116. pyplot.tight_layout()
  117. pyplot.savefig(args.output, transparent=True)
  118. if __name__ == "__main__":
  119. sys.exit(main())