web: more register chart improvements
- show a background color for future and less-than-zero regions - show points for transactions, not all line corners - hovering over point shows balance, date, posted amount and transaction - clicking a point scrolls towards that date
This commit is contained in:
		
							parent
							
								
									470835adc2
								
							
						
					
					
						commit
						f2d9c6e9c1
					
				| @ -75,6 +75,7 @@ module Hledger.Data.Amount ( | |||||||
|   mixed, |   mixed, | ||||||
|   amounts, |   amounts, | ||||||
|   filterMixedAmount, |   filterMixedAmount, | ||||||
|  |   filterMixedAmountByCommodity, | ||||||
|   normaliseMixedAmountPreservingFirstPrice, |   normaliseMixedAmountPreservingFirstPrice, | ||||||
|   normaliseMixedAmountPreservingPrices, |   normaliseMixedAmountPreservingPrices, | ||||||
|   -- ** arithmetic |   -- ** arithmetic | ||||||
| @ -429,6 +430,17 @@ amounts (Mixed as) = as | |||||||
| filterMixedAmount :: (Amount -> Bool) -> MixedAmount -> MixedAmount | filterMixedAmount :: (Amount -> Bool) -> MixedAmount -> MixedAmount | ||||||
| filterMixedAmount p (Mixed as) = Mixed $ filter p as | filterMixedAmount p (Mixed as) = Mixed $ filter p as | ||||||
| 
 | 
 | ||||||
|  | -- | Return an unnormalised MixedAmount containing exactly one Amount | ||||||
|  | -- with the specified commodity and the quantity of that commodity | ||||||
|  | -- found in the original. NB if Amount's quantity is zero it will be | ||||||
|  | -- discarded next time the MixedAmount gets normalised. | ||||||
|  | filterMixedAmountByCommodity :: Commodity -> MixedAmount -> MixedAmount | ||||||
|  | filterMixedAmountByCommodity c (Mixed as) = Mixed as' | ||||||
|  |   where | ||||||
|  |     as' = case filter ((==c) . acommodity) as of | ||||||
|  |             []   -> [nullamt{acommodity=c}] | ||||||
|  |             as'' -> [sum as''] | ||||||
|  | 
 | ||||||
| -- | Convert a mixed amount's component amounts to the commodity of their | -- | Convert a mixed amount's component amounts to the commodity of their | ||||||
| -- assigned price, if any. | -- assigned price, if any. | ||||||
| costOfMixedAmount :: MixedAmount -> MixedAmount | costOfMixedAmount :: MixedAmount -> MixedAmount | ||||||
|  | |||||||
| @ -11,9 +11,12 @@ a some base account.  They are used by hledger-web. | |||||||
| module Hledger.Reports.TransactionsReports ( | module Hledger.Reports.TransactionsReports ( | ||||||
|   TransactionsReport, |   TransactionsReport, | ||||||
|   TransactionsReportItem, |   TransactionsReportItem, | ||||||
|  |   triOrigTransaction, | ||||||
|   triDate, |   triDate, | ||||||
|  |   triAmount, | ||||||
|   triBalance, |   triBalance, | ||||||
|   triSimpleBalance, |   triCommodityAmount, | ||||||
|  |   triCommodityBalance, | ||||||
|   journalTransactionsReport, |   journalTransactionsReport, | ||||||
|   accountTransactionsReport, |   accountTransactionsReport, | ||||||
|   transactionsReportByCommodity |   transactionsReportByCommodity | ||||||
| @ -51,11 +54,12 @@ type TransactionsReportItem = (Transaction -- the original journal transaction, | |||||||
|                               ,MixedAmount -- the running balance for the current account(s) after this transaction |                               ,MixedAmount -- the running balance for the current account(s) after this transaction | ||||||
|                               ) |                               ) | ||||||
| 
 | 
 | ||||||
| triDate (t,_,_,_,_,_) = tdate t | triOrigTransaction (torig,_,_,_,_,_) = torig | ||||||
|  | triDate (_,tacct,_,_,_,_) = tdate tacct | ||||||
| triAmount (_,_,_,_,a,_) = a | triAmount (_,_,_,_,a,_) = a | ||||||
| triBalance (_,_,_,_,_,a) = a | triBalance (_,_,_,_,_,a) = a | ||||||
| triSimpleBalance (_,_,_,_,_,Mixed a) = case a of [] -> "0" | triCommodityAmount c = filterMixedAmountByCommodity c  . triAmount | ||||||
|                                                  (Amount{aquantity=q}):_ -> show q | triCommodityBalance c = filterMixedAmountByCommodity c  . triBalance | ||||||
| 
 | 
 | ||||||
| ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | ||||||
| 
 | 
 | ||||||
| @ -237,9 +241,5 @@ filterTransactionsReportByCommodity c (label,items) = | |||||||
|         go bal ((t,t2,s,o,amt,_):is) = (t,t2,s,o,amt,bal'):go bal' is |         go bal ((t,t2,s,o,amt,_):is) = (t,t2,s,o,amt,bal'):go bal' is | ||||||
|           where bal' = bal + amt |           where bal' = bal + amt | ||||||
| 
 | 
 | ||||||
| -- | Filter out all but the specified commodity from this amount. |  | ||||||
| filterMixedAmountByCommodity :: Commodity -> MixedAmount -> MixedAmount |  | ||||||
| filterMixedAmountByCommodity c (Mixed as) = Mixed $ filter ((==c). acommodity) as |  | ||||||
| 
 |  | ||||||
| ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -134,6 +134,7 @@ instance Yesod App where | |||||||
|             addScript $ StaticR js_jquery_hotkeys_js |             addScript $ StaticR js_jquery_hotkeys_js | ||||||
|             addScript $ StaticR js_jquery_flot_min_js |             addScript $ StaticR js_jquery_flot_min_js | ||||||
|             addScript $ StaticR js_jquery_flot_time_min_js |             addScript $ StaticR js_jquery_flot_time_min_js | ||||||
|  |             addScript $ StaticR js_jquery_flot_tooltip_min_js | ||||||
|             toWidget [hamlet| \<!--[if lte IE 8]> <script type="text/javascript" src="@{StaticR js_excanvas_min_js}"></script> <![endif]--> |] |             toWidget [hamlet| \<!--[if lte IE 8]> <script type="text/javascript" src="@{StaticR js_excanvas_min_js}"></script> <![endif]--> |] | ||||||
|             addStylesheet $ StaticR hledger_css |             addStylesheet $ StaticR hledger_css | ||||||
|             addScript $ StaticR hledger_js |             addScript $ StaticR hledger_js | ||||||
|  | |||||||
| @ -4,6 +4,7 @@ module Handler.RegisterR where | |||||||
| 
 | 
 | ||||||
| import Import | import Import | ||||||
| 
 | 
 | ||||||
|  | import Data.List | ||||||
| import Data.Maybe | import Data.Maybe | ||||||
| import Safe | import Safe | ||||||
| 
 | 
 | ||||||
| @ -105,31 +106,69 @@ registerChartHtml percommoditytxnreports = | |||||||
| <div#register-chart style="width:85%; height:150px; margin-bottom:1em; display:block;"> | <div#register-chart style="width:85%; height:150px; margin-bottom:1em; display:block;"> | ||||||
| <script type=text/javascript> | <script type=text/javascript> | ||||||
|  \$(document).ready(function() { |  \$(document).ready(function() { | ||||||
|    var chartdiv = $('#register-chart'); |    var $chartdiv = $('#register-chart'); | ||||||
|    if (chartdiv.is(':visible')) { |    if ($chartdiv.is(':visible')) { | ||||||
|      \$('#register-chart-label').text('#{charttitle}'); |      \$('#register-chart-label').text('#{charttitle}'); | ||||||
|      registerChart( |      var seriesData = [ | ||||||
|        chartdiv, |       $forall (c,(_,items)) <- percommoditytxnreports | ||||||
|        [ |        /* we render each commodity using two series:  | ||||||
|          $forall (comm,(_,items)) <- percommoditytxnreports |         * one with extra data points added to show a stepped balance line */ | ||||||
|           { |        { | ||||||
|            data: [ |         data: [ | ||||||
|              $forall i <- reverse items |           $forall i <- reverse items | ||||||
|               [#{dayToJsTimestamp $ triDate i}, #{triSimpleBalance i}], |            [ | ||||||
|              /* [] */ |             #{dayToJsTimestamp $ triDate i}, | ||||||
|  |             #{simpleMixedAmountQuantity $ triCommodityBalance c i}, | ||||||
|            ], |            ], | ||||||
|            label: '#{comm}', |           /* [] */ | ||||||
|            color: #{colorForCommodity comm}, |         ], | ||||||
|           }, |         label: '#{c}', | ||||||
|        ] |         color: #{colorForCommodity c}, | ||||||
|      ); |         lines: { | ||||||
|  |           show: true, | ||||||
|  |           steps: true, | ||||||
|  |         }, | ||||||
|  |         points: {  | ||||||
|  |           show: false, | ||||||
|  |         }, | ||||||
|  |         clickable: false, | ||||||
|  |         hoverable: false, | ||||||
|  |        }, | ||||||
|  |        /* and one with the original data, showing one clickable, hoverable point per transaction */ | ||||||
|  |        { | ||||||
|  |         data: [ | ||||||
|  |           $forall i <- reverse items | ||||||
|  |            [ | ||||||
|  |             #{dayToJsTimestamp $ triDate i}, | ||||||
|  |             #{simpleMixedAmountQuantity $ triCommodityBalance c i}, | ||||||
|  |             '#{show $ triCommodityAmount c i}', | ||||||
|  |             '#{show $ triCommodityBalance c i}', | ||||||
|  |             '#{concat $ intersperse "\\n" $ lines  $ show $ triOrigTransaction i}', | ||||||
|  |            ], | ||||||
|  |           /* [] */ | ||||||
|  |         ], | ||||||
|  |         label: '', | ||||||
|  |         color: '#{colorForCommodity c}', | ||||||
|  |         lines: { | ||||||
|  |           show: false, | ||||||
|  |         }, | ||||||
|  |         points: {  | ||||||
|  |           show: true, | ||||||
|  |           color: '#{colorForCommodity c}', | ||||||
|  |         }, | ||||||
|  |        }, | ||||||
|  |      ] | ||||||
|  |      var plot = registerChart($chartdiv, seriesData); | ||||||
|  |      \$chartdiv.bind("plotclick", registerChartClick); | ||||||
|    }; |    }; | ||||||
|  }); |  }); | ||||||
| |] | |] | ||||||
|  |            -- [#{dayToJsTimestamp $ ltrace "\ndate" $ triDate i}, #{ltrace "balancequantity" $ simpleMixedAmountQuantity $ triCommodityBalance c i}, '#{ltrace "balance" $ show $ triCommodityBalance c i}, '#{ltrace "amount" $ show $ triCommodityAmount c i}''], | ||||||
|  where |  where | ||||||
|    charttitle = case maybe "" (fst.snd) $ headMay percommoditytxnreports |    charttitle = case maybe "" (fst.snd) $ headMay percommoditytxnreports | ||||||
|            of "" -> "" |            of "" -> "" | ||||||
|               s  -> s++":" |               s  -> s++":" | ||||||
|    colorForCommodity = fromMaybe 0 . flip lookup commoditiesIndex |    colorForCommodity = fromMaybe 0 . flip lookup commoditiesIndex | ||||||
|    commoditiesIndex = zip (map fst percommoditytxnreports) [0..] :: [(Commodity,Int)] |    commoditiesIndex = zip (map fst percommoditytxnreports) [0..] :: [(Commodity,Int)] | ||||||
|  |    simpleMixedAmountQuantity = maybe 0 aquantity . headMay . amounts | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -32,65 +32,97 @@ $(document).ready(function() { | |||||||
| // REGISTER CHART
 | // REGISTER CHART
 | ||||||
| 
 | 
 | ||||||
| function registerChart($container, series) { | function registerChart($container, series) { | ||||||
| 	// https://github.com/flot/flot/blob/master/API.md
 |   // https://github.com/flot/flot/blob/master/API.md
 | ||||||
| 	return $container.plot( |   return $container.plot( | ||||||
|     series, |     series, | ||||||
|     { /* general chart options */ |     { /* general chart options */ | ||||||
|       series: { |       // series: {
 | ||||||
|         points: {  |       // },
 | ||||||
|           show: true, |       // yaxis: {
 | ||||||
|         }, |       //   /* ticks: 6, */
 | ||||||
|         lines: { |       // },
 | ||||||
|           show: true, |  | ||||||
|           steps: true, |  | ||||||
|         }, |  | ||||||
|         bars: { |  | ||||||
|           // show: true,
 |  | ||||||
|           // barWidth: 1000 * 60 * 60, // ms
 |  | ||||||
|         }, |  | ||||||
|       }, |  | ||||||
|       yaxis: { |  | ||||||
|         /* mode: "time", */ |  | ||||||
|         /* timeformat: "%y/%m/%d", */ |  | ||||||
|         /* ticks: 6, */ |  | ||||||
|       }, |  | ||||||
|       xaxis: { |       xaxis: { | ||||||
|         mode: "time", |         mode: "time", | ||||||
|         timeformat: "%Y/%m/%d" |         timeformat: "%Y/%m/%d" | ||||||
|         /* ticks: 6, */ |         /* ticks: 6, */ | ||||||
|       }, |       }, | ||||||
|       grid: { |       grid: { | ||||||
|         // clickable: true,
 |  | ||||||
|         // hoverable: true,
 |  | ||||||
|         // autoHighlight: true,
 |  | ||||||
|         markings: |         markings: | ||||||
|          function (axes) { |          function (axes) { | ||||||
|           // console.log(axes);
 |           var now = Date.now(); | ||||||
|           // var markings = [];
 |  | ||||||
|           // for (var x = Math.floor(axes.xaxis.min); x < axes.xaxis.max; x += 2)
 |  | ||||||
|           //   markings.push({ xaxis: { from: x, to: x + 1 } });
 |  | ||||||
|           // midx = Math.floor(axes.xaxis.min + (axes.xaxis.max - axes.xaxis.min) / 2);
 |  | ||||||
| 					var now = Date.now(); |  | ||||||
|           var markings = [ |           var markings = [ | ||||||
|  |             // {
 | ||||||
|  |             //   xaxis: { to: now },        // past
 | ||||||
|  |             //   yaxis: { from: 0, to: 0 }, // =0
 | ||||||
|  |             //  color: '#d88',
 | ||||||
|  |             //  lineWidth:1
 | ||||||
|  |             // },
 | ||||||
|             { |             { | ||||||
|               xaxis: { from: now, to: now },  |               xaxis: { to: now }, // past
 | ||||||
| 							color: '#888', |               yaxis: { to: 0 },   // <0
 | ||||||
| 							lineWidth:1 |               color: '#ffdddd', | ||||||
|  |             }, | ||||||
|  | 
 | ||||||
|  |             // {
 | ||||||
|  |             //   xaxis: { from: now, to: now }, // now
 | ||||||
|  |             //  color: '#bbb',
 | ||||||
|  |             // },
 | ||||||
|  | 
 | ||||||
|  |             { | ||||||
|  |               xaxis: { from: now }, // future
 | ||||||
|  |               yaxis: { from: 0 },   // >0
 | ||||||
|  |               // color: '#dddddd',
 | ||||||
|  |               color: '#e0e0e0', | ||||||
|             }, |             }, | ||||||
|             { |             { | ||||||
|               yaxis: { from: 0, to: 0 }, |               xaxis: { from: now }, // future
 | ||||||
| 							color: '#bb0000', |               yaxis: { to: 0 },     // <0
 | ||||||
| 							lineWidth:1 |               // color: '#ddbbbb',
 | ||||||
|  |               color: '#e8c8c8', | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |               // xaxis: { from: now },      // future
 | ||||||
|  |               yaxis: { from: 0, to: 0 }, // =0
 | ||||||
|  |               color: '#bb0000', | ||||||
|  |               lineWidth:1 | ||||||
|             }, |             }, | ||||||
|           ]; |           ]; | ||||||
|           // console.log(markings);
 |           // console.log(markings);
 | ||||||
|           return markings; |           return markings; | ||||||
|         } |         }, | ||||||
|  |         hoverable: true, | ||||||
|  |         autoHighlight: true, | ||||||
|  |         clickable: true, | ||||||
|  |       }, | ||||||
|  |       /* https://github.com/krzysu/flot.tooltip */ | ||||||
|  |       tooltip: true, | ||||||
|  |       tooltipOpts: { | ||||||
|  |         xDateFormat: "%Y/%m/%d", | ||||||
|  |         content: | ||||||
|  |           function(label, x, y, flotitem) { | ||||||
|  |             var data = flotitem.series.data[flotitem.dataIndex]; | ||||||
|  |             return data[3]+" balance on %x after "+data[2]+" posted by transaction:<pre>"+data[4]+"</pre>"; | ||||||
|  |           }, | ||||||
|  |         onHover: function(flotitem, $tooltipel) { | ||||||
|  |           $tooltipel.css('border-color',flotitem.series.color); | ||||||
|  |         }, | ||||||
|       }, |       }, | ||||||
|     } |     } | ||||||
|   ).data("plot"); |   ).data("plot"); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | function registerChartClick(ev, pos, item) { | ||||||
|  |   if (item) { | ||||||
|  |     var date = $.plot.dateGenerator(item.datapoint[0], {}); | ||||||
|  |     var dateid = $.plot.formatDate(date, '%Y-%m-%d'); | ||||||
|  |     $target = $('#'+dateid); | ||||||
|  |     if ($target.length) | ||||||
|  |       $('html, body').animate({ | ||||||
|  |         scrollTop: $target.offset().top | ||||||
|  |       }, 1000); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| //----------------------------------------------------------------------
 | //----------------------------------------------------------------------
 | ||||||
| // ADD FORM
 | // ADD FORM
 | ||||||
| 
 | 
 | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user