BasePlot   A
last analyzed

Complexity

Total Complexity 19

Size/Duplication

Total Lines 118
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 19
c 2
b 0
f 0
dl 0
loc 118
rs 10

7 Methods

Rating   Name   Duplication   Size   Complexity  
A matplot() 0 7 1
A _setaxislimits() 0 16 4
A jpg() 0 12 2
B _axisfigure() 0 27 3
A data() 0 20 4
A startend() 0 13 3
A png() 0 13 2
1
"""
2
Ways that a plot of a selected sensor can be displayed
3
"""
4
5
from flask import make_response, request
6
import datetime
7
try:
8
    from StringIO import StringIO
9
except ImportError:
10
    from io import BytesIO
11
12
from .blueprint import main
13
from ..models import Gage, Sensor, Sample, Correlation, River, Section
14
15
#import PyQt5
16
import matplotlib
17
matplotlib.use('Cairo', force=True)
18
from matplotlib.backends.backend_cairo import FigureCanvasCairo as FigureCanvas
19
20
class BasePlot(object):
21
    """
22
    Base class for all plots
23
    """
24
    def data(self):
25
        """
26
        Returns sensor data
27
28
        Defaults to data within last seven days
29
        """
30
        start, end = self.startend()
31
        if start and end:
32
            return Sample.query.filter(start < Sample.datetime,
33
                                       Sample.datetime < end,
34
                                       Sample.sensor_id == self.sid)\
35
                               .order_by(Sample.datetime)
36
        if start:
37
            return Sample.query.filter(start < Sample.datetime,
38
                                       Sample.sensor_id == self.sid)\
39
                               .order_by(Sample.datetime)
40
        seven_ago = datetime.datetime.utcnow() - datetime.timedelta(days=7)
41
        return Sample.query.filter(Sample.datetime > seven_ago,
42
                                   Sample.sensor_id == self.sid)\
43
                           .order_by(Sample.datetime)
44
45
    @staticmethod
46
    def startend():
47
        """
48
        Return datetime objects if start and end arguments are in url.
49
        Otherwise return None.
50
        """
51
        start = request.args.get('start')
52
        end = request.args.get('end')
53
        if start:
54
            start = datetime.datetime.strptime(start, '%Y%m%d')
55
        if end:
56
            end = datetime.datetime.strptime(end, '%Y%m%d')
57
        return (start, end)
58
59
    def _setaxislimits(self, axis, ymin, ymax):
60
        """
61
        Set limits for y axis. If not set on sensor, then use a buffer of 10%
62
        """
63
        if ymin == ymax:
64
            ybuff = 0.1*ymin
65
        else:
66
            ybuff = 0.1*(ymax-ymin)
67
        if self.sensor.minimum:
68
            axis.set_ylim(ymin=self.sensor.minimum)
69
        else:
70
            axis.set_ylim(ymin=ymin-ybuff)
71
        if self.sensor.maximum:
72
            axis.set_ylim(ymax=self.sensor.maximum)
73
        else:
74
            axis.set_ylim(ymax=ymax+ybuff)
75
76
    def png(self):
77
        """
78
        Returns a StringIO PNG plot for the sensor
79
        """
80
        
81
        fig = self.matplot()
82
        canvas = FigureCanvas(fig)
83
        try:
84
            png_output = StringIO()
85
        except NameError:
86
            png_output = BytesIO()
87
        canvas.print_png(png_output)
88
        return png_output.getvalue()
89
90
    def jpg(self):
91
        """
92
        Returns a StringIO JPG plot for the sensor
93
        """
94
        fig = self.matplot()
95
        canvas = FigureCanvas(fig)
96
        try:
97
            jpg_output = StringIO()
98
        except NameError:
99
            jpg_output = BytesIO()
100
        canvas.print_jpg(jpg_output)
101
        return jpg_output.getvalue()
102
103
    def _axisfigure(self):
104
        """
105
        Returns axis and figure
106
        """
107
        #import matplotlib
108
        #matplotlib.use('Qt5Agg')
109
        import PyQt5
110
        import matplotlib
111
        matplotlib.use('Qt5Agg', force=True)
112
        from matplotlib.figure import Figure
113
        import seaborn as sns
114
        sns.set()
115
        data = self.data()
116
        fig = Figure()
117
        ax = fig.add_subplot(1, 1, 1)
118
        x = []
119
        y = []
120
        for sample in data:
121
            x.append(sample.datetime)
122
            y.append(sample.value)
123
        ax.plot(x, y, '-')
124
        self._setaxislimits(ax, min(y), max(y))
125
        if self.sensor.name:
126
            ax.set_title('{0} - {1}'.format(self.sensor.gage.name, self.sensor.name))
127
        else:
128
            ax.set_title('{0} - {1}'.format(self.sensor.gage.name, self.sensor.stype.capitalize()))
129
        return ax, fig
130
131
    def matplot(self):
132
        """
133
        Returns a matplotlib figure for building into a plot
134
        """
135
        
136
        ax, fig = self._axisfigure()
137
        return fig
138
139
140
class SensorPlot(BasePlot):
141
    """
142
    Plot class for Sensors
143
144
    Arguments:
145
        gid (int): Gage.id
146
        stype (string): sensor type for gage
147
148
    Currently supports matplotlib, but designed to be adaptable to support bokeh
149
    or others
150
151
    If ?start=YYYYMMDD(&end=YYYYMMDD) argument, then the plot will use those
152
    dates instead of the default 7 days.
153
    """
154
    def __init__(self, gid, stype):
155
        self.gid = gid
156
        self.stype = stype.lower()
157
        self.sensor = Sensor.query.filter_by(gage_id=self.gid).filter_by(stype=self.stype).first_or_404()
158
        self.sid = self.sensor.id
159
160
161
class CorrelationPlot(BasePlot):
162
    """
163
    Plot class for correlations
164
    """
165
    def __init__(self, section_id, sensor_id):
166
        self.section_id = section_id
167
        self.correlation = Correlation.query.filter_by(section_id=section_id)\
168
                                            .filter_by(sensor_id=sensor_id)\
169
                                            .first_or_404()
170
        self.sid = sensor_id
171
        self.sensor = self.correlation.sensor
172
173
    def levels(self):
174
        """
175
        Return the correlated levels
176
        """
177
        return (self.correlation.minimum,
178
                self.correlation.low,
179
                self.correlation.medium,
180
                self.correlation.high,
181
                self.correlation.huge)
182
183
    def matplot(self):
184
        ax, fig = self._axisfigure()
185
        lmin, llow, lmed, lhig, lhug = self.levels()
186
        if lmin:
187
            ax.axhline(y=lmin, color='#ffffff', lw=8, ls='dashed', zorder=1)
188
        if llow:
189
            ax.axhline(y=llow, color='#fcf8e3', lw=8, ls='dashed', zorder=1)
190
        if lmed:
191
            ax.axhline(y=lmed, color='#dff0d8', lw=8, ls='dashed', zorder=1)
192
        if lhig:
193
            ax.axhline(y=lhig, color='#d9edf7', lw=8, ls='dashed', zorder=1)
194
        if lhug:
195
            ax.axhline(y=lhug, color='#f2dede', lw=8, ls='dashed', zorder=1)
196
        return fig
197
198
199
@main.route('/gage/<int:gid>/<stype>.png')
200
@main.route('/gage/<slug>/<stype>.png')
201
def gagesensorplot(stype, gid=None, slug=None):
202
    """**/gage/<id>/<sensor type>.png**
203
204
    Draw a PNG plot for the requested gage's sensor
205
    """
206
    if slug:
207
        gid = Gage.query.filter_by(slug=slug).first_or_404().id
208
    response = make_response(SensorPlot(gid, stype).png())
209
    response.headers['Content-Type'] = 'image/png'
210
    return response
211
212
213
@main.route('/gage/<int:gid>/<stype>.jpg')
214
@main.route('/gage/<int:gid>/<stype>.jpeg')
215
@main.route('/gage/<slug>/stype.jpeg')
216
@main.route('/gage/<slug>/stype.jpg')
217
def gagesensorplotjpg(stype, gid=None, slug=None):
218
    """**/gage/<id>/<sensor type>.jpg**
219
    **/gage/<id>/<sensor type>.jpeg**
220
221
    Draw a JPEG plot for the requested gage's sensor
222
    """
223
    if slug:
224
        gid = Gage.query.filter_by(slug=slug).first_or_404().id
225
    response = make_response(SensorPlot(gid, stype).jpg())
226
    response.headers['Content-Type'] = 'image/jpeg'
227
    return response
228
229
230
@main.route('/river/<river>/<section>/<gage>/<stype>.png')
231
def correlationplotpng(stype, gage, section, river):
232
    correlation = Correlation.query.join(Correlation.sensor)\
233
                                   .join(Sensor.gage)\
234
                                   .join(Correlation.section)\
235
                                   .join(Section.river)\
236
                                   .filter(River.slug==river,
237
                                           Section.slug==section,
238
                                           Sensor.stype==stype,
239
                                           Gage.slug==gage)\
240
                                   .first_or_404()
241
    print(correlation)
242
    response = make_response(CorrelationPlot(correlation.section.id, correlation.sensor.id).png())
243
    response.headers['Content-Type'] = 'image/png'
244
    return response
245