labours.py 5.4 KB

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