PageRenderTime 49ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/src/smi_unb/report/utils.py

https://gitlab.com/brenddongontijo/SMI-UnB
Python | 360 lines | 287 code | 63 blank | 10 comment | 26 complexity | 2a6c5adffae54a705ca4eb108823a8c9 MD5 | raw file
  1. import json
  2. import matplotlib.dates as mdates
  3. import matplotlib.patches as mpatches
  4. import mpld3
  5. import numpy
  6. import pandas
  7. from django.utils import timezone
  8. from django.utils.translation import ugettext as _
  9. from dateutil.relativedelta import relativedelta
  10. from matplotlib.figure import Figure
  11. from .exceptions import NoMeasurementsFoundException
  12. class GraphDateManager(object):
  13. def get_previous_days(
  14. self, week_before=(timezone.now() - timezone.timedelta(days=7))
  15. ):
  16. days_array = []
  17. for i in range(0, 7):
  18. days_array.append(week_before.day)
  19. week_before += timezone.timedelta(days=1)
  20. return days_array
  21. def get_graph_dates(
  22. self, current_measurement, previous_day,
  23. manual_initial_date, manual_final_date
  24. ):
  25. dates = []
  26. if current_measurement:
  27. dates = self.get_initial_and_current_dates()
  28. elif previous_day:
  29. previous_day = int(previous_day)
  30. dates = self.get_past_dates(previous_day)
  31. else:
  32. dates = [manual_initial_date, manual_final_date]
  33. return dates
  34. def get_initial_and_current_dates(
  35. self, current_date=timezone.now()
  36. ):
  37. initial_current_date = current_date.replace(
  38. hour=00,
  39. minute=00,
  40. second=00,
  41. microsecond=00
  42. )
  43. return [initial_current_date, current_date]
  44. def get_past_dates(self, total_past_days, current_day=timezone.now()):
  45. past_day = current_day - timezone.timedelta(
  46. days=total_past_days
  47. )
  48. initial_date = past_day.replace(
  49. hour=00,
  50. minute=00,
  51. second=00,
  52. microsecond=00
  53. )
  54. one_day = 1
  55. final_date = initial_date + timezone.timedelta(
  56. days=one_day
  57. )
  58. return [initial_date, final_date]
  59. class GraphPlotManager(object):
  60. def __init__(self):
  61. self.data_manager = GraphDataManager()
  62. def create_line_graph(
  63. self, transductor, measurement_type, initial_date, final_date
  64. ):
  65. try:
  66. x_data, y_data, square_indexes = self.data_manager.get_graph_data(
  67. transductor, measurement_type, initial_date, final_date
  68. )
  69. except NoMeasurementsFoundException:
  70. raise
  71. # Creating Figure and Subplots
  72. fig = Figure(figsize=(10, 5))
  73. ax = fig.add_subplot(1, 1, 1, axisbg='#EEEEEE')
  74. self.set_subplot_basic_information(ax, measurement_type)
  75. # Plotting data
  76. ya, yb, yc = numpy.array(y_data).T
  77. ax.plot_date(x_data, ya, '-', color='blue', label="Fase A")
  78. ax.plot_date(x_data, yb, '-', color='green', label="Fase B")
  79. ax.plot_date(x_data, yc, '-', color='red', label="Fase C")
  80. self.set_subplot_legend(ax)
  81. self.annotate_max_min_points(ax, x_data, ya, yb, yc)
  82. self.draw_squares_covering_missing_data(
  83. ax, square_indexes, x_data, ya, yb, yc)
  84. return json.dumps(mpld3.fig_to_dict(fig))
  85. def set_subplot_basic_information(self, subplot, measurement_type):
  86. measurement_string = GraphUtils.get_graph_measurement_string(
  87. measurement_type
  88. )
  89. subplot.grid(color='white', linestyle='solid')
  90. subplot.set_title(
  91. _(('Monitoramento de ' + measurement_string)),
  92. size=20
  93. )
  94. subplot.set_xlabel(_('Data'))
  95. subplot.set_ylabel(_(measurement_string))
  96. def set_subplot_legend(self, subplot):
  97. handles, labels = subplot.get_legend_handles_labels()
  98. subplot.legend(handles, labels)
  99. def annotate_max_min_points(self, subplot, x_data, ya, yb, yc):
  100. y_arrays = [ya, yb, yc]
  101. dark_colors = ['darkblue', 'darkgreen', 'darkred']
  102. for index, y_array in enumerate(y_arrays):
  103. max_y_value = numpy.amax(y_array)
  104. subplot.text(
  105. x=mdates.date2num(x_data[numpy.argmax(y_array)]),
  106. y=float(max_y_value) + .05,
  107. s='Máx',
  108. color=dark_colors[index],
  109. )
  110. subplot.scatter(
  111. x=mdates.date2num(x_data[numpy.argmax(y_array)]),
  112. y=float(max_y_value),
  113. marker='o',
  114. color=dark_colors[index]
  115. )
  116. min_y_value = numpy.amin(y_array)
  117. subplot.text(
  118. x=mdates.date2num(x_data[numpy.argmin(y_array)]),
  119. y=float(min_y_value) - .05,
  120. s='Mín',
  121. color=dark_colors[index],
  122. )
  123. subplot.scatter(
  124. x=mdates.date2num(x_data[numpy.argmin(y_array)]),
  125. y=float(min_y_value),
  126. marker='o',
  127. color=dark_colors[index]
  128. )
  129. def draw_squares_covering_missing_data(
  130. self, subplot, dict_square_indexes, x_data, ya, yb, yc
  131. ):
  132. if dict_square_indexes:
  133. y_min = numpy.concatenate([ya, yb, yc]).min()
  134. y_max = numpy.concatenate([ya, yb, yc]).max()
  135. width = (mdates.date2num(x_data[1]) - mdates.date2num(x_data[0]))
  136. height = y_max - y_min
  137. for index, value in dict_square_indexes.items():
  138. x_square = mdates.date2num(x_data[index])
  139. dx = width / 8.0
  140. dy = height / 4
  141. subplot.add_patch(
  142. mpatches.Rectangle(
  143. (x_square - dx, y_min - dy), # x, y
  144. width * max(value - 1, 0) + 2 * dx, # width
  145. height + 2 * dy, # heigth
  146. alpha=0.2,
  147. facecolor="yellow",
  148. # linewidth=0,
  149. )
  150. )
  151. class GraphDataManager(object):
  152. def get_graph_data(
  153. self, transductor, measurement_type, initial_date, final_date
  154. ):
  155. # QuerySet With All Measurements Of Specific Period
  156. qs = transductor.energymeasurements_set.filter(
  157. collection_date__range=(initial_date, final_date)
  158. )
  159. if qs.count() <= 1:
  160. raise NoMeasurementsFoundException(
  161. _('Nenhuma medição encontrada')
  162. )
  163. initial_date_copy = initial_date
  164. x_data = []
  165. y_data = []
  166. dict_square_indexes = {}
  167. time_diff = (final_date - initial_date)
  168. TWO_HOUR_SEC = 2 * 3600
  169. TWELVE_HOURS_SEC = 12 * 3600
  170. args = GraphUtils.get_three_phase_args(measurement_type)
  171. if time_diff.days <= 1:
  172. if time_diff.days is 0 and time_diff.seconds <= TWO_HOUR_SEC:
  173. # A 1 minute based graph will be made
  174. y_data, dict_square_indexes = self.filter_y_data(
  175. False, initial_date, final_date, qs, *args, minutes=1
  176. )
  177. x_data = pandas.date_range(
  178. initial_date_copy, periods=len(y_data), freq='T'
  179. )
  180. elif time_diff.days is 0 and time_diff.seconds <= TWELVE_HOURS_SEC:
  181. # A 5 minutes based graph will be made
  182. y_data, dict_square_indexes = self.filter_y_data(
  183. True, initial_date, final_date, qs, *args, minutes=5
  184. )
  185. x_data = pandas.date_range(
  186. initial_date_copy, periods=len(y_data), freq='5min'
  187. )
  188. else:
  189. # A 10 minutes based graph will be made
  190. y_data, dict_square_indexes = self.filter_y_data(
  191. True, initial_date, final_date, qs, *args, minutes=10
  192. )
  193. x_data = pandas.date_range(
  194. initial_date_copy, periods=len(y_data), freq='10min'
  195. )
  196. else:
  197. # A 1 hour based graph will be made
  198. y_data, dict_square_indexes = self.filter_y_data(
  199. True, initial_date, final_date, qs, *args, hours=1
  200. )
  201. x_data = pandas.date_range(
  202. initial_date_copy, periods=len(y_data), freq='H'
  203. )
  204. return [x_data, y_data, dict_square_indexes]
  205. def filter_y_data(
  206. self, range_date, initial_date, final_date, qs, *args, **kwargs
  207. ):
  208. # Calculating a constant for the 3 phases to fill Y data
  209. # in case of missing measurements between the dates.
  210. missing_a, missing_b, missing_c = numpy.array(qs.values_list(*args)) \
  211. .mean(axis=0).T
  212. y_data = []
  213. initial_date_copy = initial_date
  214. initial_index = 0
  215. initial_index_copy = initial_index
  216. dict_square_indexes = {}
  217. last_data_missing_value = False
  218. consecutive_missing_values = 1
  219. while initial_date_copy <= final_date:
  220. if range_date:
  221. data = qs.filter(
  222. collection_date__range=(
  223. initial_date_copy,
  224. initial_date_copy + relativedelta(**kwargs)
  225. )
  226. ).values_list(*args)
  227. else:
  228. data = qs.filter(
  229. collection_date__year=initial_date_copy.year,
  230. collection_date__month=initial_date_copy.month,
  231. collection_date__day=initial_date_copy.day,
  232. collection_date__minute=initial_date_copy.minute
  233. ).values_list(*args)
  234. if data:
  235. y_data.append(numpy.array(data).mean(axis=0))
  236. if last_data_missing_value:
  237. dict_square_indexes[
  238. initial_index_copy
  239. ] = consecutive_missing_values
  240. last_data_missing_value = False
  241. else:
  242. if last_data_missing_value:
  243. consecutive_missing_values += 1
  244. else:
  245. initial_index_copy = initial_index
  246. consecutive_missing_values = 1
  247. last_data_missing_value = True
  248. y_data.append(
  249. numpy.array(
  250. [
  251. missing_a,
  252. missing_b,
  253. missing_c
  254. ]
  255. )
  256. )
  257. initial_index += 1
  258. initial_date_copy += relativedelta(**kwargs)
  259. if last_data_missing_value:
  260. dict_square_indexes[
  261. initial_index_copy
  262. ] = consecutive_missing_values
  263. return [y_data, dict_square_indexes]
  264. class GraphUtils(object):
  265. @classmethod
  266. def get_graph_measurement_string(cls, measurement_type):
  267. measurement_string = ""
  268. if measurement_type == 'voltage':
  269. measurement_string = 'Tensão (V)'
  270. elif measurement_type == 'current':
  271. measurement_string = 'Corrente (A)'
  272. elif measurement_type == 'active_power':
  273. measurement_string = 'Potência Ativa (kW)'
  274. elif measurement_type == 'reactive_power':
  275. measurement_string = 'Potência Reativa (kVAr)'
  276. elif measurement_type == 'apparent_power':
  277. measurement_string = 'Potência Aparente (kVA)'
  278. return measurement_string
  279. @classmethod
  280. def get_three_phase_args(cls, measurement_type):
  281. args = []
  282. if measurement_type == 'voltage':
  283. args = ["voltage_a", "voltage_b", "voltage_c"]
  284. elif measurement_type == 'current':
  285. args = ["current_a", "current_b", "current_c"]
  286. elif measurement_type == 'active_power':
  287. args = ["active_power_a", "active_power_b", "active_power_c"]
  288. elif measurement_type == 'reactive_power':
  289. args = ["reactive_power_a", "reactive_power_b", "reactive_power_c"]
  290. elif measurement_type == 'apparent_power':
  291. args = ["apparent_power_a", "apparent_power_b", "apparent_power_c"]
  292. return args