/src/smi_unb/report/utils.py
Python | 360 lines | 287 code | 63 blank | 10 comment | 26 complexity | 2a6c5adffae54a705ca4eb108823a8c9 MD5 | raw file
- import json
- import matplotlib.dates as mdates
- import matplotlib.patches as mpatches
- import mpld3
- import numpy
- import pandas
- from django.utils import timezone
- from django.utils.translation import ugettext as _
- from dateutil.relativedelta import relativedelta
- from matplotlib.figure import Figure
- from .exceptions import NoMeasurementsFoundException
- class GraphDateManager(object):
- def get_previous_days(
- self, week_before=(timezone.now() - timezone.timedelta(days=7))
- ):
- days_array = []
- for i in range(0, 7):
- days_array.append(week_before.day)
- week_before += timezone.timedelta(days=1)
- return days_array
- def get_graph_dates(
- self, current_measurement, previous_day,
- manual_initial_date, manual_final_date
- ):
- dates = []
- if current_measurement:
- dates = self.get_initial_and_current_dates()
- elif previous_day:
- previous_day = int(previous_day)
- dates = self.get_past_dates(previous_day)
- else:
- dates = [manual_initial_date, manual_final_date]
- return dates
- def get_initial_and_current_dates(
- self, current_date=timezone.now()
- ):
- initial_current_date = current_date.replace(
- hour=00,
- minute=00,
- second=00,
- microsecond=00
- )
- return [initial_current_date, current_date]
- def get_past_dates(self, total_past_days, current_day=timezone.now()):
- past_day = current_day - timezone.timedelta(
- days=total_past_days
- )
- initial_date = past_day.replace(
- hour=00,
- minute=00,
- second=00,
- microsecond=00
- )
- one_day = 1
- final_date = initial_date + timezone.timedelta(
- days=one_day
- )
- return [initial_date, final_date]
- class GraphPlotManager(object):
- def __init__(self):
- self.data_manager = GraphDataManager()
- def create_line_graph(
- self, transductor, measurement_type, initial_date, final_date
- ):
- try:
- x_data, y_data, square_indexes = self.data_manager.get_graph_data(
- transductor, measurement_type, initial_date, final_date
- )
- except NoMeasurementsFoundException:
- raise
- # Creating Figure and Subplots
- fig = Figure(figsize=(10, 5))
- ax = fig.add_subplot(1, 1, 1, axisbg='#EEEEEE')
- self.set_subplot_basic_information(ax, measurement_type)
- # Plotting data
- ya, yb, yc = numpy.array(y_data).T
- ax.plot_date(x_data, ya, '-', color='blue', label="Fase A")
- ax.plot_date(x_data, yb, '-', color='green', label="Fase B")
- ax.plot_date(x_data, yc, '-', color='red', label="Fase C")
- self.set_subplot_legend(ax)
- self.annotate_max_min_points(ax, x_data, ya, yb, yc)
- self.draw_squares_covering_missing_data(
- ax, square_indexes, x_data, ya, yb, yc)
- return json.dumps(mpld3.fig_to_dict(fig))
- def set_subplot_basic_information(self, subplot, measurement_type):
- measurement_string = GraphUtils.get_graph_measurement_string(
- measurement_type
- )
- subplot.grid(color='white', linestyle='solid')
- subplot.set_title(
- _(('Monitoramento de ' + measurement_string)),
- size=20
- )
- subplot.set_xlabel(_('Data'))
- subplot.set_ylabel(_(measurement_string))
- def set_subplot_legend(self, subplot):
- handles, labels = subplot.get_legend_handles_labels()
- subplot.legend(handles, labels)
- def annotate_max_min_points(self, subplot, x_data, ya, yb, yc):
- y_arrays = [ya, yb, yc]
- dark_colors = ['darkblue', 'darkgreen', 'darkred']
- for index, y_array in enumerate(y_arrays):
- max_y_value = numpy.amax(y_array)
- subplot.text(
- x=mdates.date2num(x_data[numpy.argmax(y_array)]),
- y=float(max_y_value) + .05,
- s='Máx',
- color=dark_colors[index],
- )
- subplot.scatter(
- x=mdates.date2num(x_data[numpy.argmax(y_array)]),
- y=float(max_y_value),
- marker='o',
- color=dark_colors[index]
- )
- min_y_value = numpy.amin(y_array)
- subplot.text(
- x=mdates.date2num(x_data[numpy.argmin(y_array)]),
- y=float(min_y_value) - .05,
- s='Mín',
- color=dark_colors[index],
- )
- subplot.scatter(
- x=mdates.date2num(x_data[numpy.argmin(y_array)]),
- y=float(min_y_value),
- marker='o',
- color=dark_colors[index]
- )
- def draw_squares_covering_missing_data(
- self, subplot, dict_square_indexes, x_data, ya, yb, yc
- ):
- if dict_square_indexes:
- y_min = numpy.concatenate([ya, yb, yc]).min()
- y_max = numpy.concatenate([ya, yb, yc]).max()
- width = (mdates.date2num(x_data[1]) - mdates.date2num(x_data[0]))
- height = y_max - y_min
- for index, value in dict_square_indexes.items():
- x_square = mdates.date2num(x_data[index])
- dx = width / 8.0
- dy = height / 4
- subplot.add_patch(
- mpatches.Rectangle(
- (x_square - dx, y_min - dy), # x, y
- width * max(value - 1, 0) + 2 * dx, # width
- height + 2 * dy, # heigth
- alpha=0.2,
- facecolor="yellow",
- # linewidth=0,
- )
- )
- class GraphDataManager(object):
- def get_graph_data(
- self, transductor, measurement_type, initial_date, final_date
- ):
- # QuerySet With All Measurements Of Specific Period
- qs = transductor.energymeasurements_set.filter(
- collection_date__range=(initial_date, final_date)
- )
- if qs.count() <= 1:
- raise NoMeasurementsFoundException(
- _('Nenhuma medição encontrada')
- )
- initial_date_copy = initial_date
- x_data = []
- y_data = []
- dict_square_indexes = {}
- time_diff = (final_date - initial_date)
- TWO_HOUR_SEC = 2 * 3600
- TWELVE_HOURS_SEC = 12 * 3600
- args = GraphUtils.get_three_phase_args(measurement_type)
- if time_diff.days <= 1:
- if time_diff.days is 0 and time_diff.seconds <= TWO_HOUR_SEC:
- # A 1 minute based graph will be made
- y_data, dict_square_indexes = self.filter_y_data(
- False, initial_date, final_date, qs, *args, minutes=1
- )
- x_data = pandas.date_range(
- initial_date_copy, periods=len(y_data), freq='T'
- )
- elif time_diff.days is 0 and time_diff.seconds <= TWELVE_HOURS_SEC:
- # A 5 minutes based graph will be made
- y_data, dict_square_indexes = self.filter_y_data(
- True, initial_date, final_date, qs, *args, minutes=5
- )
- x_data = pandas.date_range(
- initial_date_copy, periods=len(y_data), freq='5min'
- )
- else:
- # A 10 minutes based graph will be made
- y_data, dict_square_indexes = self.filter_y_data(
- True, initial_date, final_date, qs, *args, minutes=10
- )
- x_data = pandas.date_range(
- initial_date_copy, periods=len(y_data), freq='10min'
- )
- else:
- # A 1 hour based graph will be made
- y_data, dict_square_indexes = self.filter_y_data(
- True, initial_date, final_date, qs, *args, hours=1
- )
- x_data = pandas.date_range(
- initial_date_copy, periods=len(y_data), freq='H'
- )
- return [x_data, y_data, dict_square_indexes]
- def filter_y_data(
- self, range_date, initial_date, final_date, qs, *args, **kwargs
- ):
- # Calculating a constant for the 3 phases to fill Y data
- # in case of missing measurements between the dates.
- missing_a, missing_b, missing_c = numpy.array(qs.values_list(*args)) \
- .mean(axis=0).T
- y_data = []
- initial_date_copy = initial_date
- initial_index = 0
- initial_index_copy = initial_index
- dict_square_indexes = {}
- last_data_missing_value = False
- consecutive_missing_values = 1
- while initial_date_copy <= final_date:
- if range_date:
- data = qs.filter(
- collection_date__range=(
- initial_date_copy,
- initial_date_copy + relativedelta(**kwargs)
- )
- ).values_list(*args)
- else:
- data = qs.filter(
- collection_date__year=initial_date_copy.year,
- collection_date__month=initial_date_copy.month,
- collection_date__day=initial_date_copy.day,
- collection_date__minute=initial_date_copy.minute
- ).values_list(*args)
- if data:
- y_data.append(numpy.array(data).mean(axis=0))
- if last_data_missing_value:
- dict_square_indexes[
- initial_index_copy
- ] = consecutive_missing_values
- last_data_missing_value = False
- else:
- if last_data_missing_value:
- consecutive_missing_values += 1
- else:
- initial_index_copy = initial_index
- consecutive_missing_values = 1
- last_data_missing_value = True
- y_data.append(
- numpy.array(
- [
- missing_a,
- missing_b,
- missing_c
- ]
- )
- )
- initial_index += 1
- initial_date_copy += relativedelta(**kwargs)
- if last_data_missing_value:
- dict_square_indexes[
- initial_index_copy
- ] = consecutive_missing_values
- return [y_data, dict_square_indexes]
- class GraphUtils(object):
- @classmethod
- def get_graph_measurement_string(cls, measurement_type):
- measurement_string = ""
- if measurement_type == 'voltage':
- measurement_string = 'Tensão (V)'
- elif measurement_type == 'current':
- measurement_string = 'Corrente (A)'
- elif measurement_type == 'active_power':
- measurement_string = 'Potência Ativa (kW)'
- elif measurement_type == 'reactive_power':
- measurement_string = 'Potência Reativa (kVAr)'
- elif measurement_type == 'apparent_power':
- measurement_string = 'Potência Aparente (kVA)'
- return measurement_string
- @classmethod
- def get_three_phase_args(cls, measurement_type):
- args = []
- if measurement_type == 'voltage':
- args = ["voltage_a", "voltage_b", "voltage_c"]
- elif measurement_type == 'current':
- args = ["current_a", "current_b", "current_c"]
- elif measurement_type == 'active_power':
- args = ["active_power_a", "active_power_b", "active_power_c"]
- elif measurement_type == 'reactive_power':
- args = ["reactive_power_a", "reactive_power_b", "reactive_power_c"]
- elif measurement_type == 'apparent_power':
- args = ["apparent_power_a", "apparent_power_b", "apparent_power_c"]
- return args