plotis 0.1.6.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- plotis/__init__.py +1 -0
- plotis/plotis.py +334 -0
- plotis-0.1.6.2.dist-info/METADATA +117 -0
- plotis-0.1.6.2.dist-info/RECORD +6 -0
- plotis-0.1.6.2.dist-info/WHEEL +4 -0
- plotis-0.1.6.2.dist-info/licenses/LICENCE +28 -0
plotis/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .plotis import *
|
plotis/plotis.py
ADDED
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
# Standard lib imports
|
|
2
|
+
import os
|
|
3
|
+
import inspect
|
|
4
|
+
import re
|
|
5
|
+
|
|
6
|
+
# Dependencies imports
|
|
7
|
+
import pandas as pd
|
|
8
|
+
|
|
9
|
+
# Specified imports
|
|
10
|
+
from contextlib import AbstractContextManager
|
|
11
|
+
from typing import Any, List
|
|
12
|
+
from types import TracebackType
|
|
13
|
+
|
|
14
|
+
class PlotIs(AbstractContextManager):
|
|
15
|
+
"""A context manager for isolating, and packaging everything
|
|
16
|
+
needed to independently reproduce plots.
|
|
17
|
+
|
|
18
|
+
The plotting logic placed in this context may only rely on
|
|
19
|
+
matplotlib.pyplot imported as `import matplotlib,pyplot as plt`.
|
|
20
|
+
|
|
21
|
+
The plotting logic in this context may only produce a single figure.
|
|
22
|
+
|
|
23
|
+
Every `.show()` and `.savefig()` call will be removed from the plotting
|
|
24
|
+
logic before being saved to `run.py`.
|
|
25
|
+
|
|
26
|
+
This class can only be used in compination with the keywork `with`.
|
|
27
|
+
|
|
28
|
+
Attributes
|
|
29
|
+
----------
|
|
30
|
+
figpath : str
|
|
31
|
+
Path of the folder in which `data.csv` and `run.py` will be saved.
|
|
32
|
+
data : pandas.DataFrame
|
|
33
|
+
Data used in plotting logic.
|
|
34
|
+
calling_filename : str
|
|
35
|
+
Name of the file calling the contructor. This will be set
|
|
36
|
+
during runtime.
|
|
37
|
+
calling_context_line_start : int
|
|
38
|
+
Line number + 1 of the constructor call. This will be set during
|
|
39
|
+
runtime.
|
|
40
|
+
calling_line_end : int
|
|
41
|
+
Last line of context related to the call of this constructor.
|
|
42
|
+
This will be set during runtime.
|
|
43
|
+
context_source_lines : List[str]
|
|
44
|
+
Lines of code contain in the context related to the call to
|
|
45
|
+
the constructor, i.e. the lines
|
|
46
|
+
[calling_context_line_start, calling_context_line_end].
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
def __init__(self, figpath: str, data: pd.DataFrame) -> None:
|
|
50
|
+
self.figpath = figpath
|
|
51
|
+
self.data = data
|
|
52
|
+
|
|
53
|
+
# Information about the calling file
|
|
54
|
+
self.calling_filename = ""
|
|
55
|
+
self.calling_context_line_start = -1 # This is set to calling line + 1
|
|
56
|
+
self.calling_line_end = -1
|
|
57
|
+
self.constructing_line = "" # Line of code conaining the constructor call
|
|
58
|
+
self.context_source_lines = []
|
|
59
|
+
|
|
60
|
+
def __enter__(self) -> Any:
|
|
61
|
+
calling_frame = inspect.stack()[1]
|
|
62
|
+
|
|
63
|
+
# Ensuring we have calling code context
|
|
64
|
+
if calling_frame.code_context is None:
|
|
65
|
+
raise Exception("Could not find code context")
|
|
66
|
+
self.constructing_line = calling_frame.code_context[0]
|
|
67
|
+
|
|
68
|
+
# Ensuring that this function is called
|
|
69
|
+
# as part of a with statement
|
|
70
|
+
regex_str = r"(\s{1,}|)with PlotIs[(](\s|\S|){1,}[)] as \S{1,}:[\n]|(\s{1,}|)with PlotIs[(](\s|\S|){1,}[)]:[\n]"
|
|
71
|
+
with_context_pattern = re.compile(regex_str)
|
|
72
|
+
if with_context_pattern.fullmatch(self.constructing_line) is None :
|
|
73
|
+
raise Exception(f"PlotIs must be called by `with`: {calling_frame.code_context[0]}")
|
|
74
|
+
|
|
75
|
+
# Parsing calling frame to set up attributes
|
|
76
|
+
self.calling_filename = calling_frame.filename
|
|
77
|
+
self.calling_context_line_start = calling_frame.lineno + 1 # Exclude calling line
|
|
78
|
+
self.calling_context_line_end = self._get_last_lineno_of_context(
|
|
79
|
+
self.calling_filename,
|
|
80
|
+
self.calling_context_line_start,
|
|
81
|
+
include_calling_line=False
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
# Extacting source lines
|
|
85
|
+
lines = []
|
|
86
|
+
with open(self.calling_filename, "r") as fp:
|
|
87
|
+
# Colleting the source lines of our context.
|
|
88
|
+
# Note that code lines start counting at 1 while
|
|
89
|
+
# list index starts at 0.
|
|
90
|
+
lbnd_inclusive = self.calling_context_line_start - 1
|
|
91
|
+
ubnd_exclusive = self.calling_context_line_end
|
|
92
|
+
lines = fp.readlines()[lbnd_inclusive:ubnd_exclusive]
|
|
93
|
+
|
|
94
|
+
# Ensuring that there is not more than one plot in the context
|
|
95
|
+
# NOTE: this method is very yanky, see _has_multiple_plots().
|
|
96
|
+
if PlotIs._has_multiple_plots(lines) is True:
|
|
97
|
+
raise Exception("Multiple figures are not supported. You cannot have more than one savefig or show calls in context")
|
|
98
|
+
|
|
99
|
+
# Formats and cleans source code and save it in attribute
|
|
100
|
+
self.context_source_lines = self._clean_context_source(lines)
|
|
101
|
+
|
|
102
|
+
# Ensuring that there are no nested with contexts using PlotIs
|
|
103
|
+
line_match_list = [with_context_pattern.match(line) is not None for line in self.context_source_lines]
|
|
104
|
+
if True in line_match_list:
|
|
105
|
+
raise Exception("PlotIs does not support nested `with` contexts using PlotIs")
|
|
106
|
+
|
|
107
|
+
def __exit__(
|
|
108
|
+
self,
|
|
109
|
+
__exc_type: type[BaseException] | None,
|
|
110
|
+
__exc_value: BaseException | None,
|
|
111
|
+
__traceback: TracebackType | None
|
|
112
|
+
) -> bool | None:
|
|
113
|
+
calling_frame = inspect.stack()[1]
|
|
114
|
+
self.calling_line_end = calling_frame.lineno
|
|
115
|
+
|
|
116
|
+
# Creates the folder in which to write the data and code
|
|
117
|
+
if not os.path.exists(self.figpath):
|
|
118
|
+
os.makedirs(self.figpath)
|
|
119
|
+
|
|
120
|
+
# Writing data to figpath
|
|
121
|
+
data_file_path = self.figpath + f"/data.csv"
|
|
122
|
+
print(f"Writing data to: {data_file_path}")
|
|
123
|
+
with open(data_file_path, "w+") as fp:
|
|
124
|
+
self.data.to_csv(fp)
|
|
125
|
+
|
|
126
|
+
# Writing concatinating data load code with
|
|
127
|
+
# context source and writing it to file
|
|
128
|
+
code_file_path = self.figpath + "/run.py"
|
|
129
|
+
print(f"Writing context code to: {code_file_path}")
|
|
130
|
+
with open(code_file_path, "w+") as fp:
|
|
131
|
+
code_to_write = self._get_import_code()
|
|
132
|
+
code_to_write += self._get_data_load_code()
|
|
133
|
+
code_to_write += self.context_source_lines
|
|
134
|
+
code_to_write += self._get_savefig_code("png")
|
|
135
|
+
fp.writelines(code_to_write)
|
|
136
|
+
|
|
137
|
+
def _get_last_lineno_of_context(
|
|
138
|
+
self,
|
|
139
|
+
calling_filename: str,
|
|
140
|
+
calling_line_no: int,
|
|
141
|
+
include_calling_line: bool = False
|
|
142
|
+
) -> int:
|
|
143
|
+
"""Returns the last line number of the calling context specified in
|
|
144
|
+
`calling_frame`.
|
|
145
|
+
"""
|
|
146
|
+
with open(calling_filename, "r") as fp:
|
|
147
|
+
lines = fp.readlines()
|
|
148
|
+
|
|
149
|
+
# Initiates the returning variable as the base case
|
|
150
|
+
last_lineno_context = len(lines)
|
|
151
|
+
|
|
152
|
+
# Specifies the line to start analysing our context from.
|
|
153
|
+
# The number is dependent on if we want to include the calling
|
|
154
|
+
# line. The reason for this is that in a for caluse, or with
|
|
155
|
+
# clause, the calling line has a different indentation than
|
|
156
|
+
# the context we are trying to analyse.
|
|
157
|
+
starting_line_no = None
|
|
158
|
+
if include_calling_line is True:
|
|
159
|
+
starting_line_no = calling_line_no
|
|
160
|
+
else:
|
|
161
|
+
starting_line_no = calling_line_no + 1
|
|
162
|
+
|
|
163
|
+
# Iterate over each line of code in starting at calling frame
|
|
164
|
+
# (or one line after it) until indentation becomes less than
|
|
165
|
+
# the initial one, i.e. stepping out of the context.
|
|
166
|
+
context_indent_base = None
|
|
167
|
+
for lineno in range(starting_line_no, len(lines)+1):
|
|
168
|
+
line = lines[lineno-1]
|
|
169
|
+
current_indent = self._get_indentation(line)
|
|
170
|
+
|
|
171
|
+
# For the first iteration we save the number
|
|
172
|
+
# of indentation defining the calling context
|
|
173
|
+
if lineno == starting_line_no:
|
|
174
|
+
context_indent_base = current_indent
|
|
175
|
+
continue
|
|
176
|
+
|
|
177
|
+
# The first time at which the number of
|
|
178
|
+
# indentations are less than the context base
|
|
179
|
+
# defines beginning of the next context.
|
|
180
|
+
#
|
|
181
|
+
# Note: that we do not consider a new line. If we do not do this
|
|
182
|
+
# code in same context but separated by new line will be
|
|
183
|
+
# incorrecly excluded.
|
|
184
|
+
any_white_space = re.compile(r"\s")
|
|
185
|
+
if current_indent < context_indent_base and not any_white_space.fullmatch(line):
|
|
186
|
+
# When current indent is less than base indent
|
|
187
|
+
# for the first time we are at first line in next
|
|
188
|
+
# context. Therefore we subtract one to get last
|
|
189
|
+
# line in context we are analysing
|
|
190
|
+
last_lineno_context = lineno - 1
|
|
191
|
+
break
|
|
192
|
+
|
|
193
|
+
return last_lineno_context
|
|
194
|
+
|
|
195
|
+
@staticmethod
|
|
196
|
+
def _get_indentation(line: str) -> int:
|
|
197
|
+
"""Gets the number of indentation charachters used in `line`.
|
|
198
|
+
|
|
199
|
+
Parameters
|
|
200
|
+
----------
|
|
201
|
+
line : str
|
|
202
|
+
Code line for which to get the number of indentations.
|
|
203
|
+
|
|
204
|
+
Returns
|
|
205
|
+
-------
|
|
206
|
+
int
|
|
207
|
+
Number of whitespace characters used for indentation in `line`.
|
|
208
|
+
"""
|
|
209
|
+
leading_spaces = len(line) - len(line.lstrip())
|
|
210
|
+
return leading_spaces
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def _get_data_load_code(self) -> List[str]:
|
|
214
|
+
"""Returns the strings of code needed to load the data, which is saved in
|
|
215
|
+
data.csv, into a variable of the same name as was supplied to the data argument
|
|
216
|
+
in the PlotIs constructor.
|
|
217
|
+
|
|
218
|
+
Returns
|
|
219
|
+
-------
|
|
220
|
+
List[str]
|
|
221
|
+
List of strings. Each entry is one line of pyhton code.
|
|
222
|
+
"""
|
|
223
|
+
# Checking if the contructor call is using PlotIs(data=xxx, xxx)
|
|
224
|
+
# or PlotIs(some_file_path, some_pd_df)
|
|
225
|
+
variable_name = ""
|
|
226
|
+
regex_str = r"data=[\S\s]+"
|
|
227
|
+
data_pattern_match = re.search(regex_str, self.constructing_line)
|
|
228
|
+
if data_pattern_match is not None:
|
|
229
|
+
# Extracts the name of the variable storing the data
|
|
230
|
+
variable_name = self.constructing_line.split("data=")[1].strip()
|
|
231
|
+
variable_name = variable_name.replace(")", "").replace(":", "")
|
|
232
|
+
variable_name = variable_name.strip()
|
|
233
|
+
else:
|
|
234
|
+
# Extracts the name of the variable storgin the data
|
|
235
|
+
variable_name = self.constructing_line.split(",")[1].strip()
|
|
236
|
+
variable_name = variable_name.replace(")", "").replace(":", "")
|
|
237
|
+
variable_name = variable_name.strip()
|
|
238
|
+
|
|
239
|
+
# Ensuring that something was catpured by above logic
|
|
240
|
+
if variable_name == "":
|
|
241
|
+
raise ValueError("Something went wrong. Could not parse varable name containing data")
|
|
242
|
+
|
|
243
|
+
# Constructing line of code to load data from data.csv into variable
|
|
244
|
+
data_file_path = self.figpath + f"/data.csv"
|
|
245
|
+
data_load_code = [
|
|
246
|
+
f"{variable_name} = pd.read_csv(\"{data_file_path}\")\n\n"
|
|
247
|
+
]
|
|
248
|
+
|
|
249
|
+
return data_load_code
|
|
250
|
+
|
|
251
|
+
def _get_savefig_code(self, format: str) -> List[str]:
|
|
252
|
+
"""Returns lines of code needed to save
|
|
253
|
+
the most recently created figure.
|
|
254
|
+
"""
|
|
255
|
+
save_path = self.figpath + "/figure." + format
|
|
256
|
+
return [f"plt.savefig(\"{save_path}\")"]
|
|
257
|
+
|
|
258
|
+
def _get_import_code(self) -> List[str]:
|
|
259
|
+
"""Returns lines of code with neccessary imports.
|
|
260
|
+
"""
|
|
261
|
+
code = [
|
|
262
|
+
"import pandas as pd\n",
|
|
263
|
+
"import matplotlib.pyplot as plt\n",
|
|
264
|
+
"\n"
|
|
265
|
+
]
|
|
266
|
+
|
|
267
|
+
return code
|
|
268
|
+
|
|
269
|
+
def _clean_context_source(self, code: List[str]) -> List[str]:
|
|
270
|
+
"""Cleans and formats the input code such that it can be writen
|
|
271
|
+
to independent pyhton file without any unexpected behaviour.
|
|
272
|
+
|
|
273
|
+
Parameters
|
|
274
|
+
----------
|
|
275
|
+
code : List[str]
|
|
276
|
+
List of code lines to be cleaned and formated.
|
|
277
|
+
|
|
278
|
+
Returns
|
|
279
|
+
-------
|
|
280
|
+
List[str]
|
|
281
|
+
Input list where each string element has been formated and
|
|
282
|
+
cleaned.
|
|
283
|
+
"""
|
|
284
|
+
# Removing indentation
|
|
285
|
+
code = [line.lstrip() for line in code]
|
|
286
|
+
|
|
287
|
+
# Translating empty lines to blank rows
|
|
288
|
+
for ix, line in enumerate(code):
|
|
289
|
+
if len(line) == 0:
|
|
290
|
+
code[ix] = "\n"
|
|
291
|
+
|
|
292
|
+
# Ensuring that last element in each code line is a line break
|
|
293
|
+
for ix, line in enumerate(code):
|
|
294
|
+
if line[len(line)-1] != "\n":
|
|
295
|
+
line = line + "\n"
|
|
296
|
+
code[ix] = line
|
|
297
|
+
|
|
298
|
+
# Removing any lines containing savefig
|
|
299
|
+
code = [line for line in code if ".savefig" not in line]
|
|
300
|
+
|
|
301
|
+
# Removing any lines containing plt.show()
|
|
302
|
+
code = [line for line in code if "plt.show(" not in line]
|
|
303
|
+
|
|
304
|
+
# Removes all in line comments
|
|
305
|
+
code = [line for line in code if line[0] != "#"]
|
|
306
|
+
|
|
307
|
+
return code
|
|
308
|
+
|
|
309
|
+
@staticmethod
|
|
310
|
+
def _has_multiple_plots(code: List[str]) -> bool:
|
|
311
|
+
"""Returns false if there are more than one
|
|
312
|
+
savefig() and or plt.show() call in `code`,
|
|
313
|
+
otherwise true.
|
|
314
|
+
|
|
315
|
+
Parameters
|
|
316
|
+
----------
|
|
317
|
+
code : List[str]
|
|
318
|
+
List of code lines in which to check if there are
|
|
319
|
+
more than one figure.
|
|
320
|
+
|
|
321
|
+
Returns
|
|
322
|
+
-------
|
|
323
|
+
bool
|
|
324
|
+
False if there are more than one savefig() and or
|
|
325
|
+
plt.show() call in `code`, otherwise it returns True.
|
|
326
|
+
"""
|
|
327
|
+
n_show = 0
|
|
328
|
+
n_savefig = 0
|
|
329
|
+
for line in code:
|
|
330
|
+
n_show += 1 if ".show()" in line else 0
|
|
331
|
+
n_savefig += 1 if "savefig(" in line else 0
|
|
332
|
+
|
|
333
|
+
return n_show > 1 or n_savefig > 1
|
|
334
|
+
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: plotis
|
|
3
|
+
Version: 0.1.6.2
|
|
4
|
+
Summary: Plot isolation tool for pyhton
|
|
5
|
+
Project-URL: Homepage, https://github.com/victorbrun/plotis
|
|
6
|
+
Project-URL: Issues, https://github.com/victorbrun/plotis/issues
|
|
7
|
+
Author-email: Victor Brun <me@victorbrun.com>
|
|
8
|
+
License-File: LICENCE
|
|
9
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Requires-Python: >=3.9
|
|
13
|
+
Requires-Dist: matplotlib
|
|
14
|
+
Requires-Dist: numpy
|
|
15
|
+
Requires-Dist: pandas
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
# `plotis`
|
|
19
|
+
`plotis` is a plot isolation tool for python. It saves the data and python code needed to independently reproduce plots using a pythonic interface.
|
|
20
|
+
|
|
21
|
+
This project was initiated to address the issue of repository size ballooning in documentation projects where images are frequently updated. Since the most common image formats are not plain text, Git cannot version control them effectively, resulting in the storage of every version of every image. Instead of converting images to a plain text format or pruning the Git history, I chose to overengineer this solution. This solution uses Python context managers to extract the necessary code and data, enabling the independent reproduction of plots.
|
|
22
|
+
|
|
23
|
+
# Installation
|
|
24
|
+
```bash
|
|
25
|
+
pip install plotis
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
# Usage
|
|
29
|
+
## Simple example
|
|
30
|
+
`plotis` relies on the context manager `PlotIs`. By placing the plotting logic inside the `PlotIs` context and providing the data used for plotting, as shown below, a folder will be created in the working directory. This folder contains the data (`<figfoldername>/data.csv`) and code (`<figfoldername>/run.py`) needed to reproduce the plot independently.
|
|
31
|
+
```python
|
|
32
|
+
# Import base dependencies
|
|
33
|
+
import pandas as pd
|
|
34
|
+
import matplotlib.pyplot as plt
|
|
35
|
+
|
|
36
|
+
# Imports PlotIs context manager from plotis package
|
|
37
|
+
from plotis import PlotIs
|
|
38
|
+
|
|
39
|
+
# Generates sample data
|
|
40
|
+
x = np.linspace(0, 4*math.pi)
|
|
41
|
+
y = np.sin(x)
|
|
42
|
+
sample_data = pd.DataFrame(data={"x": x, "y": y})
|
|
43
|
+
|
|
44
|
+
# Specify the name of the folder which will contain
|
|
45
|
+
# code and data needed to independently reproduce the plot.
|
|
46
|
+
figure_folder = "fig1"
|
|
47
|
+
|
|
48
|
+
# Creates plot inside PlotIs context
|
|
49
|
+
with PlotIs(figure_folder, sample_data):
|
|
50
|
+
# Plots data
|
|
51
|
+
sample_data.plot(x="x", y="y")
|
|
52
|
+
plt.title("Sin function")
|
|
53
|
+
plt.xlabel("x")
|
|
54
|
+
plt.ylabel("sin(x)")
|
|
55
|
+
|
|
56
|
+
# Shows plot
|
|
57
|
+
plt.show()
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## LaTeX project example
|
|
61
|
+
As this package was motivate by large documentation projects, this example will showcase how to "highjack" the compilation process of a LaTeX document to insert the figure produced by above example.
|
|
62
|
+
|
|
63
|
+
Consider a LaTeX document containing the following content:
|
|
64
|
+
```latex
|
|
65
|
+
\documentclass[12pt]{article}
|
|
66
|
+
\usepackage{graphicx}
|
|
67
|
+
\usepackage{hyperref}
|
|
68
|
+
|
|
69
|
+
\title{PlotIs example one}
|
|
70
|
+
\author{Victor Brun\\ \href{https://victorbrun.com}{victorbrun.com}}
|
|
71
|
+
\date{\today}
|
|
72
|
+
|
|
73
|
+
\begin{document}
|
|
74
|
+
|
|
75
|
+
\maketitle
|
|
76
|
+
|
|
77
|
+
\section{The sinus function}
|
|
78
|
+
The sinus function is a trigonometric function which we denote by $\sin{x}$. In figure \ref{fig:sin} we can see that it looks like a wave.
|
|
79
|
+
|
|
80
|
+
\begin{figure}[h]
|
|
81
|
+
\centering
|
|
82
|
+
\includegraphics[width=8cm]{figures/sinus_curve.png}
|
|
83
|
+
\caption{The sinus function plotted on the interval $[0, 4\pi]$.}
|
|
84
|
+
\label{fig:sin}
|
|
85
|
+
\end{figure}
|
|
86
|
+
|
|
87
|
+
\end{document}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Since you want to version control the figures in this huge LaTeX project, `figures/sinus_curve.png` does not exist in the repository (as it is a non-plain text image format). Fortunately, you have already written the logic using `plotis` (the code in the above example) needed to produce the figure `sinus_curve.png`. The result produced by running the `plotis` code, `fig1/data.csv`, and `fig1/run.py`, is sufficient to recreate the figure. You can create a Makefile to run the plotting script `fig1/run.py` and subsequently move the produced image to the appropriate folder in your LaTeX project. The Makefile is as follows:
|
|
91
|
+
```make
|
|
92
|
+
all: | plotgen move pdflatex
|
|
93
|
+
# Executes below targets in the following order
|
|
94
|
+
# 1. plotget
|
|
95
|
+
# 2. move
|
|
96
|
+
# 3. pdflatex
|
|
97
|
+
|
|
98
|
+
plotgen: sinus_curve/run.py sinus_curve/data.csv
|
|
99
|
+
# Runs the automatically generated pyhton
|
|
100
|
+
# script to create the plot
|
|
101
|
+
python3 fig1/run.py
|
|
102
|
+
|
|
103
|
+
move: sinus_curve/figure.png
|
|
104
|
+
@echo "\nCreating folder figures if it does not exist"
|
|
105
|
+
mkdir -p figures
|
|
106
|
+
|
|
107
|
+
@echo "\nCopying produced fiure to figures/"
|
|
108
|
+
cp fig1/figure.png figures/sinus_curve.png
|
|
109
|
+
|
|
110
|
+
pdflatex: main.tex
|
|
111
|
+
# Runs pdflatex twice to make sure
|
|
112
|
+
# references are created correctly
|
|
113
|
+
pdflatex main.tex
|
|
114
|
+
pdflatex main.tex
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
This can be found as a working example in `examples/ex1`.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
plotis/__init__.py,sha256=8iupCzp5AV4mIRN1ir5GOGiLKpcU4PbcZDP4g_UBCgE,22
|
|
2
|
+
plotis/plotis.py,sha256=AblIIVylWg_fIy2wHoO7VXxn9x1DlE0wNMFOZcETvkk,12831
|
|
3
|
+
plotis-0.1.6.2.dist-info/METADATA,sha256=DMJWa_qxbO3Cs3SKaIdMgkpzwvmgSn37zHZdwpoERdY,4523
|
|
4
|
+
plotis-0.1.6.2.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
|
|
5
|
+
plotis-0.1.6.2.dist-info/licenses/LICENCE,sha256=GjwAED6oFuY0O8Hpmq8JTEqsPLMY596DuBSNh46Irq8,1498
|
|
6
|
+
plotis-0.1.6.2.dist-info/RECORD,,
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024, Victor Brun
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|