New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Category charts upgrade to chart allocation ratios instead of absolute values. #1272
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some comments. This is an interesting chart modification, and needs further testing. I've also seen div/0 errors but cannot debug this at this time.
@@ -34,6 +34,13 @@ | |||
(use-modules (gnucash app-utils)) | |||
(use-modules (gnucash report)) | |||
|
|||
;; useful functions | |||
(define divide-number (lambda (x y) (gnc-numeric-div x y GNC-DENOM-AUTO (logior (GNC-DENOM-SIGFIGS 6) GNC-RND-ROUND) ))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't need this, use /
directly
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I use /
directly, it will lead to overflow errors due to division by 0.
I have some time spans where all assets are 0. In this case, ratios make no sense.
But using the gnc-numeric-divide function, the report does not crash but shows 0% for the ratio of all assets.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok. I'm not sure of all the edge cases, but I'd define a more descriptive function to explicitly demonstrate that we're handling div/0 gracefully. IIUC gnc_numeric_div
div/0 will log an error.
(define (safe-/ x y)
(if (zero? y) 0 (/ x y)))
9d31f77
to
3dc112c
Compare
@@ -552,6 +570,7 @@ developing over time")) | |||
(show-fullname? (gnc-account-get-full-name acct)) | |||
(else (xaccAccountGetName acct)))) | |||
(amounts (map gnc:gnc-monetary-amount (cadr series))) | |||
(percentage (map (lambda (x grandt) (divide-number x grandt ) ) amounts grandts)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(percentage (map (lambda (x grandt) (divide-number x grandt ) ) amounts grandts)) | |
(percentage (map divide-number amounts grandts)) |
Finally this modification would benefit from tests in |
3dc112c
to
0074307
Compare
Thank you for all the comments and suggestions. I updated the branch accordingly.
I am not able to read all the mailing list, but I will try to reply and maintain the option when I get notifactions e.g. on github. I guess the most significant edge cases are negative values, because there is no clear understanding to me how to divide e.g. a cake with negative pieces. I can write an explanation with the intended use case and what happens in case of negative values. I mainly use it to maintain a certain ratio between some of my asset classes (e.g. investment funds and fixed-interest securities). In the hope other will benefit from it as well, I wanted to share it. Thanks again for all the helpful comments. |
For copyright and attribution we may wish to add your github handle onto the report source code.
Yes it would be useful to write an explanation for the release notes. I think the most severe edge case will be how to handle mixed-account-type hierarchies such as Income[INCOME]:Salary[INCOME]:PAYETaxes[EXPENSE] for example, whereby both Salary and PAYETaxes are populated with splits (and the account balances may become negative!). But the existing reports are likely to fail too.
I think it is an interesting chart, and as bonus it is a single-option upgrade to an existing report. |
(define (safe-/ x y) | ||
(if (and (zero? x) (zero? y)) 0 (/ x y))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you sure both x and y being 0 is the only div/0? What if the assets have only two accounts: cash $400 and loan -$400?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the part where the abs value will make y 800. In this case it makes no difference. Since if x=0, y will also always be 0. We can also stick with (if (zero? y) 0 (/ x y)))
. I just added the check for x, because for x not 0, a divide by 0 error in this case would show that something is wrong.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since if x=0, y will also always be 0.
There is nothing in this function definition that guarantees this so your statement must be based on how this function is used. That's slightly risky because that opens doors to mistakes in future changes of the code.
We can also stick with
(if (zero? y) 0 (/ x y)))
. I just added the check for x, because for x not 0, a divide by 0 error in this case would show that something is wrong.
The added check adds nothing. The function will return 0 in both implementations so there's no way your calling code can tell the difference.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is nothing in this function definition that guarantees this so your statement must be based on how this function is used. That's slightly risky because that opens doors to mistakes in future changes of the code.
That is true, only in my use case if y=0, also x will be 0.
Since if x=0, y will also always be 0.
Sorry, that was is mistake. In my usecase: y=abs(x_1)+...+abs(x_N). So if y=0, all x must have been 0.
The added check adds nothing. The function will return 0 in both implementations so there's no way your calling code can tell the difference.
Consider the case y=0, x=1.
(if (zero? y) 0 (/ x y)))
will return 0
(if (and (zero? x) (zero? y)) 0 (/ x y)))
will fail due to division by 0: (/ 1 0).
In my implementation the case y=0, x=1 can't happen. So if it still occurs, something is wrong and the report will fail.
While reviewing this PR I think the |
Instead of collector->monetary which takes a collector (bag of monetaries) and returns a monetary {report-currency, amount}, return amount. Simplifies code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Final comments: I agree it is clever to show negative assets as a negative bar, it may not be strictly legitimate according to formal reporting. Many users are extremely sharp as far as accounting standards are concerned, and will ask questions. So, some documentation, including caveats on edge cases, and description how it handles negative amounts, will be crucial for users to understand its behaviour.
(for-each | ||
(lambda (date row) | ||
(gnc:html-table-append-row! | ||
table | ||
(append (list (make-cell date)) | ||
(map make-cell row) | ||
(if cols>1? | ||
(list | ||
(make-cell (apply gnc:monetary+ row))) | ||
'())))) | ||
(let ((grandt (apply l1norm (map gnc:gnc-monetary-amount row)))) | ||
(append (list (make-cell date)) | ||
(if ratio-chart? | ||
(map (lambda (x) (make-cell-percent (* (safe-/ (gnc:gnc-monetary-amount x) grandt) 100))) row) | ||
(map make-cell row)) | ||
(if cols>1? | ||
(list | ||
(make-cell (apply gnc:monetary+ row))) | ||
'()))))) | ||
date-string-list | ||
(apply zip (map cadr all-data))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of recreating grandt for each row, it'll be much more clever to reuse the grandts
already generated. For that matter, you'll want to modify the for-each
as follows:
(for-each
(lambda (date row grandt)
(gnc:html-table-append-row! ...etc...and use grandt now available for use))
date-string-list
(apply zip (map cadr all-data))
grandts)
@@ -552,6 +569,7 @@ developing over time")) | |||
(show-fullname? (gnc-account-get-full-name acct)) | |||
(else (xaccAccountGetName acct)))) | |||
(amounts (map gnc:gnc-monetary-amount (cadr series))) | |||
(percentage (map (lambda (x grandt) (safe-/ x grandt)) amounts grandts)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This line is ok for readability, but because percentage
is only used iff ratios-chart?
is #t
, it would be marginally more efficient to generate the calls to map
and safe-/
only when required; thus:
(gnc:html-chart-add-data-series!
chart label (if ratio-chart? (map safe-/ amounts grandts) amounts)
color 'stack stack 'fill fill 'urls urls)
…hub.com/exxus/gnucash into exxus-category-barchart-patch
I created a patch to show ratios in the barchart.
This is useful if you want to maintain a certain ratio between different asset classes, e.g. 60:40.
I contribute this, since I think it can help other users as well.
Before this can be merged, maybe some documentation needs to be added.
Thank you all for this really helpful piece of software.
Best regards
exxus