~ubuntu-branches/ubuntu/hoary/gnucash/hoary

« back to all changes in this revision

Viewing changes to src/scm/report/category-barchart.scm

  • Committer: Bazaar Package Importer
  • Author(s): James A. Treacy
  • Date: 2002-03-16 14:14:59 UTC
  • Revision ID: james.westby@ubuntu.com-20020316141459-wtkyyrpfovryhl1s
Tags: upstream-1.6.6
ImportĀ upstreamĀ versionĀ 1.6.6

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
2
;; category-barchart.scm: shows barchart of income/expense categories
 
3
;;  
 
4
;; By Christian Stimming <stimming@tu-harburg.de>
 
5
;;
 
6
;; This program is free software; you can redistribute it and/or    
 
7
;; modify it under the terms of the GNU General Public License as   
 
8
;; published by the Free Software Foundation; either version 2 of   
 
9
;; the License, or (at your option) any later version.              
 
10
;;                                                                  
 
11
;; This program is distributed in the hope that it will be useful,  
 
12
;; but WITHOUT ANY WARRANTY; without even the implied warranty of   
 
13
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    
 
14
;; GNU General Public License for more details.                     
 
15
;;                                                                  
 
16
;; You should have received a copy of the GNU General Public License
 
17
;; along with this program; if not, contact:
 
18
;;
 
19
;; Free Software Foundation           Voice:  +1-617-542-5942
 
20
;; 59 Temple Place - Suite 330        Fax:    +1-617-542-2652
 
21
;; Boston, MA  02111-1307,  USA       gnu@gnu.org
 
22
;;
 
23
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
24
 
 
25
;; depends must be outside module scope -- and should eventually go away.
 
26
(gnc:depend "report-html.scm")
 
27
(gnc:depend "date-utilities.scm")
 
28
 
 
29
(define-module (gnucash report category-barchart))
 
30
(use-modules (srfi srfi-1))
 
31
(use-modules (ice-9 slib))
 
32
(require 'printf)
 
33
 
 
34
;; The option names are defined here to 1. save typing and 2. avoid
 
35
;; spelling errors. The *reportnames* are defined here (and not only
 
36
;; once at the very end) because I need them to define the "other"
 
37
;; report, thus needing them twice.
 
38
(define menuname-income (N_ "Income Barchart"))
 
39
(define menuname-expense (N_ "Expense Barchart"))
 
40
(define menuname-assets (N_ "Asset Barchart"))
 
41
(define menuname-liabilities (N_ "Liability Barchart"))
 
42
;; The names are used in the menu
 
43
 
 
44
;; The menu statusbar tips.
 
45
(define menutip-income 
 
46
  (N_ "Shows a barchart with the Income per interval \
 
47
developing over time"))
 
48
(define menutip-expense 
 
49
  (N_ "Shows a barchart with the Expenses per interval \
 
50
developing over time"))
 
51
(define menutip-assets 
 
52
  (N_ "Shows a barchart with the Assets developing over time"))
 
53
(define menutip-liabilities 
 
54
  (N_ "Shows a barchart with the Liabilities \
 
55
developing over time"))
 
56
 
 
57
;; The names here are used 1. for internal identification, 2. as
 
58
;; tab labels, 3. as default for the 'Report name' option which
 
59
;; in turn is used for the printed report title.
 
60
(define reportname-income (N_ "Income Over Time"))
 
61
(define reportname-expense (N_ "Expense Over Time"))
 
62
(define reportname-assets (N_ "Assets Over Time"))
 
63
(define reportname-liabilities (N_ "Liabilities Over Time"))
 
64
 
 
65
;; Option names
 
66
(define optname-from-date (N_ "From"))
 
67
(define optname-to-date (N_ "To"))
 
68
(define optname-stepsize (N_ "Step Size"))
 
69
(define optname-report-currency (N_ "Report's currency"))
 
70
(define optname-price-source (N_ "Price Source"))
 
71
 
 
72
(define optname-accounts (N_ "Accounts"))
 
73
(define optname-levels (N_ "Show Accounts until level"))
 
74
 
 
75
(define optname-fullname (N_ "Show long account names"))
 
76
(define optname-stacked (N_ "Use Stacked Bars"))
 
77
(define optname-slices (N_ "Maximum Bars"))
 
78
(define optname-plot-width (N_ "Plot Width"))
 
79
(define optname-plot-height (N_ "Plot Height"))
 
80
 
 
81
(define (options-generator account-types)
 
82
  (let* ((options (gnc:new-options)) 
 
83
         (add-option 
 
84
          (lambda (new-option)
 
85
            (gnc:register-option options new-option))))
 
86
 
 
87
    ;; General tab
 
88
    (gnc:options-add-date-interval!
 
89
     options gnc:pagename-general
 
90
     optname-from-date optname-to-date "a")
 
91
 
 
92
    (gnc:options-add-interval-choice! 
 
93
     options gnc:pagename-general optname-stepsize "b" 'MonthDelta)
 
94
 
 
95
    (gnc:options-add-currency! 
 
96
     options gnc:pagename-general optname-report-currency "c")
 
97
 
 
98
    (gnc:options-add-price-source! 
 
99
     options gnc:pagename-general
 
100
     optname-price-source "d" 'weighted-average)
 
101
 
 
102
    ;; Accounts tab
 
103
    (add-option
 
104
     (gnc:make-account-list-option
 
105
      gnc:pagename-accounts optname-accounts
 
106
      "a"
 
107
      (N_ "Report on these accounts, if chosen account level allows.")
 
108
      (lambda ()
 
109
        (gnc:filter-accountlist-type 
 
110
         account-types
 
111
         (gnc:group-get-subaccounts (gnc:get-current-group))))
 
112
      (lambda (accounts)
 
113
        (list #t
 
114
              (gnc:filter-accountlist-type account-types accounts)))
 
115
      #t))
 
116
    
 
117
    (gnc:options-add-account-levels! 
 
118
     options gnc:pagename-accounts optname-levels "c" 
 
119
     (N_ "Show accounts to this depth and not further") 
 
120
     2)
 
121
 
 
122
    ;; Display tab
 
123
    (add-option
 
124
     (gnc:make-simple-boolean-option
 
125
      gnc:pagename-display optname-fullname
 
126
      "a" (N_ "Show the full account name in legend?") #f))
 
127
 
 
128
    (add-option
 
129
     (gnc:make-simple-boolean-option
 
130
      gnc:pagename-display optname-stacked
 
131
      "b" 
 
132
      (N_ "Show barchart as stacked barchart? (Guppi>=0.35.4 required)") 
 
133
      #t))
 
134
 
 
135
    (add-option
 
136
     (gnc:make-number-range-option
 
137
      gnc:pagename-display optname-slices
 
138
      "c" (N_ "Maximum number of bars in the chart") 8
 
139
      2 24 0 1))
 
140
 
 
141
    (gnc:options-add-plot-size! 
 
142
     options gnc:pagename-display 
 
143
     optname-plot-width optname-plot-height "c" 400 400)
 
144
 
 
145
    (gnc:options-set-default-section options gnc:pagename-general)
 
146
 
 
147
    options))
 
148
 
 
149
;; This is the rendering function. It accepts a database of options
 
150
;; and generates an object of type <html-document>.  See the file
 
151
;; report-html.txt for documentation; the file report-html.scm
 
152
;; includes all the relevant Scheme code. The option database passed
 
153
;; to the function is one created by the options-generator function
 
154
;; defined above.
 
155
 
 
156
;; FIXME: the exchange rate should change every time interval, of
 
157
;; course, but right now we assume the very last exchange rate to be
 
158
;; constant over the whole report period. Note that this might get
 
159
;; *really* complicated.
 
160
 
 
161
(define (category-barchart-renderer report-obj reportname 
 
162
                                    account-types do-intervals?)
 
163
  ;; A helper functions for looking up option values.
 
164
  (define (get-option section name)
 
165
    (gnc:option-value 
 
166
     (gnc:lookup-option 
 
167
      (gnc:report-options report-obj) section name)))
 
168
  
 
169
  (let ((to-date-tp (gnc:timepair-end-day-time 
 
170
                     (gnc:date-option-absolute-time
 
171
                      (get-option gnc:pagename-general 
 
172
                                  optname-to-date))))
 
173
        (from-date-tp (gnc:timepair-start-day-time 
 
174
                       (gnc:date-option-absolute-time
 
175
                        (get-option gnc:pagename-general 
 
176
                                    optname-from-date))))
 
177
        (interval (get-option gnc:pagename-general optname-stepsize))
 
178
        (report-currency (get-option gnc:pagename-general
 
179
                                     optname-report-currency))
 
180
        (price-source (get-option gnc:pagename-general
 
181
                                  optname-price-source))
 
182
        (report-title (get-option gnc:pagename-general 
 
183
                                  gnc:optname-reportname))
 
184
 
 
185
        (accounts (get-option gnc:pagename-accounts optname-accounts))
 
186
        (account-levels (get-option gnc:pagename-accounts optname-levels))
 
187
        
 
188
        (stacked? (get-option gnc:pagename-display optname-stacked))
 
189
        (show-fullname? (get-option gnc:pagename-display optname-fullname))
 
190
        (max-slices (get-option gnc:pagename-display optname-slices))
 
191
        (height (get-option gnc:pagename-display optname-plot-height))
 
192
        (width (get-option gnc:pagename-display optname-plot-width))
 
193
        
 
194
        (document (gnc:make-html-document))
 
195
        (chart (gnc:make-html-barchart))
 
196
        (topl-accounts (gnc:filter-accountlist-type 
 
197
                        account-types
 
198
                        (gnc:group-get-account-list 
 
199
                         (gnc:get-current-group)))))
 
200
    
 
201
    ;; Returns true if the account a was selected in the account
 
202
    ;; selection option.
 
203
    (define (show-acct? a)
 
204
      (member a accounts))
 
205
 
 
206
    (gnc:debug accounts)
 
207
    (if (not (null? accounts))
 
208
        
 
209
        ;; Define more helper variables.
 
210
        
 
211
        (let* ((commodity-list (gnc:accounts-get-commodities 
 
212
                                (append 
 
213
                                 (gnc:acccounts-get-all-subaccounts accounts)
 
214
                                 accounts)
 
215
                                report-currency))
 
216
               (exchange-fn (gnc:case-exchange-time-fn 
 
217
                             price-source report-currency 
 
218
                             commodity-list to-date-tp))
 
219
               (tree-depth (if (equal? account-levels 'all)
 
220
                               (gnc:get-current-group-depth)
 
221
                               account-levels))
 
222
               ;; This is the list of date intervals to calculate.
 
223
               (dates-list (if do-intervals?
 
224
                               (gnc:make-date-interval-list
 
225
                                (gnc:timepair-start-day-time from-date-tp) 
 
226
                                (gnc:timepair-end-day-time to-date-tp)
 
227
                                (eval interval))
 
228
                               (gnc:make-date-list
 
229
                                (gnc:timepair-end-day-time from-date-tp) 
 
230
                                (gnc:timepair-end-day-time to-date-tp)
 
231
                                (eval interval))))
 
232
               ;; Here the date strings for the x-axis labels are
 
233
               ;; created.
 
234
               (date-string-list
 
235
                (map (lambda (date-list-item)
 
236
                       (gnc:timepair-to-datestring
 
237
                        (if do-intervals?
 
238
                            (car date-list-item)
 
239
                            date-list-item)))
 
240
                     dates-list))
 
241
               (other-anchor "")
 
242
               (all-data '()))
 
243
          
 
244
          ;; Converts a commodity-collector into one single double
 
245
          ;; number, depending on the report currency and the
 
246
          ;; exchange-fn calculated above. Returns a double.
 
247
          (define (collector->double c date)
 
248
            ;; Future improvement: Let the user choose which kind of
 
249
            ;; currency combining she want to be done. 
 
250
            (gnc:numeric-to-double 
 
251
             (gnc:gnc-monetary-amount
 
252
              (gnc:sum-collector-commodity 
 
253
               c report-currency 
 
254
               (lambda (a b) (exchange-fn a b date))))))
 
255
          
 
256
          ;; Calculates the net balance (profit or loss) of an account in
 
257
          ;; the given time interval. date-list-entry is a pair containing
 
258
          ;; the start- and end-date of that interval. If subacct?==#t,
 
259
          ;; the subaccount's balances are included as well. Returns a
 
260
          ;; double, exchanged into the report-currency by the above
 
261
          ;; conversion function, and possibly with reversed sign.
 
262
          (define (get-balance account date-list-entry subacct?)
 
263
            ((if (gnc:account-reverse-balance? account)
 
264
                 - +)
 
265
             (if do-intervals?
 
266
                 (collector->double
 
267
                  (gnc:account-get-comm-balance-interval 
 
268
                   account 
 
269
                   (first date-list-entry) 
 
270
                   (second date-list-entry) subacct?)
 
271
                  (second date-list-entry))
 
272
                 (collector->double
 
273
                  (gnc:account-get-comm-balance-at-date
 
274
                   account date-list-entry subacct?)
 
275
                  date-list-entry))))
 
276
          
 
277
          ;; Creates the <balance-list> to be used in the function
 
278
          ;; below. 
 
279
          (define (account->balance-list account subacct?)
 
280
            (map 
 
281
             (lambda (d) (get-balance account d subacct?))
 
282
             dates-list))
 
283
          
 
284
          ;; Calculates all account's balances. Returns a list of pairs:
 
285
          ;; (<account> <balance-list>), like '((Earnings (10.0 11.2))
 
286
          ;; (Gifts (12.3 14.5))), where each element of <balance-list>
 
287
          ;; is the balance corresponding to one element in
 
288
          ;; <dates-list>.
 
289
          ;;
 
290
          ;; If current-depth >= tree-depth, then the balances are
 
291
          ;; calculated *with* subaccount's balances. Else only the
 
292
          ;; current account is regarded. Note: All accounts in accts
 
293
          ;; and all their subaccounts are processed, but a balances is
 
294
          ;; calculated and returned *only* for those accounts where
 
295
          ;; show-acct? is true. This is necessary because otherwise we
 
296
          ;; would forget an account that is selected but not its
 
297
          ;; parent.
 
298
          (define (traverse-accounts current-depth accts)
 
299
            (if (< current-depth tree-depth)
 
300
                (let ((res '()))
 
301
                  (for-each
 
302
                   (lambda (a)
 
303
                     (begin
 
304
                       (if (show-acct? a)
 
305
                           (set! res 
 
306
                                 (cons (list a (account->balance-list a #f))
 
307
                                       res)))
 
308
                       (set! res (append
 
309
                                  (traverse-accounts
 
310
                                   (+ 1 current-depth)
 
311
                                   (gnc:account-get-immediate-subaccounts a))
 
312
                                  res))))
 
313
                   accts)
 
314
                  res)
 
315
                ;; else (i.e. current-depth == tree-depth)
 
316
                (map
 
317
                 (lambda (a)
 
318
                   (list a (account->balance-list a #t)))
 
319
                 (filter show-acct? accts))))
 
320
          
 
321
          ;; Sort the account list according to the account code field.
 
322
          (set! all-data (sort 
 
323
                          (filter (lambda (l) 
 
324
                                    (not (= 0.0 (apply + (cadr l))))) 
 
325
                                  (traverse-accounts 1 topl-accounts))
 
326
                          (lambda (a b) 
 
327
                            (string<? (gnc:account-get-code (car a))
 
328
                                      (gnc:account-get-code (car b))))))
 
329
          ;; Or rather sort by total amount?
 
330
          ;;(< (apply + (cadr a)) 
 
331
          ;;   (apply + (cadr b))))))
 
332
          ;; Other sort criteria: max. amount, standard deviation of amount,
 
333
          ;; min. amount; ascending, descending. FIXME: Add user options to
 
334
          ;; choose sorting.
 
335
          
 
336
          
 
337
          ;;(gnc:warn "all-data" all-data)
 
338
 
 
339
          ;; Proceed if the data is non-zeros
 
340
          (if 
 
341
           (and (not (null? all-data))
 
342
                (gnc:not-all-zeros (map cadr all-data)))
 
343
           (begin 
 
344
             ;; Set chart title, subtitle etc.
 
345
             (gnc:html-barchart-set-title! chart report-title)
 
346
             (gnc:html-barchart-set-subtitle!
 
347
              chart (sprintf #f
 
348
                             (if do-intervals?
 
349
                                 (_ "%s to %s")
 
350
                                 (_ "Balances %s to %s"))
 
351
                             (gnc:timepair-to-datestring from-date-tp) 
 
352
                             (gnc:timepair-to-datestring to-date-tp)))
 
353
             (gnc:html-barchart-set-width! chart width)
 
354
             (gnc:html-barchart-set-height! chart height)
 
355
             
 
356
             ;; row labels etc.
 
357
             (gnc:html-barchart-set-row-labels! chart date-string-list)
 
358
             ;; FIXME: axis labels are not yet supported by
 
359
             ;; libguppitank.
 
360
             (gnc:html-barchart-set-y-axis-label!
 
361
              chart (gnc:commodity-get-mnemonic report-currency))
 
362
             (gnc:html-barchart-set-row-labels-rotated?! chart #t)
 
363
             (gnc:html-barchart-set-stacked?! chart stacked?)
 
364
             ;; If this is a stacked barchart, then reverse the legend.
 
365
             (gnc:html-barchart-set-legend-reversed?! chart stacked?)
 
366
             
 
367
             ;; If we have too many categories, we sum them into a new
 
368
             ;; 'other' category and add a link to a new report with just
 
369
             ;; those accounts.
 
370
             (if (> (length all-data) max-slices)
 
371
                 (let* ((start (take all-data (- max-slices 1)))
 
372
                        (finish (drop all-data (- max-slices 1)))
 
373
                        (other-sum (map 
 
374
                                    (lambda (l) (apply + l))
 
375
                                    (apply zip (map cadr finish)))))
 
376
                   (set! all-data
 
377
                         (append start
 
378
                                 (list (list (_ "Other") other-sum))))
 
379
                   (let* ((options (gnc:make-report-options reportname))
 
380
                          (id #f))
 
381
                     ;; now copy all the options
 
382
                     (gnc:options-copy-values 
 
383
                      (gnc:report-options report-obj) options)
 
384
                     ;; and set the destination accounts
 
385
                     (gnc:option-set-value
 
386
                      (gnc:lookup-option options gnc:pagename-accounts 
 
387
                                         optname-accounts)
 
388
                      (map car finish))
 
389
                     ;; Set the URL to point to this report.
 
390
                     (set! id (gnc:make-report reportname options))
 
391
                     (set! other-anchor (gnc:report-anchor-text id)))))
 
392
             
 
393
             
 
394
             ;; This adds the data. Note the apply-zip stuff: This
 
395
             ;; transposes the data, i.e. swaps rows and columns. Pretty
 
396
             ;; cool, eh? Courtesy of dave_p.
 
397
             (if (not (null? all-data))
 
398
                 (gnc:html-barchart-set-data! 
 
399
                  chart 
 
400
                  (apply zip (map cadr all-data))))
 
401
             
 
402
             ;; Labels and colors
 
403
             (gnc:html-barchart-set-col-labels!
 
404
              chart (map (lambda (pair)
 
405
                           (if (string? (car pair))
 
406
                               (car pair)
 
407
                               ((if show-fullname?
 
408
                                    gnc:account-get-full-name
 
409
                                    gnc:account-get-name) (car pair))))
 
410
                         all-data))
 
411
             (gnc:html-barchart-set-col-colors! 
 
412
              chart
 
413
              (gnc:assign-colors (length all-data)))
 
414
             
 
415
             ;; set the URLs; the slices are links to other reports
 
416
             (let 
 
417
                 ((urls
 
418
                   (map 
 
419
                    (lambda (pair)
 
420
                      (if 
 
421
                       (string? (car pair))
 
422
                       other-anchor
 
423
                       (let* ((acct (car pair))
 
424
                              (subaccts 
 
425
                               (gnc:account-get-immediate-subaccounts acct)))
 
426
                         (if (null? subaccts)
 
427
                             ;; if leaf-account, make this an anchor
 
428
                             ;; to the register.
 
429
                             (gnc:account-anchor-text acct)
 
430
                             ;; if non-leaf account, make this a link
 
431
                             ;; to another report which is run on the
 
432
                             ;; immediate subaccounts of this account
 
433
                             ;; (and including this account).
 
434
                             (gnc:make-report-anchor
 
435
                              reportname
 
436
                              report-obj
 
437
                              (list
 
438
                               (list gnc:pagename-accounts optname-accounts
 
439
                                     (cons acct subaccts))
 
440
                               (list gnc:pagename-accounts optname-levels
 
441
                                     (+ 1 tree-depth))
 
442
                               (list gnc:pagename-general 
 
443
                                     gnc:optname-reportname
 
444
                                     ((if show-fullname?
 
445
                                          gnc:account-get-full-name
 
446
                                          gnc:account-get-name) acct))))))))
 
447
                    all-data)))
 
448
               (gnc:html-barchart-set-button-1-bar-urls! 
 
449
                chart (append urls urls))
 
450
               ;; The legend urls do the same thing.
 
451
               (gnc:html-barchart-set-button-1-legend-urls! 
 
452
                chart (append urls urls)))
 
453
             
 
454
             (gnc:html-document-add-object! document chart))
 
455
 
 
456
           ;; else if empty data
 
457
           (gnc:html-document-add-object!
 
458
            document
 
459
            (gnc:html-make-empty-data-warning
 
460
             report-title (gnc:report-id report-obj)))))
 
461
        
 
462
        ;; else if no accounts selected
 
463
        (gnc:html-document-add-object! 
 
464
         document 
 
465
         (gnc:html-make-no-account-warning 
 
466
          report-title (gnc:report-id report-obj))))
 
467
    
 
468
    document))
 
469
 
 
470
(for-each 
 
471
 (lambda (l)
 
472
   (gnc:define-report
 
473
    'version 1
 
474
    'name (car l)
 
475
    'menu-path (if (caddr l)
 
476
                   (list gnc:menuname-income-expense)
 
477
                   (list gnc:menuname-asset-liability))
 
478
    'menu-name (cadddr l)
 
479
    'menu-tip (car (cddddr l))
 
480
    'options-generator (lambda () (options-generator (cadr l)))
 
481
    'renderer (lambda (report-obj)
 
482
                (category-barchart-renderer report-obj 
 
483
                                            (car l) 
 
484
                                            (cadr l)
 
485
                                            (caddr l)))))
 
486
 (list 
 
487
  ;; reportname, account-types, do-intervals?, 
 
488
  ;; menu-reportname, menu-tip
 
489
  (list reportname-income '(income) #t menuname-income menutip-income)
 
490
  (list reportname-expense '(expense) #t menuname-expense menutip-expense)
 
491
  (list reportname-assets 
 
492
        '(asset bank cash checking savings money-market 
 
493
                stock mutual-fund currency)
 
494
        #f menuname-assets menutip-assets)
 
495
  (list reportname-liabilities 
 
496
        '(liability credit credit-line)
 
497
        #f menuname-liabilities menutip-liabilities)))