render_cov_graph 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. #!/usr/bin/env python3
  2. import argparse
  3. import matplotlib.pyplot as plt
  4. import matplotlib.dates as mdates
  5. import datetime as dt
  6. import json
  7. import sys
  8. import numpy as np
  9. def adjust_spacing(arr, minimum_spacing):
  10. min_val = min(arr)
  11. mid_val = mid(arr)
  12. if min_val <= 5:
  13. idx = arr.index(min_val)
  14. min_val = 5
  15. arr[idx] = min_val
  16. if (mid_val - min_val) < minimum_spacing:
  17. idx = arr.index(mid_val)
  18. mid_val = min_val + minimum_spacing
  19. arr[idx] = mid_val
  20. arr = np.array(arr)
  21. reversed = sorted(arr, reverse=True)
  22. adjusted = reversed[0:1]
  23. for num in reversed[1:]:
  24. diff = adjusted[-1] - minimum_spacing
  25. adjusted.append(num if num < diff else diff)
  26. respaced = list(np.array(adjusted)[np.argsort(np.argsort(-arr))])
  27. return respaced
  28. def mid(tuple_of_three):
  29. sorted_values = sorted(tuple_of_three)
  30. return sorted_values[1]
  31. def annotate_callout(ctx, percent, text_y, color):
  32. ctx.annotate(f'{percent:0.2f}%',
  33. xycoords=('axes fraction', 'data'),
  34. textcoords='axes fraction',
  35. xy=(1, percent),
  36. xytext=(0.97, text_y),
  37. fontsize=12,
  38. backgroundcolor=color,
  39. bbox=dict(boxstyle='round', edgecolor=color, facecolor=color, pad=0.4))
  40. # Create an argument parser
  41. parser = argparse.ArgumentParser(description="Generate a line chart from JSON data and save it as an SVG file.")
  42. parser.add_argument("--output", required=True, help="Output SVG filename")
  43. parser.add_argument("--title", required=True, help="Chart title")
  44. # Parse the command-line arguments
  45. args = parser.parse_args()
  46. fontName = 'Avenir Next'
  47. hsmall = {
  48. 'family': fontName,
  49. 'size': 8,
  50. 'weight': 'light' }
  51. hmedium = {
  52. 'family':fontName,
  53. 'size': 12,
  54. 'weight': 'light' }
  55. htitle = {
  56. 'family':fontName,
  57. 'size': 26,
  58. 'weight': 'light' }
  59. hsubtitle = {
  60. 'family':fontName,
  61. 'size': 14 }
  62. title = args.title
  63. output_filename = args.output
  64. time_limit_days_ago = 10
  65. width = 12
  66. height = 7
  67. # Read JSON data from stdin
  68. try:
  69. data = json.load(sys.stdin)
  70. except json.JSONDecodeError as e:
  71. print("Error parsing JSON input:", e)
  72. sys.exit(1)
  73. # Sort data by datetime
  74. data.sort(key=lambda x: x["datetime"])
  75. today = dt.datetime.utcnow()
  76. time_limit = today - dt.timedelta(days = time_limit_days_ago)
  77. results = filter(lambda x: x["datetime"] > time_limit.isoformat(), data)
  78. data = list(results)
  79. # Extract percentage values from fn, line, and region
  80. fn_percentages = [float(node["fn"].split()[0].replace("%", "")) for node in data]
  81. ln_percentages = [float(node["line"].split()[0].replace("%", "")) for node in data]
  82. percentages = [fn_percentages, ln_percentages]
  83. # Convert datetime strings to datetime objects
  84. datetimes = [dt.datetime.fromisoformat(node["datetime"][:-6]) for node in data]
  85. line_colors = ["#106A77","#50486C"]
  86. labels = ["Function","Line"]
  87. # Create a time series plot
  88. latest = (fn_percentages[-1], ln_percentages[-1])
  89. perc_max = max(max(fn_percentages), max(ln_percentages))
  90. perc_max = min(100, perc_max + 10)
  91. perc_min = min(min(fn_percentages), min(ln_percentages))
  92. perc_min = max(0, perc_min - 10)
  93. perc_range = (perc_max / 100) - (perc_min / 100)
  94. normalised = [(perc - perc_min) / perc_range for perc in latest]
  95. text_y = [(perc / 100) for perc in adjust_spacing(normalised, 10)]
  96. with plt.style.context('dark_background'):
  97. plt.rcParams['font.family'] = fontName
  98. plt.rcParams['font.size'] = 8
  99. plt.figure(figsize=(width, height))
  100. plt.gca().xaxis.set_major_formatter(mdates.DateFormatter("%b %d"))
  101. plt.gca().xaxis.set_major_locator(mdates.DayLocator(interval=1))
  102. for i in [0,1]:
  103. plt.plot(datetimes, percentages[i], label=labels[i], linewidth=2, color=line_colors[i])
  104. annotate_callout(plt, latest[i], text_y[i], line_colors[i])
  105. plt.xlabel(f"Last {time_limit_days_ago} Days", **hsubtitle)
  106. plt.ylabel("Coverage %",**hsmall)
  107. plt.title(f"{title}", **htitle)
  108. plt.ylim([max(0, perc_min - 10), min(100, perc_max + 10)])
  109. plt.legend(frameon=False)
  110. plt.grid(which="Major", color="#444")
  111. plt.grid(which="Minor", color="#333")
  112. plt.box(False)
  113. # Save the plot directly as SVG without converting text to paths
  114. plt.savefig(output_filename, format="svg")
  115. # Optionally, display a message indicating that the SVG file has been saved
  116. print(f"Line chart saved as {output_filename}")