From 55caf8ce490fcc460e5b6803f6ec17011a91bdf5 Mon Sep 17 00:00:00 2001 From: Florian Mounier Date: Tue, 17 Feb 2015 17:51:34 +0100 Subject: [PATCH] Fix js options and 2.6 --- CHANGELOG | 2 +- pygal/graph/base.py | 27 ++++++++++++------- pygal/graph/gauge.py | 4 +-- pygal/graph/graph.py | 56 +++++++++++++++++++-------------------- pygal/graph/horizontal.py | 4 +-- pygal/graph/pie.py | 8 +++--- pygal/graph/radar.py | 4 +-- pygal/graph/treemap.py | 4 +-- pygal/state.py | 3 ++- pygal/svg.py | 10 +++---- pygal/table.py | 45 +++++++++++++++---------------- 11 files changed, 87 insertions(+), 80 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index e423c6d..5ce28d5 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,5 @@ V 2.0.0 UNRELEASED - Rework the ghost mechanism to come back to a more object oriented behavior (Maybe) + Rework the ghost mechanism to come back to a more object oriented behavior, storing all state in a state object which is created every render. V 1.7.0 Remove DateY and replace it by real XY datetime, date, time and timedelta support. (#188) diff --git a/pygal/graph/base.py b/pygal/graph/base.py index 2ac28a4..3f873ae 100644 --- a/pygal/graph/base.py +++ b/pygal/graph/base.py @@ -34,6 +34,7 @@ from pygal.adapters import ( not_zero, positive, decimal_to_float) from functools import reduce from uuid import uuid4 +import io class BaseGraph(object): @@ -86,8 +87,6 @@ class BaseGraph(object): def prepare_values(self, raw, offset=0): """Prepare the values to start with sane values""" from pygal import Worldmap, FrenchMapDepartments, Histogram - if self.x_labels is not None: - self.x_labels = list(map(to_unicode, self.x_labels)) if self.zero == 0 and isinstance( self, (Worldmap, FrenchMapDepartments)): self.zero = 1 @@ -169,17 +168,22 @@ class BaseGraph(object): values.append(value) serie_config = SerieConfig() - serie_config(**{k: v for k, v in self.state.__dict__.items() - if k in dir(serie_config)}) + serie_config(**dict((k, v) for k, v in self.state.__dict__.items() + if k in dir(serie_config))) serie_config(**serie_config_kwargs) series.append( Serie(offset + len(series), title, values, serie_config, metadata)) return series - def setup(self): + def setup(self, **kwargs): """Init the graph""" - self.state = State(self) + # Keep labels in case of map + if getattr(self, 'x_labels', None) is not None: + self.x_labels = list(map(to_unicode, self.x_labels)) + if getattr(self, 'y_labels', None) is not None: + self.y_labels = list(self.y_labels) + self.state = State(self, **kwargs) self.series = self.prepare_values( self.raw_series) or [] self.secondary_series = self.prepare_values( @@ -191,7 +195,7 @@ class BaseGraph(object): self._x_2nd_labels = None self._y_2nd_labels = None self.nodes = {} - self.margin = Margin( + self.margin_box = Margin( self.margin_top or self.margin, self.margin_right or self.margin, self.margin_bottom or self.margin, @@ -217,7 +221,7 @@ class BaseGraph(object): def render(self, is_unicode=False, **kwargs): """Render the graph, and return the svg string""" - self.setup() + self.setup(**kwargs) svg = self.svg.render( is_unicode=is_unicode, pretty_print=self.pretty_print) self.teardown() @@ -225,9 +229,11 @@ class BaseGraph(object): def render_tree(self): """Render the graph, and return (l)xml etree""" + self.setup() svg = self.svg.root for f in self.xml_filters: svg = f(svg) + self.teardown() return svg def render_table(self, **kwargs): @@ -264,7 +270,7 @@ class BaseGraph(object): def render_to_file(self, filename, **kwargs): """Render the graph, and write it to filename""" - with open(filename, 'w', encoding='utf-8') as f: + with io.open(filename, 'w', encoding='utf-8') as f: f.write(self.render(is_unicode=True, **kwargs)) def render_to_png(self, filename=None, dpi=72, **kwargs): @@ -312,7 +318,7 @@ class BaseGraph(object): explicit_size=True ) spark_options.update(kwargs) - return self.make_instance(spark_options).render() + return self.render(**spark_options) def _repr_svg_(self): """Display svg in IPython notebook""" @@ -320,3 +326,4 @@ class BaseGraph(object): def _repr_png_(self): """Display png in IPython notebook""" + return self.render_to_png() diff --git a/pygal/graph/gauge.py b/pygal/graph/gauge.py index 7afbf84..17d5c06 100644 --- a/pygal/graph/gauge.py +++ b/pygal/graph/gauge.py @@ -37,8 +37,8 @@ class Gauge(Graph): view_class = PolarThetaView self.view = view_class( - self.width - self.margin.x, - self.height - self.margin.y, + self.width - self.margin_box.x, + self.height - self.margin_box.y, self._box) def needle(self, serie): diff --git a/pygal/graph/graph.py b/pygal/graph/graph.py index b695b5c..4744331 100644 --- a/pygal/graph/graph.py +++ b/pygal/graph/graph.py @@ -63,8 +63,8 @@ class Graph(BaseGraph): view_class = View self.view = view_class( - self.width - self.margin.x, - self.height - self.margin.y, + self.width - self.margin_box.x, + self.height - self.margin_box.y, self._box) def _make_graph(self): @@ -81,7 +81,7 @@ class Graph(BaseGraph): self.nodes['plot'] = self.svg.node( self.nodes['graph'], class_="plot", transform="translate(%d, %d)" % ( - self.margin.left, self.margin.top)) + self.margin_box.left, self.margin_box.top)) self.svg.node(self.nodes['plot'], 'rect', class_='background', x=0, y=0, @@ -93,15 +93,15 @@ class Graph(BaseGraph): self.nodes['overlay'] = self.svg.node( self.nodes['graph'], class_="plot overlay", transform="translate(%d, %d)" % ( - self.margin.left, self.margin.top)) + self.margin_box.left, self.margin_box.top)) self.nodes['text_overlay'] = self.svg.node( self.nodes['graph'], class_="plot text-overlay", transform="translate(%d, %d)" % ( - self.margin.left, self.margin.top)) + self.margin_box.left, self.margin_box.top)) self.nodes['tooltip_overlay'] = self.svg.node( self.nodes['graph'], class_="plot tooltip-overlay", transform="translate(%d, %d)" % ( - self.margin.left, self.margin.top)) + self.margin_box.left, self.margin_box.top)) self.nodes['tooltip'] = self.svg.node( self.nodes['tooltip_overlay'], transform='translate(0 0)', @@ -279,8 +279,8 @@ class Graph(BaseGraph): return truncation = self.truncate_legend if self.legend_at_bottom: - x = self.margin.left + self.spacing - y = (self.margin.top + self.view.height + + x = self.margin_box.left + self.spacing + y = (self.margin_box.top + self.view.height + self._x_title_height + self._x_labels_height + self.spacing) cols = self.legend_at_bottom_columns or ceil( @@ -293,7 +293,7 @@ class Graph(BaseGraph): available_space, self.legend_font_size) else: x = self.spacing - y = self.margin.top + self.spacing + y = self.margin_box.top + self.spacing cols = 1 if not truncation: truncation = 15 @@ -321,13 +321,13 @@ class Graph(BaseGraph): enumerate(zip(self._secondary_legends, repeat(True))))) # draw secondary axis on right - x = self.margin.left + self.view.width + self.spacing + x = self.margin_box.left + self.view.width + self.spacing if self._y_2nd_labels: h, w = get_texts_box( cut(self._y_2nd_labels), self.label_font_size) x += self.spacing + max(w * cos(rad(self.y_label_rotation)), h) - y = self.margin.top + self.spacing + y = self.margin_box.top + self.spacing secondary_legends = self.svg.node( self.nodes['graph'], class_='legends', @@ -383,13 +383,13 @@ class Graph(BaseGraph): def _make_x_title(self): """Make the X-Axis title""" - y = (self.height - self.margin.bottom + + y = (self.height - self.margin_box.bottom + self._x_labels_height) if self._x_title: for i, title_line in enumerate(self._x_title, 1): text = self.svg.node( self.nodes['title'], 'text', class_='title', - x=self.margin.left + self.view.width / 2, + x=self.margin_box.left + self.view.width / 2, y=y + i * (self.title_font_size + self.spacing) ) text.text = title_line @@ -397,7 +397,7 @@ class Graph(BaseGraph): def _make_y_title(self): """Make the Y-Axis title""" if self._y_title: - yc = self.margin.top + self.view.height / 2 + yc = self.margin_box.top + self.view.height / 2 for i, title_line in enumerate(self._y_title, 1): text = self.svg.node( self.nodes['title'], 'text', class_='title', @@ -524,15 +524,15 @@ class Graph(BaseGraph): cols = (self._order // self.legend_at_bottom_columns if self.legend_at_bottom_columns else ceil(sqrt(self._order)) or 1) - self.margin.bottom += self.spacing + h_max * round( + self.margin_box.bottom += self.spacing + h_max * round( cols - 1) * 1.5 + h_max else: if series_group is self.series: legend_width = self.spacing + w + self.legend_box_size - self.margin.left += legend_width + self.margin_box.left += legend_width self._legend_at_left_width += legend_width else: - self.margin.right += ( + self.margin_box.right += ( self.spacing + w + self.legend_box_size) self._x_labels_height = 0 @@ -546,13 +546,13 @@ class Graph(BaseGraph): self._x_labels_height = self.spacing + max( w * sin(rad(self.x_label_rotation)), h) if xlabels is self._x_labels: - self.margin.bottom += self._x_labels_height + self.margin_box.bottom += self._x_labels_height else: - self.margin.top += self._x_labels_height + self.margin_box.top += self._x_labels_height if self.x_label_rotation: - self.margin.right = max( + self.margin_box.right = max( w * cos(rad(self.x_label_rotation)), - self.margin.right) + self.margin_box.right) if self.show_y_labels: for ylabels in (self._y_labels, self._y_2nd_labels): @@ -560,10 +560,10 @@ class Graph(BaseGraph): h, w = get_texts_box( cut(ylabels), self.label_font_size) if ylabels is self._y_labels: - self.margin.left += self.spacing + max( + self.margin_box.left += self.spacing + max( w * cos(rad(self.y_label_rotation)), h) else: - self.margin.right += self.spacing + max( + self.margin_box.right += self.spacing + max( w * cos(rad(self.y_label_rotation)), h) self._title = split_title( @@ -571,27 +571,27 @@ class Graph(BaseGraph): if self.title: h, _ = get_text_box(self._title[0], self.title_font_size) - self.margin.top += len(self._title) * (self.spacing + h) + self.margin_box.top += len(self._title) * (self.spacing + h) self._x_title = split_title( - self.x_title, self.width - self.margin.x, self.title_font_size) + self.x_title, self.width - self.margin_box.x, self.title_font_size) self._x_title_height = 0 if self._x_title: h, _ = get_text_box(self._x_title[0], self.title_font_size) height = len(self._x_title) * (self.spacing + h) - self.margin.bottom += height + self.margin_box.bottom += height self._x_title_height = height + self.spacing self._y_title = split_title( - self.y_title, self.height - self.margin.y, + self.y_title, self.height - self.margin_box.y, self.title_font_size) self._y_title_height = 0 if self._y_title: h, _ = get_text_box(self._y_title[0], self.title_font_size) height = len(self._y_title) * (self.spacing + h) - self.margin.left += height + self.margin_box.left += height self._y_title_height = height + self.spacing @cached_property diff --git a/pygal/graph/horizontal.py b/pygal/graph/horizontal.py index 6b6060c..2a0bef3 100644 --- a/pygal/graph/horizontal.py +++ b/pygal/graph/horizontal.py @@ -48,6 +48,6 @@ class HorizontalGraph(Graph): view_class = HorizontalView self.view = view_class( - self.width - self.margin.x, - self.height - self.margin.y, + self.width - self.margin_box.x, + self.height - self.margin_box.y, self._box) diff --git a/pygal/graph/pie.py b/pygal/graph/pie.py index e6d153d..844c36a 100644 --- a/pygal/graph/pie.py +++ b/pygal/graph/pie.py @@ -43,11 +43,11 @@ class Pie(Graph): total_perc = 0 original_start_angle = start_angle if self.half_pie: - center = ((self.width - self.margin.x) / 2., - (self.height - self.margin.y) / 1.25) + center = ((self.width - self.margin_box.x) / 2., + (self.height - self.margin_box.y) / 1.25) else: - center = ((self.width - self.margin.x) / 2., - (self.height - self.margin.y) / 2.) + center = ((self.width - self.margin_box.x) / 2., + (self.height - self.margin_box.y) / 2.) radius = min(center) for i, val in enumerate(serie.values): diff --git a/pygal/graph/radar.py b/pygal/graph/radar.py index a0a7e42..0afbba0 100644 --- a/pygal/graph/radar.py +++ b/pygal/graph/radar.py @@ -60,8 +60,8 @@ class Radar(Line): view_class = PolarView self.view = view_class( - self.width - self.margin.x, - self.height - self.margin.y, + self.width - self.margin_box.x, + self.height - self.margin_box.y, self._box) def _x_axis(self, draw_axes=True): diff --git a/pygal/graph/treemap.py b/pygal/graph/treemap.py index 93179ac..0be375d 100644 --- a/pygal/graph/treemap.py +++ b/pygal/graph/treemap.py @@ -118,8 +118,8 @@ class Treemap(Graph): if total == 0: return - gw = self.width - self.margin.x - gh = self.height - self.margin.y + gw = self.width - self.margin_box.x + gh = self.height - self.margin_box.y self.view.box.xmin = self.view.box.ymin = x = y = 0 self.view.box.xmax = w = (total * gw / gh) ** .5 diff --git a/pygal/state.py b/pygal/state.py index a6545d9..e4e4e2c 100644 --- a/pygal/state.py +++ b/pygal/state.py @@ -23,6 +23,7 @@ Class holding state during render class State(object): - def __init__(self, graph): + def __init__(self, graph, **kwargs): self.__dict__.update(**graph.config.__dict__) self.__dict__.update(**graph.__dict__) + self.__dict__.update(**kwargs) diff --git a/pygal/svg.py b/pygal/svg.py index 3e223d5..169f33d 100644 --- a/pygal/svg.py +++ b/pygal/svg.py @@ -124,16 +124,16 @@ class Svg(object): common_script = self.node(self.defs, 'script', type='text/javascript') def get_js_dict(): - return dict((k, getattr(self.graph, k)) for k in dir(self) - if not k.startswith('_') and - k in dir(self.graph.config)) + return dict((k, getattr(self.graph.state, k)) + for k in dir(self.graph.config) + if not k.startswith('_') and not hasattr( + getattr(self.graph.config, k), '__call__')) def json_default(o): if isinstance(o, (datetime, date)): return o.isoformat() if hasattr(o, 'to_dict'): - o = o.to_dict() - print(o) + return o.to_dict() return json.JSONEncoder().default(o) common_script.text = " = ".join( diff --git a/pygal/table.py b/pygal/table.py index 3466bab..11e62ad 100644 --- a/pygal/table.py +++ b/pygal/table.py @@ -21,7 +21,6 @@ Table maker """ -from pygal.graph.base import BaseGraph from pygal.util import template from lxml.html import builder, tostring import uuid @@ -32,18 +31,17 @@ class HTML(object): return getattr(builder, attr.upper()) -class Table(BaseGraph): +class Table(object): _dual = None - def __init__(self, chart, series, secondary_series, uuid, xml_filters): + def __init__(self, chart): "Init the table" - self.uuid = uuid - self.series = series or [] - self.secondary_series = secondary_series or [] - self.xml_filters = xml_filters or [] - self.__dict__.update(chart.state) + self.chart = chart def render(self, total=False, transpose=False, style=False): + self.chart.setup() + ln = self.chart._len + fmt = self.chart._format html = HTML() attrs = {} @@ -54,22 +52,22 @@ class Table(BaseGraph): _ = lambda x: x if x is not None else '' - if self.x_labels: - labels = [None] + list(self.x_labels) - if len(labels) < self._len: - labels += [None] * (self._len + 1 - len(labels)) - if len(labels) > self._len + 1: - labels = labels[:self._len + 1] + if self.chart.x_labels: + labels = [None] + list(self.chart.x_labels) + if len(labels) < ln: + labels += [None] * (ln + 1 - len(labels)) + if len(labels) > ln + 1: + labels = labels[:ln + 1] table.append(labels) if total: if len(table): table[0].append('Total') else: - table.append([None] * (self._len + 1) + ['Total']) - acc = [0] * (self._len + 1) + table.append([None] * (ln + 1) + ['Total']) + acc = [0] * (ln + 1) - for i, serie in enumerate(self.series): + for i, serie in enumerate(self.chart.series): row = [serie.title] if total: sum_ = 0 @@ -77,18 +75,18 @@ class Table(BaseGraph): if total: acc[j] += value sum_ += value - row.append(self._format(value)) + row.append(fmt(value)) if total: acc[-1] += sum_ - row.append(self._format(sum_)) + row.append(fmt(sum_)) table.append(row) - width = self._len + 1 + width = ln + 1 if total: width += 1 table.append(['Total']) for val in acc: - table[-1].append(self._format(val)) + table[-1].append(fmt(val)) # Align values len_ = max([len(r) for r in table] or [0]) @@ -105,7 +103,7 @@ class Table(BaseGraph): tbody = [] tfoot = [] - if not transpose or self.x_labels: + if not transpose or self.chart.x_labels: # There's always series title but not always x_labels thead = [table[0]] tbody = table[1:] @@ -185,6 +183,7 @@ class Table(BaseGraph): table = tostring(html.style( template(css, **attrs), scoped='scoped')) + table - if self.disable_xml_declaration: + if self.chart.disable_xml_declaration: table = table.decode('utf-8') + self.chart.teardown() return table