From e26f9319cccab6cbfd1debe1b148ee7fce57417c Mon Sep 17 00:00:00 2001
From: Tom Keffer
Having made a career in C++ and Java, I was also interested in some more modern languages, so I thought I'd try either Python or Ruby (although, truth be told, the roots of Python are nearly as old as C++!). I picked Python because its libraries @@ -425,19 +424,19 @@ the setup.py script does include special provisions fo updating your configuration file weewx.conf, which can be handy when upgrading to a later version.
This section covers configuring your archive and statistical database (if -necessary; this step is required only if you are moving from -wview to weewx), configuring your -weather station, and configuring the configuration file -weewx.conf.
+This section covers configuring your archive and statistical database (if necessary; +this step is required only if you are moving from +wview to weewx), configuring your weather +station, and configuring the configuration file weewx.conf.
In the following, $WEEWX_ROOT refers to the -weewx root directory, generally /home/weewx.
+weewx root directory, generally /home/weewx. +This section is necessary only if you are moving from wview to weewx and wish to transfer your old data over. If you are starting afresh, you do not -need to follow this section --- the two main databases are created and populated -automatically by weewx.
+need to follow this section — the two main databases are created and populated automatically +by weewx.Two databases are maintained by weewx:
Because wview and weewx use identical schema for the first of these (the -archive database), it can be just copied over. However, the second (the -statistical databases) are different --- the weewx statistical database must be -built manually and backfilled. This is done using the configuration -script configure.py.
-Here's a summary of how to transfer your wview data to -weewx.
+Because wview and weewx use identical schema for the first of these (the archive +database), it can be just copied over. However, the second (the statistical databases) +are different — the weewx statistical database must be built manually and backfilled. +This is done using the configuration script configure.py. +
+Here's a summary of how to transfer your wview data to weewx.
mkdir $WEEWX_ROOT/archive
cp /usr/local/var/wview/archive/wview-archive.sdb $WEEWX_ROOT/archive/weewx.sdb
$WEEWX_ROOT/bin/configure.py --create-stats $WEEWX_ROOT/weewx.conf
@@ -465,10 +463,9 @@ MB) of data (while wview was running in the background).
The time on the VP is automatically synchronized with the -weewx server every four hours. -However, you should run a NTP daemon on your server to insure that it is synchronized -with the correct time. Doing so will greatly reduce errors, especially if you send -data to services such as the Weather Underground.
+weewx server every four hours. However, you should run a NTP daemon on your +server to insure that it is synchronized with the correct time. Doing so will greatly +reduce errors, especially if you send data to services such as the Weather Underground.The archive interval is set in the main configuration file $WEEWX_ROOT/weewx.conf. Look for the entry archive_interval @@ -683,17 +680,30 @@ this is each of the SQL types to be included in the image. Values specified in t level above can be overridden. For example, here's a typical set of options for sub-sub-section [[[monthrain]]]:
[[[monthrain]]]
- [[[[rain]]]]
plot_type = bar
- aggregate_type = sum
- aggregate_interval = 86400
- label = Rain (daily avg)
This will generate an image file with name monthrain.png. -In it will be a bar plot showing SQL type 'rain'. The aggregation type will be summing -(overriding the averaging specified in sub-section [[month_images]], +It will be a bar plot. Option yscale controls the y-axis +scaling — if left out, the scale will automatically be chosen. However, in this +example we are choosing to exercise some degree of control by specifying values +explicitly. It is a 3-way tuple (ylow, +yhigh, min_interval), where +ymin and ymax are the minimum +and maximum y-axis values, respectively, and min_interval +is the minimum tick interval. If set to 'None', the corresponding +value will be automatically chosen. So, in this example, we are letting +weewx pick sensible y minimum and maximum values, but +we are requiring that the tick increment (min_interval) +be at least 0.02. Continuing on in the example, there will be only one SQL type +plotted, rain. The aggregation type will be summing (overriding +the averaging specified in sub-section [[month_images]], so you get the total rain over the aggregate period rather than the average) over -an aggregation interval of 86,400 seconds (one day). It will also include the indicated -label.
+an aggregation interval of 86,400 seconds (one day). The plot line will be titled +with the indicated label ('Rain (daily avg)')More than one SQL type can be included in a plot. For example, here's how to generate a plot with the week's outside temperature as well as dewpoint:
[[[monthtempdew]]]
diff --git a/weeplot/genplot.py b/weeplot/genplot.py
index 32026d89..5d52d5b6 100644
--- a/weeplot/genplot.py
+++ b/weeplot/genplot.py
@@ -296,28 +296,27 @@ class GeneralPlot(object):
"""Calculates y scaling. Can be used 'as-is' for most purposes.
"""
- if self.yscale is None:
- # The filter is necessary because unfortunately the value 'None' is not
- # excluded from min and max (i.e., min(None, x) is not necessarily x).
- # Plus, min of an empty list throws a ValueError exception.
- ymin = ymax = None
- for line in self.line_list:
- try :
- yline_min = min(filter(lambda v : v is not None, line.y))
- ymin = min(yline_min, ymin) if ymin is not None else yline_min
- except ValueError:
- pass
- try :
- yline_max = max(filter(lambda v : v is not None, line.y))
- ymax = max(yline_max, ymax) if ymax is not None else yline_max
- except ValueError:
- pass
-
- if ymin is None and ymax is None :
- # No valid data. Pick an arbitrary scaling
- self.yscale=(0.0, 1.0, 0.2)
- else:
- self.yscale = weeplot.utilities.scale(ymin, ymax)
+ # The filter is necessary because unfortunately the value 'None' is not
+ # excluded from min and max (i.e., min(None, x) is not necessarily x).
+ # Plus, min of an empty list throws a ValueError exception.
+ ymin = ymax = None
+ for line in self.line_list:
+ try :
+ yline_min = min(filter(lambda v : v is not None, line.y))
+ ymin = min(yline_min, ymin) if ymin is not None else yline_min
+ except ValueError:
+ pass
+ try :
+ yline_max = max(filter(lambda v : v is not None, line.y))
+ ymax = max(yline_max, ymax) if ymax is not None else yline_max
+ except ValueError:
+ pass
+
+ if ymin is None and ymax is None :
+ # No valid data. Pick an arbitrary scaling
+ self.yscale=(0.0, 1.0, 0.2)
+ else:
+ self.yscale = weeplot.utilities.scale(ymin, ymax, self.yscale)
def _calcXLabelFormat(self):
if self.x_label_format is None:
diff --git a/weeplot/utilities.py b/weeplot/utilities.py
index 7b5d06de..932f1f7d 100644
--- a/weeplot/utilities.py
+++ b/weeplot/utilities.py
@@ -16,25 +16,31 @@ import math
import weeplot
-def scale(fmn, fmx, nsteps = 10, prescale = None):
+def scale(fmn, fmx, prescale = (None, None, None), nsteps = 10):
"""Calculates an appropriate min, max, and step size for scaling axes on a plot.
+ The origin (zero) is guaranteed to be on an interval boundary.
+
fmn: The minimum data value
fmx: The maximum data value. Must be greater than or equal to fmn.
- nsteps: The nominal number of desired steps. Default = 10
+ prescale: A 3-way tuple. A non-None min or max value (positions 0 and 1,
+ respectively) will be fixed to that value. A non-None interval (position 2)
+ be at least as big as that value. Default = (None, None, None)
- prescale: One or more of the results may be preset. [optional]
+ nsteps: The nominal number of desired steps. Default = 10
Returns: a three-way tuple. First value is the lowest scale value, second the highest.
The third value is the step (increment) between them.
"""
- minscale = maxscale = interval = None
+
+ (minscale, maxscale, min_interval) = prescale
+ # Make sure fmn and fmx are float values, in case a user passed
+ # in integers:
+ fmn = float(fmn)
+ fmx = float(fmx)
- if prescale is not None :
- (minscale, maxscale, interval) = prescale
-
if fmx < fmn :
raise weeplot.ViolatedPrecondition, "scale() called with max value less than min value"
@@ -44,31 +50,58 @@ def scale(fmn, fmx, nsteps = 10, prescale = None):
else :
fmx = fmn + .01*abs(fmn)
- if interval is None:
- range = float(fmx) - float(fmn)
- steps = range / nsteps
-
- mag = math.floor(math.log10(steps))
- magPow = math.pow(10.0, mag)
- magMsd = math.floor(steps/magPow + 0.5)
-
- if magMsd > 5.0:
- magMsd = 10.0
- elif magMsd > 2.0:
- magMsd = 5.0
- elif magMsd > 1.0:
- magMsd = 2
+ range = fmx - fmn
+ steps = range / nsteps
- interval = magMsd * magPow
+ mag = math.floor(math.log10(steps))
+ magPow = math.pow(10.0, mag)
+ magMsd = math.floor(steps/magPow + 0.5)
- if minscale is None:
- Nmin = math.floor(fmn / interval)
- minscale = Nmin * interval
+ if magMsd > 5.0:
+ magMsd = 10.0
+ elif magMsd > 2.0:
+ magMsd = 5.0
+ elif magMsd > 1.0:
+ magMsd = 2
- if maxscale is None:
- Nmax = math.ceil(fmx / interval)
- maxscale = Nmax * interval
+ # This will be the nominal interval size
+ interval = magMsd * magPow
+ # Test it against the desired minimum, if any
+ if min_interval is None or interval > min_interval:
+ # Either no min interval was specified, or its safely
+ # less than the chosen interval.
+ if minscale is None:
+ minscale = interval * math.floor(fmn / interval)
+
+ if maxscale is None:
+ maxscale = interval * math.ceil(fmx / interval)
+
+ else:
+
+ # The request for a minimum interval has kicked in.
+ # Sometimes this can make for a plot with just one or
+ # two intervals in it. Adjust the min and max values
+ # to get a nice plot
+ interval = min_interval
+
+ if minscale is None:
+ if maxscale is None:
+ # Both can float. Pick values so the range is near the bottom
+ # of the scale:
+ minscale = interval * math.floor(fmn / interval)
+ maxscale = minscale + interval * nsteps
+ else:
+ # Only minscale can float
+ minscale = maxscale - interval * nsteps
+ else:
+ if maxscale is None:
+ # Only maxscale can float
+ maxscale = minscale + interval * nsteps
+ else:
+ # Both are fixed --- nothing to be done
+ pass
+
return (minscale, maxscale, interval)
@@ -273,6 +306,7 @@ if __name__ == '__main__' :
assert(scale(1.1, 12.3) == (1.0, 13.0, 1.0))
assert(scale(-1.1, 12.3) == (-2.0, 13.0, 1.0))
assert(scale(-12.1, -5.3) == (-13.0, -5.0, 1.0))
+ assert(scale(0.0, 0.05, (None, None, .1), 10) == (0.0, 1.0, 0.1))
t= time.time()
scaletime(t - 24*3600 - 20, t)
diff --git a/weeutil/weeutil.py b/weeutil/weeutil.py
index 874d63ba..c57cb045 100644
--- a/weeutil/weeutil.py
+++ b/weeutil/weeutil.py
@@ -81,9 +81,15 @@ def get_font_handle(fontpath, *args):
return font
+def convertToFloat(seq):
+ """Convert a sequence with strings to floats, honoring 'Nones'"""
+
+ if seq is None: return None
+ res = [None if s in ('None', 'none') else float(s) for s in seq]
+ return res
-def accumulatescalars(d):
- """Merges scalar options above a ConfigObj section with itself, accumulating the results.
+def accumulateLeaves(d):
+ """Merges leaf options above a ConfigObj section with itself, accumulating the results.
This routine is useful for specifying defaults near the root node,
then having them overridden in the leaf nodes of a ConfigObj.
@@ -97,7 +103,7 @@ def accumulatescalars(d):
cum_dict = configobj.ConfigObj()
else :
# Otherwise, recursively accumulate scalars above me
- cum_dict = accumulatescalars(d.parent)
+ cum_dict = accumulateLeaves(d.parent)
# Now merge my scalars into the results:
merge_dict = {}
diff --git a/weewx.conf b/weewx.conf
index f5279bce..2a6aec1e 100644
--- a/weewx.conf
+++ b/weewx.conf
@@ -261,6 +261,12 @@ socket_timeout = 20
aggregate_type = none
width = 1
time_length = 86400 # == 24 hours
+ # The following merits an explanation. The y-axis scale used for plotting
+ # can be controlled using option 'yscale'. It is a 3-way tuple, with
+ # values (ylow, yhigh, min_interval). If set to "None", a parameter is
+ # set automatically, otherwise the value is used. However, in the case of
+ # min_interval, what is set is the *minimum* y-axis tick interval.
+ yscale = None, None, None
[[day_images]]
x_label_format = %H:%M
@@ -279,16 +285,20 @@ socket_timeout = 20
[[[[heatindex]]]]
[[[dayrain]]]
+ # Make sure the y-axis increment is at least 0.02 for the rain plot:
+ yscale = None, None, 0.02
+ plot_type = bar
[[[[rain]]]]
- plot_type = bar
aggregate_type = sum
aggregate_interval = 3600
label = Rain (hourly avg)
+
[[[dayrx]]]
[[[[rxCheckPercent]]]]
[[[daypond]]]
+ yscale = None, None, 0.5
[[[[extraTemp1]]]]
[[[daywind]]]
@@ -300,6 +310,8 @@ socket_timeout = 20
[[[[inHumidity]]]]
[[[daywinddir]]]
+ # Hardwire in the y-axis scale for wind direction:
+ yscale = 0.0, 360.0, 60.0
[[[[windDir]]]]
[[week_images]]
@@ -321,13 +333,15 @@ socket_timeout = 20
[[[[heatindex]]]]
[[[weekrain]]]
+ yscale = None, None, 0.02
+ plot_type = bar
[[[[rain]]]]
- plot_type = bar
aggregate_type = sum
aggregate_interval = 86400
label = Rain (daily avg)
[[[weekpond]]]
+ yscale = None, None, 0.5
[[[[extraTemp1]]]]
[[[weekrx]]]
@@ -343,6 +357,7 @@ socket_timeout = 20
[[[[inHumidity]]]]
[[[weekwinddir]]]
+ yscale = 0.0, 360.0, 60.0
[[[[windDir]]]]
[[month_images]]
@@ -364,13 +379,15 @@ socket_timeout = 20
[[[[heatindex]]]]
[[[monthrain]]]
+ yscale = None, None, 0.02
+ plot_type = bar
[[[[rain]]]]
- plot_type = bar
aggregate_type = sum
aggregate_interval = 86400
label = Rain (daily avg)
[[[monthpond]]]
+ yscale = None, None, 0.5
[[[[extraTemp1]]]]
[[[monthrx]]]
@@ -386,6 +403,7 @@ socket_timeout = 20
[[[[inHumidity]]]]
[[[monthwinddir]]]
+ yscale = 0.0, 360.0, 60.0
[[[[windDir]]]]
[[year_images]]
@@ -413,14 +431,16 @@ socket_timeout = 20
[[[[heatindex]]]]
[[[yearrain]]]
+ yscale = None, None, 0.02
+ plot_type = bar
[[[[rain]]]]
- plot_type = bar
aggregate_type = sum
# aggregate_interval = 2629800 # Magic number: the length of a nominal month
aggregate_interval = 604800 # == 1 week
label = Rain (weekly avg)
[[[yearpond]]]
+ yscale = None, None, 0.5
[[[[extraTemp1]]]]
[[[yearrx]]]
@@ -431,6 +451,7 @@ socket_timeout = 20
[[[[inHumidity]]]]
[[[yearwinddir]]]
+ yscale = 0.0, 360.0, 60.0
[[[[windDir]]]]
diff --git a/weewx/__init__.py b/weewx/__init__.py
index 153f31fa..a61ead42 100644
--- a/weewx/__init__.py
+++ b/weewx/__init__.py
@@ -12,7 +12,7 @@
"""
import time
-__version__="1.2.0a1"
+__version__="1.2.0a2"
# Holds the program launch time in unix epoch seconds:
# Useful for calculating 'uptime.'
diff --git a/weewx/genimages.py b/weewx/genimages.py
index bb455049..197e98d6 100644
--- a/weewx/genimages.py
+++ b/weewx/genimages.py
@@ -60,10 +60,13 @@ class GenImages(object):
for plotname in self.image_dict[timespan].sections :
# Accumulate all options from parent nodes:
- plot_options = weeutil.weeutil.accumulatescalars(self.image_dict[timespan][plotname])
+ plot_options = weeutil.weeutil.accumulateLeaves(self.image_dict[timespan][plotname])
# Get the name of the file that the image is going to be saved to:
img_file = os.path.join(self.image_root, '%s.png' % plotname)
+
+ if plotname == 'dayrain':
+ pass
# Check whether this plot needs to be done:
s = plot_options.get('aggregate_interval')
@@ -81,7 +84,7 @@ class GenImages(object):
# Loop over each line type ('outTemp', 'rain', etc.) within the plot.
for line_type in line_type_list:
# Accumulate options from parent nodes.
- line_options[line_type] = weeutil.weeutil.accumulatescalars(self.image_dict[timespan][plotname][line_type])
+ line_options[line_type] = weeutil.weeutil.accumulateLeaves(self.image_dict[timespan][plotname][line_type])
# Look for aggregation type:
aggregate_type = line_options[line_type].get('aggregate_type')
@@ -108,6 +111,7 @@ class GenImages(object):
# Set the min, max time axis here.
plot.setXScaling((minstamp, maxstamp, timeinc))
+ plot.setYScaling(weeutil.weeutil.convertToFloat(plot_options.get('yscale')))
# Get a suitable bottom label:
bottom_label_format = plot_options.get('bottom_label_format', '%m/%d/%y %H:%M')