-
Notifications
You must be signed in to change notification settings - Fork 0
/
Plot2D.py
445 lines (329 loc) · 16.1 KB
/
Plot2D.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
# =====================================================================================================================
# ===================================================== PLOT2D ========================================================
# =====================================================================================================================
"""Script for 2D plotting of data stored in FITS files, ascii tables
Part of the LancAstro.py project for XGAL
Author: Harry Baker
Date: 31.07.2019
Version: 1.3
== RELEASE ==
Example:
$ SCPTest2.py
Todo:
* Move parameters out to a separate config file
"""
# =====================================================================================================================
# IMPORTS
# =====================================================================================================================
import matplotlib as mpl
import matplotlib.pyplot as plt
import numpy as np
import random
import Dependencies.progress_bar as pb
# =====================================================================================================================
# FUNCTIONS
# =====================================================================================================================
def convert_to_array(var):
"""Checks if parameter supplied is an array and corrects if not
Args:
var (np.array/array/str/float): Parameter value/array to be checked and corrected
Return:
var
"""
if not isinstance(var, (list, tuple, np.ndarray)):
var = np.array(var)
return var
def default_lengths(param, n_var):
"""
Checks every parameter list is the same length. i.e if user only defines min and max for 1 variable.
Corrects these to all have same length
Args:
param (np.array): Parameter array to be set to correct length
n_var (int): Number of variables therefore the length to set param to
Return:
param
"""
# Uses convert_to_array to make sure entered params are arrays
param = convert_to_array(param)
# Checks if every parameter is the length of the number of variables
if len(param) != n_var:
if print_on:
print("WARNING: Not every variable has defined range limits!")
print("Setting all variables to default of 1st entry \n")
for i in range(len(param)):
param[i] = param[0]
for i in range(n_var - len(param)):
param.append(param[0])
return param
def colour_cycle(n_var, COLOURS):
"""Checks if a colour is defined for every variable and if not, appends the missing entries to random RGBa values
Args:
n_var (int): Number of variables to be plotted
COLOURS ([str]): List of strings defining colours for each plot
Returns:
COLOURS ([str]): Amended list of colour definitions
"""
if len(COLOURS) != n_var:
for i in range(n_var):
r = lambda: random.randint(0, 255)
random_colour = '#%02X%02X%02X' % (r(), r(), r())
if i < len(COLOURS):
COLOURS[i] = random_colour
else:
COLOURS.append(random_colour)
def construct_ticks(axis_range, num_x, num_y):
"""Creates lists defining the positions of x and y axis ticks
Args:
axis_range (np.array): Range of axis
num_x (int): Number of x-ticks desired
num_y (int): Number of y-ticks desired
Returns:
x_ticks ([str]): Positions of x-axis ticks
y_ticks ([str]): Positions of y-axis ticks
"""
x_step = (axis_range[1] - axis_range[0]) / float(num_x)
y_step = (axis_range[3] - axis_range[2]) / float(num_y)
x_ticks = np.round(np.arange(axis_range[0], axis_range[1], x_step), 1)
y_ticks = np.round(np.arange(axis_range[2], axis_range[3], y_step), 1)
return x_ticks, y_ticks
def plot(x, y, x_error=[None], y_error=[None], DATALABELS=[None], COLOURS=[None], FILL_COLOURS=[None], SHADE=[0.5],
SIZES=[None], POINTSTYLES=['none'], EDGECOLOURS=[None], EDGEWID=[None], LWID=[2], ERBWID=[2]):
""" Method capable of plotting multiple sets of 2D variables
Args:
x ([[float]]): All arrays of x-axis data to plot
y ([[float]]): All arrays of y-axis data to plot
x_error ([[float]]/[[tuple]]): Errors for each x value. Supports tuples for asymmetric errors
y_error ([[float]]/[[tuple]]): Errors for each y value. Supports tuples for asymmetric errors
DATALABELS ([str]): Labels for legend of plots
COLOURS ([str]): Colours for each plot
FILL_COLOURS ([str]): Colour of fill for plot. If None is entered then no fill is used
SHADE ([float]): Alpha value for fill under each plot
SIZES ([float]): Size of points for each plot
POINTSTYLES ([str]): Style of point/ line for each plot
EDGECOLOURS ([str]): Colour for points of each plot
EDGEWID ([float]): Width of point edge for each plot
LWID ([float]): Width of line for each plot
ERBWID ([float]): Width of error bars for each plot
Returns:
HANDLES: Handles for each plot to be used to construct a legend and final figure
"""
HANDLES = []
x = convert_to_array(x)
y = convert_to_array(y)
n_var = len(x)
# Makes sure all parameters are the same length as number of plots
x_error = default_lengths(x_error, n_var)
y_error = default_lengths(y_error, n_var)
DATALABELS = default_lengths(DATALABELS, n_var)
COLOURS = default_lengths(COLOURS, n_var)
FILL_COLOURS = default_lengths(FILL_COLOURS, n_var)
SIZES = default_lengths(SIZES, n_var)
POINTSTYLES = default_lengths(POINTSTYLES, n_var)
EDGECOLOURS = default_lengths(EDGECOLOURS, n_var)
SHADE = default_lengths(SHADE, n_var)
LWID = default_lengths(LWID, n_var)
ERBWID = default_lengths(ERBWID, n_var)
EDGEWID = default_lengths(EDGEWID, n_var)
# Initialise progress bar for plotting
if print_on:
pb.printProgressBar(0, len(x), prefix='BEGINNING PLOTTING', suffix='COMPLETE', length=40)
# Run through x and y sets and create plots
for i in range(len(x)):
# Update progress bar
if print_on:
pb.printProgressBar(i, len(x), prefix='PLOTTING VARIABLE %d' % (i + 1), suffix='COMPLETE', length=40)
# Plot x and y within parameters
PLOT = plt.errorbar(np.array(x[i]), np.array(y[i]), xerr=x_error[i], yerr=y_error[i], color=COLOURS[i],
ms=SIZES[i], fmt=POINTSTYLES[i], mec=EDGECOLOURS[i], lw=LWID[i], elinewidth=ERBWID[i],
mew=EDGEWID[i])
# Appends the artist label for this plot to HANDLES for legend entry
if DATALABELS[i] is not None:
HANDLES.append(PLOT[0])
# Fills underneath line/scatters
# WARNING: THIS IS IN ALPHA DEVELOPMENT!
if FILL_COLOURS[i] is not None:
plt.fill(np.array(x[i]), np.array(y[i]), alpha=SHADE[i], color=FILL_COLOURS[i])
# Finishes progress bar
if print_on:
pb.printProgressBar(len(x), len(x), prefix='FINISHED PLOTTING!', suffix='COMPLETE!', length=40)
return HANDLES
def determine_axis(x, y):
""" Determines the min and max of x and y axis from data bounds
Args:
x ([[float]]): All x-axis data
y ([[float]]): All y-axis data
Return:
axis_range (np.array): The dimensions of the axes
"""
# Tells Python to edit these variables globally not locally
# i.e everywhere, not just in this method
global x_min, x_max, y_min, y_max, axis_range
# Arrays to hold the min/max of each variable of DATASET's x and y
x_mins = []
x_maxs = []
y_mins = []
y_maxs = []
# Cycles through x and y to find min/max of each variable
for i in range(len(x)):
x_mins.append(np.min(x[i]))
x_maxs.append(np.max(x[i]))
for j in range(len(y)):
y_mins.append(np.min(y[j]))
y_maxs.append(np.max(y[j]))
# Adds the min/max of the min/maxs to axis range
axis_range = [np.min(x_mins), np.max(x_maxs), np.min(y_mins), np.max(y_maxs)]
# Adds a 10% border to axis dimensions so data can be more clearly seen
axis_range = add_borders(axis_range)
return axis_range
def add_borders(axis_range):
"""Adds a 10% extra border to the axis dimensions
Args:
axis_range (np.array): Length 4 array of x and y min and maxs of axes
Returns:
axis_range (np.array): With a 10% extra range
"""
for i in range(len(axis_range)):
if i == 0 or i == 2:
if axis_range[i] > 0:
axis_range[i] = 0.9 * axis_range[i]
if axis_range[i] < 0:
axis_range[i] = 1.1 * axis_range[i]
if i == 1 or i == 3:
if axis_range[i] < 0:
axis_range[i] = 0.9 * axis_range[i]
if axis_range[i] > 0:
axis_range[i] = 1.1 * axis_range[i]
return axis_range
def create_figure(x, y, x_error=[None], y_error=[None], DATALABELS=[None], COLOURS=[None], FILL_COLOURS=[None],
SHADE=[0.5], SIZES=[None], POINTSTYLES=['none'], EDGECOLOURS=[None], EDGEWID=[2], LWID=[2],
ERBWID=[2], figure_name="Fig1.pdf", x_label='x-label', y_label='y-label', figsize=(10, 10),
axis_range=[0.0, 10.0, 0.0, 10.0], x_ticks=None, x_tick_labels=None,
y_ticks=None, y_tick_labels=None):
""" Method capable of plotting multiple sets of 2D variables
Args:
x ([[float]]): All arrays of x-axis data to plot
y ([[float]]): All arrays of y-axis data to plot
x_error ([[float]]/[[tuple]]): Errors for each x value. Supports tuples for asymmetric errors
y_error ([[float]]/[[tuple]]): Errors for each y value. Supports tuples for asymmetric errors
DATALABELS ([str]): Labels for legend of plots
COLOURS ([str]): Colours for each plot
FILL_COLOURS ([str]): Colour of fill for plot. If None is entered then no fill is used
SHADE ([float]): Alpha value for fill under each plot
SIZES ([float]): Size of points for each plot
POINTSTYLES ([str]): Style of point/ line for each plot
EDGECOLOURS ([str]): Colour for points of each plot
EDGEWID ([float]): Width of point edge for each plot
LWID ([float]): Width of line for each plot
ERBWID ([float]): Width of error bars for each plot
"""
# WELCOME MESSAGE ========================================================
if print_on:
print("\nWELCOME TO Plot2D")
print("PART OF THE LancAstro PACKAGE \n")
# Sets up the figure size from parameters before plotting commences
plt.figure(figsize=figsize)
# Sets the x,y-axis scales from parameters
plt.xscale(x_scale)
plt.yscale(y_scale)
# Calls plot method to plot each variable from the dataset
HANDLES = plot(x, y, x_error=x_error, y_error=y_error, DATALABELS=DATALABELS, COLOURS=COLOURS,
FILL_COLOURS=FILL_COLOURS, SHADE=SHADE, SIZES=SIZES, POINTSTYLES=POINTSTYLES, EDGEWID=EDGEWID,
EDGECOLOURS=EDGECOLOURS, LWID=LWID, ERBWID=ERBWID)
# If set to, automatically sets the min and max of the axis
# from the range of all the data
if auto_axis:
axis_range = determine_axis(x, y)
# Places grid if set to do so
plt.grid(grid)
# Adds minor ticks to axis if set to do so
if minorticks:
plt.minorticks_on()
# Creates figure axis from defined parameters
plt.axis(axis_range)
# Creates x and y axis labels from settings
plt.xlabel(r'%s' % x_label, {'color': "%s" % x_colour, 'fontsize': x_size})
plt.ylabel(r'%s' % y_label, {'color': '%s' % y_colour, 'fontsize': y_size})
# If true, creates a legend
if legend_on:
if print_on:
print("Producing legend")
leg = plt.legend(handles=HANDLES, labels=DATALABELS)
# Sets legend frame to be transparent if set to do so
if not frame:
leg.get_frame().set_facecolor('none')
# Removes legend border if set to False
if not frame_border:
leg.get_frame().set_linewidth(0.0)
# Sets frame border to defiend
if frame_border:
leg.get_frame().set_edgecolor(frame_colour)
# Sets ticks on the top and right axis if true
if both_axis:
plt.tick_params(which="both", direction="in", top=True, right=True)
if print_on:
print("Saving figure to %s" % figure_name)
if save_fig:
plt.savefig(figure_name)
if print_on:
print("Figure plotted! SCIENCE!")
if display_figure:
plt.show()
else:
plt.close()
# =====================================================================================================================
# PARAMETERS
# =====================================================================================================================
# FIGURE AND AXIS SIZES ===============================================================================================
mpl.rcParams['xtick.labelsize'] = 14 # Size of x-tick labels
mpl.rcParams['ytick.labelsize'] = 14 # Size of y-tick labels
mpl.rcParams['axes.labelsize'] = 14 # Size of axes labels
mpl.rcParams['image.origin'] = 'lower' #
mpl.rcParams['axes.linewidth'] = 2.5 # Size of axes line width
mpl.rcParams['xtick.major.size'] = 14 # Size of major x-ticks
mpl.rcParams['xtick.minor.size'] = 6 # Size of minor x-ticks
mpl.rcParams['xtick.major.width'] = 2.5 # Width of major x-ticks
mpl.rcParams['xtick.minor.width'] = 1.5 # Width of minor x-ticks
mpl.rcParams['ytick.major.size'] = 14 # Size of major y-ticks
mpl.rcParams['ytick.minor.size'] = 6 # Size of minor y-ticks
mpl.rcParams['ytick.major.width'] = 2.5 # Width of major y-ticks
mpl.rcParams['ytick.minor.width'] = 1.5 # Width of minor y-ticks
# Sets font style and size
mpl.rcParams.update({'font.size': 18, 'font.weight': 'bold'})
# FIGURE PARAMETERS ===================================================================================================
share_plot = True # If true, plot all variables onto same axis. If false, outputs each plot in separate figures
figure_name = 'Test.pdf' # Defines default filename
figsize = (10, 7) # Defines figure size
display_figure = True # Opens figure if true
save_fig = True # Saves the figure to file if true
print_on = False # Includes print statements in output if true
# POINTS & FILL PARAMETERS ============================================================================================
POINTSTYLES = ['*', 'o'] # Point style
SIZES = [1, 2] # Point sizes
COLOURS = ['0.3', 'b'] # Point fill colours
EDGECOLOURS = ['face', 'g'] # Point edge colours
# AXIS PARAMETERS =====================================================================================================
x_min = 0.0 # Minimum x-axis value to plot
x_max = 10.0 # Maximum x-axis value to plot
y_min = 0.0 # Minimum y-axis value to plot
y_max = 10.0 # Maximum y-axis value to plot
axis_range = [x_min, x_max, y_min, y_max]
x_scale = 'linear' # Defines x-axis scaling
y_scale = 'linear' # Defines y-axis scaling
auto_axis = True # Auto define dimesions of axises
both_axis = True # Places ticks on top and right axes too
auto_tick = True # Auto create the ticks
# AXIS LABELS =========================================================================================================
x_label = "x-label" # x-axis label
y_label = "y-label" # y-axis label
x_size = 16 # Size of x-axis label
y_size = 16 # Size of y-axis label
x_colour = "k" # Colour of x-axis label
y_colour = "k" # Colour of y-axis label
minorticks = True # Places minor ticks on axis if True
grid = False # Places grid on plot if True
# LEGEND PARAMETERS ===================================================================================================
legend_on = True # Creates legend if true
frame_border = False # Puts border around the legend
frame = True # Sets frame to visible
frame_colour = "k" # Sets frame colour
# END PROGRAM =========================================================================================================