burndown.py 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. from argparse import Namespace
  2. import contextlib
  3. import io
  4. import json
  5. import sys
  6. from typing import List, TYPE_CHECKING
  7. import numpy
  8. import tqdm
  9. from labours.burndown import load_burndown
  10. from labours.plotting import apply_plot_style, deploy_plot, get_plot_path, import_pyplot
  11. from labours.utils import default_json, parse_date
  12. if TYPE_CHECKING:
  13. from pandas.core.indexes.datetimes import DatetimeIndex
  14. def plot_burndown(
  15. args: Namespace,
  16. target: str,
  17. name: str,
  18. matrix: numpy.ndarray,
  19. date_range_sampling: 'DatetimeIndex',
  20. labels: List[int],
  21. granularity: int,
  22. sampling: int,
  23. resample: str,
  24. ) -> None:
  25. if args.output and args.output.endswith(".json"):
  26. data = locals().copy()
  27. del data["args"]
  28. data["type"] = "burndown"
  29. if args.mode == "project" and target == "project":
  30. output = args.output
  31. else:
  32. if target == "project":
  33. name = "project"
  34. output = get_plot_path(args.output, name)
  35. with open(output, "w") as fout:
  36. json.dump(data, fout, sort_keys=True, default=default_json)
  37. return
  38. matplotlib, pyplot = import_pyplot(args.backend, args.style)
  39. pyplot.stackplot(date_range_sampling, matrix, labels=labels)
  40. if args.relative:
  41. for i in range(matrix.shape[1]):
  42. matrix[:, i] /= matrix[:, i].sum()
  43. pyplot.ylim(0, 1)
  44. legend_loc = 3
  45. else:
  46. legend_loc = 2
  47. legend = pyplot.legend(loc=legend_loc, fontsize=args.font_size)
  48. pyplot.ylabel("Lines of code")
  49. pyplot.xlabel("Time")
  50. apply_plot_style(
  51. pyplot.gcf(), pyplot.gca(), legend, args.background, args.font_size, args.size
  52. )
  53. pyplot.xlim(
  54. parse_date(args.start_date, date_range_sampling[0]),
  55. parse_date(args.end_date, date_range_sampling[-1]),
  56. )
  57. locator = pyplot.gca().xaxis.get_major_locator()
  58. # set the optimal xticks locator
  59. if "M" not in resample:
  60. pyplot.gca().xaxis.set_major_locator(matplotlib.dates.YearLocator())
  61. locs = pyplot.gca().get_xticks().tolist()
  62. if len(locs) >= 16:
  63. pyplot.gca().xaxis.set_major_locator(matplotlib.dates.YearLocator())
  64. locs = pyplot.gca().get_xticks().tolist()
  65. if len(locs) >= 16:
  66. pyplot.gca().xaxis.set_major_locator(locator)
  67. if locs[0] < pyplot.xlim()[0]:
  68. del locs[0]
  69. endindex = -1
  70. if len(locs) >= 2 and pyplot.xlim()[1] - locs[-1] > (locs[-1] - locs[-2]) / 2:
  71. locs.append(pyplot.xlim()[1])
  72. endindex = len(locs) - 1
  73. startindex = -1
  74. if len(locs) >= 2 and locs[0] - pyplot.xlim()[0] > (locs[1] - locs[0]) / 2:
  75. locs.append(pyplot.xlim()[0])
  76. startindex = len(locs) - 1
  77. pyplot.gca().set_xticks(locs)
  78. # hacking time!
  79. labels = pyplot.gca().get_xticklabels()
  80. if startindex >= 0:
  81. labels[startindex].set_text(date_range_sampling[0].date())
  82. labels[startindex].set_text = lambda _: None
  83. labels[startindex].set_rotation(30)
  84. labels[startindex].set_ha("right")
  85. if endindex >= 0:
  86. labels[endindex].set_text(date_range_sampling[-1].date())
  87. labels[endindex].set_text = lambda _: None
  88. labels[endindex].set_rotation(30)
  89. labels[endindex].set_ha("right")
  90. title = "%s %d x %d (granularity %d, sampling %d)" % (
  91. (name,) + matrix.shape + (granularity, sampling)
  92. )
  93. output = args.output
  94. if output:
  95. if args.mode == "project" and target == "project":
  96. output = args.output
  97. else:
  98. if target == "project":
  99. name = "project"
  100. output = get_plot_path(args.output, name)
  101. deploy_plot(title, output, args.background)
  102. def plot_many_burndown(args: Namespace, target: str, header, parts):
  103. if not args.output:
  104. print("Warning: output not set, showing %d plots." % len(parts))
  105. stdout = io.StringIO()
  106. for name, matrix in tqdm.tqdm(parts):
  107. with contextlib.redirect_stdout(stdout):
  108. plot_burndown(
  109. args, target, *load_burndown(header, name, matrix, args.resample)
  110. )
  111. sys.stdout.write(stdout.getvalue())