diff --git a/pygal/config.py b/pygal/config.py index 5cea2371..2e70f0eb 100644 --- a/pygal/config.py +++ b/pygal/config.py @@ -320,6 +320,10 @@ class Config(CommonConfig): "ie: For hermite interpolation, you can set the cardinal tension with" "{'type': 'cardinal', 'c': .5}", int) + mode = Key( + None, str, "Value", "Sets the mode to be used. (Currently only supported on box plot)", + "May be %s" % ' or '.join(["1.5IQR", "extremes"])) + order_min = Key( None, int, "Value", "Minimum order of scale, defaults to None") diff --git a/pygal/graph/box.py b/pygal/graph/box.py index eec92fe7..5e41f13a 100644 --- a/pygal/graph/box.py +++ b/pygal/graph/box.py @@ -47,7 +47,10 @@ def _format(self): def format_maybe_quartile(x): if is_list_like(x): - return 'Q1: %s Q2: %s Q3: %s' % tuple(map(sup, x[1:4])) + if self.mode == "extremes": + return 'Min: %s Q1: %s Q2: %s Q3: %s Max: %s' % tuple(map(sup, x)) + else: + return 'Q1: %s Q2: %s Q3: %s' % tuple(map(sup, x[1:4])) else: return sup(x) return format_maybe_quartile @@ -58,7 +61,7 @@ def _compute(self): within the rendering process """ for serie in self.series: - serie.values = self._box_points(serie.values) + serie.values = self._box_points(serie.values, self.mode) if self._min: self._box.ymin = min(self._min, self.zero) @@ -160,10 +163,15 @@ def _draw_box(self, parent_node, quartiles, box_index): sum(quartiles) / len(quartiles))) @staticmethod - def _box_points(values): + def _box_points(values, mode='1.5IQR'): """ - Return a 5-tuple of Q1 - 1.5 * IQR, Q1, Median, Q3, + Default mode: (mode='1.5IQR' or unset) + Return a 5-tuple of Q1 - 1.5 * IQR, Q1, Median, Q3, and Q3 + 1.5 * IQR for a list of numeric values. + Extremes mode: (mode='extremes') + Return a 5-tuple of minimum, Q1, Median, Q3, + and maximum for a list of numeric values. + The iterator values may include None values. @@ -203,6 +211,10 @@ def median(seq): q3 = 0.25 * s[3*m+1] + 0.75 * s[3*m+2] iqr = q3 - q1 - q0 = q1 - 1.5 * iqr - q4 = q3 + 1.5 * iqr + if mode == 'extremes': + q0 = min(s) + q4 = max(s) + else: + q0 = q1 - 1.5 * iqr + q4 = q3 + 1.5 * iqr return q0, q1, q2, q3, q4 diff --git a/pygal/test/test_box.py b/pygal/test/test_box.py index 6cec21bc..492c3a3e 100644 --- a/pygal/test/test_box.py +++ b/pygal/test/test_box.py @@ -49,6 +49,35 @@ def test_quartiles(): assert q3 == 4 assert q4 == 4 +def test_quartiles_min_extremes(): + a = [-2.0, 3.0, 4.0, 5.0, 8.0] # odd test data + q0, q1, q2, q3, q4 = Box._box_points(a, mode='extremes') + + assert q1 == 7.0 / 4.0 + assert q2 == 4.0 + assert q3 == 23 / 4.0 + assert q0 == -2.0 # min + assert q4 == 8.0 # max + + b = [1.0, 4.0, 6.0, 8.0] # even test data + q0, q1, q2, q3, q4 = Box._box_points(b, mode='extremes') + + assert q2 == 5.0 + + c = [2.0, None, 4.0, 6.0, None] # odd with None elements + q0, q1, q2, q3, q4 = Box._box_points(c, mode='extremes') + + assert q2 == 4.0 + + d = [4] + q0, q1, q2, q3, q4 = Box._box_points(d, mode='extremes') + + assert q0 == 4 + assert q1 == 4 + assert q2 == 4 + assert q3 == 4 + assert q4 == 4 + def test_simple_box(): box = ghostedBox()