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:
Simon Michael 2014-07-18 16:45:46 -07:00
parent 470835adc2
commit f2d9c6e9c1
5 changed files with 144 additions and 60 deletions

View File

@ -75,6 +75,7 @@ module Hledger.Data.Amount (
mixed,
amounts,
filterMixedAmount,
filterMixedAmountByCommodity,
normaliseMixedAmountPreservingFirstPrice,
normaliseMixedAmountPreservingPrices,
-- ** arithmetic
@ -429,6 +430,17 @@ amounts (Mixed as) = as
filterMixedAmount :: (Amount -> Bool) -> MixedAmount -> MixedAmount
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
-- assigned price, if any.
costOfMixedAmount :: MixedAmount -> MixedAmount

View File

@ -11,9 +11,12 @@ a some base account. They are used by hledger-web.
module Hledger.Reports.TransactionsReports (
TransactionsReport,
TransactionsReportItem,
triOrigTransaction,
triDate,
triAmount,
triBalance,
triSimpleBalance,
triCommodityAmount,
triCommodityBalance,
journalTransactionsReport,
accountTransactionsReport,
transactionsReportByCommodity
@ -51,11 +54,12 @@ type TransactionsReportItem = (Transaction -- the original journal 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
triBalance (_,_,_,_,_,a) = a
triSimpleBalance (_,_,_,_,_,Mixed a) = case a of [] -> "0"
(Amount{aquantity=q}):_ -> show q
triCommodityAmount c = filterMixedAmountByCommodity c . triAmount
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
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
-------------------------------------------------------------------------------

View File

@ -134,6 +134,7 @@ instance Yesod App where
addScript $ StaticR js_jquery_hotkeys_js
addScript $ StaticR js_jquery_flot_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]--> |]
addStylesheet $ StaticR hledger_css
addScript $ StaticR hledger_js

View File

@ -4,6 +4,7 @@ module Handler.RegisterR where
import Import
import Data.List
import Data.Maybe
import Safe
@ -105,31 +106,69 @@ registerChartHtml percommoditytxnreports =
<div#register-chart style="width:85%; height:150px; margin-bottom:1em; display:block;">
<script type=text/javascript>
\$(document).ready(function() {
var chartdiv = $('#register-chart');
if (chartdiv.is(':visible')) {
var $chartdiv = $('#register-chart');
if ($chartdiv.is(':visible')) {
\$('#register-chart-label').text('#{charttitle}');
registerChart(
chartdiv,
[
$forall (comm,(_,items)) <- percommoditytxnreports
{
data: [
$forall i <- reverse items
[#{dayToJsTimestamp $ triDate i}, #{triSimpleBalance i}],
/* [] */
var seriesData = [
$forall (c,(_,items)) <- percommoditytxnreports
/* we render each commodity using two series:
* one with extra data points added to show a stepped balance line */
{
data: [
$forall i <- reverse items
[
#{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
charttitle = case maybe "" (fst.snd) $ headMay percommoditytxnreports
of "" -> ""
s -> s++":"
colorForCommodity = fromMaybe 0 . flip lookup commoditiesIndex
commoditiesIndex = zip (map fst percommoditytxnreports) [0..] :: [(Commodity,Int)]
simpleMixedAmountQuantity = maybe 0 aquantity . headMay . amounts

View File

@ -32,65 +32,97 @@ $(document).ready(function() {
// REGISTER CHART
function registerChart($container, series) {
// https://github.com/flot/flot/blob/master/API.md
return $container.plot(
// https://github.com/flot/flot/blob/master/API.md
return $container.plot(
series,
{ /* general chart options */
series: {
points: {
show: true,
},
lines: {
show: true,
steps: true,
},
bars: {
// show: true,
// barWidth: 1000 * 60 * 60, // ms
},
},
yaxis: {
/* mode: "time", */
/* timeformat: "%y/%m/%d", */
/* ticks: 6, */
},
// series: {
// },
// yaxis: {
// /* ticks: 6, */
// },
xaxis: {
mode: "time",
timeformat: "%Y/%m/%d"
/* ticks: 6, */
},
grid: {
// clickable: true,
// hoverable: true,
// autoHighlight: true,
markings:
function (axes) {
// console.log(axes);
// 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 now = Date.now();
var markings = [
// {
// xaxis: { to: now }, // past
// yaxis: { from: 0, to: 0 }, // =0
// color: '#d88',
// lineWidth:1
// },
{
xaxis: { from: now, to: now },
color: '#888',
lineWidth:1
xaxis: { to: now }, // past
yaxis: { to: 0 }, // <0
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 },
color: '#bb0000',
lineWidth:1
xaxis: { from: now }, // future
yaxis: { to: 0 }, // <0
// color: '#ddbbbb',
color: '#e8c8c8',
},
{
// xaxis: { from: now }, // future
yaxis: { from: 0, to: 0 }, // =0
color: '#bb0000',
lineWidth:1
},
];
// console.log(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");
}
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