Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extract page layout and use it in plotting #58

Open
wants to merge 9 commits into
base: font-and-figure-info
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -287,3 +287,7 @@ TSWLatexianTemp*
tags
# ignore index.html generated by textidote
index.html

# ignore *-layout.csv generated during make
*-layout.csv

7 changes: 0 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,3 @@ the following features:
- The [ACMart fonts](https://github.com/opencompl/paper-template/blob/master/acmart.cls#L720-L728)
use [Inconsolata](https://ctan.org/pkg/inconsolata?lang=en) for monospace,
[Libertine](https://ctan.org/pkg/libertine?lang=en) for serif, and [newtx](https://ctan.org/pkg/newtx?lang=en) for math.
- The Libertine `ttf` can be obtained as "linux libertine" on distros, and Inconsolata `ttf` as "Inconsolata".
- The default font size of the body is `11pt`.
- Figures
- The paper is of A4 size, which in *physical units* is `210 x 297 mm`.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unfortunately, this information depends on the paper class used for ACM template papers.

- Choose figure size in *physical units* (`mm`/`inch`) based on how much of A4 it occupies and export to PDF.
- Import figure PDF into paper using `\includegraphics{path/to/figure}`. This occupies the desired space in the paper; there is need to use
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this has been integrated to the plotting script

`\includegraphics[width=<insert-width-here>]{path/to/figure}`.
46 changes: 45 additions & 1 deletion paper.tex
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
\usepackage{thmtools} % required for autoref to lemmas
\usepackage{algorithm}
\usepackage[noend]{algpseudocode}
\usepackage{xfp}

\input{tex/setup.tex}
\input{tex/acm.tex}
Expand Down Expand Up @@ -129,6 +130,44 @@

\newdelimitedcommand{toolname}{Tool}

% helper macro to fully expand dimension expressions
\newcommand{\expanddim}[1]{\the\dimexpr#1\relax}

% Define the conversion function from any length to inches
\makeatletter
\newcommand{\convertToInches}[2]{%
\edef\@temp{\expandafter\expandafter\expandafter\strip@pt\expandafter\dimexpr#1\relax}%
\edef#2{\fpeval{round(\@temp / 72.27, 2)}}%
}
\makeatother

% create a new file for output
\newwrite\myfile
\immediate\openout\myfile=\jobname-layout.csv

% write widths to file
\makeatletter
\newcommand{\writePageWidths}{
\newcommand{\mylen}{}%
\convertToInches{\columnwidth}{\mylen}%
\immediate\write\myfile{columnwidth, \mylen}%
\convertToInches{\textwidth}{\mylen}%
\immediate\write\myfile{textwidth, \mylen}%
\convertToInches{\columnsep}{\mylen}%
\immediate\write\myfile{columnsep, \mylen}%
\convertToInches{\textheight}{\mylen}%
\immediate\write\myfile{textheight, \mylen}%
\convertToInches{\paperwidth}{\mylen}%
\immediate\write\myfile{paperwidth, \mylen}%
\convertToInches{\paperheight}{\mylen}%
\immediate\write\myfile{paperheight, \mylen}%
}
\makeatother

\AtEndDocument{
\writePageWidths
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could move this to setup.tex; it basically extracts basic paper lengths in a csv file.

\usepackage{booktabs}
\newcommand{\ra}[1]{\renewcommand{\arraystretch}{#1}}

Expand Down Expand Up @@ -520,8 +559,12 @@ \subsubsection{Plots} We use matplotlib to create performance
setting fonttype to 42.
\end{itemize}

We recommend considering the relevant layout lengths (e.g., the column width) when choosing figure sizes for plots and other figures, to avoid unnecessary scaling when imported with \texttt{\textbackslash{}includegraphics}.
We export layout lengths (in inches) to \texttt{paper-layout.csv}.
The \texttt{plot.py} script shows how to use this information for \autoref{fig:speedup}.

\begin{figure}
\includegraphics[width=\columnwidth]{plots/speedup}
\includegraphics{plots/speedup}
\caption{Improved running speed after 4 weeks of training.
}
\label{fig:speedup}
Expand Down Expand Up @@ -663,6 +706,7 @@ \subsubsection{Managing acronyms automatically}
\item To obtain plural form, we use \texttt{\textbackslash{}acp\{ir\}} giving: \acp{ir}.
\end{itemize}


\end{draftonly}


Expand Down
158 changes: 90 additions & 68 deletions plots/plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@
import matplotlib.pyplot as plt
import numpy as np
import math
import csv

from typing import Callable


def setGlobalDefaults():
## Use TrueType fonts instead of Type 3 fonts
#
Expand All @@ -26,7 +28,7 @@ def setGlobalDefaults():

## Legend defaults
matplotlib.rcParams['legend.frameon'] = False

# Hide the right and top spines
#
# This reduces the number of lines in the plot. Lines typically catch
Expand All @@ -36,7 +38,6 @@ def setGlobalDefaults():
matplotlib.rcParams['axes.spines.right'] = False
matplotlib.rcParams['axes.spines.top'] = False

matplotlib.rcParams['figure.figsize'] = 5, 2

# Color palette
light_gray = "#cacaca"
Expand All @@ -50,6 +51,7 @@ def setGlobalDefaults():
black = "#000000"
white = "#ffffff"


def save(figure, name):
# Do not emit a creation date, creator name, or producer. This will make the
# content of the pdfs we generate more deterministic.
Expand All @@ -59,9 +61,10 @@ def save(figure, name):

# Close figure to avoid warning about too many open figures.
plt.close(figure)

print(f'written to {name}')


# helper for str_from_float.
# format float in scientific with at most *digits* digits.
#
Expand All @@ -71,53 +74,63 @@ def save(figure, name):
def get_scientific(x: float, digits: int):
# get scientific without leading zeros or + in exp
def get(x: float, prec: int) -> str:
result = f'{x:.{prec}e}'
result = result.replace('e+', 'e')
while 'e0' in result:
result = result.replace('e0', 'e')
while 'e-0' in result:
result = result.replace('e-0', 'e-')
return result
result = f'{x:.{prec}e}'
result = result.replace('e+', 'e')
while 'e0' in result:
result = result.replace('e0', 'e')
while 'e-0' in result:
result = result.replace('e-0', 'e-')
return result

result = get(x, digits)
len_after_e = len(result.split('e')[1])
prec = max(0, digits - len_after_e - 2)
return get(x, prec)


# format float with at most *digits* digits.
# if the number is too small or too big,
# it will be formatted in scientific notation,
# optionally a suffix can be passed for the unit.
#
# note: this displays different numbers with different
# precision depending on their length, as much as can fit.
def str_from_float(x: float, digits: int = 3, suffix: str = '') -> str:
result = f'{x:.{digits}f}'
before_decimal = result.split('.')[0]
if len(before_decimal) == digits:
return before_decimal
if len(before_decimal) > digits:
# we can't even fit the integral part
return get_scientific(x, digits)

result = result[:digits + 1] # plus 1 for the decimal point
if float(result) == 0:
# we can't even get one significant figure
return get_scientific(x, digits)

return result[:digits + 1]
def str_from_float(x: float, digits: int = 2, suffix: str = '') -> str:
result = f'{x:.{digits}f}'
before_decimal = result.split('.')[0]
if len(before_decimal) == digits:
return before_decimal
if len(before_decimal) > digits:
# we can't even fit the integral part
return get_scientific(x, digits)

result = result[: digits + 1] # plus 1 for the decimal point
if float(result) == 0:
# we can't even get one significant figure
return get_scientific(x, digits)

return result[: digits + 1]


# Attach a text label above each bar in *rects*, displaying its height
def autolabel(ax, rects, label_from_height: Callable[[float], str] =str_from_float, xoffset=0, yoffset=1, **kwargs):
def autolabel(
ax,
rects,
label_from_height: Callable[[float], str] = str_from_float,
xoffset=0,
yoffset=1,
**kwargs,
):
# kwargs is directly passed to ax.annotate and overrides defaults below
assert 'xytext' not in kwargs, "use xoffset and yoffset instead of xytext"
default_kwargs = dict(
xytext=(xoffset, yoffset),
fontsize="smaller",
fontsize="7",
rotation=0,
ha='center',
va='bottom',
textcoords='offset points')
textcoords='offset points',
)

for rect in rects:
height = rect.get_height()
Expand All @@ -127,35 +140,38 @@ def autolabel(ax, rects, label_from_height: Callable[[float], str] =str_from_flo
**(default_kwargs | kwargs),
)


# utility to print times as 1h4m, 1d15h, 143.2ms, 10.3s etc.
def str_from_ms(ms):
def maybe_val_with_unit(val, unit):
return f'{val}{unit}' if val != 0 else ''
def maybe_val_with_unit(val, unit):
return f'{val}{unit}' if val != 0 else ''

if ms < 1000:
return f'{ms:.3g}ms'

if ms < 1000:
return f'{ms:.3g}ms'
s = ms / 1000
ms = 0
if s < 60:
return f'{s:.3g}s'

s = ms / 1000
ms = 0
if s < 60:
return f'{s:.3g}s'
m = int(s // 60)
s -= 60 * m
if m < 60:
return f'{m}m{maybe_val_with_unit(math.floor(s), "s")}'

m = int(s // 60)
s -= 60*m
if m < 60:
return f'{m}m{maybe_val_with_unit(math.floor(s), "s")}'
h = int(m // 60)
m -= 60 * h
if h < 24:
return f'{h}h{maybe_val_with_unit(m, "m")}'

h = int(m // 60)
m -= 60*h;
if h < 24:
return f'{h}h{maybe_val_with_unit(m, "m")}'
d = int(h // 24)
h -= 24 * d
return f'{d}d{maybe_val_with_unit(h, "h")}'

d = int(h // 24)
h -= 24*d
return f'{d}d{maybe_val_with_unit(h, "h")}'

def autolabel_ms(ax, rects, **kwargs):
autolabel(ax, rects, label_from_height=str_from_ms, **kwargs)
autolabel(ax, rects, label_from_height=str_from_ms, **kwargs)


# Plot an example speedup plot
def plot_speedup():
Expand All @@ -164,57 +180,63 @@ def plot_speedup():
women_means = [1.8, 1.5, 1.1, 1.3, 0.9]

x = np.arange(len(labels)) # the label locations
width = 0.35 # the width of the bars
width = 0.4 # the width of the bars

fig, ax = plt.subplots()
rects1 = ax.bar(x - width / 2,
men_means,
width,
label='Men',
color=light_blue)
rects2 = ax.bar(x + width / 2,
women_means,
width,
label='Women',
color=dark_blue)
rects1 = ax.bar(x - width / 2, men_means, width, label='Men', color=light_blue)
rects2 = ax.bar(x + width / 2, women_means, width, label='Women', color=dark_blue)

# Y-Axis Label
#
# Use a horizontal label for improved readability.
ax.set_ylabel('Speedup',
rotation='horizontal',
position=(1, 1.05),
horizontalalignment='left',
verticalalignment='bottom')
ax.set_ylabel(
'Speedup',
rotation='horizontal',
position=(1, 1.05),
horizontalalignment='left',
verticalalignment='bottom',
)

# Add some text for labels, title and custom x-axis tick labels, etc.
ax.set_xticks(x)
ax.set_xticklabels(labels)

ax.legend(ncol=100,
loc='lower right',
bbox_to_anchor=(0, 1, 1, 0))
ax.legend(ncol=100, loc='lower right', bbox_to_anchor=(0, 1, 1, 0))

autolabel(ax, rects1)
autolabel(ax, rects2)

save(fig, 'speedup.pdf')


def csv_to_dict(file):
data = {}

if file is not None:
data = dict(csv.reader(file))

return data


def main():
parser = argparse.ArgumentParser(
prog='plot',
description='Plot the figures for this paper',
)
parser.add_argument('names', nargs='+', choices=['all', 'speedup'])
parser.add_argument('--layout', type=argparse.FileType('r'))
args = parser.parse_args()

setGlobalDefaults()
layout = csv_to_dict(args.layout)

plotAll = 'all' in args.names

if 'speedup' in args.names or plotAll:
plot_speedup()
with matplotlib.rc_context(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is where the plotting script configuration is applied from the exported CSV values.
If the file is not provided, we use sensible defaults.

{'figure.figsize': (layout.get('columnwidth', 3), 2)}
):
plot_speedup()


if __name__ == "__main__":
Expand Down
Binary file modified plots/speedup.pdf
Binary file not shown.
Loading