Browse Source

Fix a lot of things + refactoring

pull/8/head
Florian Mounier 13 years ago
parent
commit
ee2ed90733
  1. 29
      pygal/graph/bar.py
  2. 19
      pygal/graph/base.py
  3. 60
      pygal/graph/graph.py
  4. 18
      pygal/graph/line.py
  5. 8
      pygal/graph/pie.py
  6. 3
      pygal/graph/radar.py
  7. 1
      pygal/graph/stackedline.py
  8. 20
      pygal/serie.py
  9. 20
      pygal/svg.py

29
pygal/graph/bar.py

@ -102,27 +102,16 @@ class Bar(Graph):
width=bar_inner_width, width=bar_inner_width,
height=height, height=height,
class_='rect reactive tooltip-trigger') class_='rect reactive tooltip-trigger')
self.svg.node(bar, 'desc', class_="value").text = val
tooltip_positions = map( x += bar_inner_width / 2
str, (x + bar_inner_width / 2, y + height / 2)) y += height / 2
self.svg.node(bar, 'desc',
class_="x centered"
).text = tooltip_positions[int(self.horizontal)]
self.svg.node(bar, 'desc',
class_="y centered"
).text = tooltip_positions[int(not self.horizontal)]
if self.horizontal: if self.horizontal:
x += .3 * self.value_font_size x, y = y, x
y += height / 2
else: self._tooltip_data(bar, val, x, y, classes="centered")
y += height / 2 + .3 * self.value_font_size self._static_value(serie_node, val, x, y)
if self.print_values:
self.svg.transposable_node(
serie_node['text_overlay'], 'text',
class_='centered',
x=x + bar_inner_width / 2,
y=y
).text = val if self.print_zeroes or val != '0' else ''
return stack_vals return stack_vals
def _compute(self): def _compute(self):

19
pygal/graph/base.py

@ -23,7 +23,7 @@ Base for pygal charts
from __future__ import division from __future__ import division
import io import io
from pygal.serie import Serie from pygal.serie import Serie, Value
from pygal.view import Margin, Box from pygal.view import Margin, Box
from pygal.util import get_text_box, get_texts_box, cut, rad, humanize from pygal.util import get_text_box, get_texts_box, cut, rad, humanize
from pygal.svg import Svg from pygal.svg import Svg
@ -35,6 +35,8 @@ from math import sin, cos
class BaseGraph(object): class BaseGraph(object):
"""Graphs commons""" """Graphs commons"""
__value__ = Value
def __init__(self, config=None, **kwargs): def __init__(self, config=None, **kwargs):
"""Init the graph""" """Init the graph"""
self.config = config or Config() self.config = config or Config()
@ -51,7 +53,8 @@ class BaseGraph(object):
def add(self, title, values): def add(self, title, values):
"""Add a serie to this graph""" """Add a serie to this graph"""
self.series.append(Serie(title, values, len(self.series))) self.series.append(
Serie(title, list(values), len(self.series), self.__value__))
def reinit(self): def reinit(self):
"""(Re-)Init the graph""" """(Re-)Init the graph"""
@ -119,6 +122,11 @@ class BaseGraph(object):
"""Getter for the maximum series size""" """Getter for the maximum series size"""
return max([len(serie.values) for serie in self.series]) return max([len(serie.values) for serie in self.series])
@cached_property
def _max(self):
"""Getter for the maximum series value"""
return max(self._values)
def _draw(self): def _draw(self):
"""Draw all the things""" """Draw all the things"""
self._compute() self._compute()
@ -134,6 +142,13 @@ class BaseGraph(object):
return False return False
return True return True
def _uniformize_data(self):
"""Make all series to max len"""
for serie in self.series:
if len(serie.values) < self._len:
diff = self._len - len(serie.values)
serie.metadata += diff * [self.__value__(0)]
def _render(self): def _render(self):
"""Make the graph internally""" """Make the graph internally"""
self.reinit() self.reinit()

60
pygal/graph/graph.py

@ -36,11 +36,15 @@ class Graph(BaseGraph):
"""Draw all decorations""" """Draw all decorations"""
self._set_view() self._set_view()
self._make_graph() self._make_graph()
self._x_axis() self._axes()
self._y_axis()
self._legend() self._legend()
self._title() self._title()
def _axes(self):
"""Draw axes"""
self._x_axis()
self._y_axis()
def _set_view(self): def _set_view(self):
"""Assign a view to current graph""" """Assign a view to current graph"""
self.view = (LogView if self.logarithmic else View)( self.view = (LogView if self.logarithmic else View)(
@ -92,14 +96,14 @@ class Graph(BaseGraph):
self.svg.node(text, 'tspan', class_='label') self.svg.node(text, 'tspan', class_='label')
self.svg.node(text, 'tspan', class_='value') self.svg.node(text, 'tspan', class_='value')
def _x_axis(self): def _x_axis(self, draw_axes=True):
"""Make the x axis: labels and guides""" """Make the x axis: labels and guides"""
if not self._x_labels: if not self._x_labels:
return return
axis = self.svg.node(self.nodes['plot'], class_="axis x") axis = self.svg.node(self.nodes['plot'], class_="axis x")
if 0 not in [label[1] for label in self._x_labels]: if 0 not in [label[1] for label in self._x_labels] and draw_axes:
self.svg.node(axis, 'path', self.svg.node(axis, 'path',
d='M%f %f v%f' % (0, 0, self.view.height), d='M%f %f v%f' % (0, 0, self.view.height),
class_='line') class_='line')
@ -107,10 +111,12 @@ class Graph(BaseGraph):
guides = self.svg.node(axis, class_='guides') guides = self.svg.node(axis, class_='guides')
x = self.view.x(position) x = self.view.x(position)
y = self.view.height + 5 y = self.view.height + 5
self.svg.node(guides, 'path', if draw_axes:
d='M%f %f v%f' % (x, 0, self.view.height), self.svg.node(
guides, 'path',
d='M%f %f v%f' % (x, 0, self.view.height),
class_='%sline' % ( class_='%sline' % (
'guide ' if position != 0 else '')) 'guide ' if position != 0 else ''))
text = self.svg.node(guides, 'text', text = self.svg.node(guides, 'text',
x=x, x=x,
y=y + .5 * self.label_font_size + 5 y=y + .5 * self.label_font_size + 5
@ -120,14 +126,14 @@ class Graph(BaseGraph):
text.attrib['transform'] = "rotate(%d %f %f)" % ( text.attrib['transform'] = "rotate(%d %f %f)" % (
self.x_label_rotation, x, y) self.x_label_rotation, x, y)
def _y_axis(self): def _y_axis(self, draw_axes=True):
"""Make the y axis: labels and guides""" """Make the y axis: labels and guides"""
if not self._y_labels: if not self._y_labels:
return return
axis = self.svg.node(self.nodes['plot'], class_="axis y") axis = self.svg.node(self.nodes['plot'], class_="axis y")
if 0 not in [label[1] for label in self._y_labels]: if 0 not in [label[1] for label in self._y_labels] and draw_axes:
self.svg.node(axis, 'path', self.svg.node(axis, 'path',
d='M%f %f h%f' % (0, self.view.height, self.view.width), d='M%f %f h%f' % (0, self.view.height, self.view.width),
class_='line') class_='line')
@ -138,11 +144,13 @@ class Graph(BaseGraph):
)) ))
x = -5 x = -5
y = self.view.y(position) y = self.view.y(position)
self.svg.node(guides, 'path', if draw_axes:
d='M%f %f h%f' % (0, y, self.view.width), self.svg.node(
class_='%s%sline' % ( guides, 'path',
'major ' if major else '', d='M%f %f h%f' % (0, y, self.view.width),
'guide ' if position != 0 else '')) class_='%s%sline' % (
'major ' if major else '',
'guide ' if position != 0 else ''))
text = self.svg.node(guides, 'text', text = self.svg.node(guides, 'text',
x=x, x=x,
y=y + .35 * self.label_font_size, y=y + .35 * self.label_font_size,
@ -226,3 +234,27 @@ class Graph(BaseGraph):
coord = tuple(reversed(coord)) coord = tuple(reversed(coord))
interpolateds.append(coord) interpolateds.append(coord)
return interpolateds return interpolateds
def _tooltip_data(self, node, value, x, y, classes=None):
self.svg.node(node, 'desc', class_="value").text = value
if classes is None:
classes = []
if x > self.view.width / 2:
classes.append('left')
if y > self.view.height / 2:
classes.append('top')
classes = ' '.join(classes)
self.svg.node(node, 'desc',
class_="x " + classes).text = str(x)
self.svg.node(node, 'desc',
class_="y " + classes).text = str(y)
def _static_value(self, serie_node, value, x, y):
if self.print_values:
self.svg.node(
serie_node['text_overlay'], 'text',
class_='centered',
x=x,
y=y + self.value_font_size / 3
).text = value if self.print_zeroes or value != '0' else ''

18
pygal/graph/line.py

@ -75,17 +75,11 @@ class Line(Graph):
val = self._get_value(serie.points, i) val = self._get_value(serie.points, i)
self.svg.node(dots, 'circle', cx=x, cy=y, r=2.5, self.svg.node(dots, 'circle', cx=x, cy=y, r=2.5,
class_='dot reactive tooltip-trigger') class_='dot reactive tooltip-trigger')
self.svg.node(dots, 'desc', class_="value").text = val self._tooltip_data(dots, val, x, y)
self.svg.node(dots, 'desc', self._static_value(
class_="x " + classes).text = str(x) serie_node, val,
self.svg.node(dots, 'desc', x + self.value_font_size,
class_="y " + classes).text = str(y) y + self.value_font_size)
if self.print_values:
self.svg.node(
serie_node['text_overlay'], 'text',
x=x + self.value_font_size,
y=y + self.value_font_size,
).text = val
if self.stroke: if self.stroke:
if self.interpolate: if self.interpolate:
@ -97,7 +91,7 @@ class Line(Graph):
class_='line reactive' + (' nofill' if not self.fill else '')) class_='line reactive' + (' nofill' if not self.fill else ''))
def _compute(self): def _compute(self):
x_pos = [x / float(self._len - 1) for x in range(self._len) x_pos = [x / (self._len - 1) for x in range(self._len)
] if self._len != 1 else [.5] # Center if only one value ] if self._len != 1 else [.5] # Center if only one value
for serie in self.series: for serie in self.series:
if not hasattr(serie, 'points'): if not hasattr(serie, 'points'):

8
pygal/graph/pie.py

@ -22,6 +22,7 @@ Pie chart
""" """
from __future__ import division from __future__ import division
from pygal.serie import PositiveValue
from pygal.util import decorate from pygal.util import decorate
from pygal.graph.graph import Graph from pygal.graph.graph import Graph
from math import pi from math import pi
@ -30,6 +31,8 @@ from math import pi
class Pie(Graph): class Pie(Graph):
"""Pie graph""" """Pie graph"""
__value__ = PositiveValue
def slice(self, serie_node, start_angle, serie, total): def slice(self, serie_node, start_angle, serie, total):
"""Make a serie slice""" """Make a serie slice"""
@ -69,11 +72,6 @@ class Pie(Graph):
original_start_angle, center, val) original_start_angle, center, val)
return serie_angle return serie_angle
def _compute(self):
for serie in self.series:
serie.values = map(lambda x: max(x, 0), serie.values)
return super(Pie, self)._compute()
def _plot(self): def _plot(self):
total = sum(map(sum, map(lambda x: x.values, self.series))) total = sum(map(sum, map(lambda x: x.values, self.series)))

3
pygal/graph/radar.py

@ -23,6 +23,7 @@ Radar chart
from __future__ import division from __future__ import division
from pygal.graph.line import Line from pygal.graph.line import Line
from pygal.serie import PositiveValue
from pygal.view import PolarView from pygal.view import PolarView
from pygal.util import deg, cached_property, compute_scale from pygal.util import deg, cached_property, compute_scale
from math import cos, pi from math import cos, pi
@ -31,6 +32,8 @@ from math import cos, pi
class Radar(Line): class Radar(Line):
"""Kiviat graph""" """Kiviat graph"""
__value__ = PositiveValue
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.x_pos = None self.x_pos = None
self._rmax = None self._rmax = None

1
pygal/graph/stackedline.py

@ -41,6 +41,7 @@ class StackedLine(Line):
return new_values return new_values
def _compute(self): def _compute(self):
self._uniformize_data()
x_pos = [x / float(self._len - 1) for x in range(self._len) x_pos = [x / float(self._len - 1) for x in range(self._len)
] if self._len != 1 else [.5] # Center if only one value ] if self._len != 1 else [.5] # Center if only one value
accumulation = [0] * self._len accumulation = [0] * self._len

20
pygal/serie.py

@ -18,27 +18,41 @@
# along with pygal. If not, see <http://www.gnu.org/licenses/>. # along with pygal. If not, see <http://www.gnu.org/licenses/>.
""" """
Little helpers for series Little helpers for series
""" """
from pygal.util import cut
class Serie(object): class Serie(object):
"""Serie containing title, values and the graph serie index""" """Serie containing title, values and the graph serie index"""
def __init__(self, title, values, index): def __init__(self, title, values, index, value_class):
self.title = title self.title = title
if isinstance(values, dict) or not hasattr(values, '__iter__'): if isinstance(values, dict) or not hasattr(values, '__iter__'):
values = [values] values = [values]
self.metadata = map(Value, values) self.metadata = map(value_class, values)
self.values = [value.value for value in self.metadata]
self.index = index self.index = index
@property
def values(self):
return cut(self.metadata, 'value')
class Value(object): class Value(object):
"""Value container"""
def __init__(self, value): def __init__(self, value):
if not isinstance(value, dict): if not isinstance(value, dict):
value = {'value': value} value = {'value': value}
self.__dict__.update(value) self.__dict__.update(value)
class PositiveValue(Value):
"""Positive or zero value container"""
def __init__(self, value):
super(PositiveValue, self).__init__(max(0, value))
class Label(object): class Label(object):
"""A label with his position""" """A label with his position"""
def __init__(self, label, pos): def __init__(self, label, pos):

20
pygal/svg.py

@ -152,21 +152,11 @@ class Svg(object):
to[2], to[2],
get_radius(small_radius), int(angle > pi), to[3]), get_radius(small_radius), int(angle > pi), to[3]),
class_='slice reactive tooltip-trigger') class_='slice reactive tooltip-trigger')
self.node(node, 'desc', class_="value").text = val x, y = diff(center, project(
tooltip_position = map( (radius + small_radius) / 2, start_angle + angle / 2))
str, diff(center, project(
(radius + small_radius) / 2, start_angle + angle / 2))) self.graph._tooltip_data(node, val, x, y, classes="centered")
self.node(node, 'desc', self.graph._static_value(serie_node, val, x, y)
class_="x centered").text = tooltip_position[0]
self.node(node, 'desc',
class_="y centered").text = tooltip_position[1]
if self.graph.print_values:
self.node(
serie_node['text_overlay'], 'text',
class_='centered',
x=tooltip_position[0],
y=tooltip_position[1]
).text = val if self.graph.print_zeroes or val != '0%' else ''
def pre_render(self, no_data=False): def pre_render(self, no_data=False):
"""Last things to do before rendering""" """Last things to do before rendering"""

Loading…
Cancel
Save