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,
height=height,
class_='rect reactive tooltip-trigger')
self.svg.node(bar, 'desc', class_="value").text = val
tooltip_positions = map(
str, (x + bar_inner_width / 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)]
x += bar_inner_width / 2
y += height / 2
if self.horizontal:
x += .3 * self.value_font_size
y += height / 2
else:
y += height / 2 + .3 * self.value_font_size
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 ''
x, y = y, x
self._tooltip_data(bar, val, x, y, classes="centered")
self._static_value(serie_node, val, x, y)
return stack_vals
def _compute(self):

19
pygal/graph/base.py

@ -23,7 +23,7 @@ Base for pygal charts
from __future__ import division
import io
from pygal.serie import Serie
from pygal.serie import Serie, Value
from pygal.view import Margin, Box
from pygal.util import get_text_box, get_texts_box, cut, rad, humanize
from pygal.svg import Svg
@ -35,6 +35,8 @@ from math import sin, cos
class BaseGraph(object):
"""Graphs commons"""
__value__ = Value
def __init__(self, config=None, **kwargs):
"""Init the graph"""
self.config = config or Config()
@ -51,7 +53,8 @@ class BaseGraph(object):
def add(self, title, values):
"""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):
"""(Re-)Init the graph"""
@ -119,6 +122,11 @@ class BaseGraph(object):
"""Getter for the maximum series size"""
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):
"""Draw all the things"""
self._compute()
@ -134,6 +142,13 @@ class BaseGraph(object):
return False
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):
"""Make the graph internally"""
self.reinit()

60
pygal/graph/graph.py

@ -36,11 +36,15 @@ class Graph(BaseGraph):
"""Draw all decorations"""
self._set_view()
self._make_graph()
self._x_axis()
self._y_axis()
self._axes()
self._legend()
self._title()
def _axes(self):
"""Draw axes"""
self._x_axis()
self._y_axis()
def _set_view(self):
"""Assign a view to current graph"""
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_='value')
def _x_axis(self):
def _x_axis(self, draw_axes=True):
"""Make the x axis: labels and guides"""
if not self._x_labels:
return
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',
d='M%f %f v%f' % (0, 0, self.view.height),
class_='line')
@ -107,10 +111,12 @@ class Graph(BaseGraph):
guides = self.svg.node(axis, class_='guides')
x = self.view.x(position)
y = self.view.height + 5
self.svg.node(guides, 'path',
d='M%f %f v%f' % (x, 0, self.view.height),
if draw_axes:
self.svg.node(
guides, 'path',
d='M%f %f v%f' % (x, 0, self.view.height),
class_='%sline' % (
'guide ' if position != 0 else ''))
'guide ' if position != 0 else ''))
text = self.svg.node(guides, 'text',
x=x,
y=y + .5 * self.label_font_size + 5
@ -120,14 +126,14 @@ class Graph(BaseGraph):
text.attrib['transform'] = "rotate(%d %f %f)" % (
self.x_label_rotation, x, y)
def _y_axis(self):
def _y_axis(self, draw_axes=True):
"""Make the y axis: labels and guides"""
if not self._y_labels:
return
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',
d='M%f %f h%f' % (0, self.view.height, self.view.width),
class_='line')
@ -138,11 +144,13 @@ class Graph(BaseGraph):
))
x = -5
y = self.view.y(position)
self.svg.node(guides, 'path',
d='M%f %f h%f' % (0, y, self.view.width),
class_='%s%sline' % (
'major ' if major else '',
'guide ' if position != 0 else ''))
if draw_axes:
self.svg.node(
guides, 'path',
d='M%f %f h%f' % (0, y, self.view.width),
class_='%s%sline' % (
'major ' if major else '',
'guide ' if position != 0 else ''))
text = self.svg.node(guides, 'text',
x=x,
y=y + .35 * self.label_font_size,
@ -226,3 +234,27 @@ class Graph(BaseGraph):
coord = tuple(reversed(coord))
interpolateds.append(coord)
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)
self.svg.node(dots, 'circle', cx=x, cy=y, r=2.5,
class_='dot reactive tooltip-trigger')
self.svg.node(dots, 'desc', class_="value").text = val
self.svg.node(dots, 'desc',
class_="x " + classes).text = str(x)
self.svg.node(dots, 'desc',
class_="y " + classes).text = str(y)
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
self._tooltip_data(dots, val, x, y)
self._static_value(
serie_node, val,
x + self.value_font_size,
y + self.value_font_size)
if self.stroke:
if self.interpolate:
@ -97,7 +91,7 @@ class Line(Graph):
class_='line reactive' + (' nofill' if not self.fill else ''))
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
for serie in self.series:
if not hasattr(serie, 'points'):

8
pygal/graph/pie.py

@ -22,6 +22,7 @@ Pie chart
"""
from __future__ import division
from pygal.serie import PositiveValue
from pygal.util import decorate
from pygal.graph.graph import Graph
from math import pi
@ -30,6 +31,8 @@ from math import pi
class Pie(Graph):
"""Pie graph"""
__value__ = PositiveValue
def slice(self, serie_node, start_angle, serie, total):
"""Make a serie slice"""
@ -69,11 +72,6 @@ class Pie(Graph):
original_start_angle, center, val)
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):
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 pygal.graph.line import Line
from pygal.serie import PositiveValue
from pygal.view import PolarView
from pygal.util import deg, cached_property, compute_scale
from math import cos, pi
@ -31,6 +32,8 @@ from math import cos, pi
class Radar(Line):
"""Kiviat graph"""
__value__ = PositiveValue
def __init__(self, *args, **kwargs):
self.x_pos = None
self._rmax = None

1
pygal/graph/stackedline.py

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

20
pygal/serie.py

@ -18,27 +18,41 @@
# along with pygal. If not, see <http://www.gnu.org/licenses/>.
"""
Little helpers for series
"""
from pygal.util import cut
class Serie(object):
"""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
if isinstance(values, dict) or not hasattr(values, '__iter__'):
values = [values]
self.metadata = map(Value, values)
self.values = [value.value for value in self.metadata]
self.metadata = map(value_class, values)
self.index = index
@property
def values(self):
return cut(self.metadata, 'value')
class Value(object):
"""Value container"""
def __init__(self, value):
if not isinstance(value, dict):
value = {'value': 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):
"""A label with his position"""
def __init__(self, label, pos):

20
pygal/svg.py

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

Loading…
Cancel
Save