Source code for pygal.config

# -*- coding: utf-8 -*-
# This file is part of pygal
#
# A python svg graph plotting library
# Copyright © 2012-2016 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 holding all options and their default values."""

from copy import deepcopy

from pygal import formatters
from pygal.interpolate import INTERPOLATIONS
from pygal.style import DefaultStyle, Style

CONFIG_ITEMS = []
callable = type(lambda: 1)


[docs]class Key(object): """ Represents a config parameter. A config parameter has a name, a default value, a type, a category, a documentation, an optional longer documentatation and an optional subtype for list style option. Most of these informations are used in cabaret to auto generate forms representing these options. """ _categories = [] def __init__( self, default_value, type_, category, doc, subdoc="", subtype=None ): """Create a configuration key""" self.value = default_value self.type = type_ self.doc = doc self.category = category self.subdoc = subdoc self.subtype = subtype self.name = "Unbound" if category not in self._categories: self._categories.append(category) CONFIG_ITEMS.append(self) def __repr__(self): """ Make a documentation repr. This is a hack to generate doc from inner doc """ return """ Type: %s%s      Default: %r      %s%s """ % ( self.type.__name__, (' of %s' % self.subtype.__name__) if self.subtype else '', self.value, self.doc, (' %s' % self.subdoc) if self.subdoc else '' ) @property def is_boolean(self): """Return `True` if this parameter is a boolean""" return self.type == bool @property def is_numeric(self): """Return `True` if this parameter is numeric (int or float)""" return self.type in (int, float) @property def is_string(self): """Return `True` if this parameter is a string""" return self.type == str @property def is_dict(self): """Return `True` if this parameter is a mapping""" return self.type == dict @property def is_list(self): """Return `True` if this parameter is a list""" return self.type == list
[docs] def coerce(self, value): """Cast a string into this key type""" if self.type == Style: return value elif self.type == list: return self.type( map(self.subtype, map(lambda x: x.strip(), value.split(','))) ) elif self.type == dict: rv = {} for pair in value.split(','): key, val = pair.split(':') key = key.strip() val = val.strip() try: rv[key] = self.subtype(val) except Exception: rv[key] = val return rv return self.type(value)
[docs]class MetaConfig(type): """Config metaclass. Used to get the key name and set it on the value.""" def __new__(mcs, classname, bases, classdict): """Get the name of the key and set it on the key""" for k, v in classdict.items(): if isinstance(v, Key): v.name = k return type.__new__(mcs, classname, bases, classdict)
[docs]class BaseConfig(MetaConfig('ConfigBase', (object, ), {})): """ This class holds the common method for configs. A config object can be instanciated with keyword arguments and updated on call with keyword arguments. """ 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): if v.is_list and v.value is not None: v = list(v.value) else: v = v.value setattr(self, k, v) self._update(kwargs) def __call__(self, **kwargs): """Can be updated with kwargs""" self._update(kwargs) def _update(self, kwargs): """Update the config with the given dictionary""" from pygal.util import merge dir_self_set = set(dir(self)) merge( self.__dict__, dict([(k, v) for (k, v) in kwargs.items() if not k.startswith('_') and k in dir_self_set]) )
[docs] def to_dict(self): """Export a JSON serializable dictionary of the config""" 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
[docs] def copy(self): """Copy this config object into another""" return deepcopy(self)
[docs]class CommonConfig(BaseConfig): """Class holding options used in both chart and serie configuration""" stroke = Key( True, bool, "Look", "Line dots (set it to false to get a scatter plot)" ) show_dots = Key(True, bool, "Look", "Set to false to remove dots") show_only_major_dots = Key( False, bool, "Look", "Set to true to show only major dots according to their majored label" ) dots_size = Key(2.5, float, "Look", "Radius of the dots") fill = Key(False, bool, "Look", "Fill areas under lines") stroke_style = Key( None, dict, "Look", "Stroke style of serie element.", "This is a dict which can contain a " "'width', 'linejoin', 'linecap', 'dasharray' " "and 'dashoffset'" ) rounded_bars = Key( None, int, "Look", "Set this to the desired radius in px (for Bar-like charts)" ) inner_radius = Key( 0, float, "Look", "Piechart inner radius (donut), must be <.9" ) allow_interruptions = Key( False, bool, "Look", "Break lines on None values" ) formatter = Key( None, callable, "Value", "A function to convert raw value to strings for this chart or serie", "Default to value_formatter in most charts, it depends on dual charts." "(Can be overriden by value with the formatter metadata.)" )
[docs]class Config(CommonConfig): """Class holding config values""" style = Key( DefaultStyle, Style, "Style", "Style holding values injected in css" ) css = Key( ('file://style.css', 'file://graph.css'), list, "Style", "List of css file", "It can be any uri from file:///tmp/style.css to //domain/style.css", str ) classes = Key(('pygal-chart', ), list, "Style", "Classes of the root svg node", str) defs = Key([], list, "Misc", "Extraneous defs to be inserted in svg", "Useful for adding gradients / patterns…", str) # Look # title = Key( None, str, "Look", "Graph title.", "Leave it to None to disable title." ) x_title = Key( None, str, "Look", "Graph X-Axis title.", "Leave it to None to disable X-Axis title." ) y_title = Key( None, str, "Look", "Graph Y-Axis title.", "Leave it to None to disable Y-Axis title." ) width = Key(800, int, "Look", "Graph width") height = Key(600, int, "Look", "Graph height") show_x_guides = Key( False, bool, "Look", "Set to true to always show x guide lines" ) show_y_guides = Key( True, bool, "Look", "Set to false to hide y guide 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_at_bottom_columns = Key( None, int, "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" ) stack_from_top = Key( False, bool, "Look", "Stack from top to zero, this makes the stacked " "data match the legend order" ) spacing = Key(10, int, "Look", "Space between titles/legend/axes") margin = Key(20, int, "Look", "Margin around chart") margin_top = Key(None, int, "Look", "Margin around top of chart") margin_right = Key(None, int, "Look", "Margin around right of chart") margin_bottom = Key(None, int, "Look", "Margin around bottom of chart") margin_left = Key(None, int, "Look", "Margin around left of chart") tooltip_border_radius = Key(0, int, "Look", "Tooltip border radius") tooltip_fancy_mode = Key( True, bool, "Look", "Fancy tooltips", "Print legend, x label in tooltip and use serie color for value." ) inner_radius = Key( 0, float, "Look", "Piechart inner radius (donut), must be <.9" ) half_pie = Key(False, bool, "Look", "Create a half-pie chart") 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_x_labels = Key(True, bool, "Label", "Set to false to hide x-labels") 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 ) y_labels_major = Key( None, list, "Label", "Y labels that will be marked major. Default: auto", subtype=str ) y_labels_major_every = Key( None, int, "Label", "Mark every n-th y label as major." ) y_labels_major_count = Key( None, int, "Label", "Mark n evenly distributed y labels as major." ) show_minor_y_labels = Key( True, bool, "Label", "Set to false to hide y-labels not marked major" ) show_y_labels = Key(True, bool, "Label", "Set to false to hide y-labels") 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" ) missing_value_fill_truncation = Key( "x", str, "Look", "Filled series with missing x and/or y values at the end of a series " "are closed at the first value with a missing " "'x' (default), 'y' or 'either'" ) # Value # x_value_formatter = Key( formatters.default, callable, "Value", "A function to convert abscissa numeric value to strings " "(used in XY and Date charts)" ) value_formatter = Key( formatters.default, callable, "Value", "A function to convert ordinate numeric value to strings" ) logarithmic = Key( False, bool, "Value", "Display values in logarithmic scale" ) interpolate = Key( None, str, "Value", "Interpolation", "May be %s" % ' or '.join(INTERPOLATIONS) ) interpolation_precision = Key( 250, int, "Value", "Number of interpolated points between two values" ) interpolation_parameters = Key( {}, dict, "Value", "Various parameters for parametric interpolations", "ie: For hermite interpolation, you can set the cardinal tension with" "{'type': 'cardinal', 'c': .5}", int ) box_mode = Key( 'extremes', str, "Value", "Sets the mode to be used. " "(Currently only supported on box plot)", "May be %s" % ' or '.join(["1.5IQR", "extremes", "tukey", "stdev", "pstdev"]) ) order_min = Key( None, int, "Value", "Minimum order of scale, defaults to None" ) min_scale = Key( 4, int, "Value", "Minimum number of scale graduation for auto scaling" ) max_scale = Key( 16, int, "Value", "Maximum number of scale graduation for auto scaling" ) range = Key( None, list, "Value", "Explicitly specify min and max of values", "(ie: (0, 100))", int ) secondary_range = Key( None, list, "Value", "Explicitly specify min and max of secondary values", "(ie: (0, 100))", int ) xrange = Key( None, list, "Value", "Explicitly specify min and max of x values " "(used in XY and Date charts)", "(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" ) print_values = Key(False, bool, "Text", "Display values as text over plot") dynamic_print_values = Key( False, bool, "Text", "Show values only on hover" ) print_values_position = Key( 'center', str, "Text", "Customize position of `print_values`. " "(For bars: `top`, `center` or `bottom`)" ) print_zeroes = Key(True, bool, "Text", "Display zero values as well") print_labels = Key(False, bool, "Text", "Display value labels") truncate_legend = Key( None, int, "Text", "Legend string length truncation threshold", "None = auto, Negative for none" ) truncate_label = Key( None, int, "Text", "Label string length truncation threshold", "None = auto, Negative for none" ) # Misc # js = Key(('//kozea.github.io/pygal.js/2.0.x/pygal-tooltips.min.js', ), list, "Misc", "List of js file", "It can be any uri from file:///tmp/ext.js to //domain/ext.js", str) disable_xml_declaration = Key( False, bool, "Misc", "Don't write xml declaration and return str instead of string", "useful for writing output directly in html" ) force_uri_protocol = Key( 'https', str, "Misc", "Default uri protocol", "Default protocol for external files. " "Can be set to None to use a // uri" ) 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" ) no_prefix = Key(False, bool, "Misc", "Don't prefix css") inverse_y_axis = Key(False, bool, "Misc", "Inverse Y axis direction")
[docs]class SerieConfig(CommonConfig): """Class holding serie config values""" title = Key( None, str, "Look", "Serie title.", "Leave it to None to disable title." ) secondary = Key( False, bool, "Misc", "Set it to put the serie in a second axis" )