basechart.py 7.19 KB
Newer Older
1 2 3 4
"""
This module holds classes that create the HTML and supply the data for Charts and
Reports.
"""
5 6 7 8 9
import time, logging, copy, importlib
import bmsapp.models, bmsapp.global_vars, bmsapp.readingdb.bmsdata
import bmsapp.calcs.transforms, bmsapp.schedule
import bmsapp.view_util, bmsapp.data_util
import chart_config
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

# Make a logger for this module
_logger = logging.getLogger('bms.' + __name__)


class BldgChartType:
    """Class to provide descriptive information about a particular type of chart related
    to one building.
    """

    def __init__(self, id, title, class_name):
        """Initializes the chart type.

            Args:
                id (int): ID number uniquely identifying this chart type.
                title (string): Text that will be displayed in select controls and chart titles.
                class_name (string): The name of the Python class in this charts.py used to create the chart.
        """
        self.id = id        
        self.title = title  
        self.class_name = class_name   

# These are the possible chart types currently implemented, in the order they will be 
# presented to the User.
BLDG_CHART_TYPES = [
35 36 37 38 39 40 41
    BldgChartType(0, 'Dashboard', 'dashboard.Dashboard'),
    BldgChartType(1, 'Current Sensor Values', 'currentvalues.CurrentValues'),
    BldgChartType(2, 'Plot Sensor Values over Time', 'timeseries.TimeSeries'),
    BldgChartType(3, 'Hourly Profile of a Sensor', 'hourlyprofile.HourlyProfile'),
    BldgChartType(4, 'Histogram of a Sensor', 'histogram.Histogram'),
    BldgChartType(5, 'Sensor X vs Y Scatter Plot', 'xyplot.XYplot'),
    BldgChartType(6, 'Download Sensor Data to Excel', 'exportdata.ExportData')
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
]

# The ID of the Time Series chart above, as it is needed in code below.
TIME_SERIES_CHART_ID = 2


def find_chart_type(chart_id):
    """Returns the BldgChartType for a given ID.

        Args:
            chart_id (int): The ID number of the requested chart type.

        Returns:
            The BldgChartType having the requested ID.  Returns None if no match.
    """
    for ch in BLDG_CHART_TYPES:
        if ch.id == chart_id:
            return ch

    return None

def get_chart_object(request_params):
    """Returns the appropriate chart object identified by the arguments.

        Args:
            request_params: The parameters (request.GET) passed in by the user further qualifying the chart.

        Returns:
            A chart object descending from BaseChart.
    """

    # Get building ID and chart ID from the request parameters
74 75
    bldg_id = bmsapp.view_util.to_int(request_params['select_bldg'])
    chart_id = bmsapp.view_util.to_int(request_params['select_chart'])
76 77

    if bldg_id=='multi':
78
        chart_info = bmsapp.models.MultiBuildingChart.objects.get(id=chart_id)
79 80 81 82 83
        class_name = chart_info.chart_type.class_name
    else:
        chart_info = find_chart_type(chart_id)
        class_name = chart_info.class_name

84 85 86 87 88
    # need to dynamically get a class object based on the class_name.
    # class_name is in the format <module name>.<class name>; break it apart.
    mod_name, bare_class_name = class_name.split('.')
    mod = importlib.import_module('bmsapp.reports.%s' % mod_name.strip())
    
89
    # get a reference to the class referred to by class_name
90
    chart_class = getattr(mod, bare_class_name.strip())
91 92 93 94 95 96 97 98 99 100 101

    # instantiate and return the chart from this class
    return chart_class(chart_info, bldg_id, request_params)


class BaseChart(object):
    """Base class for all of the chart classes.
    """

    def __init__(self, chart_info, bldg_id, request_params):
        """
102 103 104 105 106
        'chart_info' is the models.MultiBuildingChart object for the chart if it
        is a multi-building chart; for single-building charts, it is the BldgChartType
        object (the BldgChartType class is in this module).  'bldg_id'
        is the id of the building being charted, or 'multi' for multi-building
        charts. 'request_params' are the parameters
107 108 109 110 111
        passed in by the user through the Get http request.
        """
        self.chart_info = chart_info
        self.bldg_id = bldg_id

112 113 114
        # if this is a chart for a single building, get the associated building model object,
        # and the occupied schedule for the building if it is present.
        self.schedule = None
115
        if bldg_id != 'multi':
116 117 118
            self.building = bmsapp.models.Building.objects.get(id=bldg_id)
            if len(self.building.schedule.strip()):
                self.schedule = bmsapp.schedule.Schedule(self.building.schedule, self.building.timezone)
119 120 121 122 123 124

        self.request_params = request_params

        # for the multi-building chart object, take the keyword parameter string 
        # and convert it to a dictionary.
        if bldg_id == 'multi':
125 126 127 128 129
            self.chart_params = bmsapp.calcs.transforms.makeKeywordArgs(chart_info.parameters)

        # open the reading database and save it for use by the methods of this object.
        # It is closed automatically in the destructor of the BMSdata class.
        self.reading_db = bmsapp.readingdb.bmsdata.BMSdata(bmsapp.global_vars.DATA_DB_FILENAME)
130 131 132 133 134 135 136 137 138 139 140 141

    def get_ts_range(self):
        """
        Returns the start and stop timestamp as determined by the GET parameters that were posted
        from the "time_period" Select control.
        """
        tm_per = self.request_params['time_period']
        if tm_per != "custom":
            st_ts = int(time.time()) - int(tm_per) * 24 * 3600
            end_ts = time.time() + 3600.0    # adding an hour to be sure all records are caught
        else:
            st_date = self.request_params['start_date']
142
            st_ts = bmsapp.data_util.datestr_to_ts(st_date) if len(st_date) else 0
143
            end_date = self.request_params['end_date']
144
            end_ts = bmsapp.data_util.datestr_to_ts(end_date + " 23:59:59") if len(end_date) else time.time() + 3600.0
145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167

        return st_ts, end_ts

    def get_chart_options(self, chart_type='highcharts'):
        """
        Returns a configuration object for a Highcharts or Highstock chart.  Must make a
        copy of the original so that it is not modified.
        """
        opt = chart_config.highcharts_opt if chart_type=='highcharts' else chart_config.highstock_opt
        opt = copy.deepcopy(opt)
        if hasattr(self, 'building'):
            opt['title']['text'] = '%s: %s' % (self.chart_info.title, self.building.title)
        else:
            opt['title']['text'] = self.chart_info.title
        return opt

    def result(self):
        '''
        This method should be overridden to return a dictionary with an 
        'html' key holding the HTML of the results and an 'objects' key
        holding a list of JavaScript objects to create.  Each object is a
        two-tuple with the first element being the string identifying the
        object type and the second element being a configuration dictionary
168 169 170 171 172 173
        for that object type.  'bmsappX-Y.Z.js' must understand the string
        describing the JavaScript object.
        Alternatively, this method can return a django.http.HttpResponse
        object, which will be returned directly to the client application;
        this approach is used the exportdata.ExportData class to return an
        Excel spreadsheet.
174 175 176
        '''
        return {'html': self.__class__.__name__, 'objects': []}