1# -*- coding: utf-8 -*- 2# This file is part of pygal 3# 4# A python svg graph plotting library 5# Copyright © 2012-2016 Kozea 6# 7# This library is free software: you can redistribute it and/or modify it under 8# the terms of the GNU Lesser General Public License as published by the Free 9# Software Foundation, either version 3 of the License, or (at your option) any 10# later version. 11# 12# This library is distributed in the hope that it will be useful, but WITHOUT 13# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 14# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 15# details. 16# 17# You should have received a copy of the GNU Lesser General Public License 18# along with pygal. If not, see <http://www.gnu.org/licenses/>. 19"""Config module holding all options and their default values.""" 20 21from copy import deepcopy 22 23from pygal import formatters 24from pygal.interpolate import INTERPOLATIONS 25from pygal.style import DefaultStyle, Style 26 27CONFIG_ITEMS = [] 28callable = type(lambda: 1) 29 30 31class Key(object): 32 33 """ 34 Represents a config parameter. 35 36 A config parameter has a name, a default value, a type, 37 a category, a documentation, an optional longer documentatation 38 and an optional subtype for list style option. 39 40 Most of these informations are used in cabaret to auto generate 41 forms representing these options. 42 """ 43 44 _categories = [] 45 46 def __init__( 47 self, default_value, type_, category, doc, 48 subdoc="", subtype=None): 49 """Create a configuration key""" 50 self.value = default_value 51 self.type = type_ 52 self.doc = doc 53 self.category = category 54 self.subdoc = subdoc 55 self.subtype = subtype 56 self.name = "Unbound" 57 if category not in self._categories: 58 self._categories.append(category) 59 60 CONFIG_ITEMS.append(self) 61 62 def __repr__(self): 63 """ 64 Make a documentation repr. 65 This is a hack to generate doc from inner doc 66 """ 67 return """ 68 Type: %s%s 69 Default: %r 70 %s%s 71 """ % ( 72 self.type.__name__, 73 (' of %s' % self.subtype.__name__) if self.subtype else '', 74 self.value, 75 self.doc, 76 (' %s' % self.subdoc) if self.subdoc else '' 77 ) 78 79 @property 80 def is_boolean(self): 81 """Return `True` if this parameter is a boolean""" 82 return self.type == bool 83 84 @property 85 def is_numeric(self): 86 """Return `True` if this parameter is numeric (int or float)""" 87 return self.type in (int, float) 88 89 @property 90 def is_string(self): 91 """Return `True` if this parameter is a string""" 92 return self.type == str 93 94 @property 95 def is_dict(self): 96 """Return `True` if this parameter is a mapping""" 97 return self.type == dict 98 99 @property 100 def is_list(self): 101 """Return `True` if this parameter is a list""" 102 return self.type == list 103 104 def coerce(self, value): 105 """Cast a string into this key type""" 106 if self.type == Style: 107 return value 108 elif self.type == list: 109 return self.type( 110 map( 111 self.subtype, map( 112 lambda x: x.strip(), value.split(',')))) 113 elif self.type == dict: 114 rv = {} 115 for pair in value.split(','): 116 key, val = pair.split(':') 117 key = key.strip() 118 val = val.strip() 119 try: 120 rv[key] = self.subtype(val) 121 except: 122 rv[key] = val 123 return rv 124 return self.type(value) 125 126 127class MetaConfig(type): 128 129 """Config metaclass. Used to get the key name and set it on the value.""" 130 131 def __new__(mcs, classname, bases, classdict): 132 """Get the name of the key and set it on the key""" 133 for k, v in classdict.items(): 134 if isinstance(v, Key): 135 v.name = k 136 137 return type.__new__(mcs, classname, bases, classdict) 138 139 140class BaseConfig(MetaConfig('ConfigBase', (object,), {})): 141 142 """ 143 This class holds the common method for configs. 144 145 A config object can be instanciated with keyword arguments and 146 updated on call with keyword arguments. 147 """ 148 149 def __init__(self, **kwargs): 150 """Can be instanciated with config kwargs""" 151 for k in dir(self): 152 v = getattr(self, k) 153 if (k not in self.__dict__ and not 154 k.startswith('_') and not 155 hasattr(v, '__call__')): 156 if isinstance(v, Key): 157 if v.is_list and v.value is not None: 158 v = list(v.value) 159 else: 160 v = v.value 161 setattr(self, k, v) 162 self._update(kwargs) 163 164 def __call__(self, **kwargs): 165 """Can be updated with kwargs""" 166 self._update(kwargs) 167 168 def _update(self, kwargs): 169 """Update the config with the given dictionary""" 170 from pygal.util import merge 171 dir_self_set = set(dir(self)) 172 merge( 173 self.__dict__, dict([ 174 (k, v) for (k, v) in kwargs.items() 175 if not k.startswith('_') and k in dir_self_set])) 176 177 def to_dict(self): 178 """Export a JSON serializable dictionary of the config""" 179 config = {} 180 for attr in dir(self): 181 if not attr.startswith('__'): 182 value = getattr(self, attr) 183 if hasattr(value, 'to_dict'): 184 config[attr] = value.to_dict() 185 elif not hasattr(value, '__call__'): 186 config[attr] = value 187 return config 188 189 def copy(self): 190 """Copy this config object into another""" 191 return deepcopy(self) 192 193 194class CommonConfig(BaseConfig): 195 196 """Class holding options used in both chart and serie configuration""" 197 198 stroke = Key( 199 True, bool, "Look", 200 "Line dots (set it to false to get a scatter plot)") 201 202 show_dots = Key(True, bool, "Look", "Set to false to remove dots") 203 204 show_only_major_dots = Key( 205 False, bool, "Look", 206 "Set to true to show only major dots according to their majored label") 207 208 dots_size = Key(2.5, float, "Look", "Radius of the dots") 209 210 fill = Key( 211 False, bool, "Look", "Fill areas under lines") 212 213 stroke_style = Key(None, dict, "Look", "Stroke style of serie element.", 214 "This is a dict which can contain a " 215 "'width', 'linejoin', 'linecap', 'dasharray' " 216 "and 'dashoffset'") 217 218 rounded_bars = Key( 219 None, int, "Look", 220 "Set this to the desired radius in px (for Bar-like charts)") 221 222 inner_radius = Key( 223 0, float, "Look", "Piechart inner radius (donut), must be <.9") 224 225 allow_interruptions = Key( 226 False, bool, "Look", "Break lines on None values") 227 228 formatter = Key( 229 None, callable, "Value", 230 "A function to convert raw value to strings for this chart or serie", 231 "Default to value_formatter in most charts, it depends on dual charts." 232 "(Can be overriden by value with the formatter metadata.)") 233 234 235class Config(CommonConfig): 236 237 """Class holding config values""" 238 239 style = Key( 240 DefaultStyle, Style, "Style", "Style holding values injected in css") 241 242 css = Key( 243 ('file://style.css', 'file://graph.css'), list, "Style", 244 "List of css file", 245 "It can be any uri from file:///tmp/style.css to //domain/style.css", 246 str) 247 248 classes = Key( 249 ('pygal-chart',), 250 list, "Style", "Classes of the root svg node", 251 str) 252 253 defs = Key( 254 [], 255 list, "Misc", "Extraneous defs to be inserted in svg", 256 "Useful for adding gradients / patterns…", 257 str) 258 259 # Look # 260 title = Key( 261 None, str, "Look", 262 "Graph title.", "Leave it to None to disable title.") 263 264 x_title = Key( 265 None, str, "Look", 266 "Graph X-Axis title.", "Leave it to None to disable X-Axis title.") 267 268 y_title = Key( 269 None, str, "Look", 270 "Graph Y-Axis title.", "Leave it to None to disable Y-Axis title.") 271 272 width = Key( 273 800, int, "Look", "Graph width") 274 275 height = Key( 276 600, int, "Look", "Graph height") 277 278 show_x_guides = Key(False, bool, "Look", 279 "Set to true to always show x guide lines") 280 281 show_y_guides = Key(True, bool, "Look", 282 "Set to false to hide y guide lines") 283 284 show_legend = Key( 285 True, bool, "Look", "Set to false to remove legend") 286 287 legend_at_bottom = Key( 288 False, bool, "Look", "Set to true to position legend at bottom") 289 290 legend_at_bottom_columns = Key( 291 None, int, "Look", "Set to true to position legend at bottom") 292 293 legend_box_size = Key( 294 12, int, "Look", "Size of legend boxes") 295 296 rounded_bars = Key( 297 None, int, "Look", "Set this to the desired radius in px") 298 299 stack_from_top = Key( 300 False, bool, "Look", "Stack from top to zero, this makes the stacked " 301 "data match the legend order") 302 303 spacing = Key( 304 10, int, "Look", 305 "Space between titles/legend/axes") 306 307 margin = Key( 308 20, int, "Look", 309 "Margin around chart") 310 311 margin_top = Key( 312 None, int, "Look", 313 "Margin around top of chart") 314 315 margin_right = Key( 316 None, int, "Look", 317 "Margin around right of chart") 318 319 margin_bottom = Key( 320 None, int, "Look", 321 "Margin around bottom of chart") 322 323 margin_left = Key( 324 None, int, "Look", 325 "Margin around left of chart") 326 327 tooltip_border_radius = Key(0, int, "Look", "Tooltip border radius") 328 329 tooltip_fancy_mode = Key( 330 True, bool, "Look", "Fancy tooltips", 331 "Print legend, x label in tooltip and use serie color for value.") 332 333 inner_radius = Key( 334 0, float, "Look", "Piechart inner radius (donut), must be <.9") 335 336 half_pie = Key( 337 False, bool, "Look", "Create a half-pie chart") 338 339 x_labels = Key( 340 None, list, "Label", 341 "X labels, must have same len than data.", 342 "Leave it to None to disable x labels display.", 343 str) 344 345 x_labels_major = Key( 346 None, list, "Label", 347 "X labels that will be marked major.", 348 subtype=str) 349 350 x_labels_major_every = Key( 351 None, int, "Label", 352 "Mark every n-th x label as major.") 353 354 x_labels_major_count = Key( 355 None, int, "Label", 356 "Mark n evenly distributed labels as major.") 357 358 show_x_labels = Key( 359 True, bool, "Label", "Set to false to hide x-labels") 360 361 show_minor_x_labels = Key( 362 True, bool, "Label", "Set to false to hide x-labels not marked major") 363 364 y_labels = Key( 365 None, list, "Label", 366 "You can specify explicit y labels", 367 "Must be a list of numbers", float) 368 369 y_labels_major = Key( 370 None, list, "Label", 371 "Y labels that will be marked major. Default: auto", 372 subtype=str) 373 374 y_labels_major_every = Key( 375 None, int, "Label", 376 "Mark every n-th y label as major.") 377 378 y_labels_major_count = Key( 379 None, int, "Label", 380 "Mark n evenly distributed y labels as major.") 381 382 show_minor_y_labels = Key( 383 True, bool, "Label", "Set to false to hide y-labels not marked major") 384 385 show_y_labels = Key( 386 True, bool, "Label", "Set to false to hide y-labels") 387 388 x_label_rotation = Key( 389 0, int, "Label", "Specify x labels rotation angles", "in degrees") 390 391 y_label_rotation = Key( 392 0, int, "Label", "Specify y labels rotation angles", "in degrees") 393 394 missing_value_fill_truncation = Key( 395 "x", str, "Look", 396 "Filled series with missing x and/or y values at the end of a series " 397 "are closed at the first value with a missing " 398 "'x' (default), 'y' or 'either'") 399 400 # Value # 401 x_value_formatter = Key( 402 formatters.default, callable, "Value", 403 "A function to convert abscissa numeric value to strings " 404 "(used in XY and Date charts)") 405 406 value_formatter = Key( 407 formatters.default, callable, "Value", 408 "A function to convert ordinate numeric value to strings") 409 410 logarithmic = Key( 411 False, bool, "Value", "Display values in logarithmic scale") 412 413 interpolate = Key( 414 None, str, "Value", "Interpolation", 415 "May be %s" % ' or '.join(INTERPOLATIONS)) 416 417 interpolation_precision = Key( 418 250, int, "Value", "Number of interpolated points between two values") 419 420 interpolation_parameters = Key( 421 {}, dict, "Value", "Various parameters for parametric interpolations", 422 "ie: For hermite interpolation, you can set the cardinal tension with" 423 "{'type': 'cardinal', 'c': .5}", int) 424 425 box_mode = Key( 426 'extremes', str, "Value", "Sets the mode to be used. " 427 "(Currently only supported on box plot)", 428 "May be %s" % ' or '.join([ 429 "1.5IQR", "extremes", "tukey", "stdev", "pstdev"])) 430 431 order_min = Key( 432 None, int, "Value", 433 "Minimum order of scale, defaults to None") 434 435 min_scale = Key( 436 4, int, "Value", 437 "Minimum number of scale graduation for auto scaling") 438 439 max_scale = Key( 440 16, int, "Value", 441 "Maximum number of scale graduation for auto scaling") 442 443 range = Key( 444 None, list, "Value", "Explicitly specify min and max of values", 445 "(ie: (0, 100))", int) 446 447 secondary_range = Key( 448 None, list, "Value", 449 "Explicitly specify min and max of secondary values", 450 "(ie: (0, 100))", int) 451 452 xrange = Key( 453 None, list, "Value", "Explicitly specify min and max of x values " 454 "(used in XY and Date charts)", 455 "(ie: (0, 100))", int) 456 457 include_x_axis = Key( 458 False, bool, "Value", "Always include x axis") 459 460 zero = Key( 461 0, int, "Value", 462 "Set the ordinate zero value", 463 "Useful for filling to another base than abscissa") 464 465 # Text # 466 no_data_text = Key( 467 "No data", str, "Text", "Text to display when no data is given") 468 469 print_values = Key( 470 False, bool, 471 "Text", "Display values as text over plot") 472 473 dynamic_print_values = Key( 474 False, bool, 475 "Text", "Show values only on hover") 476 477 print_values_position = Key( 478 'center', str, 479 "Text", "Customize position of `print_values`. " 480 "(For bars: `top`, `center` or `bottom`)") 481 482 print_zeroes = Key( 483 True, bool, 484 "Text", "Display zero values as well") 485 486 print_labels = Key( 487 False, bool, 488 "Text", "Display value labels") 489 490 truncate_legend = Key( 491 None, int, "Text", 492 "Legend string length truncation threshold", 493 "None = auto, Negative for none") 494 495 truncate_label = Key( 496 None, int, "Text", 497 "Label string length truncation threshold", 498 "None = auto, Negative for none") 499 500 # Misc # 501 js = Key( 502 ('//kozea.github.io/pygal.js/2.0.x/pygal-tooltips.min.js',), 503 list, "Misc", "List of js file", 504 "It can be any uri from file:///tmp/ext.js to //domain/ext.js", 505 str) 506 507 disable_xml_declaration = Key( 508 False, bool, "Misc", 509 "Don't write xml declaration and return str instead of string", 510 "useful for writing output directly in html") 511 512 force_uri_protocol = Key( 513 'https', str, "Misc", 514 "Default uri protocol", 515 "Default protocol for external files. " 516 "Can be set to None to use a // uri") 517 518 explicit_size = Key( 519 False, bool, "Misc", "Write width and height attributes") 520 521 pretty_print = Key( 522 False, bool, "Misc", "Pretty print the svg") 523 524 strict = Key( 525 False, bool, "Misc", 526 "If True don't try to adapt / filter wrong values") 527 528 no_prefix = Key( 529 False, bool, "Misc", 530 "Don't prefix css") 531 532 inverse_y_axis = Key(False, bool, "Misc", "Inverse Y axis direction") 533 534 535class SerieConfig(CommonConfig): 536 537 """Class holding serie config values""" 538 539 title = Key( 540 None, str, "Look", 541 "Serie title.", "Leave it to None to disable title.") 542 543 secondary = Key( 544 False, bool, "Misc", 545 "Set it to put the serie in a second axis") 546