Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/python_nvd3-0.14.2-py3.8.egg/nvd3/NVD3Chart.py: 75%
207 statements
« prev ^ index » next coverage.py v7.3.2, created at 2023-10-03 06:25 +0000
« prev ^ index » next coverage.py v7.3.2, created at 2023-10-03 06:25 +0000
1#!/usr/bin/python
2# -*- coding: utf-8 -*-
4"""
5Python-nvd3 is a Python wrapper for NVD3 graph library.
6NVD3 is an attempt to build re-usable charts and chart components
7for d3.js without taking away the power that d3.js gives you.
9Project location : https://github.com/areski/python-nvd3
10"""
12from __future__ import unicode_literals
13from optparse import OptionParser
14from jinja2 import Environment, PackageLoader
15from slugify import slugify
16try:
17 import simplejson as json
18except ImportError:
19 import json
21CONTENT_FILENAME = "./content.html"
22PAGE_FILENAME = "./page.html"
25pl = PackageLoader('nvd3', 'templates')
26jinja2_env = Environment(lstrip_blocks=True, trim_blocks=True, loader=pl)
28template_content = jinja2_env.get_template(CONTENT_FILENAME)
29template_page = jinja2_env.get_template(PAGE_FILENAME)
32def stab(tab=1):
33 """
34 create space tabulation
35 """
36 return ' ' * 4 * tab
39class NVD3Chart(object):
40 """
41 NVD3Chart Base class.
42 """
43 #: chart count
44 count = 0
45 #: directory holding the assets (bower_components)
46 assets_directory = './bower_components/'
48 # this attribute is overridden by children of this
49 # class
50 CHART_FILENAME = None
51 template_environment = Environment(lstrip_blocks=True, trim_blocks=True,
52 loader=pl)
54 def __init__(self, **kwargs):
55 """
56 This is the base class for all the charts. The following keywords are
57 accepted:
59 :keyword: **display_container** - default: ``True``
60 :keyword: **jquery_on_ready** - default: ``False``
61 :keyword: **charttooltip_dateformat** - default: ``'%d %b %Y'``
62 :keyword: **name** - default: the class name
63 ``model`` - set the model (e.g. ``pieChart``, `
64 ``LineWithFocusChart``, ``MultiBarChart``).
65 :keyword: **color_category** - default - ``None``
66 :keyword: **color_list** - default - ``None``
67 used by pieChart (e.g. ``['red', 'blue', 'orange']``)
68 :keyword: **margin_bottom** - default - ``20``
69 :keyword: **margin_left** - default - ``60``
70 :keyword: **margin_right** - default - ``60``
71 :keyword: **margin_top** - default - ``30``
72 :keyword: **height** - default - ``''``
73 :keyword: **width** - default - ``''``
74 :keyword: **show_values** - default - ``False``
75 :keyword: **stacked** - default - ``False``
76 :keyword: **focus_enable** - default - ``False``
77 :keyword: **resize** - define - ``False``
78 :keyword: **no_data_message** - default - ``None`` or nvd3 default
79 :keyword: **xAxis_rotateLabel** - default - ``0``
80 :keyword: **xAxis_staggerLabel** - default - ``False``
81 :keyword: **xAxis_showMaxMin** - default - ``True``
82 :keyword: **right_align_y_axis** - default - ``False``
83 :keyword: **show_controls** - default - ``True``
84 :keyword: **show_legend** - default - ``True``
85 :keyword: **show_labels** - default - ``True``
86 :keyword: **tag_script_js** - default - ``True``
87 :keyword: **use_interactive_guideline** - default - ``False``
88 :keyword: **chart_attr** - default - ``None``
89 :keyword: **extras** - default - ``None``
91 Extra chart modifiers. Use this to modify different attributes of
92 the chart.
93 :keyword: **x_axis_date** - default - False
94 Signal that x axis is a date axis
95 :keyword: **date_format** - default - ``%x``
96 see https://github.com/mbostock/d3/wiki/Time-Formatting
97 :keyword: **y_axis_scale_min** - default - ``''``.
98 :keyword: **y_axis_scale_max** - default - ``''``.
99 :keyword: **x_axis_format** - default - ``''``.
100 :keyword: **y_axis_format** - default - ``''``.
101 :keyword: **style** - default - ``''``
102 Style modifiers for the DIV container.
103 :keyword: **color_category** - default - ``category10``
105 Acceptable values are nvd3 categories such as
106 ``category10``, ``category20``, ``category20c``.
107 """
108 # set the model
109 self.model = self.__class__.__name__ #: The chart model,
111 #: an Instance of Jinja2 template
112 self.template_page_nvd3 = template_page
113 self.template_content_nvd3 = template_content
114 self.series = []
115 self.axislist = {}
116 # accepted keywords
117 self.display_container = kwargs.get('display_container', True)
118 self.charttooltip_dateformat = kwargs.get('charttooltip_dateformat',
119 '%d %b %Y')
120 self._slugify_name(kwargs.get('name', self.model))
121 self.jquery_on_ready = kwargs.get('jquery_on_ready', False)
122 self.color_category = kwargs.get('color_category', None)
123 self.color_list = kwargs.get('color_list', None)
124 self.margin_bottom = kwargs.get('margin_bottom', 20)
125 self.margin_left = kwargs.get('margin_left', 60)
126 self.margin_right = kwargs.get('margin_right', 60)
127 self.margin_top = kwargs.get('margin_top', 30)
128 self.height = kwargs.get('height', '')
129 self.width = kwargs.get('width', '')
130 self.show_values = kwargs.get('show_values', False)
131 self.stacked = kwargs.get('stacked', False)
132 self.focus_enable = kwargs.get('focus_enable', False)
133 self.resize = kwargs.get('resize', False)
134 self.no_data_message = kwargs.get('no_data_message', None)
135 self.xAxis_rotateLabel = kwargs.get('xAxis_rotateLabel', 0)
136 self.xAxis_staggerLabel = kwargs.get('xAxis_staggerLabel', False)
137 self.xAxis_showMaxMin = kwargs.get('xAxis_showMaxMin', True)
138 self.right_align_y_axis = kwargs.get('right_align_y_axis', False)
139 self.show_controls = kwargs.get('show_controls', True)
140 self.show_legend = kwargs.get('show_legend', True)
141 self.show_labels = kwargs.get('show_labels', True)
142 self.tooltip_separator = kwargs.get('tooltip_separator')
143 self.tag_script_js = kwargs.get('tag_script_js', True)
144 self.use_interactive_guideline = kwargs.get("use_interactive_guideline",
145 False)
146 self.chart_attr = kwargs.get("chart_attr", {})
147 self.extras = kwargs.get('extras', None)
148 self.style = kwargs.get('style', '')
149 self.date_format = kwargs.get('date_format', '%x')
150 self.x_axis_date = kwargs.get('x_axis_date', False)
151 self.y_axis_scale_min = kwargs.get('y_axis_scale_min', '')
152 self.y_axis_scale_max = kwargs.get('y_axis_scale_max', '')
153 #: x-axis contain date format or not
154 # possible duplicate of x_axis_date
155 self.date_flag = kwargs.get('date_flag', False)
156 self.x_axis_format = kwargs.get('x_axis_format', '')
157 # Load remote JS assets or use the local bower assets?
158 self.remote_js_assets = kwargs.get('remote_js_assets', True)
159 self.callback = kwargs.get('callback', None)
161 # None keywords attribute that should be modified by methods
162 # We should change all these to _attr
164 self.htmlcontent = '' #: written by buildhtml
165 self.htmlheader = ''
166 #: Place holder for the graph (the HTML div)
167 #: Written by ``buildcontainer``
168 self.container = u''
169 #: Header for javascript code
170 self.containerheader = u''
171 # CDN http://cdnjs.com/libraries/nvd3/ needs to make sure it's up to
172 # date
173 self.header_css = [
174 '<link href="%s" rel="stylesheet" />' % h for h in
175 (
176 'https://cdnjs.cloudflare.com/ajax/libs/nvd3/1.8.6/nv.d3.min.css' if self.remote_js_assets else self.assets_directory + 'nvd3/src/nv.d3.css',
177 )
178 ]
180 self.header_js = [
181 '<script src="%s"></script>' % h for h in
182 (
183 'https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js' if self.remote_js_assets else self.assets_directory + 'd3/d3.min.js',
184 'https://cdnjs.cloudflare.com/ajax/libs/nvd3/1.8.6/nv.d3.min.js' if self.remote_js_assets else self.assets_directory + 'nvd3/nv.d3.min.js'
185 )
186 ]
188 #: Javascript code as string
189 self.jschart = None
190 self.custom_tooltip_flag = False
191 self.charttooltip = ''
192 self.serie_no = 1
194 def _slugify_name(self, name):
195 """Slufigy name with underscore"""
196 self.name = slugify(name, separator='_')
198 def add_serie(self, y, x, name=None, extra=None, **kwargs):
199 """
200 add serie - Series are list of data that will be plotted
201 y {1, 2, 3, 4, 5} / x {1, 2, 3, 4, 5}
203 **Attributes**:
205 * ``name`` - set Serie name
206 * ``x`` - x-axis data
207 * ``y`` - y-axis data
209 kwargs:
211 * ``shape`` - for scatterChart, you can set different shapes
212 (circle, triangle etc...)
213 * ``size`` - for scatterChart, you can set size of different shapes
214 * ``type`` - for multiChart, type should be bar
215 * ``bar`` - to display bars in Chart
216 * ``color_list`` - define list of colors which will be
217 used by pieChart
218 * ``color`` - set axis color
219 * ``disabled`` -
221 extra:
223 * ``tooltip`` - set tooltip flag
224 * ``date_format`` - set date_format for tooltip if x-axis is in
225 date format
227 """
228 if not name:
229 name = "Serie %d" % (self.serie_no)
231 # For scatterChart shape & size fields are added in serie
232 if 'shape' in kwargs or 'size' in kwargs:
233 csize = kwargs.get('size', 1)
234 cshape = kwargs.get('shape', 'circle')
236 serie = [{
237 'x': x[i],
238 'y': j,
239 'shape': cshape,
240 'size': csize[i] if isinstance(csize, list) else csize
241 } for i, j in enumerate(y)]
242 else:
243 if self.model == 'pieChart':
244 serie = [{'label': x[i], 'value': y} for i, y in enumerate(y)]
245 else:
246 serie = [{'x': x[i], 'y': y} for i, y in enumerate(y)]
248 data_keyvalue = {'values': serie, 'key': name}
250 # multiChart
251 # Histogram type='bar' for the series
252 if 'type' in kwargs and kwargs['type']:
253 data_keyvalue['type'] = kwargs['type']
255 # Define on which Y axis the serie is related
256 # a chart can have 2 Y axis, left and right, by default only one Y Axis is used
257 if 'yaxis' in kwargs and kwargs['yaxis']:
258 data_keyvalue['yAxis'] = kwargs['yaxis']
259 else:
260 if self.model != 'pieChart':
261 data_keyvalue['yAxis'] = '1'
263 if 'bar' in kwargs and kwargs['bar']:
264 data_keyvalue['bar'] = 'true'
266 if 'disabled' in kwargs and kwargs['disabled']:
267 data_keyvalue['disabled'] = 'true'
269 if 'color' in kwargs and kwargs['color']:
270 data_keyvalue['color'] = kwargs['color']
272 if extra:
273 if self.model == 'pieChart':
274 if 'color_list' in extra and extra['color_list']:
275 self.color_list = extra['color_list']
277 if extra.get('date_format'):
278 self.charttooltip_dateformat = extra['date_format']
280 if extra.get('tooltip'):
281 self.custom_tooltip_flag = True
283 if self.model != 'pieChart':
284 _start = extra['tooltip']['y_start']
285 _end = extra['tooltip']['y_end']
286 _start = ("'" + str(_start) + "' + ") if _start else ''
287 _end = (" + '" + str(_end) + "'") if _end else ''
289 if self.model == 'pieChart':
290 _start = extra['tooltip']['y_start']
291 _end = extra['tooltip']['y_end']
292 _start = ("'" + str(_start) + "' + ") if _start else ''
293 _end = (" + '" + str(_end) + "'") if _end else ''
295 # Increment series counter & append
296 self.serie_no += 1
297 self.series.append(data_keyvalue)
299 def add_chart_extras(self, extras):
300 """
301 Use this method to add extra d3 properties to your chart.
302 For example, you want to change the text color of the graph::
304 chart = pieChart(name='pieChart', color_category='category20c', height=400, width=400)
306 xdata = ["Orange", "Banana", "Pear", "Kiwi", "Apple", "Strawberry", "Pineapple"]
307 ydata = [3, 4, 0, 1, 5, 7, 3]
309 extra_serie = {"tooltip": {"y_start": "", "y_end": " cal"}}
310 chart.add_serie(y=ydata, x=xdata, extra=extra_serie)
312 The above code will create graph with a black text, the following will change it::
314 text_white="d3.selectAll('#pieChart text').style('fill', 'white');"
315 chart.add_chart_extras(text_white)
317 The above extras will be appended to the java script generated.
319 Alternatively, you can use the following initialization::
321 chart = pieChart(name='pieChart',
322 color_category='category20c',
323 height=400, width=400,
324 extras=text_white)
325 """
326 self.extras = extras
328 def set_graph_height(self, height):
329 """Set Graph height"""
330 self.height = str(height)
332 def set_graph_width(self, width):
333 """Set Graph width"""
334 self.width = str(width)
336 def set_containerheader(self, containerheader):
337 """Set containerheader"""
338 self.containerheader = containerheader
340 def set_date_flag(self, date_flag=False):
341 """Set date flag"""
342 self.date_flag = date_flag
344 def set_custom_tooltip_flag(self, custom_tooltip_flag):
345 """Set custom_tooltip_flag & date_flag"""
346 self.custom_tooltip_flag = custom_tooltip_flag
348 def __str__(self):
349 """return htmlcontent"""
350 self.buildhtml()
351 return self.htmlcontent
353 def buildcontent(self):
354 """Build HTML content only, no header or body tags. To be useful this
355 will usually require the attribute `jquery_on_ready` to be set which
356 will wrap the js in $(function(){<regular_js>};)
357 """
358 self.buildcontainer()
359 # if the subclass has a method buildjs this method will be
360 # called instead of the method defined here
361 # when this subclass method is entered it does call
362 # the method buildjschart defined here
363 self.buildjschart()
364 self.htmlcontent = self.template_content_nvd3.render(chart=self)
366 def buildhtml(self):
367 """Build the HTML page
368 Create the htmlheader with css / js
369 Create html page
370 Add Js code for nvd3
371 """
372 self.buildcontent()
373 self.content = self.htmlcontent
374 self.htmlcontent = self.template_page_nvd3.render(chart=self)
376 # this is used by django-nvd3
377 def buildhtmlheader(self):
378 """generate HTML header content"""
379 self.htmlheader = ''
380 # If the JavaScript assets have already been injected, don't bother re-sourcing them.
381 global _js_initialized
382 if '_js_initialized' not in globals() or not _js_initialized:
383 for css in self.header_css:
384 self.htmlheader += css
385 for js in self.header_js:
386 self.htmlheader += js
388 def buildcontainer(self):
389 """generate HTML div"""
390 if self.container:
391 return
393 # Create SVG div with style
394 if self.width:
395 if self.width[-1] != '%':
396 self.style += 'width:%spx;' % self.width
397 else:
398 self.style += 'width:%s;' % self.width
399 if self.height:
400 if self.height[-1] != '%':
401 self.style += 'height:%spx;' % self.height
402 else:
403 self.style += 'height:%s;' % self.height
404 if self.style:
405 self.style = 'style="%s"' % self.style
407 self.container = self.containerheader + \
408 '<div id="%s"><svg %s></svg></div>\n' % (self.name, self.style)
410 def buildjschart(self):
411 """generate javascript code for the chart"""
412 self.jschart = ''
414 # Include data
415 self.series_js = json.dumps(self.series)
417 def create_x_axis(self, name, label=None, format=None, date=False, custom_format=False):
418 """Create X-axis"""
419 axis = {}
420 if custom_format and format:
421 axis['tickFormat'] = format
422 elif format:
423 if format == 'AM_PM':
424 axis['tickFormat'] = "function(d) { return get_am_pm(parseInt(d)); }"
425 else:
426 axis['tickFormat'] = "d3.format(',%s')" % format
428 if label:
429 axis['axisLabel'] = "'" + label + "'"
431 # date format : see https://github.com/mbostock/d3/wiki/Time-Formatting
432 if date:
433 self.dateformat = format
434 axis['tickFormat'] = ("function(d) { return d3.time.format('%s')"
435 "(new Date(parseInt(d))) }\n"
436 "" % self.dateformat)
437 # flag is the x Axis is a date
438 if name[0] == 'x':
439 self.x_axis_date = True
441 # Add new axis to list of axis
442 self.axislist[name] = axis
444 # Create x2Axis if focus_enable
445 if name == "xAxis" and self.focus_enable:
446 self.axislist['x2Axis'] = axis
448 def create_y_axis(self, name, label=None, format=None, custom_format=False):
449 """
450 Create Y-axis
451 """
452 axis = {}
454 if custom_format and format:
455 axis['tickFormat'] = format
456 elif format:
457 axis['tickFormat'] = "d3.format(',%s')" % format
459 if label:
460 axis['axisLabel'] = "'" + label + "'"
462 # Add new axis to list of axis
463 self.axislist[name] = axis
466class TemplateMixin(object):
467 """
468 A mixin that override buildcontent. Instead of building the complex
469 content template we exploit Jinja2 inheritance. Thus each chart class
470 renders it's own chart template which inherits from content.html
471 """
472 def buildcontent(self):
473 """Build HTML content only, no header or body tags. To be useful this
474 will usually require the attribute `jquery_on_ready` to be set which
475 will wrap the js in $(function(){<regular_js>};)
476 """
477 self.buildcontainer()
478 # if the subclass has a method buildjs this method will be
479 # called instead of the method defined here
480 # when this subclass method is entered it does call
481 # the method buildjschart defined here
482 self.buildjschart()
483 self.htmlcontent = self.template_chart_nvd3.render(chart=self)
486def _main():
487 """
488 Parse options and process commands
489 """
490 # Parse arguments
491 usage = "usage: nvd3.py [options]"
492 parser = OptionParser(usage=usage,
493 version=("python-nvd3 - Charts generator with "
494 "nvd3.js and d3.js"))
495 parser.add_option("-q", "--quiet",
496 action="store_false", dest="verbose", default=True,
497 help="don't print messages to stdout")
499 (options, args) = parser.parse_args()
502if __name__ == '__main__':
503 _main()