diff --git a/demo/simple_test.py b/demo/simple_test.py index a1e9ff5..94fdc27 100755 --- a/demo/simple_test.py +++ b/demo/simple_test.py @@ -172,12 +172,14 @@ xy.title = "XY test" xy.render_to_file('out-xy.svg') pie = Pie(Config(style=NeonStyle)) -pie.add('test', [lnk(11, 'Foo'), {'value': 8, 'label': 'Few'}, 21]) -pie.add('test2', [lnk(29), None, 9]) -pie.add('test3', [24, 10, 32]) -pie.add('test4', [20, lnk(18), 9]) -pie.add('test5', [17, 5, 10]) -pie.add('test6', [None, None, 10]) +# pie.add('test', [lnk(11, 'Foo'), {'value': 8, 'label': 'Few'}, 21]) +# pie.add('test2', [lnk(29), None, 9]) +# pie.add('test3', [24, 10, 32]) +# pie.add('test4', [20, lnk(18), 9]) +# pie.add('test5', [17, 5, 10]) +# pie.add('test6', [None, None, 10]) +for i in range(30): + pie.add(str(i) + '!' * i, [i, 30 - i]) # pie.included_js = [] # pie.external_js = [ diff --git a/pygal/config.py b/pygal/config.py index 5c43c85..13f7c9f 100644 --- a/pygal/config.py +++ b/pygal/config.py @@ -60,7 +60,7 @@ class Config(object): #: Set to false to remove legend show_legend = True #: Set to true to position legend at bottom - legend_at_bottom = True + legend_at_bottom = False #: Set to false to remove dots show_dots = True #: Size of legend boxes @@ -106,8 +106,8 @@ class Config(object): disable_xml_declaration = False #: Write width and height attributes explicit_size = False - #: Legend string length truncation threshold - truncate_legend = 15 + #: Legend string length truncation threshold (None = auto) + truncate_legend = None #: Label string length truncation threshold (None = auto) truncate_label = None diff --git a/pygal/graph/base.py b/pygal/graph/base.py index f7e3619..c53b8d6 100644 --- a/pygal/graph/base.py +++ b/pygal/graph/base.py @@ -30,7 +30,7 @@ from pygal.util import ( from pygal.svg import Svg from pygal.config import Config from pygal.util import cached_property -from math import sin, cos +from math import sin, cos, sqrt class BaseGraph(object): @@ -86,10 +86,13 @@ class BaseGraph(object): """Compute graph margins from set texts""" if self.show_legend: h, w = get_texts_box( - map(lambda x: truncate(x, self.truncate_legend), + map(lambda x: truncate(x, self.truncate_legend or 15), cut(self.series, 'title')), self.legend_font_size) - self.margin.right += 10 + w + self.legend_box_size + if self.legend_at_bottom: + self.margin.bottom += 10 + h * int(sqrt(len(self.series))) + else: + self.margin.right += 10 + w + self.legend_box_size if self.title: h, w = get_text_box(self.title, self.title_font_size) @@ -98,12 +101,15 @@ class BaseGraph(object): if self._x_labels: h, w = get_texts_box( cut(self._x_labels), self.label_font_size) - self.margin.bottom += 10 + max( + self._x_labels_height = 10 + max( w * sin(rad(self.x_label_rotation)), h) + self.margin.bottom += self._x_labels_height if self.x_label_rotation: self.margin.right = max( w * cos(rad(self.x_label_rotation)), self.margin.right) + else: + self._x_labels_height = 0 if self._y_labels: h, w = get_texts_box( cut(self._y_labels), self.label_font_size) diff --git a/pygal/graph/graph.py b/pygal/graph/graph.py index 4937fd0..001aaac 100644 --- a/pygal/graph/graph.py +++ b/pygal/graph/graph.py @@ -26,7 +26,7 @@ from pygal.interpolate import interpolation from pygal.graph.base import BaseGraph from pygal.view import View, LogView from pygal.util import is_major, truncate, reverse_text_len -from math import isnan, pi +from math import isnan, pi, sqrt, floor, ceil class Graph(BaseGraph): @@ -181,29 +181,50 @@ class Graph(BaseGraph): """Make the legend box""" if not self.show_legend: return + truncation = self.truncate_legend + if self.legend_at_bottom: + x = self.margin.left + 10 + y = (self.margin.top + self.view.height + + self._x_labels_height + 10) + cols = ceil(sqrt(len(self.series))) + if not truncation: + available_space = self.width / cols - ( + self.legend_box_size + 5) + truncation = int(reverse_text_len( + available_space, self.legend_font_size)) + else: + x = self.margin.left + self.view.width + 10 + y = self.margin.top + 10 + cols = 1 + if not truncation: + truncation = 15 + legends = self.svg.node( self.nodes['graph'], class_='legends', - transform='translate(%d, %d)' % ( - self.margin.left + self.view.width + 10, - self.margin.top + 10)) + transform='translate(%d, %d)' % (x, y)) + + x_step = self.width / cols for i, title in enumerate(self._legends): + col = i % cols + row = i // cols + legend = self.svg.node( legends, class_='legend reactive activate-serie', id="activate-serie-%d" % i) self.svg.node( legend, 'rect', - x=0, - y=1.5 * i * self.legend_box_size, + x=col * x_step, + y=1.5 * row * self.legend_box_size, width=self.legend_box_size, height=self.legend_box_size, - class_="color-%d reactive" % i + class_="color-%d reactive" % (i % 16) ) - truncated = truncate(title, self.truncate_legend) + truncated = truncate(title, truncation) # Serious magical numbers here self.svg.node( legend, 'text', - x=self.legend_box_size + 5, - y=1.5 * i * self.legend_box_size + x=col * x_step + self.legend_box_size + 5, + y=1.5 * row * self.legend_box_size + .5 * self.legend_box_size + .3 * self.legend_font_size ).text = truncated @@ -224,13 +245,13 @@ class Graph(BaseGraph): return dict( plot=self.svg.node( self.nodes['plot'], - class_='series serie-%d color-%d' % (serie, serie)), + class_='series serie-%d color-%d' % (serie, serie % 16)), overlay=self.svg.node( self.nodes['overlay'], - class_='series serie-%d color-%d' % (serie, serie)), + class_='series serie-%d color-%d' % (serie, serie % 16)), text_overlay=self.svg.node( self.nodes['text_overlay'], - class_='series serie-%d color-%d' % (serie, serie))) + class_='series serie-%d color-%d' % (serie, serie % 16))) def _interpolate(self, ys, xs, polar=False, xy=False, xy_xmin=None, xy_rng=None): diff --git a/pygal/util.py b/pygal/util.py index 0581df4..2c27c56 100644 --- a/pygal/util.py +++ b/pygal/util.py @@ -217,7 +217,7 @@ def cycle_fill(short_list, max_len): """Fill a list to max_len using a cycle of it""" short_list = list(short_list) list_cycle = cycle(short_list) - while len(short_list) < 16: + while len(short_list) < max_len: short_list.append(list_cycle.next()) return short_list