~danieljabailey/inkscape/arc_node_editor

« back to all changes in this revision

Viewing changes to share/extensions/nicechart.py

  • Committer: Martin Owens
  • Date: 2016-02-27 17:38:36 UTC
  • Revision ID: doctormo@gmail.com-20160227173836-448wc1eyir6qizss
Remove Maren from AUTHORS (on request) and add nicecharts from upstream where it was abandoned

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/env python
 
2
# -*- coding: utf-8 -*-
 
3
#  nicechart.py
 
4
#
 
5
#  Copyright 2011-2016
 
6
#  
 
7
#  Christoph Sterz 
 
8
#  Florian Weber
 
9
#  Maren Hachmann
 
10
#  
 
11
#  This program is free software; you can redistribute it and/or modify
 
12
#  it under the terms of the GNU General Public License as published by
 
13
#  the Free Software Foundation; either version 3 of the License, or
 
14
#  (at your option) any later version.
 
15
#  
 
16
#  This program is distributed in the hope that it will be useful,
 
17
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
18
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
19
#  GNU General Public License for more details.
 
20
#  
 
21
#  You should have received a copy of the GNU General Public License
 
22
#  along with this program; if not, write to the Free Software
 
23
#  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 
24
#  MA 02110-1301, USA.
 
25
#  
 
26
 
 
27
# TODO / Ideas: 
 
28
# allow negative values for bar charts
 
29
# show values for stacked bar charts
 
30
# don't create a new layer for each chart, but a normal group
 
31
# correct bar height for stacked bars (it's only half as high as it should be, double)
 
32
# adjust position of heading
 
33
# use aliasing workaround for stacked bars (e.g. let the rectangles overlap)
 
34
 
 
35
# Example CSV file contents:
 
36
'''
 
37
Month;1978;1979;1980;1981
 
38
January;2;1,3;0.1;2.3
 
39
February;6.5;2.4;1.2;6.1
 
40
March;7.4;6.7;7.9;4.7
 
41
April;7.7;6.4;8.2;8.9
 
42
May;10.9;11.7;18.7;11.1
 
43
June;12.6;14.2;14.7;14.7
 
44
July;16.5;15.5;17.5;15.1
 
45
August;15.9;15.4;14.6;16.6
 
46
September;14;14.5;13.2;15.3
 
47
October;11.9;13.9;11.5;9.2
 
48
November;6.7;8.5;7;6.6
 
49
December;6.4;2.2;6.3;3.5
 
50
'''
 
51
# The extension creates one chart for a single value column in one go,
 
52
# e.g. chart all temperatures for all months of the year 1978 into one chart.
 
53
# (for this, select column 0 for labels and column 1 for values).
 
54
# "1978" etc. can be used as heading (Need not be numeric. If not used delete the heading line.)
 
55
# Month names can be used as labels
 
56
# Values can be shown, in addition to labels (doesn't work with stacked bar charts)
 
57
# Values can contain commas as decimal separator, as long as delimiter isn't comma
 
58
# Negative values are not yet supported.
 
59
 
 
60
 
 
61
import re
 
62
import sys
 
63
import math
 
64
import inkex
 
65
 
 
66
from simplestyle import *
 
67
 
 
68
#www.sapdesignguild.org/goodies/diagram_guidelines/color_palettes.html#mss
 
69
COLOUR_TABLE = {
 
70
  "red": ["#460101", "#980101", "#d40000", "#f44800", "#fb8b00", "#eec73e", "#d9bb7a", "#fdd99b"],
 
71
  "blue": ["#000442", "#0F1781", "#252FB7", "#3A45E1", "#656DDE", "#8A91EC"],
 
72
  "gray": ["#222222", "#444444", "#666666", "#888888", "#aaaaaa", "#cccccc", "#eeeeee"],
 
73
  "contrast": ["#0000FF", "#FF0000", "#00FF00", "#CF9100", "#FF00FF", "#00FFFF"],
 
74
  "sap": ["#f8d753", "#5c9746", "#3e75a7", "#7a653e", "#e1662a", "#74796f", "#c4384f",
 
75
          "#fff8a3", "#a9cc8f", "#b2c8d9", "#bea37a", "#f3aa79", "#b5b5a9", "#e6a5a5"]
 
76
}
 
77
 
 
78
def get_color_scheme(name="default"):
 
79
    return COLOUR_TABLE.get(name.lower(), COLOUR_TABLE['red'])
 
80
 
 
81
 
 
82
class NiceChart(inkex.Effect):
 
83
    """
 
84
    Inkscape extension that can draw pie charts and bar charts 
 
85
    (stacked, single, horizontally or vertically) 
 
86
    with optional drop shadow, from a csv file or from pasted text
 
87
    """
 
88
    
 
89
    def __init__(self):
 
90
        """
 
91
        Constructor.
 
92
        Defines the "--what" option of a script.
 
93
        """
 
94
        # Call the base class constructor.
 
95
        inkex.Effect.__init__(self)
 
96
        
 
97
        # Define string option "--what" with "-w" shortcut and default chart values.
 
98
        self.OptionParser.add_option('-w', '--what', action='store',
 
99
              type='string', dest='what', default='22,11,67',
 
100
              help='Chart Values')
 
101
            
 
102
        # Define string option "--type" with "-t" shortcut.
 
103
        self.OptionParser.add_option("-t", "--type", action="store",
 
104
              type="string", dest="type", default='',
 
105
              help="Chart Type")
 
106
        
 
107
        # Define bool option "--blur" with "-b" shortcut.
 
108
        self.OptionParser.add_option("-b", "--blur", action="store",
 
109
              type="inkbool", dest="blur", default='True',
 
110
              help="Blur Type")
 
111
        
 
112
        # Define string option "--file" with "-f" shortcut.
 
113
        self.OptionParser.add_option("-f", "--filename", action="store",
 
114
              type="string", dest="filename", default='',
 
115
              help="Name of File")
 
116
        
 
117
        # Define string option "--input_type" with "-i" shortcut.
 
118
        self.OptionParser.add_option("-i", "--input_type", action="store",
 
119
              type="string", dest="input_type", default='file',
 
120
              help="Chart Type")
 
121
        
 
122
        # Define string option "--delimiter" with "-d" shortcut.
 
123
        self.OptionParser.add_option("-d", "--delimiter", action="store",
 
124
              type="string", dest="csv_delimiter", default=';',
 
125
              help="delimiter")
 
126
              
 
127
        # Define string option "--colors" with "-c" shortcut.
 
128
        self.OptionParser.add_option("-c", "--colors", action="store",
 
129
              type="string", dest="colors", default='default',
 
130
              help="color-scheme")
 
131
 
 
132
        # Define string option "--colors_override"
 
133
        self.OptionParser.add_option("", "--colors_override", action="store",
 
134
              type="string", dest="colors_override", default='',
 
135
              help="color-scheme-override")
 
136
        
 
137
 
 
138
        self.OptionParser.add_option("", "--reverse_colors", action="store",
 
139
              type="inkbool", dest="reverse_colors", default='False',
 
140
              help="reverse color-scheme")
 
141
        
 
142
        self.OptionParser.add_option("-k", "--col_key", action="store",
 
143
              type="int", dest="col_key", default='0',
 
144
              help="column that contains the keys")
 
145
        
 
146
        
 
147
        self.OptionParser.add_option("-v", "--col_val", action="store",
 
148
              type="int", dest="col_val", default='1',
 
149
              help="column that contains the values")
 
150
              
 
151
        self.OptionParser.add_option("", "--encoding", action="store",
 
152
              type="string", dest="encoding", default='utf-8',
 
153
              help="encoding of the CSV file, e.g. utf-8")
 
154
        
 
155
        self.OptionParser.add_option("", "--headings", action="store",
 
156
              type="inkbool", dest="headings", default='False',
 
157
              help="the first line of the CSV file consists of headings for the columns")
 
158
              
 
159
        self.OptionParser.add_option("-r", "--rotate", action="store",
 
160
              type="inkbool", dest="rotate", default='False',
 
161
              help="Draw barchart horizontally")
 
162
            
 
163
        self.OptionParser.add_option("-W", "--bar-width", action="store",
 
164
            type="int", dest="bar_width", default='10',
 
165
            help="width of bars")
 
166
        
 
167
        self.OptionParser.add_option("-p", "--pie-radius", action="store",
 
168
            type="int", dest="pie_radius", default='100',
 
169
            help="radius of pie-charts")
 
170
            
 
171
        self.OptionParser.add_option("-H", "--bar-height", action="store",
 
172
            type="int", dest="bar_height", default='100',
 
173
            help="height of bars")
 
174
            
 
175
        self.OptionParser.add_option("-O", "--bar-offset", action="store",
 
176
            type="int", dest="bar_offset", default='5',
 
177
            help="distance between bars")
 
178
            
 
179
        self.OptionParser.add_option("", "--stroke-width", action="store",
 
180
            type="float", dest="stroke_width", default='1')
 
181
            
 
182
        self.OptionParser.add_option("-o", "--text-offset", action="store",
 
183
            type="int", dest="text_offset", default='5',
 
184
            help="distance between bar and descriptions")
 
185
        
 
186
        self.OptionParser.add_option("", "--heading-offset", action="store",
 
187
            type="int", dest="heading_offset", default='50',
 
188
            help="distance between chart and chart title")
 
189
        
 
190
        self.OptionParser.add_option("", "--segment-overlap", action="store",
 
191
            type="inkbool", dest="segment_overlap", default='False',
 
192
            help="work around aliasing effects by letting pie chart segments overlap")
 
193
            
 
194
        self.OptionParser.add_option("-F", "--font", action="store",
 
195
            type="string", dest="font", default='sans-serif',
 
196
            help="font of description")
 
197
            
 
198
        self.OptionParser.add_option("-S", "--font-size", action="store",
 
199
            type="int", dest="font_size", default='10',
 
200
            help="font size of description")
 
201
        
 
202
        self.OptionParser.add_option("-C", "--font-color", action="store",
 
203
            type="string", dest="font_color", default='black',
 
204
            help="font color of description")
 
205
        #Dummy:
 
206
        self.OptionParser.add_option("","--input_sections")
 
207
 
 
208
        self.OptionParser.add_option("-V", "--show_values", action="store",
 
209
            type="inkbool", dest="show_values", default='False',
 
210
            help="Show values in chart")
 
211
    
 
212
    def effect(self):
 
213
        """
 
214
        Effect behaviour.
 
215
        Overrides base class' method and inserts a nice looking chart into SVG document.
 
216
        """
 
217
        # Get script's "--what" option value and process the data type --- i concess the if term is a little bit of magic
 
218
        what = self.options.what
 
219
        keys = []
 
220
        values = []
 
221
        orig_values = []
 
222
        keys_present = True
 
223
        pie_abs = False
 
224
        cnt = 0
 
225
        csv_file_name = self.options.filename
 
226
        csv_delimiter = self.options.csv_delimiter
 
227
        input_type = self.options.input_type
 
228
        col_key = self.options.col_key
 
229
        col_val = self.options.col_val
 
230
        show_values = self.options.show_values
 
231
        encoding = self.options.encoding.strip() or 'utf-8'
 
232
        headings = self.options.headings
 
233
        heading_offset = self.options.heading_offset
 
234
        
 
235
        if input_type == "\"file\"":
 
236
            csv_file = open(csv_file_name, "r")
 
237
            
 
238
            for linenum, line in enumerate(csv_file):
 
239
                value = line.decode(encoding).split(csv_delimiter)
 
240
                #make sure that there is at least one value (someone may want to use it as description)
 
241
                if len(value) >= 1:
 
242
                    # allow to parse headings as strings
 
243
                    if linenum == 0 and headings:
 
244
                        heading = value[col_val]
 
245
                    else:
 
246
                        keys.append(value[col_key])
 
247
                        # replace comma decimal separator from file by colon, 
 
248
                        # to avoid file editing for people whose programs output
 
249
                        # values with comma
 
250
                        values.append(float(value[col_val].replace(",",".")))
 
251
            csv_file.close()
 
252
            
 
253
        elif input_type == "\"direct_input\"":
 
254
            what = re.findall("([A-Z|a-z|0-9]+:[0-9]+\.?[0-9]*)", what)
 
255
            for value in what:
 
256
                value = value.split(":")
 
257
                keys.append(value[0])
 
258
                values.append(float(value[1]))
 
259
 
 
260
        # warn about negative values (not yet supported)
 
261
        for value in values:
 
262
            if value < 0:
 
263
              inkex.errormsg("Negative values are currently not supported!")
 
264
              return
 
265
 
 
266
        # Get script's "--type" option value.
 
267
        charttype = self.options.type
 
268
 
 
269
        if charttype == "pie_abs":
 
270
            pie_abs = True
 
271
            charttype = "pie"
 
272
 
 
273
        # Get access to main SVG document element and get its dimensions.
 
274
        svg = self.document.getroot()
 
275
        
 
276
        # Get the page attibutes:
 
277
        width  = self.getUnittouu(svg.get('width'))
 
278
        height = self.getUnittouu(svg.attrib['height'])
 
279
        
 
280
        # Create a new layer.
 
281
        layer = inkex.etree.SubElement(svg, 'g')
 
282
        layer.set(inkex.addNS('label', 'inkscape'), 'Chart-Layer: %s' % (what))
 
283
        layer.set(inkex.addNS('groupmode', 'inkscape'), 'layer')
 
284
        
 
285
        # Check if a drop shadow should be drawn:
 
286
        draw_blur = self.options.blur
 
287
        
 
288
        if draw_blur:
 
289
            # Get defs of Document
 
290
            defs = self.xpathSingle('/svg:svg//svg:defs')
 
291
            if defs == None:
 
292
                defs = inkex.etree.SubElement(self.document.getroot(), inkex.addNS('defs', 'svg'))
 
293
            
 
294
            # Create new Filter
 
295
            filt = inkex.etree.SubElement(defs,inkex.addNS('filter', 'svg'))
 
296
            filtId = self.uniqueId('filter')
 
297
            self.filtId = 'filter:url(#%s);' % filtId
 
298
            for k, v in [('id', filtId), ('height', "3"),
 
299
                         ('width', "3"),
 
300
                         ('x', '-0.5'), ('y', '-0.5')]:
 
301
                filt.set(k, v)
 
302
            
 
303
            # Append Gaussian Blur to that Filter
 
304
            fe = inkex.etree.SubElement(filt, inkex.addNS('feGaussianBlur', 'svg'))
 
305
            fe.set('stdDeviation', "1.1")
 
306
        
 
307
        # Set Default Colors
 
308
        self.options.colors_override.strip()
 
309
        if len(self.options.colors_override) > 0:
 
310
            colors = self.options.colors_override
 
311
        else:
 
312
            colors = self.options.colors
 
313
 
 
314
        if colors[0].isalpha():
 
315
            colors = get_color_scheme(colors)
 
316
        else:
 
317
            colors = re.findall("(#[0-9a-fA-F]{6})", colors)
 
318
            #to be sure we create a fallback:
 
319
            if len(colors) == 0:
 
320
                colors = get_color_scheme()
 
321
        
 
322
        color_count = len(colors)
 
323
        
 
324
        if self.options.reverse_colors:
 
325
            colors.reverse()
 
326
        
 
327
        # Those values should be self-explanatory:
 
328
        bar_height = self.options.bar_height
 
329
        bar_width = self.options.bar_width
 
330
        bar_offset = self.options.bar_offset
 
331
        # offset of the description in stacked-bar-charts:
 
332
        # stacked_bar_text_offset=self.options.stacked_bar_text_offset
 
333
        text_offset = self.options.text_offset
 
334
        # prevents ugly aliasing effects between pie chart segments by overlapping
 
335
        segment_overlap = self.options.segment_overlap
 
336
        
 
337
        # get font
 
338
        font = self.options.font
 
339
        font_size = self.options.font_size
 
340
        font_color = self.options.font_color
 
341
        
 
342
        # get rotation
 
343
        rotate = self.options.rotate
 
344
        
 
345
        pie_radius = self.options.pie_radius
 
346
        stroke_width = self.options.stroke_width
 
347
 
 
348
        if charttype == "bar":
 
349
        #########
 
350
        ###BAR###
 
351
        #########
 
352
            
 
353
            # iterate all values, use offset to draw the bars in different places
 
354
            offset = 0
 
355
            color = 0
 
356
            
 
357
            # Normalize the bars to the largest value
 
358
            try:
 
359
                value_max = max(values)
 
360
            except ValueError:
 
361
                value_max = 0.0
 
362
 
 
363
            for x in range(len(values)):
 
364
                orig_values.append(values[x])
 
365
                values[x] = (values[x]/value_max) * bar_height
 
366
 
 
367
            # Draw Single bars with their shadows
 
368
            for value in values:
 
369
                
 
370
                # draw drop shadow, if necessary
 
371
                if draw_blur:
 
372
                    # Create shadow element
 
373
                    shadow = inkex.etree.Element(inkex.addNS("rect", "svg"))
 
374
                    # Set chart position to center of document. Make it horizontal or vertical
 
375
                    if not rotate:
 
376
                        shadow.set('x', str(width/2 + offset + 1))
 
377
                        shadow.set('y', str(height/2 - int(value) + 1))
 
378
                        shadow.set("width", str(bar_width))
 
379
                        shadow.set("height", str(int(value)))
 
380
                    else:
 
381
                        shadow.set('y', str(width/2 + offset + 1))
 
382
                        shadow.set('x', str(height/2 + 1))
 
383
                        shadow.set("height", str(bar_width))
 
384
                        shadow.set("width", str(int(value)))
 
385
                        
 
386
                    # Set shadow blur (connect to filter object in xml path)
 
387
                    shadow.set("style", "filter:url(#filter)")
 
388
                
 
389
                # Create rectangle element
 
390
                rect = inkex.etree.Element(inkex.addNS('rect', 'svg'))
 
391
                
 
392
                # Set chart position to center of document.
 
393
                if not rotate:
 
394
                    rect.set('x', str(width/2 + offset))
 
395
                    rect.set('y', str(height/2 - int(value)))
 
396
                    rect.set("width", str(bar_width))
 
397
                    rect.set("height", str(int(value)))
 
398
                else:
 
399
                    rect.set('y', str(width/2 + offset))
 
400
                    rect.set('x', str(height/2))
 
401
                    rect.set("height", str(bar_width))
 
402
                    rect.set("width", str(int(value)))
 
403
                    
 
404
                rect.set("style", "fill:" + colors[color % color_count])
 
405
                
 
406
                # If keys are given, create text elements
 
407
                if keys_present:
 
408
                    text = inkex.etree.Element(inkex.addNS('text', 'svg'))
 
409
                    if not rotate: #=vertical
 
410
                        text.set("transform", "matrix(0,-1,1,0,0,0)")
 
411
                        #y after rotation:
 
412
                        text.set("x", "-" + str(height/2 + text_offset)) 
 
413
                        #x after rotation:
 
414
                        text.set("y", str(width/2 + offset + bar_width/2 + font_size/3))
 
415
                    else: #=horizontal
 
416
                        text.set("y", str(width/2 + offset + bar_width/2 + font_size/3))
 
417
                        text.set("x", str(height/2 - text_offset))
 
418
                    
 
419
                    text.set("style", "font-size:" + str(font_size)\
 
420
                           + "px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:"\
 
421
                           + font + ";-inkscape-font-specification:Bitstream Charter;text-align:end;text-anchor:end;fill:"\
 
422
                           + font_color)
 
423
 
 
424
                    text.text = keys[cnt]
 
425
 
 
426
                # Increase Offset and Color
 
427
                #offset=offset+bar_width+bar_offset
 
428
                color = (color + 1) % 8
 
429
                # Connect elements together.
 
430
                if draw_blur:
 
431
                    layer.append(shadow)
 
432
                layer.append(rect)
 
433
                if keys_present:
 
434
                    layer.append(text)
 
435
                    
 
436
                if show_values:
 
437
                    vtext = inkex.etree.Element(inkex.addNS('text', 'svg'))
 
438
                    if not rotate: #=vertical
 
439
                        vtext.set("transform", "matrix(0,-1,1,0,0,0)")
 
440
                        #y after rotation:
 
441
                        vtext.set("x", "-"+str(height/2+text_offset-value-text_offset-text_offset)) 
 
442
                        #x after rotation:
 
443
                        vtext.set("y", str(width/2+offset+bar_width/2+font_size/3))
 
444
                    else: #=horizontal
 
445
                        vtext.set("y", str(width/2+offset+bar_width/2+font_size/3))
 
446
                        vtext.set("x", str(height/2-text_offset+value+text_offset+text_offset))
 
447
 
 
448
                    vtext.set("style", "font-size:"+str(font_size)\
 
449
                            + "px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:"\
 
450
                            + font + ";-inkscape-font-specification:Bitstream Charter;text-align:start;text-anchor:start;fill:"\
 
451
                            + font_color)
 
452
 
 
453
                    vtext.text = str(int(orig_values[cnt]))
 
454
                    layer.append(vtext)
 
455
                
 
456
                cnt = cnt+1
 
457
                offset = offset + bar_width + bar_offset
 
458
            
 
459
            # set x position for heading line
 
460
            if not rotate:
 
461
                heading_x = width/2 # TODO: adjust
 
462
            else:
 
463
                heading_x = width/2 # TODO: adjust
 
464
 
 
465
                
 
466
        elif charttype == "pie":
 
467
        #########
 
468
        ###PIE###
 
469
        #########
 
470
            # Iterate all values to draw the different slices
 
471
            color = 0
 
472
            
 
473
            # Create the shadow first (if it should be created):
 
474
            if draw_blur:
 
475
                shadow = inkex.etree.Element(inkex.addNS("circle", "svg"))
 
476
                shadow.set('cx', str(width/2))
 
477
                shadow.set('cy', str(height/2))
 
478
                shadow.set('r', str(pie_radius))
 
479
                shadow.set("style", "filter:url(#filter);fill:#000000")
 
480
                layer.append(shadow)
 
481
            
 
482
            
 
483
            # Add a grey background circle with a light stroke
 
484
            background = inkex.etree.Element(inkex.addNS("circle", "svg"))
 
485
            background.set("cx", str(width/2))
 
486
            background.set("cy", str(height/2))
 
487
            background.set("r", str(pie_radius))
 
488
            background.set("style", "stroke:#ececec;fill:#f9f9f9")
 
489
            layer.append(background)
 
490
            
 
491
            #create value sum in order to divide the slices
 
492
            try:
 
493
                valuesum = sum(values)
 
494
                
 
495
            except ValueError:
 
496
                valuesum = 0
 
497
 
 
498
            if pie_abs:
 
499
                valuesum = 100
 
500
 
 
501
            num_values = len(values)
 
502
 
 
503
            # Set an offsetangle
 
504
            offset = 0
 
505
            
 
506
            # Draw single slices
 
507
            for i in range(num_values):
 
508
                value = values[i]
 
509
                # Calculate the PI-angles for start and end
 
510
                angle = (2*3.141592) / valuesum * float(value)
 
511
                start = offset
 
512
                end = offset + angle
 
513
                
 
514
                # proper overlapping
 
515
                if segment_overlap:
 
516
                    if i != num_values-1:
 
517
                        end += 0.09 # add a 5° overlap
 
518
                    if i == 0:
 
519
                        start -= 0.09 # let the first element overlap into the other direction
 
520
 
 
521
                #then add the slice
 
522
                pieslice = inkex.etree.Element(inkex.addNS("path", "svg"))
 
523
                pieslice.set(inkex.addNS('type', 'sodipodi'), 'arc')
 
524
                pieslice.set(inkex.addNS('cx', 'sodipodi'), str(width/2))
 
525
                pieslice.set(inkex.addNS('cy', 'sodipodi'), str(height/2))
 
526
                pieslice.set(inkex.addNS('rx', 'sodipodi'), str(pie_radius))
 
527
                pieslice.set(inkex.addNS('ry', 'sodipodi'), str(pie_radius))
 
528
                pieslice.set(inkex.addNS('start', 'sodipodi'), str(start))
 
529
                pieslice.set(inkex.addNS('end', 'sodipodi'), str(end))
 
530
                pieslice.set("style", "fill:"+ colors[color % color_count] + ";stroke:none;fill-opacity:1")
 
531
                
 
532
                #If text is given, draw short paths and add the text
 
533
                if keys_present:
 
534
                    path = inkex.etree.Element(inkex.addNS("path", "svg"))
 
535
                    path.set("d", "m " 
 
536
                                + str((width/2) + pie_radius * math.cos(angle/2 + offset)) + "," 
 
537
                                + str((height/2) + pie_radius * math.sin(angle/2 + offset)) + " " 
 
538
                                + str((text_offset - 2) * math.cos(angle/2 + offset)) + "," 
 
539
                                + str((text_offset - 2) * math.sin(angle/2 + offset)))
 
540
                    
 
541
                    path.set("style", "fill:none;stroke:" 
 
542
                                    + font_color + ";stroke-width:" + str(stroke_width) 
 
543
                                    + "px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1")
 
544
                    layer.append(path)
 
545
                    text = inkex.etree.Element(inkex.addNS('text', 'svg'))
 
546
                    text.set("x", str((width/2) + (pie_radius + text_offset) * math.cos(angle/2 + offset)))
 
547
                    text.set("y", str((height/2) + (pie_radius + text_offset) * math.sin(angle/2 + offset) + font_size/3))
 
548
                    textstyle = "font-size:" + str(font_size) \
 
549
                                + "px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:" \
 
550
                                + font + ";-inkscape-font-specification:Bitstream Charter;fill:" + font_color 
 
551
                    # check if it is right or left of the Pie
 
552
                    if math.cos(angle/2 + offset) > 0:
 
553
                        text.set("style", textstyle)
 
554
                    else:
 
555
                        text.set("style", textstyle + ";text-align:end;text-anchor:end")
 
556
                    text.text = keys[cnt]
 
557
                    if show_values:
 
558
                        text.text = text.text + "(" + str(values[cnt])
 
559
 
 
560
                        if pie_abs:
 
561
                            text.text = text.text + " %"
 
562
                        
 
563
                        text.text = text.text + ")"
 
564
                    
 
565
                    cnt = cnt + 1
 
566
                    layer.append(text)
 
567
                
 
568
                # increase the rotation-offset and the colorcycle-position
 
569
                offset = offset + angle
 
570
                color = (color + 1) % 8
 
571
                
 
572
                # append the objects to the extension-layer
 
573
                layer.append(pieslice)
 
574
                
 
575
            # set x position for heading line
 
576
            heading_x = width/2 - pie_radius # TODO: adjust
 
577
                
 
578
        elif charttype == "stbar":
 
579
        #################
 
580
        ###STACKED BAR###
 
581
        #################
 
582
            # Iterate over all values to draw the different slices
 
583
            color = 0
 
584
            
 
585
            #create value sum in order to divide the bars
 
586
            try:
 
587
                valuesum = sum(values)
 
588
            except ValueError:
 
589
                valuesum = 0.0
 
590
 
 
591
            for value in values:
 
592
                valuesum = valuesum + float(value)
 
593
            
 
594
            # Init offset
 
595
            offset = 0
 
596
            
 
597
            if draw_blur:
 
598
                # Create rectangle element
 
599
                shadow = inkex.etree.Element(inkex.addNS("rect", "svg"))
 
600
                # Set chart position to center of document.
 
601
                if not rotate:
 
602
                    shadow.set('x', str(width/2))
 
603
                    shadow.set('y', str(height/2 - bar_height/2)) 
 
604
                else:
 
605
                    shadow.set('x', str(width/2))
 
606
                    shadow.set('y', str(height/2))
 
607
                # Set rectangle properties
 
608
                if not rotate:
 
609
                    shadow.set("width", str(bar_width))
 
610
                    shadow.set("height", str(bar_height/2))
 
611
                else:
 
612
                    shadow.set("width",str(bar_height/2))
 
613
                    shadow.set("height", str(bar_width))
 
614
                # Set shadow blur (connect to filter object in xml path)
 
615
                shadow.set("style", "filter:url(#filter)")
 
616
                layer.append(shadow)
 
617
            
 
618
            i = 0
 
619
            # Draw Single bars
 
620
            for value in values:
 
621
                
 
622
                # Calculate the individual heights normalized on 100units
 
623
                normedvalue = (bar_height / valuesum) * float(value)
 
624
                
 
625
                # Create rectangle element
 
626
                rect = inkex.etree.Element(inkex.addNS('rect', 'svg'))
 
627
                
 
628
                # Set chart position to center of document.
 
629
                if not rotate:
 
630
                    rect.set('x', str(width / 2 ))
 
631
                    rect.set('y', str(height / 2 - offset - normedvalue))
 
632
                else:
 
633
                    rect.set('x', str(width / 2 + offset ))
 
634
                    rect.set('y', str(height / 2 ))
 
635
                # Set rectangle properties
 
636
                if not rotate:
 
637
                    rect.set("width", str(bar_width))
 
638
                    rect.set("height", str(normedvalue))
 
639
                else:
 
640
                    rect.set("height", str(bar_width))
 
641
                    rect.set("width", str(normedvalue))
 
642
                rect.set("style", "fill:" + colors[color % color_count])
 
643
                
 
644
                #If text is given, draw short paths and add the text
 
645
                # TODO: apply overlap workaround for visible gaps in between
 
646
                if keys_present:
 
647
                    if not rotate:
 
648
                        path = inkex.etree.Element(inkex.addNS("path", "svg"))
 
649
                        path.set("d","m " + str((width + bar_width)/2) + ","
 
650
                                    + str(height/2 - offset - (normedvalue / 2)) + " "
 
651
                                    + str(bar_width/2 + text_offset) + ",0")
 
652
                        path.set("style", "fill:none;stroke:" + font_color
 
653
                                        + ";stroke-width:" + str(stroke_width)
 
654
                                        + "px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1")
 
655
                        layer.append(path)
 
656
                        text = inkex.etree.Element(inkex.addNS('text', 'svg'))
 
657
                        text.set("x", str(width/2 + bar_width + text_offset + 1))
 
658
                        text.set("y", str(height/ 2 - offset + font_size/3 - (normedvalue/2)))
 
659
                        text.set("style", "font-size:" + str(font_size)
 
660
                                 + "px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:"
 
661
                                 + font + ";-inkscape-font-specification:Bitstream Charter;fill:" + font_color)
 
662
                        text.text = keys[cnt]
 
663
                        cnt = cnt + 1
 
664
                        layer.append(text)
 
665
                    else:
 
666
                        path = inkex.etree.Element(inkex.addNS("path", "svg"))
 
667
                        path.set("d","m " + str((width)/2 + offset + normedvalue/2) + ","
 
668
                                    + str(height / 2 + bar_width/2) + " 0," 
 
669
                                    + str(bar_width/2 + (font_size * i) + text_offset)) #line
 
670
                        path.set("style", "fill:none;stroke:" + font_color
 
671
                                 + ";stroke-width:" + str(stroke_width)
 
672
                                 + "px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1")
 
673
                        layer.append(path)
 
674
                        text = inkex.etree.Element(inkex.addNS('text', 'svg'))
 
675
                        text.set("x", str((width)/2 + offset + normedvalue/2 - font_size/3))
 
676
                        text.set("y", str((height/2) + bar_width + (font_size * (i + 1)) + text_offset))
 
677
                        text.set("style", "font-size:" + str(font_size) 
 
678
                                        + "px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:"
 
679
                                        + font + ";-inkscape-font-specification:Bitstream Charter;fill:" + font_color)
 
680
                        text.text = keys[color]
 
681
                        layer.append(text)
 
682
                
 
683
                # Increase Offset and Color
 
684
                offset = offset + normedvalue
 
685
                color = (color + 1) % 8
 
686
                
 
687
                # Draw rectangle
 
688
                layer.append(rect)
 
689
                i += 1
 
690
            
 
691
            # set x position for heading line
 
692
            if not rotate:
 
693
                heading_x = width/2 + offset + normedvalue # TODO: adjust
 
694
            else:
 
695
                heading_x = width/2 + offset + normedvalue # TODO: adjust
 
696
                
 
697
        if headings and input_type == "\"file\"":
 
698
            headingtext = inkex.etree.Element(inkex.addNS('text', 'svg'))
 
699
            headingtext.set("y", str(height/2 + heading_offset))
 
700
            headingtext.set("x", str(heading_x))
 
701
            headingtext.set("style", "font-size:" + str(font_size + 4)\
 
702
                    + "px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:"\
 
703
                    + font + ";-inkscape-font-specification:Bitstream Charter;text-align:end;text-anchor:end;fill:"\
 
704
                    + font_color)
 
705
 
 
706
            headingtext.text = heading
 
707
            layer.append(headingtext)
 
708
    
 
709
    def getUnittouu(self, param):
 
710
        try:
 
711
            return inkex.unittouu(param)
 
712
        except AttributeError:
 
713
            return self.unittouu(param)
 
714
 
 
715
if __name__ == '__main__':
 
716
    # Create effect instance and apply it.
 
717
    effect = NiceChart()
 
718
    effect.affect()