123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150 |
- #!/usr/bin/env python3
- import argparse
- import matplotlib.pyplot as plt
- import matplotlib.dates as mdates
- import datetime as dt
- import json
- import sys
- import numpy as np
- def adjust_spacing(arr, minimum_spacing):
- min_val = min(arr)
- mid_val = mid(arr)
- if min_val <= 5:
- idx = arr.index(min_val)
- min_val = 5
- arr[idx] = min_val
- if (mid_val - min_val) < minimum_spacing:
- idx = arr.index(mid_val)
- mid_val = min_val + minimum_spacing
- arr[idx] = mid_val
- arr = np.array(arr)
- reversed = sorted(arr, reverse=True)
- adjusted = reversed[0:1]
- for num in reversed[1:]:
- diff = adjusted[-1] - minimum_spacing
- adjusted.append(num if num < diff else diff)
- respaced = list(np.array(adjusted)[np.argsort(np.argsort(-arr))])
- return respaced
- def mid(tuple_of_three):
- sorted_values = sorted(tuple_of_three)
- return sorted_values[1]
- def annotate_callout(ctx, percent, text_y, color):
- ctx.annotate(f'{percent:0.2f}%',
- xycoords=('axes fraction', 'data'),
- textcoords='axes fraction',
- xy=(1, percent),
- xytext=(0.97, text_y),
- fontsize=12,
- backgroundcolor=color,
- bbox=dict(boxstyle='round', edgecolor=color, facecolor=color, pad=0.4))
- # Create an argument parser
- parser = argparse.ArgumentParser(description="Generate a line chart from JSON data and save it as an SVG file.")
- parser.add_argument("--output", required=True, help="Output SVG filename")
- parser.add_argument("--title", required=True, help="Chart title")
- # Parse the command-line arguments
- args = parser.parse_args()
- fontName = 'Avenir Next'
- hsmall = {
- 'family': fontName,
- 'size': 8,
- 'weight': 'light' }
- hmedium = {
- 'family':fontName,
- 'size': 12,
- 'weight': 'light' }
- htitle = {
- 'family':fontName,
- 'size': 26,
- 'weight': 'light' }
- hsubtitle = {
- 'family':fontName,
- 'size': 14 }
- title = args.title
- output_filename = args.output
- time_limit_days_ago = 10
- width = 12
- height = 7
- # Read JSON data from stdin
- try:
- data = json.load(sys.stdin)
- except json.JSONDecodeError as e:
- print("Error parsing JSON input:", e)
- sys.exit(1)
- # Sort data by datetime
- data.sort(key=lambda x: x["datetime"])
- today = dt.datetime.utcnow()
- time_limit = today - dt.timedelta(days = time_limit_days_ago)
- results = filter(lambda x: x["datetime"] > time_limit.isoformat(), data)
- data = list(results)
- # Extract percentage values from fn, line, and region
- fn_percentages = [float(node["fn"].split()[0].replace("%", "")) for node in data]
- ln_percentages = [float(node["line"].split()[0].replace("%", "")) for node in data]
- percentages = [fn_percentages, ln_percentages]
- # Convert datetime strings to datetime objects
- datetimes = [dt.datetime.fromisoformat(node["datetime"][:-6]) for node in data]
- line_colors = ["#106A77","#50486C"]
- labels = ["Function","Line"]
- # Create a time series plot
- latest = (fn_percentages[-1], ln_percentages[-1])
- perc_max = max(max(fn_percentages), max(ln_percentages))
- perc_max = min(100, perc_max + 10)
- perc_min = min(min(fn_percentages), min(ln_percentages))
- perc_min = max(0, perc_min - 10)
- perc_range = (perc_max / 100) - (perc_min / 100)
- normalised = [(perc - perc_min) / perc_range for perc in latest]
- text_y = [(perc / 100) for perc in adjust_spacing(normalised, 10)]
- with plt.style.context('dark_background'):
- plt.rcParams['font.family'] = fontName
- plt.rcParams['font.size'] = 8
- plt.figure(figsize=(width, height))
- plt.gca().xaxis.set_major_formatter(mdates.DateFormatter("%b %d"))
- plt.gca().xaxis.set_major_locator(mdates.DayLocator(interval=1))
- for i in [0,1]:
- plt.plot(datetimes, percentages[i], label=labels[i], linewidth=2, color=line_colors[i])
- annotate_callout(plt, latest[i], text_y[i], line_colors[i])
- plt.xlabel(f"Last {time_limit_days_ago} Days", **hsubtitle)
- plt.ylabel("Coverage %",**hsmall)
- plt.title(f"{title}", **htitle)
- plt.ylim([max(0, perc_min - 10), min(100, perc_max + 10)])
- plt.legend(frameon=False)
- plt.grid(which="Major", color="#444")
- plt.grid(which="Minor", color="#333")
- plt.box(False)
- # Save the plot directly as SVG without converting text to paths
- plt.savefig(output_filename, format="svg")
- # Optionally, display a message indicating that the SVG file has been saved
- print(f"Line chart saved as {output_filename}")
|