burndown.py 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  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(pyplot.gcf(), pyplot.gca(), legend, args.background,
  51. args.font_size, args.size)
  52. pyplot.xlim(parse_date(args.start_date, date_range_sampling[0]),
  53. parse_date(args.end_date, date_range_sampling[-1]))
  54. locator = pyplot.gca().xaxis.get_major_locator()
  55. # set the optimal xticks locator
  56. if "M" not in resample:
  57. pyplot.gca().xaxis.set_major_locator(matplotlib.dates.YearLocator())
  58. locs = pyplot.gca().get_xticks().tolist()
  59. if len(locs) >= 16:
  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(locator)
  64. if locs[0] < pyplot.xlim()[0]:
  65. del locs[0]
  66. endindex = -1
  67. if len(locs) >= 2 and pyplot.xlim()[1] - locs[-1] > (locs[-1] - locs[-2]) / 2:
  68. locs.append(pyplot.xlim()[1])
  69. endindex = len(locs) - 1
  70. startindex = -1
  71. if len(locs) >= 2 and locs[0] - pyplot.xlim()[0] > (locs[1] - locs[0]) / 2:
  72. locs.append(pyplot.xlim()[0])
  73. startindex = len(locs) - 1
  74. pyplot.gca().set_xticks(locs)
  75. # hacking time!
  76. labels = pyplot.gca().get_xticklabels()
  77. if startindex >= 0:
  78. labels[startindex].set_text(date_range_sampling[0].date())
  79. labels[startindex].set_text = lambda _: None
  80. labels[startindex].set_rotation(30)
  81. labels[startindex].set_ha("right")
  82. if endindex >= 0:
  83. labels[endindex].set_text(date_range_sampling[-1].date())
  84. labels[endindex].set_text = lambda _: None
  85. labels[endindex].set_rotation(30)
  86. labels[endindex].set_ha("right")
  87. title = "%s %d x %d (granularity %d, sampling %d)" % \
  88. ((name,) + matrix.shape + (granularity, sampling))
  89. output = args.output
  90. if output:
  91. if args.mode == "project" and target == "project":
  92. output = args.output
  93. else:
  94. if target == "project":
  95. name = "project"
  96. output = get_plot_path(args.output, name)
  97. deploy_plot(title, output, args.background)
  98. def plot_many_burndown(args: Namespace, target: str, header, parts):
  99. if not args.output:
  100. print("Warning: output not set, showing %d plots." % len(parts))
  101. stdout = io.StringIO()
  102. for name, matrix in tqdm.tqdm(parts):
  103. with contextlib.redirect_stdout(stdout):
  104. plot_burndown(args, target, *load_burndown(header, name, matrix, args.resample))
  105. sys.stdout.write(stdout.getvalue())