mirror of https://github.com/Kozea/pygal.git
Python to generate nice looking SVG graph
http://pygal.org/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
312 lines
9.1 KiB
312 lines
9.1 KiB
# -*- coding: utf-8 -*- |
|
# This file is part of pygal |
|
# |
|
# A python svg graph plotting library |
|
# Copyright © 2012 Kozea |
|
# |
|
# This library is free software: you can redistribute it and/or modify it under |
|
# the terms of the GNU Lesser General Public License as published by the Free |
|
# Software Foundation, either version 3 of the License, or (at your option) any |
|
# later version. |
|
# |
|
# This library is distributed in the hope that it will be useful, but WITHOUT |
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
|
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more |
|
# details. |
|
# |
|
# You should have received a copy of the GNU Lesser General Public License |
|
# along with pygal. If not, see <http://www.gnu.org/licenses/>. |
|
|
|
""" |
|
Config module with all options |
|
""" |
|
from copy import deepcopy |
|
from pygal.style import Style, DefaultStyle |
|
|
|
|
|
class FontSizes(object): |
|
"""Container for font sizes""" |
|
|
|
CONFIG_ITEMS = [] |
|
|
|
|
|
class Key(object): |
|
|
|
_categories = [] |
|
|
|
def __init__( |
|
self, default_value, type_, category, doc, |
|
subdoc="", subtype=None): |
|
|
|
self.value = default_value |
|
self.type = type_ |
|
self.doc = doc |
|
self.category = category |
|
self.subdoc = subdoc |
|
self.subtype = subtype |
|
self.name = "Unbound" |
|
if not category in self._categories: |
|
self._categories.append(category) |
|
|
|
CONFIG_ITEMS.append(self) |
|
|
|
@property |
|
def is_boolean(self): |
|
return self.type == bool |
|
|
|
@property |
|
def is_numeric(self): |
|
return self.type == int |
|
|
|
@property |
|
def is_string(self): |
|
return self.type == str |
|
|
|
@property |
|
def is_list(self): |
|
return self.type == list |
|
|
|
def coerce(self, value): |
|
if self.type == Style: |
|
return value |
|
elif self.type == list: |
|
return self.type( |
|
map( |
|
self.subtype, map( |
|
lambda x: x.strip(), value.split(',')))) |
|
return self.type(value) |
|
|
|
|
|
class MetaConfig(type): |
|
def __new__(mcs, classname, bases, classdict): |
|
for k, v in classdict.items(): |
|
if isinstance(v, Key): |
|
v.name = k |
|
return type.__new__(mcs, classname, bases, classdict) |
|
|
|
|
|
class Config(object): |
|
"""Class holding config values""" |
|
|
|
__metaclass__ = MetaConfig |
|
|
|
style = Key( |
|
DefaultStyle, Style, "Style", "Style holding values injected in css") |
|
|
|
css = Key( |
|
('style.css', 'graph.css'), list, "Style", |
|
"List of css file", |
|
"It can be an absolute file path or an external link", |
|
str) |
|
|
|
############ Look ############ |
|
title = Key( |
|
None, str, "Look", |
|
"Graph title.", "Leave it to None to disable title.") |
|
|
|
width = Key( |
|
800, int, "Look", "Graph width") |
|
|
|
height = Key( |
|
600, int, "Look", "Graph height") |
|
|
|
show_dots = Key(True, bool, "Look", "Set to false to remove dots") |
|
|
|
dots_size = Key(2.5, float, "Look", "Radius of the dots") |
|
|
|
stroke = Key( |
|
True, bool, "Look", |
|
"Line dots (set it to false to get a scatter plot)") |
|
|
|
fill = Key( |
|
False, bool, "Look", "Fill areas under lines") |
|
|
|
show_legend = Key( |
|
True, bool, "Look", "Set to false to remove legend") |
|
|
|
legend_at_bottom = Key( |
|
False, bool, "Look", "Set to true to position legend at bottom") |
|
|
|
legend_box_size = Key( |
|
12, int, "Look", "Size of legend boxes") |
|
|
|
rounded_bars = Key( |
|
None, int, "Look", "Set this to the desired radius in px") |
|
|
|
############ Label ############ |
|
x_labels = Key( |
|
None, list, "Label", |
|
"X labels, must have same len than data.", |
|
"Leave it to None to disable x labels display.", |
|
str) |
|
|
|
x_labels_major = Key( |
|
None, list, "Label", |
|
"X labels that will be marked major.", |
|
subtype=str) |
|
|
|
x_labels_major_every = Key( |
|
None, int, "Label", |
|
"Mark every n-th x label as major.") |
|
|
|
x_labels_major_count = Key( |
|
None, int, "Label", |
|
"Mark n evenly distributed labels as major.") |
|
|
|
show_minor_x_labels = Key( |
|
True, bool, "Label", "Set to false to hide x-labels not marked major") |
|
|
|
y_labels = Key( |
|
None, list, "Label", |
|
"You can specify explicit y labels", |
|
"Must be a list of numbers", float) |
|
|
|
x_label_rotation = Key( |
|
0, int, "Label", "Specify x labels rotation angles", "in degrees") |
|
|
|
y_label_rotation = Key( |
|
0, int, "Label", "Specify y labels rotation angles", "in degrees") |
|
|
|
############ Value ############ |
|
human_readable = Key( |
|
False, bool, "Value", "Display values in human readable format", |
|
"(ie: 12.4M)") |
|
|
|
value_formatter = Key( |
|
None, type(lambda: 1), "Value", |
|
"A function to convert numeric value to strings") |
|
|
|
logarithmic = Key( |
|
False, bool, "Value", "Display values in logarithmic scale") |
|
|
|
interpolate = Key( |
|
None, str, "Value", "Interpolation, this requires scipy module", |
|
"May be any of 'linear', 'nearest', 'zero', 'slinear', 'quadratic," |
|
"'cubic', 'krogh', 'barycentric', 'univariate'," |
|
"or an integer specifying the order" |
|
"of the spline interpolator") |
|
|
|
interpolation_precision = Key( |
|
250, int, "Value", "Number of interpolated points between two values") |
|
|
|
order_min = Key( |
|
None, int, "Value", "Minimum order of scale, defaults to None") |
|
|
|
range = Key( |
|
None, list, "Value", "Explicitly specify min and max of values", |
|
"(ie: (0, 100))", int) |
|
|
|
include_x_axis = Key( |
|
False, bool, "Value", "Always include x axis") |
|
|
|
zero = Key( |
|
0, int, "Value", |
|
"Set the ordinate zero value", |
|
"Useful for filling to another base than abscissa") |
|
|
|
############ Text ############ |
|
no_data_text = Key( |
|
"No data", str, "Text", "Text to display when no data is given") |
|
|
|
label_font_size = Key(10, int, "Text", "Label font size") |
|
|
|
major_label_font_size = Key(10, int, "Text", "Major label font size") |
|
|
|
value_font_size = Key(8, int, "Text", "Value font size") |
|
|
|
tooltip_font_size = Key(20, int, "Text", "Tooltip font size") |
|
|
|
title_font_size = Key(16, int, "Text", "Title font size") |
|
|
|
legend_font_size = Key(14, int, "Text", "Legend font size") |
|
|
|
no_data_font_size = Key(64, int, "Text", "No data text font size") |
|
|
|
print_values = Key( |
|
True, bool, |
|
"Text", "Print values when graph is in non interactive mode") |
|
|
|
print_zeroes = Key( |
|
False, bool, |
|
"Text", "Print zeroes when graph is in non interactive mode") |
|
|
|
truncate_legend = Key( |
|
None, int, "Text", |
|
"Legend string length truncation threshold", "None = auto") |
|
|
|
truncate_label = Key( |
|
None, int, "Text", |
|
"Label string length truncation threshold", "None = auto") |
|
|
|
############ Misc ############ |
|
js = Key( |
|
('http://kozea.github.com/pygal.js/javascripts/svg.jquery.js', |
|
'http://kozea.github.com/pygal.js/javascripts/pygal-tooltips.js'), |
|
list, "Misc", "List of js file", |
|
"It can be a filepath or an external link", |
|
str) |
|
|
|
disable_xml_declaration = Key( |
|
False, bool, "Misc", |
|
"Don't write xml declaration and return str instead of string", |
|
"usefull for writing output directly in html") |
|
|
|
explicit_size = Key( |
|
False, bool, "Misc", "Write width and height attributes") |
|
|
|
pretty_print = Key( |
|
False, bool, "Misc", "Pretty print the svg") |
|
|
|
strict = Key( |
|
False, bool, "Misc", |
|
"If True don't try to adapt / filter wrong values") |
|
|
|
def __init__(self, **kwargs): |
|
"""Can be instanciated with config kwargs""" |
|
for k in dir(self): |
|
v = getattr(self, k) |
|
if (k not in self.__dict__ and not |
|
k.startswith('_') and not |
|
hasattr(v, '__call__')): |
|
if isinstance(v, Key): |
|
v = v.value |
|
setattr(self, k, v) |
|
|
|
self.css = list(self.css) |
|
self.js = list(self.js) |
|
self._update(kwargs) |
|
|
|
def __call__(self, **kwargs): |
|
"""Can be updated with kwargs""" |
|
self._update(kwargs) |
|
|
|
def _update(self, kwargs): |
|
self.__dict__.update( |
|
dict([(k, v) for (k, v) in kwargs.items() |
|
if not k.startswith('_') and k in dir(self)])) |
|
|
|
def font_sizes(self, with_unit=True): |
|
"""Getter for all font size configs""" |
|
fs = FontSizes() |
|
for name in dir(self): |
|
if name.endswith('_font_size'): |
|
setattr( |
|
fs, |
|
name.replace('_font_size', ''), |
|
('%dpx' % getattr(self, name)) |
|
if with_unit else getattr(self, name)) |
|
return fs |
|
|
|
def to_dict(self): |
|
config = {} |
|
for attr in dir(self): |
|
if not attr.startswith('__'): |
|
value = getattr(self, attr) |
|
if hasattr(value, 'to_dict'): |
|
config[attr] = value.to_dict() |
|
elif not hasattr(value, '__call__'): |
|
config[attr] = value |
|
return config |
|
|
|
def copy(self): |
|
return deepcopy(self)
|
|
|