From e2c6e89df38ddb94edba7f91a58d31ef485f276d Mon Sep 17 00:00:00 2001 From: Antonio Vivace Date: Thu, 10 Nov 2022 15:30:21 +0100 Subject: [PATCH 1/2] Move to matplotlib, make plots affected by CSS theme --- src/imgs/src/graph_render.py | 153 ++++++++++++++++++++--------------- 1 file changed, 86 insertions(+), 67 deletions(-) diff --git a/src/imgs/src/graph_render.py b/src/imgs/src/graph_render.py index 1e8bfe51..a0dadb58 100644 --- a/src/imgs/src/graph_render.py +++ b/src/imgs/src/graph_render.py @@ -1,82 +1,101 @@ -import pygal -from pygal.style import Style -import math from sys import argv, stderr -# ------------------------------------------------------------------------------------------------ -# Configuration Constants -# ------------------------------------------------------------------------------------------------ +import lxml +import matplotlib.pyplot as plt +import pandas as pd +from bs4 import BeautifulSoup -# Rotation of X-Axis labels in degrees -x_label_rotation = 0.01 -# Number of labels to be displayed on the X-Axis -x_label_count = 10 +def gen_graph(in_path, title): -# ------------------------------------------------------------------------------------------------ -# The first line of the file must contain the X- and Y-Axis labels seperated by commas. -# The following lines are expected to contain the graph data in a comma-separated format -# and in the same order as the Axis labels. -# ------------------------------------------------------------------------------------------------ + ## Let's draw the plot -def gen_graph(in_path, title): - custom_style = Style( - font_family="Inter", - label_font_size=12, - major_label_font_size=12, - title_font_size=16 + plt.rcParams["figure.figsize"] = [7.50, 3.50] + plt.rcParams["figure.autolayout"] = True + plt.rcParams["font.family"] = "Inter" + # Assume fonts are installed on the machine where the SVG will be viewed + # (we load Inter with the webpage so it should be there) + plt.rcParams["svg.fonttype"] = "none" + + # Those are just used to "fingerprint" the resulting elements in the SVG export, + # they will be replaced by CSS variables + COLOR_BASE = "#FFCD01".lower() + COLOR_LINE = "#FFCD02".lower() + + # Set everything to the base color + plt.rcParams["text.color"] = COLOR_BASE + plt.rcParams["axes.labelcolor"] = COLOR_BASE + plt.rcParams["xtick.color"] = COLOR_BASE + plt.rcParams["ytick.color"] = COLOR_BASE + + # Read the values to plot from the input CSV + df = pd.read_csv(in_path) + + # Set the color of the actual plot line to the secondary color + plot = df.set_index("Time (ms)").plot( + legend=None, gid="fitted_curve", color=COLOR_LINE ) - # Create Line Chart Object and Open File - chart = pygal.Line( - height=450, - show_dots=False, - show_legend=False, - show_minor_x_labels=False, - x_label_rotation=x_label_rotation, - style=custom_style + # Add grid lines on the y values + plot.yaxis.grid(True) + + # Set the color of the plot box to the base color too + plt.setp(plot.spines.values(), color=COLOR_BASE) + + # Add title at the top + plt.title(title) + plt.ylabel(df.columns[1]) + plt.savefig("test.svg", transparent=True) + + ## Manipulate the SVG render of the plot to replace colors with CSS variables + with open("test.svg", "r") as f: + contents = f.read() + # It's an SVG, so let's use the XML parser + soup = BeautifulSoup(contents, "xml") + + replace_style_property(soup, "path", "stroke", COLOR_BASE, "var(--fg)") + + replace_style_property(soup, "path", "stroke", COLOR_LINE, "var(--inline-code-color)") + + replace_style_property(soup, "text", "fill", COLOR_BASE, "var(--fg)") + + # Write the altered SVG file + with open("output.svg", "wb") as f_output: + f_output.write(soup.prettify("utf-8")) + print(soup) + + +def replace_style_property( + soup, element_name, css_property, value_to_replace, new_value +): + """ + Given a `Soup`, a CSS `property` applied inline, replace the a `specific value` + this property can assume with `another` one in all the elements with the specified `name` + + E.g. the style of all the "path" elements whith a CSS property "fill" of + "#ffcd01" will change to "var(--fg): + + `fill: #ffcd01; font: 12px 'Inter'; text-anchor: middle` + to + `fill: var(--fg); font: 12px 'Inter'; text-anchor: middle` + + Soup and soup ResultSet are modified in-place + """ + found_elements = soup.find_all( + element_name, + style=lambda value: value and f"{css_property}: {value_to_replace}" in value, ) - csv = open(in_path, "r").readlines() - - # Set Chart and Axis Titles - chart.title = title - headers = csv.pop(0).split(",") - chart.x_title = headers[0] - chart.y_title = headers[1] - - # Generate label spacing variables - min_x_val = float(csv[0].split(",")[0]) - max_x_val = float(csv[len(csv) - 1].split(",")[0]) - x_mod_val = (max_x_val - min_x_val) / x_label_count - - # Generate graph data arrays - x_labels = [] - x_labels_major = [] - y_data = [] - last_x = None - for line in csv: - # Add data to label arrays - data = line.split(",") - x_labels.append(data[0]) - y_data.append(float(data[1])) - - # Check if current X-Label should be Major Label - xval_float = float(data[0]) - if last_x is not None and ((last_x % x_mod_val) > (xval_float % x_mod_val)): - x_labels_major.append(math.floor(xval_float)) - x_labels.append(math.floor(xval_float)) - last_x = xval_float - - # Load graph data into chart object and save to file - chart.x_labels = x_labels - chart.x_labels_major = x_labels_major - - chart.add("", y_data) - print(chart.render(is_unicode=True)) + # Replace the color magic value with the CSS variable + for element in found_elements: + element["style"] = element["style"].replace( + f"{css_property}: {value_to_replace}", f"{css_property}: {new_value}" + ) + + return if len(argv) != 3: print("Usage: python3 graph_render.py ", file=stderr) exit(1) -gen_graph(argv[1], argv[2]) \ No newline at end of file +gen_graph(argv[1], argv[2]) From 0618895102c87485765c9c041f404553b73211d4 Mon Sep 17 00:00:00 2001 From: Antonio Vivace Date: Thu, 10 Nov 2022 15:48:14 +0100 Subject: [PATCH 2/2] Add basic README for the figure generation folder --- src/imgs/src/README.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 src/imgs/src/README.md diff --git a/src/imgs/src/README.md b/src/imgs/src/README.md new file mode 100644 index 00000000..9760391d --- /dev/null +++ b/src/imgs/src/README.md @@ -0,0 +1,7 @@ +# imgs src + +This folder contains the source code and the datasets used to generate some plots. + +Those plots are included in Pan Docs as normal SVG figures, but their generation is part of pipeline. + +The scripts are traditional matplotlib executions but you may notice some additional manipulation on the resulting SVGs to set the lines and text to CSS variables instead of hardcoded colors.