packages feed

hax (empty) → 0.0.1

raw patch · 23 files changed

+6318/−0 lines, 23 filesdep +Decimaldep +aesondep +arraysetup-changed

Dependencies added: Decimal, aeson, array, base, blaze-html, blaze-markup, boxes, bytestring, containers, directory, filepath, mtl, split, template-haskell, text, transformers

Files

+ LICENSE view
@@ -0,0 +1,21 @@+MIT License++Copyright (c) 2017 Johannes Gerer++Permission is hereby granted, free of charge, to any person obtaining a copy+of this software and associated documentation files (the "Software"), to deal+in the Software without restriction, including without limitation the rights+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+copies of the Software, and to permit persons to whom the Software is+furnished to do so, subject to the following conditions:++The above copyright notice and this permission notice shall be included in all+copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+SOFTWARE.
+ README.md view
@@ -0,0 +1,4 @@+# hax+Haskell cash-flow and tax simulation++
+ Setup.hs view
@@ -0,0 +1,12 @@+import Distribution.Simple+import Distribution.Simple.Setup+import Distribution.Simple.Haddock+main = defaultMainWithHooks simpleUserHooks{+  haddockHook = \p l h f -> haddockHook simpleUserHooks p l h f{+     haddockHoogle       = Flag True,+     haddockHtml         = Flag True,+     haddockExecutables  = Flag True,+     haddockHscolour     = Flag True+     }+  }+
+ hax.cabal view
@@ -0,0 +1,62 @@+name: hax+version: 0.0.1+cabal-version: >=1.10+build-type: Simple+license: MIT+license-file: LICENSE+maintainer: Johannes Gerer <oss@johannesgerer.com>+stability: Experimental+homepage: http://johannesgerer.com/hax+bug-reports: http://github.com/johannesgerer/hax/issues+synopsis: Haskell cash-flow and tax simulation+description:+    This package contains a library that for a double-entry accounting based cash-flow simulation with a detailed translation of the German tax code including personal income tax (Einkommensteuer), corporate tax (Körperschaftsteuer) and trade/business tax (Gewerbesteuer).+    .+    See <https://github.com/johannesgerer/hax Readme> on Github.+category: Finance+author: Johannes Gerer+extra-source-files:+    README.md+    stack.yaml+    static/Chart.js+    static/charts-code.js+    static/charts.html+    static/code.js++executable hax+    main-is: main.hs+    build-depends:+        Decimal >=0.4.2 && <0.5,+        aeson >=0.11.2.1 && <0.12,+        array >=0.5.0.0 && <0.6,+        base >=4.9.0.0 && <4.10,+        blaze-html >=0.8.1.3 && <0.9,+        blaze-markup >=0.7.1.1 && <0.8,+        boxes >=0.1.4 && <0.2,+        bytestring >=0.10.8.1 && <0.11,+        containers >=0.5.5.1 && <0.6,+        directory >=1.2.6.2 && <1.3,+        filepath >=1.4.1.0 && <1.5,+        mtl >=2.2.1 && <2.3,+        split >=0.2.3.1 && <0.3,+        template-haskell >=2.11.0.0 && <2.12,+        text >=1.2.2.1 && <1.3,+        transformers >=0.5.2.0 && <0.6+    default-language: Haskell2010+    hs-source-dirs: src+    other-modules:+        HAX.Accounting+        HAX.Assets+        HAX.Bookkeeping+        HAX.Bookkeeping.Internal+        HAX.Common+        HAX.Germany+        HAX.Germany.NatuerlichePerson+        HAX.Germany.GmbH+        HAX.Germany.Einkommensteuer+        HAX.Germany.Gewerbe+        HAX.Germany.Subjekte+        HAX.Example+        HAX.Report+    ghc-options: -O2 -threaded -rtsopts -with-rtsopts=-N -H1200M+
+ src/HAX/Accounting.hs view
@@ -0,0 +1,138 @@+{-# LANGUAGE+ ExistentialQuantification+, NoMonomorphismRestriction+ #-}+-- | This module provides +--+-- * the types to build a 'World' of accounting 'Entity's and+--+-- * functions to compute the ledger resulting from the entities'+-- accounting actions+--+-- * built from combinators found in "Bookkeeping" and "Assets".+--+-- Use "Report" to display the results.+module HAX.Accounting where++import HAX.Bookkeeping.Internal+import HAX.Bookkeeping+import Control.Monad.RWS.Strict+import HAX.Common+import qualified Data.Map as M++  +-- * Accounting Entities++data World = World { wLife :: (ADate,ADate) -- ^ time interval+                   , wEntites :: [Entity]+                   }+++  +             +-- | An entity keeping accounts over its assets.+data Entity = forall body . Body body => Entity  {+  entName :: EntityName+  -- ^ the name of the entity. This will serve as an 'AccountName'+  -- when booking transaction between this entity an another entity.+  , entAssets :: [AssetName]+  -- ^ the entity's asset accounts+  , entMonthlyAction ::  AccountingRW body ()+  -- the entity's monthly accounting action+  , entBody :: body+  -- ^ the entity's 'Body', defining its nominal acounts.+  }++-- | The body of an entity. +class Body body where+  -- | The only requirement is that each Body has a list of nominal+  -- accounts associated with it. See "Germany" for instances.+  nominalAccounts :: body -> [String]+  bodyMonthly :: AccountingRW body () -- ^ custom actin of the entity+                 -> AccountingRW body ()  -- ^ monthly action++-- * Running actions and generating ledgers++-- | Executes an accounting action in a simple environment (for+-- testing purposes) with no accounts.+--+-- >>> simple (date 12 2016) NatuerlichePerson{pGeburt=date 12 1960} alter+-- 56++simple :: ADate -> body -> AccountingRW body a -> IO a+simple date body action = do+  bals <- newArray ((date,1),(date,1)) 0+  txns <- newArray (date,date) []+  let ledger = UNSAFE_Ledger bals txns+  fst <$> evalRWST action (Env ledger M.empty date body Nothing) ()+  ++-- | Generate the ledger of the 'World'. It will include all nominal+-- and asset accounts of all 'n' entities, as well as 'n*(n-1)'+-- transactional accounts between each pair of entities.+--+-- Furthermore, it will check the balances of these transactional+-- accounts at the end of every month using+-- 'checkTransactionalAcountSymmetry'.+generate ::  World -> IO FullLedger+generate (World times entities) = do+  let entityNames = entName <$> entities+      eAccounts (Entity name assets _ body) =+        assets ++ nominalAccounts body+        ++ (filter (name /=) entityNames)+      allAccounts = M.fromList $  zip accountList [1..]+      accountList = [ FAN (entName e) a | e <- entities, a <- eAccounts e ]+      nAccs = M.size allAccounts+  when (length accountList /= M.size allAccounts) $+    error $ "AccountNames are not unique: "+++    (unlines$show<$>accountList)+  printf "Created a ledger with %v accounts\n" nAccs+  bals <- newArray ((fst times,1),(snd times,nAccs)) 0+  txns <- newArray times []+  let ledger = UNSAFE_Ledger bals txns+      monthAction = do+        uNSAFE_carryOver+        checkTransactionalAcountSymmetry entityNames+        sequence_ [withRWT (\e -> e{eBody=body, eName=Just name})+                   $ bodyMonthly action+                  | (Entity name _ action body) <- entities ]+  result <- sequence [ runRWST monthAction+                         (Env ledger allAccounts date () Nothing) ()+                      | date <- range times  ]+  -- this is safe, as the ioarrays do not leave this function+  ledger' <- lunsafeFix ledger+  return $ FullLedger ledger' allAccounts++withRWT :: (r' -> r) -> RWST r w () m a -> RWST r' w () m a+withRWT f = withRWST $ \r s -> (f r,())+evalRWT :: Monad m => RWST r () () m a -> r -> m a+evalRWT action env = liftM fst $ evalRWST action env ()++-- * Helpers++-- | For each pair of entities, 'entity1' and 'entity2', there are two+-- transactional accounts, as each entity keeps its own books about+-- its transactions with the other entity. If no entity made a+-- mistake, the accounts should have opposite balances that cancel+-- each other. This function informs about a violation of this+-- property.+checkTransactionalAcountSymmetry :: (Monoid w, Ledger l) => [EntityName] -> Acc s l w ()+checkTransactionalAcountSymmetry entities = do+  let pairs = [  FAN e1 e2 | e1:rest <- tails entities,  e2 <- rest ]+      cb = currentBalance . UNSAFE_AccountN+  date <- reader eDate+  forM_ pairs $ \p -> do+    v1 <- cb p+    v2 <- negate <$> cb (swapFAN p)+    when (v1 /= v2) $ lift $ printf+      "symmtery violation at %v: %s %v /= %s %v\n"+      date (show p) v1 (show $ swapFAN p) v2+  ++-- | constructs an action reading an information from the current body+readBody :: Monoid w => (s -> a) -> Acc s l w a+readBody f = reader $ f . eBody++-- | construct an action that depends on the body+withBody :: Monoid w => (s -> Acc s l w a) -> Acc s l w a+withBody = (reader eBody >>=)
+ src/HAX/Assets.hs view
@@ -0,0 +1,197 @@+{-# LANGUAGE TypeSynonymInstances+, NoMonomorphismRestriction+, FlexibleInstances+ #-}++-- | This module provides a collection of accounting actions for+-- different asset/account types implemented via the 'Asset' class.+--+-- Use these actions to build more complex accounting actions for+-- 'Entity's from "Accounting".+--+-- They are implemented using the more basic combinators from+-- "Bookkeeping".+--+-- Many assets make use the 'InterestRate's defined below.+module HAX.Assets where++import HAX.Bookkeeping+import Control.Monad.Reader+import HAX.Common++-- * The Asset class++-- | An asset is anything that can be handled within an accounting+-- action.+class Asset a where+  handle :: a -> AccountingRW s ()+  -- ^ derives the action corresponding to the asset's characteristics+                             +-- * Transactional Account with asymmetric interest rates.++-- | This assets calculates interest according to its average positive+-- and negative account balances over the last time period and debits+-- it againt the 'InterestRate's 'iSource' accounts.+-- +-- Other names: Revolving credit, line of credit, Kontokorrent+data TransactionalAccount = TransactionalAccount {+  taCredit :: InterestRate+  -- ^ credit interest for negative balances+  -- for one period (not neccessarily p.a.)+  , taDebit :: InterestRate+  -- ^ debit interest for positive balances+  -- for one period (not neccessarily p.a.)+  , taAcc :: AccountName  -- ^ asset's account name+  , taPeriod :: ASpan -- ^ period in months+  }+                            +instance Asset TransactionalAccount where+  handle (TransactionalAccount icredit idebit acc period) = do+    let avg m = sum m / fromIntegral (sMonths period)+        tx a b c = interestTx a (b ++ " für "++ show acc) acc (Just $ return $ avg c)+    date <- curDate+    onceEvery period (month 12) $ do+      (debit,credit) <- partition (>0) <$> balancesSince (shift (1-period) date) acc+      when ((sum $ debit ++ fmap negate credit) /= 0) $+        logLedger $ printf "%s: positive balances: %v, negative balances: %v"+        acc (PList debit) (PList credit)+      tx icredit "Sollzins" credit+      tx idebit  "Habenzins" debit+      return ()++-- *  Loan with fixed (annuity) or variable payments++-- | Select a payment schedule+data PaymentType = Linear Decimal+                   -- ^ Decreasing payments with fixed repay and+                   -- decreasing interest portions.+                   --+                   -- The number specifies the repay as a fraction of+                   -- the principal amount.+                 | Annuity Decimal+                 -- ^ Fixed payments with decreasing interest and+                 -- increasing repay portions.+                 --+                 -- The number specifies the initial repay as a+                 -- fraction of the principal amount.+                 deriving Show++data Loan = Loan { lPrincipal :: Amount -- ^ Principal amount payed to lPaymentAccount+                 , lPType :: PaymentType+                 , lPaymentAcc ::  AccountName  -- ^ account for the payments+                 , lLoanAcc    ::  AccountName  -- ^ account for the open balance+                 , lInterest :: InterestRate+                   -- ^ interest rate for one period (not neccessarily p.a.)+                 , lStart  :: ADate -- ^ payout date+                 , lPeriod :: ASpan -- ^ period of the interest payments+                 }++instance Asset Loan where+  handle (Loan principal pType paymentAcc loanAcc ir start period) = do+    date <- curDate+    let name = show loanAcc+        dur = duration pType ir+    onceAt start $ fromTo principal ("Principal for "++name) loanAcc paymentAcc+      >> lift (do printf "Loan %v runs for %v periods\n" name $ show $ roundTo 3 $ conv dur+                  -- printf "%s == %s" (show pType) $ show $ annuity dur (iRate ir) - (iRate ir)+              )+    onlyAfter start $ onceEvery period start $ do+      curInterest <- interestTx ir ("Interest on "++name) loanAcc Nothing+      let payment (Linear x) = x*principal + curInterest+          payment (Annuity x)= x*principal + interest ir principal+      fromToLimit (negate $ roundTo 3 $ payment pType) ("Rate for "++name) loanAcc paymentAcc++-- * Liquitidy Simulation++-- | This 'Asset' produces a list of twelve payments, one for each+-- month, that are shifted in such a way, that they sum up to zero+data Liquidity = Liquidity { lFrom :: AccountName+                           , lTo :: AccountName+                           , lPayments :: [Amount]+                           }++instance Asset Liquidity where+  handle (Liquidity from to pays) = do+    m <-reader (getMonth.eDate)+    fromTo (pays !! pred m) "Liquidity Simulation" from to+    where avg = sum (take 12 pays) / 12+    +  +-- ** Helpers++-- | Calculate the number of periods time until the loan is completely repaid.+duration :: PaymentType -> InterestRate -> Decimal+duration ptype ir = conv $ duration' ptype $ iRate ir+  where duration' (Linear x)  _    = conv $ 1/x+        duration' (Annuity x) 0    = conv $ 1/x+        duration' (Annuity x) rate = negate $ on logBase conv (1 + rate) (x/(rate + x))++-- | Calculate the annuity (as a fraction of the principal) for a+-- given number of periods interest rate.+annuity :: Decimal -- ^ number of periods+        -> Decimal -- ^ interest rate+        -> Amount+annuity duration' rate' = conv $ ((1 + rate) ** duration) * rate/(((1 + rate) ** duration) - 1)+  where rate = conv rate' :: Double+        duration = conv duration' :: Double++++-- * Fixed Payments++-- | Recurring Fixed Payment +data FixedPayment = FixedPayment {+  fPayment :: Amount+  , fStart :: ADate+  , fOffset :: ADate+  , fPeriod :: ASpan+  , fSource :: AccountName+  , fSink :: AccountName+  , fComment :: Comment+  }++instance Asset FixedPayment where+  handle (FixedPayment am start offset period source sink comment) =+    onlyAfter start $ onceEvery period offset $ fromTo am comment source sink++        +-- * Interest Rates ++-- | Interest rates together with the nominal account, the could be+-- identified with the source (for incoming interest payments) or sink+-- (for outgoing, i.e. negative interest payments).+--+-- For example, if the interest is an expense, 'iSource' could be+-- \"Expenses\". If the interest is capital yield, 'iSource' could be+-- \"Income\".+data InterestRate = IR {+  iRate :: Amount+  , iSource :: AccountName+    -- ^ this can be something like \"Earnings\" if the interest can+    -- be considered earnings or another entity, if+  }++-- | Calculate the interest for a given amount+interest :: InterestRate -> Amount -> Amount+interest ir am = roundTo 2 $ am * (iRate ir)+++-- | Calculate the interest for a current balance of an account+currentInterest :: (Monoid w, Ledger l) => InterestRate-> AccountName -> AmountA s l w+currentInterest ir acc = interest ir <$> currentBalance acc++-- | Calculate and transfer the interest from the 'iSource' account to+-- some other account.+interestTx :: InterestRate -> String -- ^ comment+              -> AccountName -- ^ Sink account+              -> Maybe (AmountRW s)+              -- ^ optional: Use this amount instead of the balance of+              -- the sink account+              -> AmountRW s+interestTx ir comment acc maybeAm = do+   am <- maybe (currentInterest ir acc) (interest ir <$>) maybeAm+   when (am /= 0) $+     fromTo am comment (iSource ir) acc+   return am+                                    +
+ src/HAX/Bookkeeping.hs view
@@ -0,0 +1,218 @@+{-# LANGUAGE +NoMonomorphismRestriction+ #-}+{-# OPTIONS_HADDOCK ignore-exports #-}++-- | This module contains the accounting combinators that can be used+-- to __build complex accounting actions__.+-- +-- All combinators are __guaranteed to only allow balanced transactions__ that adhere to the double-entry bookkeeping+-- standards.+--+-- The module "Accounting"+-- contains the functions to __run these actions__ and calculate the+-- resulting ledger.+module HAX.Bookkeeping+       (AccountNumber+       , Acc+       , AccPair+       , AccountName(AccountN)+       , AccountingRW+       , AccountingReadOnly+       , AccountsMap+       , AmountA+       , AmountRW+       , AssetName(..)+       , BalancingTx(..)+       , EntityName+       , Environment(..)+       , FixedLedger(..)+       , FullLedger(..)+       , LedgerBounds+       , FullAccountName(..)+       , Ledger(..)+       , LogEntry(..)+       , EntityLogEntry+       , Posting+       , Tx(tPostings,tComment)+       , Transfer+       , balancesSince+       , closingTx+       , balanceAt+       , logMsg+       , singleResult+       , singleLog+       , curDate+       , currentBalance+       , fixed+       , fromTo+       , fromToLimit+       , haben+       , onceAt+       , atYearEnd+       , sortedAccountNames+       , onceEvery+       , onlyAfter+       , schedule+       , soll+       , transferAll+       , tx+       , logLedger+       ) where++import HAX.Bookkeeping.Internal+import Control.Monad.Reader+import Data.Array+import HAX.Common++-- * Transactions++-- | Applies a balanced transaction to the ledger at the current date.+tx ::  BalancingTx-> AccountingRW s ()+tx balancingTx =  do+  UNSAFE_Ledger bals txns <- reader eLedger+  date  <- reader eDate+  name <- reader (fromMaybe "" . eName)+  balancedTx <- balanceTx balancingTx+  lift $ updateArray txns date ((name,LTx balancedTx):)+  mapM_ uNSAFE_addToBalance $ tPostings balancedTx+++-- | as 'tx' but taking a list of accounts that should be emptied completely+closingTx :: [AccountName] -> BalancingTx -> AccountingRW s ()+closingTx accs btx = do+  postings <- forM accs $ \acc -> (,) acc . negate <$> currentBalance acc+  tx $ btx{txPostings = txPostings btx ++ postings}+                     +             +-- | Type for simple transfers between two accounts+type Transfer s = String -> AccountName -> AccountName -> AccountingRW s ()++-- | Apply a simple transaction+fromTo ::Amount -> Transfer s+fromTo amount comment from to = do+  tx $ BalancingTx comment to $ [(from,negate amount)]++-- | Apply a simple transaction, but ensure, that source does not+-- change sign+fromToLimit :: Amount -> Transfer s+fromToLimit amount comment from to = do+  am <- op <$> currentBalance from+  fromTo am comment from to+  where op = if amount > 0 then min amount else max amount++-- | Transfer all funds from one account to the other+transferAll :: Transfer s+transferAll comment from to = do am <- currentBalance from+                                 fromTo am comment from to++-- * Balances ++-- | Get the current balance of an account+currentBalance :: AccPair l w => AccountName -> Acc s l w Amount+currentBalance name = fst <$> readEntryForName name++-- | Get the balance of a <http://de.wikipedia.org/wiki/Soll Soll>+-- account (uses the amounts directly as stored in the ledger)+soll :: AccPair l w => AccountName -> Acc s l w Amount+soll = currentBalance++-- | Get the balance of a <http://de.wikipedia.org/wiki/Haben Haben> Account +-- account (negates the internally stored amounts)+haben :: AccPair l w => AccountName -> Acc s l w Amount+haben = fmap negate . soll+++-- | Get the balance at a certain date+balanceAt :: AccPair l w => ADate -> AccountName -> Acc s l w Amount+balanceAt d = uNSAFE_at d . currentBalance++-- | Get the balances since a certain date+balancesSince :: AccPair l w => ADate -> AccountName -> Acc s l w [Amount]+balancesSince since acc = do+  date  <- reader eDate+  acc' <- accountNumber acc+  start <- fst <$> timeInterval+  lift . lAccountHistory (max since start,date) (\t -> (t,acc'))+    =<< reader eLedger++-- * Date combinators+                    +-- | Get the current date+curDate :: Monoid w => Acc s l w ADate+curDate = reader eDate++-- | Restrict an accounting action to a certain date+onceAt :: Monoid w => ADate -> Acc s l w () -> Acc s l w ()+onceAt d a = do {d2 <- curDate ; when (d == d2) a }++-- | Execute an action periodically+onceEvery :: Monoid w => ASpan -- ^ Period+             -> ADate -- ^ Offset+             -> Acc s l w a -> Acc s l w ()+onceEvery period offset action = do { date <- curDate ;+     when (period `divides` (dateSpan offset date)) $ action >> return () }++-- | Execute an action at the end of every year+atYearEnd ::  Monoid w => Acc s l w a -> Acc s l w ()+atYearEnd = onceEvery 12 (month 12)++-- | Executes an action only after a certain date+onlyAfter :: Monoid w => ADate -> Acc s l w a -> Acc s l w ()+onlyAfter start action = do { date <- curDate ;+     when (date > start) $ action >> return () }+                                ++-- | Perform an accounting action now, but run it with a modified (future)+-- date. E.g.+--+-- > schedule (date 12 2016) $ tx1+--+-- All changes that tx1 performs will be written to the ledger right+-- now, but only modify balances at 12/2016.+schedule :: Monoid w => ADate -> Acc s l w a -> Acc s l w a+schedule date action =do+  cdate <- curDate+  if (date < cdate ) then+    error $ printf+    "cannot schedule in the past. \"%v\" lies before today \"%v\""+    date cdate+  else uNSAFE_at date action++-- * Logging++-- | Write a log entry to stdout or to the ledger+logMsg :: Bool -- ^ True = to stdout, False = to ledger+          -> String -> AccountingRW s ()+logMsg toStdout s = do+  txns <- reader $ lUNSAFE_LogEntries . eLedger+  name <- reader (fromMaybe "" . eName)+  date <- reader eDate+  let pair = if toStdout then show (date,name)+             else name+      entry = printf ("(%s) "++s++"\n") pair+  lift $ if toStdout+         then putStrLn entry+         else updateArray txns date ((name,LComment s):)++-- | Write a log entry to the ledger+logLedger :: String -> AccountingRW s ()+logLedger = logMsg False+++-- | within the ReadOnly Monad: register a single log entry consisting+-- of a formating string and a value+singleLog :: PrintfArg t => String -- ^ formatting string+             -> t -- ^ value+             -> AccountingReadOnly body ()+singleLog a b = tell $ logLedger $ printf a b++-- | within the ReadOnly Monad: return the value of an action and+-- register a single log entry describing the value, but only if it s+-- not zero+singleResult :: (PrintfArg a, Num a, Eq a) => String -- ^ formatting string+             -> AccountingReadOnly body a -- ^ action+             -> AccountingReadOnly body a+singleResult name action = do value <- action+                              when (value /=0 ) $ singleLog (name++": %v\n") value+                              return value
+ src/HAX/Bookkeeping/Internal.hs view
@@ -0,0 +1,351 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE TypeSynonymInstances+, FlexibleInstances+, TypeFamilies+, ConstraintKinds+, ExistentialQuantification+, MultiParamTypeClasses+, UndecidableInstances+, NoMonomorphismRestriction+, DeriveGeneric+ #-}+{-# OPTIONS_HADDOCK not-home #-}+++-- | This module contains the internal type and functions not to be+-- used directly as most of them are unsafe, meaning that they allow+-- actions that violate double-entry contraints or actions+-- on accounts other than the current body's accounts (via+-- 'UNSAFE_AccountN').+--+-- Therefore, __do not use this module directly__, use "Bookkeeping" instead.+module HAX.Bookkeeping.Internal where++import           HAX.Common+import           Control.Monad.RWS.Strict+import           Data.Array+import           Data.Array.Unsafe+import           Data.Functor.Compose+import qualified Data.Map as M+import           GHC.Generics+++-- * Account Names and Numbers++type AccountNumber = Int+type AssetName = String+type EntityName = String++-- | uniquely identifying name used to lookup the account numbers+data FullAccountName = FAN+                       { fEntity :: EntityName+                       , fAccount :: String+                       }+                 deriving (Eq, Ord,Show, Generic)++swapFAN :: FullAccountName -> FullAccountName+swapFAN (FAN a b) = FAN b a++-- | this type is used to make functions taking 'AccountNames' polymorphic. +data AccountName = AccountN String+                   -- ^ account for the current entity. Only these+                   -- accounts should be accessible in accounting+                   -- actions+                 | UNSAFE_AccountN FullAccountName+                   -- ^ full account for internal use only+                 deriving (Eq, Ord,Show)++instance PrintfArg AccountName where+  formatArg (AccountN s) = formatString s+  formatArg (UNSAFE_AccountN _) = error "not implemented: formatArg (UNSAFE_AccountN)"+             +                          +-- | 'String's are automatically converted to (safe) 'AccoutN'ames, if+-- the -XOverloadedStrings extensions is active. (See "Data.String").+instance IsString AccountName where+  fromString = AccountN ++  +-- | The map from 'FullAccountName's to 'AccountNumber's used+-- internally to address the efficient 'Ledger' array storage+type AccountsMap = M.Map FullAccountName AccountNumber++-- | Extract accounts names order by their internal account numbers+sortedAccountNames :: AccountsMap -> [FullAccountName]+sortedAccountNames = fmap fst . sortBy (comparing snd) . M.toList+++-- | extract the 'AccountNumber' for a 'FullAccountName' from an 'AccountsMap'+internalAccountNumber :: FullAccountName -> AccountsMap -> AccountNumber+internalAccountNumber name accs = fromMaybe (error $+                                             printf "Account '%s' not found\nAvailable Accounts:\n%s"+                                             (show name) $ unlines $ show <$> M.toList accs)+                           $ M.lookup name accs+  ++-- * Postings and Transactions++type Posting = (AccountName,Amount)+type InternalPosting = (AccountNumber,Amount)+++-- | A transaction that is already balanced. Such an object can only+-- be built from 'BalancingTx' using 'balanceTx' and is never needed+-- as function input. This format is used to log the transactions in+-- the 'Ledger' 's 'LogEntry'.+data Tx = UNSAFE_Tx {  tComment  :: Comment+                    , tPostings :: [InternalPosting]+                    }+        deriving (Show,Generic)++-- | A transaction involving only accounts relative to a body, and+-- that is self balancing through the use of an account for the+-- remains+data BalancingTx = BalancingTx { txComment :: Comment+                               , txRemains :: AccountName+                               , txPostings :: [Posting]+                               }++-- | Balance a 'BalancingTx' and prepend the entitiyName to the+-- comment. This is only used internally+balanceTx :: (Monoid w) => BalancingTx -> Acc s l w Tx+balanceTx (BalancingTx comment remains postings) = do+  name <- nameErr "Transactions are only allowed in the presence of an entity"+  fmap (UNSAFE_Tx $ printf "(%s) %s" name comment) $ +    -- convert to account numbers: +    mapM (\(name,amount) -> do number <- accountNumber name+                               return (number,amount) )+    -- add a balancing posting:+    $ (remains,negate $ sum $ snd <$> postings):postings +    +-- * The Ledger++-- | Information that is logged while the ledger is built+data LogEntry = LTx Tx -- ^ Transactions of the current time period+              | LComment String -- ^ Random comment to be put into the ledger+                deriving (Generic,Show)++type EntityLogEntry = (EntityName,LogEntry)++type LedgerIndex = (ADate,AccountNumber)+type LedgerBounds = (LedgerIndex, LedgerIndex)++-- | This class defines what a 'Ledger' is:+class Ledger l where+  -- | it has bounds+  lBounds :: l -> IO LedgerBounds+  -- | single entries for a given account and date+  -- consisting of the account balance and the log+  -- entries for that date can be read.+  lReadEntry :: LedgerIndex -> l -> IO (Amount,[EntityLogEntry])+  -- | the account history can be read+  lAccountHistory :: (ADate,ADate) -> (ADate -> LedgerIndex) -> l -> IO [Amount]+  -- | it can be fixed into an immutable type+  lFix :: l -> IO FixedLedger+  -- | fix withou making a copy. This is has to be safe to use, if the+  -- mutable version is never modified after the freeze operation.+  lunsafeFix :: l -> IO FixedLedger++  +-- | 'LedgerRW' implements a writable (within the IO monad) 'Ledger'.+-- +-- The total balance is always zero and no transactions that depend on+-- future values are allowed.  This is guaranteed, by not exporting+-- UNSAFE_Ledger and instead, the ledger is only changed using the+-- exported safe functions. E.g. `tx`, 'fromTo', ...+data LedgerRW = UNSAFE_Ledger+  -- | Balances for each Date and Account+  { lUNSAFE_Bals :: IOArray LedgerIndex Amount+  -- | Transactions and Comments for each Date+  , lUNSAFE_LogEntries :: IOArray ADate [EntityLogEntry]+  }+                +-- | This type implements the Ledger in immutable form, suitable as+-- the main result of the whole program or for accounting accounts+-- that are garantueed to not change the ledger.+data FixedLedger =  FixedLedger+  { lBals :: Array LedgerIndex Amount+  , lLogEntries :: Array ADate [EntityLogEntry]+  }++data FullLedger = FullLedger+  { flLedger :: FixedLedger+  , flAccounts :: AccountsMap+  }++  -- lEase :: Acc s l a -> Accounting s a -- run any kind of action in the full read-write monad+  -- this is never needed, as there is reason to restrict a type to the+  -- FixedLedger. Only the other way round: if an action changes+  -- something, it will have the LedgerRW type and cannot be `fixed`/++instance Ledger LedgerRW where+  lBounds (UNSAFE_Ledger ledger _) = getBounds ledger+  lReadEntry (date,acc) (UNSAFE_Ledger bals txns) =+    liftM2 (,) (readArray bals (date,acc)) $ readArray txns date+  lAccountHistory times f (UNSAFE_Ledger bals _) =+    getElems  =<< mapIndices times f bals+  lFix (UNSAFE_Ledger bals txns) = liftM2 FixedLedger (freeze bals) $ freeze txns+  lunsafeFix (UNSAFE_Ledger bals txns) = liftM2 FixedLedger (unsafeFreeze bals) $ unsafeFreeze txns++  +instance Ledger FixedLedger where+  lBounds (FixedLedger ledger _) = return $ bounds ledger+  lReadEntry (date,acc) (FixedLedger bals txns) =+    return (bals ! (date,acc), txns ! date)+  lAccountHistory times f (FixedLedger bals _) =+    return $ elems $ ixmap times f bals+  lFix = return+  lunsafeFix = return++-- * Accounting Environment++-- | Represents the environment an accounting action is run on.+data Environment body ledger = Env {+  eLedger :: ledger  -- ^ the ledger of the whole world (i.e. all bodys+  , eAccounts :: AccountsMap -- ^ map of all accounts managed by the ledger+  , eDate :: ADate -- ^ current date+  , eBody :: body  -- ^ current body +  , eName :: Maybe EntityName -- ^ current body's name+  }++                              +-- * Accounting Actions++-- | The Accounting Monad+--+-- This monad is a stack of Reader Writer and IO monad.+--+-- Actions from this monad can read an immutable environment. This+-- environment however, contains references to mutable arrays (see+-- 'LedgerRW'), which can be modified through IO actions lifted into+-- this monad into this+-- monad.+--+-- The 'body' type variable will contain the type of the 'Accounting.Body' the+-- current accounting action is concerned with. +type Acc body ledger writer = RWST (Environment body ledger) writer () IO+                    +-- | A specializations for read-write accounting actions with no+-- (i.e. trivial '()') writer output+type AccountingRW body = Acc body LedgerRW ()+-- | A specialization for read-only actions. These actions can however+-- produce read-write actions as output via the 'Writer' Monad. This+-- is used in 'fixed'.+type AccountingReadOnly body = Acc body FixedLedger (AccountingRW body ())++instance Monad m => Monoid (m ()) where+  mappend = (>>)+  mempty = return ()+++-- | Short-cut class used in type signatures involving 'Acc' and its derivatives+class (Monoid w, Ledger l) => AccPair l w where++instance (Monoid w, Ledger l) => AccPair l w where+  ++-- | type synonym for an accounting action that has an amount as result+type AmountRW body = AccountingRW body Amount+type AmountA body l w = Acc body l w Amount++-- | run a read only action and its genrated read-write output within+-- a general accounting action and pass on its result.+fixed :: AccountingReadOnly s a -> AccountingRW s a+fixed actionRO = do e <- ask+                    l' <- lift . lFix $ eLedger e+                    (res,actionRW) <- lift $ evalRWST actionRO e{eLedger = l'} ()+                    actionRW+                    return res++-- ################ Instances #######################+instance Monoid w => Eq (AmountA s l w) where+  (==) = error "Eq (AmountA s l) is impossible"+  +-- | Allows to use 'min' and 'max' and its derivaties directly on actions that+-- return an amount:+-- +-- > min ( "Cash") (balanceAt date "Cash") :: AmountA s l+--+instance Monoid w => Ord (AmountA s l w) where+  min = liftM2 min+  max = liftM2 max+  (<=) = error "(<=) for (AmountA s l) is impossible"++-- | Allows to use '+','-','*','negate' directly on actions that+-- return an amount. Furthermore any numeral can be used directly as+-- (trivial) accounting action:+-- +-- > soll "Cash" + 4 :: AmountA s l+--+instance Monoid w => Num (AmountA s l w) where+  (*) = liftM2 (*)+  (+) = liftM2 (+)+  (-) = liftM2 (-)+  negate = fmap negate+  abs = fmap abs+  signum = fmap signum+  fromInteger = return . fromInteger++instance Monoid w => Fractional (AmountA s l w) where+  (/) = liftM2 (/)+  recip = fmap recip+  fromRational = return . fromRational++-- * Internal Helper Functions+++ledgerBounds :: (Monoid w, Ledger l) => Acc s l w (LedgerIndex, LedgerIndex)+ledgerBounds = lift . lBounds =<< reader eLedger++readEntryForNumber ::  (Monoid w, Ledger l) => AccountNumber -> Acc s l w (Amount,[EntityLogEntry])+readEntryForNumber acc = do date  <- reader eDate+                            lift . lReadEntry (date,acc) =<< reader eLedger+++accountsNumbers :: (Monoid w, Ledger l) => Acc s l w [AccountNumber]+accountsNumbers = range2 <$> ledgerBounds+++timeInterval :: (Monoid w, Ledger l) => Acc s l w (ADate,ADate)+timeInterval = both fst <$> ledgerBounds++accountNumber :: Monoid w => AccountName -> Acc s l w AccountNumber+accountNumber (UNSAFE_AccountN acc) = internalAccountNumber acc <$> reader eAccounts+accountNumber (AccountN acc) = do+  entName <- reader $ nameErr $ printf "'AccountN %s' is not defined" acc+  internalAccountNumber (FAN entName acc) <$> reader eAccounts++-- | Tries to get the 'eName' of the current entity and throws an+-- error if it is Nothing.+nameErr msg = reader (fromMaybe err . eName)+  where err = error $ printf+              "There is no current entity. "++ msg++readEntryForName :: (Monoid w, Ledger l) => AccountName -> Acc s l w (Amount,[EntityLogEntry])+readEntryForName acc = readEntryForNumber =<< accountNumber acc+++-- * Internal UNSAFE Functions++-- | perform an accounting action at any date+uNSAFE_at :: Monoid w => ADate -> Acc s l w a -> Acc s l w a+uNSAFE_at date = local (\e -> e{eDate=date})++uNSAFE_addToBalance :: InternalPosting -> AccountingRW s ()+uNSAFE_addToBalance (acc,amount) = do+  UNSAFE_Ledger bals _ <- reader eLedger+  date  <- reader eDate+  lift $ updateArray bals (date,acc) (+amount)++-- | add last month's balances to previous month's. This is performed+-- once for every time step in generate+uNSAFE_carryOver :: AccountingRW s ()+uNSAFE_carryOver = do+  start <- fst <$> timeInterval+  oldD <- reader $ (shift $ - 1) . eDate+  when (start <= oldD) $ accountsNumbers >>=+    (mapM_  $ \ac -> do+        oldB <- fst <$> uNSAFE_at oldD (readEntryForNumber ac)+        uNSAFE_addToBalance (ac,oldB))++                 
+ src/HAX/Common.hs view
@@ -0,0 +1,251 @@+{-# LANGUAGE NoMonomorphismRestriction+,  TypeSynonymInstances+,  FlexibleInstances+ #-}++-- | This module provides some common functions and reexports basic+-- modules.+module HAX.Common (+  -- * Basic Types+   Amount+  , Comment++  -- * Ranges and Arrays+  , range1+  , bounds1+  , range2+  , bounds2+  , updateArray+  , assocArray+    +   -- * Utility functions+  , when'+  , both+  , conv+  , positivePart+  , assert+  , PList(..)+    +  -- * Dates+  , ADate()+  , date+  , endOfYear+  , month+  , yearMonth+  , getMonth+  , getYear++  -- * Date spans++  , ASpan(..)+  , months+  , yearMonthSpan+  , yearSpan+  , divides+  , dateSpan+  , shift++   -- * Reexported modules+  ,module Control.Applicative+  ,module Control.Arrow+  ,module Control.Monad+  ,module Control.Monad.RWS.Strict+  ,module Data.Array+  ,module Data.Array.IO+  ,module Data.Decimal+  ,module Data.Function+  ,module Data.List+  ,module Data.Maybe+  ,module Data.Monoid+  ,module Data.Ord+  ,module Data.Ratio+  ,module Data.String+  ,module Data.Tuple+  ,module Text.Printf+  ,module Text.Show+  ) where ++import Control.Applicative+import Control.Arrow+import Control.Monad+import Control.Monad.RWS.Strict+import Control.Monad.Trans (lift)+import Data.Array+import Data.Array.IO+import Data.Decimal+import Data.Function+import Data.List hiding (span)+import Data.Maybe+import Data.Monoid+import Data.Ord+import Data.Ratio+import Data.String+import Data.Tuple+import Prelude hiding (span)+import Text.Printf+import Text.Show++type Amount = Decimal+type Comment = String++-- | accounting dates+data ADate = UNSAFE_ADate { dUNSAFE_Months :: Int -- ^ 0 corresponds to January 0+                          }+          deriving (Eq,Ord,Ix)++-- | represents a time span between accounting dates+data ASpan = ASpan { sMonths :: Int -- ^ 0 corresponds to 0 time difference+                          }+          deriving (Eq,Ord,Ix)++getMonth :: ADate -> Int+getMonth = snd . yearMonth++getYear :: ADate -> Int+getYear = fst . yearMonth+++-- | convert a month to a pair of 'Int's.+yearMonth :: ADate -> (Int , Int) -- ^ (year,month)+yearMonth (UNSAFE_ADate m) = second succ $ m `divMod` 12++-- | Create an 'ADate' value that corresponds to the last month of the+-- given year.+endOfYear :: Int -> ADate+endOfYear y = UNSAFE_ADate $ 12 * (y+1) -1++-- | Create an 'ADate' value that corresponds to the given month in+-- year 0. (Fails if argument is outside of [1..12])+month :: Int -> ADate+month m  = if m >12 || m<1 then error $ printf "Bad Month: %v" m+               else UNSAFE_ADate $ m - 1++-- | Create an 'ASpan' value corresponding to the given number of+-- monhts.+months :: Int -> ASpan+months = ASpan++-- | convert a span to a pair of 'Int's.+yearMonthSpan :: ASpan -> (Int , Int) -- ^ (year,month)+yearMonthSpan (ASpan m) =  m `divMod` 12+           +yearSpan :: ASpan -> Int+yearSpan = fst . yearMonthSpan ++-- | Calculate the span between two dates+dateSpan :: ADate -- ^ start date+     -> ADate -- ^ end date+     -> ASpan+dateSpan a b = ASpan $ on (-) dUNSAFE_Months  b a++-- | Shift a date by a given date span.+shift :: ASpan -> ADate -> ADate+shift s d = UNSAFE_ADate $ dUNSAFE_Months d + sMonths s+                    +-- | Construct an 'ADate' value from a month an a year+date :: Int -> Int -> ADate+date m y = if y < 1800 then error $ printf "Year %v is earlier than 1800???" y+           else shift (ASpan $ 12 * y) $ month m++instance Show ASpan where+  show s= show (sMonths s) ++ " months"++instance Show ADate where+  show = (\(y,m) -> printf "%2d/%02d" m $ y`mod` 100) . yearMonth++instance PrintfArg (PList Decimal) where+  formatArg ds _ = showListWith showsD $ pList ds++newtype PList a = PList { pList :: [a] }++instance PrintfArg ADate where+  formatArg = formatString . show++-- | Check if the second span is a multiple of the first.+divides :: ASpan -> ASpan -> Bool+divides a b = forSpan2 mod b a == 0+            +forSpan2 f = (ASpan .) . on f sMonths+forSpan1 f =  ASpan . f . sMonths++-- 'ASpan's support the usual arithmetics+instance Num ASpan where+  (+) = forSpan2 (+)+  (*) = forSpan2 (*)+  (-) = forSpan2 (-)+  fromInteger = ASpan . fromInteger+  abs = forSpan1 abs+  signum = forSpan1 signum++-- | helper function to update an MArray value+updateArray :: (MArray a e m, Ix i) => a i e -> i -> (e->e) -> m ()+updateArray a i f = readArray a i >>= writeArray a i . f++                    +instance (Show a, Integral a) => PrintfArg (DecimalRaw a) where+  formatArg d _ = showsD d+  -- parseFormat = error "printf parsing not implemented for DecimalRaw"++showsD = shows . roundTo 2 . (/1000)+++-- | Get the range of the first dimension of an array+range1 = range . bounds1+-- | Get the bounds of the first dimension of an array+bounds1 = both fst+          +-- | Get the range of the second dimension of an array+range2 = range . bounds2+-- | Get the bounds of the second dimension of an array+bounds2 = both snd+++-- | apply a function to both elements of a pair+both :: (b -> c) -> (b, b) -> (c, c)+both = join (***)++-- | apply a function to both elements of a pair+bothM :: Monad m => (b -> m c) -> (b, b) -> m (c, c)+bothM f (a,b) = do a' <- f a+                   b' <- f b+                   return (a',b')++-- -- more general+-- ,RankNTypes+-- ,ScopedTypeVariables+-- ,ConstraintKinds+-- both :: (as a1, as a2) => (forall a. as a => a->b) -> (a1,a2) -> (b,b)+-- both f = f *** f++-- | Return a value only if the condition holds and zero otherwise.+when' :: Num a => Bool -> a -> a+when' a b = if a then b else 0+++-- | Conversion between different fractional types. E.g. between+-- 'Double' and 'Decimal'.+conv :: (Real a, Fractional c) => a -> c+conv = (fromRational . toRational)+++-- | generate an array and take the bounds from the input data+assocArray :: Ix i =>+             [(i, e)] -- ^ a list of associations of the form (index, value)+          -> Array i e+assocArray assocs = array (minimum indx,maximum indx) assocs+  where indx = fst <$> assocs++-- | bound checking array acces with error msg+-- (?!) :: Ix i => Array i e -> (String,i) -> e+-- array ?! (String,index) = if bounds array `inRange` index+--                           then array ! index+--                           else error index ++-- | take the positive par of a 'Num'+positivePart a = max a 0++-- | assert that the result of an action satisfies a certain condition or print an error message+assert condition getAm msg = do+  am <- getAm+  when (not $ condition am) $ lift $ putStrLn $ "ASSERTION FAILED: " ++ msg+  return am
+ src/HAX/Example.hs view
@@ -0,0 +1,167 @@+{-# LANGUAGE + NoMonomorphismRestriction+, OverloadedStrings+ #-}+module HAX.Example where++import HAX.Bookkeeping+import HAX.Report+import Data.Ix+import HAX.Accounting+import HAX.Assets+import HAX.Common+import HAX.Germany++start = date 1 2014+example1 = World (start,date 12 2015) [facebookE, markZuck]+            +facebookE = Entity "Facebook" ["AV"] facebookAction facebookGmbH++facebookGmbH = GmbH facebook++facebook = Gewerbe { gwAngestellte = [markZuckNat]+               , gwMonatlMietkosten = 9930+               , gwWaegen = [car]+               , gwTreibender  = facebookGmbH+               , gwHebesatz    = 3.5 -- Some town in Germany+               }+++markZuck = Entity "MarkZuck" ["Haus","Hausdarlehen","Cash" ] markZuckAction  markZuckNat++markZuckNat = NatuerlichePerson{ pGeburt = date 12 1960+                         , kinderMitKindergeldImHaushalt = True+                         , landOderForstwirt = False+                         , pSplitting = False+                         , krankenUndPflegeOhneZuschuesse = True+                         , krankenUndPflegeOhneZuschuessePartner = Nothing+                         , kinderMitKindergeld = 2+                         , kinderFreibetragsVerdopplung = False+                         , pAuswaertigeKinderInBerufsausbildung = 1+                         , pWagen = car+                         , pBruttoGehaltMtl = 4000+                         , pLohnsteuerMtl = 1300+                         , pVersicherungsPflicht = False+                         , pGewerbe = Nothing+                         }++car = AutoMonatl { aKmPrivatOhneArbeitsfahrten = (45000 - 7300 - 2 * 33 * 250)/12+                , aKmGeschaeftl =  7300/12+                , aKmArbeitsstaette = 32+                , aArbeitsTage = 250 / 12+                , aFixKosten = 208+                , aLeasing = 400+                , aSpritKostenProKm = 8/100 * 1.4+                , aListenPreis = 35600+                , aFirmenWagen = Pauschal+                }++markZuckAction = do++  onceAt start $ do+    tx $ BalancingTx "Anfangswerte"+      privatVermoegen+      [("Haus",240000)+      ,("Facebook",-430000)+      ]+  ++  -- handle $ FixedPayment 600 start start 1 "SdE" "Cash" "Miete"+  -- -- -- balanceInterest $ IAccount ent LineOfCredit ir+  -- handle $ Loan 100000 (Annuity $ (0.11378694062058245-ir2)/12)+  --   "Cash" "Hausdarlehen" (IR ir2 "SdE") (date 1 2014) 1+  +  handle $ TransactionalAccount (IR verrechZins privatAusgaben)+    (IR verrechZins sonstigeEinkuenfteAusKapitalvermoegen)+    "Facebook" 12++  -- -- -- transferAll "clearing the shit" Cash Trash+  +  -- fromTo 10 "mntl. Entnahme" "Facebook" "Cash" ++  when (not $ pVersicherungsPflicht markZuckNat) $ do+    tx $ BalancingTx "Kranken- & Pflegeversicherung"+      privatAusgaben+      [(krankenundPflegeversicherungBasisbeitraege, 6184/12)+      ,(giroKonto, -632)+      ]+                                                    +        +  +  onceAt (date 12 2012) $ fromTo 449 "Erstattungsüberhaenge aus Kirchensteuer"+    erstattungsUeberhaenge privatVermoegen +  return ()++++    +facebookAction = do+  -- fromTo 10 "mntl. Entnahme" "Kasse" "MarkZuck" +  onceAt start $ do+    tx $ BalancingTx "Anfangswerte"+      "AV"+      [("MarkZuck",430000)+      ,(gewinnVerlustVortraege,-409000)+      ]++  handle $ TransactionalAccount (IR 0.082 schuldZinsen)+    (IR 0.005 sonstigerUeberschuss)+    giroKonto 12+  +  handle $ TransactionalAccount (IR verrechZins schuldZinsen)+    (IR verrechZins sonstigerUeberschuss)+    "MarkZuck" 12+  +  tx $ BalancingTx "Ertrag"+    sonstigerUeberschuss+    [(giroKonto,+      (2109789   -- "Umsatz"+       +   7036   -- verrechnung Sachbezüge+       +   2993   -- sonstige Erträge+       -1266300   -- "Wareneinsatz"+       - 390930   -- Lohn+       +  48000   -- Lohn MarkZuck (wird im Gewerbe behandelt)+       +   3900   -- Lohn + Abgaben Putzfrau+       - 161250   -- LohnAbgaben+       - 162901   -- Raumkosten+       + 119160   -- miete (wird im Gewerbe behandelt)+       -   6924   -- Versicheurngen+       -   5783   -- Reparaturen+       -  22940   -- KFz+       +  12240   -- KFz + Sprit MarkZuck (wird im Gewerbe behandelt)+       -  44939   -- Werbekosten & Reise+       -   7755   -- Warenabgabe+       -  27803   -- Verschiedene betriebliche Kosten+       -    190   -- sonstige Verluste+       +    450   -- Kst Aufzinsung+       -    508   -- KFz Steuer+       -- -   8400   -- Zinsen+       -- +  17018   -- Zinsen MarkZuck+      )/12) +    ,("AV", negate+      (1196       -- Abschreibungen+       )/12)+     ]++  handle $ Liquidity sonstigerUeberschuss giroKonto+    [ -27184.58+    , -64996.74+    , -16134.78+    , -11022.01+    , -1467.94+    , -4401.49+    ,  10538.55+    ,  7653.95+    ,  8071.67+    , -20976.63+    , -5815.15+    ,  167876.36+    ]++  return ()++       +       +    +ir2 = 0.0242+verrechZins = 0.046
+ src/HAX/Germany.hs view
@@ -0,0 +1,22 @@+{-# LANGUAGE + NoMonomorphismRestriction+, OverloadedStrings+, TypeSynonymInstances+, FlexibleInstances+, TypeFamilies+, FlexibleContexts+ #-}++-- | This module imports everything needed for German Tax calculation & Accounting.+module HAX.Germany (+  module HAX.Germany.Einkommensteuer+  ,module HAX.Germany.Gewerbe+  ,module HAX.Germany.NatuerlichePerson+  ,module HAX.Germany.GmbH+  ,module HAX.Germany.Subjekte+) where+import HAX.Germany.Subjekte+import HAX.Germany.NatuerlichePerson+import HAX.Germany.GmbH+import HAX.Germany.Einkommensteuer+import HAX.Germany.Gewerbe
+ src/HAX/Germany/Einkommensteuer.hs view
@@ -0,0 +1,498 @@+{-# LANGUAGE + NoMonomorphismRestriction+, OverloadedStrings+ #-}++-- | +-- TODO: (lohnsteuern) werbungskosten+-- +-- Beachten: Lebensversicherungen+-- https://www.smartsteuer.de/portal/lexikon/L/Lebensversicherung.html#D063053300009+--+-- http://www.finanzen-versicherungen-blog.de/besteuerung-lebensversicherung-auszahlung-lebensversicherung-steuerfreie-lebensversicherung/+--+module HAX.Germany.Einkommensteuer where++import           HAX.Accounting+import           HAX.Bookkeeping+import           Control.Monad.Writer+import qualified Data.Map as M+import           HAX.Common+import           HAX.Germany.Subjekte+import           HAX.Germany.Gewerbe+-- * Konten+-- ** Vermögen+privatVermoegen = "Verm"+        ++-- ** Aufwandskonten++ertragsKonten = [ erstattungsUeberhaenge+                , privateVerausserungsgeschaefte+                , einkuenfteAusBeteiligungenAnKapitalgesellschaftenKapEst+                , einkuenfteAusBeteiligungenAnKapitalgesellschaften+                , sonstigeEinkuenfteAusKapitalvermoegenKapEst+                , sonstigeEinkuenfteAusKapitalvermoegen+                , ausserordentlicheEinkuenfte+                , arbeitslohn+                , steuerfreieEinnahmen+                , uebrigeSummeDerEinkuenfte+                , einkuenfteAusGewerbebetrieb+                ]+++einkuenfteAusGewerbebetrieb = "EGW"+steuerfreieEinnahmen = "StFrei" ++-- | positive oder negative summe aller Einkunftsarten außer aus+-- Kapitalvermögen und privaten Veräußerungsgeschäften+uebrigeSummeDerEinkuenfte = "SdE" +arbeitslohn = "Lohn" +werbungskostensNichtSelbststaendigeArbeitOhneFahrten = "WerbNSA" +ausserordentlicheEinkuenfte = "ausserE" +-- ^ gesetz. Renten, Pensionen, Bezüge, .. +-- im Sinne des https://www.jurion.de/Gesetze/EStG/24a+-- +-- https://www.smartsteuer.de/portal/lexikon/A/Altersentlastungsbetrag.html+einkuenfteAusBeteiligungenAnKapitalgesellschaftenKapEst = "EBKK"+-- ^ nicht solche, die der tariflichen/persönlichen ESt. unterliegen (z.B. aus+-- Betriebsvermögen)+einkuenfteAusBeteiligungenAnKapitalgesellschaften = "EBK"+-- ^ beachten: 40% steuerfrei (?)+sonstigeEinkuenfteAusKapitalvermoegenKapEst = "EKVE"+-- ^ nicht solche, die der tariflichen/persönlichen ESt. unterliegen+sonstigeEinkuenfteAusKapitalvermoegen = "EKV"+-- ^ einkuenfteAusKapitalvermoegen'hen nach 32d Abs 2.+-- http://www2.nwb.de/portal/content/ir/service/news/news_1413444.aspx+-- http://www.haufe.de/finance/finance-office-professional/rechtmaessigkeit-der-tariflichen-besteuerung-nach-32d-abs-2-nr-1b-estg_idesk_PI11525_HI5088406.html+--+-- https://www.smartsteuer.de/portal/lexikon/E/Einkuenfte-aus-Kapitalvermoegen.html+privateVerausserungsgeschaefte = "PV" +erstattungsUeberhaenge = "eUeb" +                            +-- * Aufwandskonten++aufwandsKonten = [ sonstigeVorsorgeAufwendungOhneBasisbeitraege+                 , krankenundPflegeversicherungBasisbeitraege+                 , privatAusgaben+                 , altersvorsorgeAufwendungenArbeitgeberAnteil+                 , altersvorsorgeAufwendungenEigenanteil+                 , werbungskostensNichtSelbststaendigeArbeitOhneFahrten+                 , lohnSteuer+                 ]+                         +lohnSteuer = "LSt" ++altersvorsorgeAufwendungenEigenanteil = "AvAE"+-- ^ gesetzliche Renten / Rürüp++riesterRentenEinzahlung :: IsString a => a+riesterRentenEinzahlung = error "riesterRente not implemented"+-- ^ not implemented!+--+-- riester rente (+ zulagen. aber nur bis 2100 €??. von+-- Steuerersparnis durch SA wird die Zulage abgezogen. Aber nur wenn+-- ergebnis positiv)+altersvorsorgeAufwendungenArbeitgeberAnteil = "AvAA" ++krankenundPflegeversicherungBasisbeitraege = "KPB" +-- ^ Kranken- und Pflegeversicherung Basisbeitraege" --+-- nur 96% der Leistungen wenn Krankengeld enthalten++sonstigeVorsorgeAufwendungOhneBasisbeitraege = "sVA" ++privatAusgaben = "PA" ++-- * Types++type AccNat l w = Acc NatuerlichePerson l w+type Betrag l w = Acc NatuerlichePerson l w Amount++type AccNatRW = AccountingRW NatuerlichePerson+type BetragRW = AccountingRW NatuerlichePerson Amount++type AccNatRO = AccountingReadOnly NatuerlichePerson+type BetragRO = AccountingReadOnly NatuerlichePerson Amount++-- * Utility++kinderGeld :: NatuerlichePerson -> Amount+kinderGeld p = g $ kinderMitKindergeld p+  where g 1 = 184+        g 2 = g 1 + 184+        g 3 = g 2 + 190+        g n = g (n-1) + 215++-- | Calcuate the current age in years+alter :: Monoid w => AccNat l w Int+alter = do geb <- readBody pGeburt+           date <- reader eDate+           return $ yearSpan $ dateSpan geb date++-- | Calcuate in which year reaches a certain age+jahrInDemManSoAltWird :: Monoid w => Int -> AccNat l w Int+jahrInDemManSoAltWird x = readBody $ getYear . shift (months $ 12 * x) . pGeburt++                 +splitFaktor p = if pSplitting p then 2 else 1++-- * Berechnung+                +-- | Calculates of the Einkommensteuer for all possible variants,+-- selects the cheapest (Günstigervergleich) and applies it+-- (e.g. Verlust Vortäge).+einkommenSteuer :: GewerbeSteuer -> BetragRW+einkommenSteuer gewst = fixed $ pass $ do+  name <- reader eName+  moeglichkeiten <- forM (range (minBound,maxBound)) -- [V False False () ()]+    $ \v -> (,) v <$> listen (einkommenSteuerVariante gewst v + abgeltungsSteuer v)+  let (variante,(steuer,action)) =+        minimumBy (comparing $ fst.snd) moeglichkeiten++  (soli,actionSoli) <- listen $ solidaritaetsZuschlag gewst variante++  let actualAction = do logLedger $ printf "Die Günstigerprüfung ergibt: %v\n" $ show variante+                        logLedger $ printf "Ersparnisse gegenüber anderen Varianten: %v\n" $+                           show $ (roundTo 0.(steuer-).fst.snd) <$> moeglichkeiten+                        action+                        actionSoli+  return (steuer, const actualAction)+++-- * Günstigervergleich+  +-- | Type representing the different possible variants of+-- Einkommensteuer calculation to be compared +data Variante = V { mitKinderFreibetrag :: Bool+                  , teileinkuenfteVerfahren :: Bool+                  -- ^ wahlrecht nach § 32d Abs. 2 Nr. 3 EStG+                  -- https://www.smartsteuer.de/portal/lexikon/A/Aktien.html+                  -- https://www.smartsteuer.de/portal/lexikon/A/Abgeltungsteuer.html#D063109700011+                  -- ^ http://de.wikipedia.org/wiki/Teileink%C3%BCnfteverfahren+                  --+                  --  https://www.smartsteuer.de/portal/lexikon/E/Einkuenfte-aus-Kapitalvermoegen.html#D063026000028+                  --+                  -- damit ist nicht gemeint 32d Abs 6+                  -- https://www.smartsteuer.de/portal/lexikon/A/Abgeltungsteuer.html#D063109700006+                  -- http://www.steuernetz.de/aav_steuernetz/lexikon/K-36317.xhtml?currentModule=home+                  -- +                  -- sondern 32 Abs. 2 Nr. 3 EStG+                  , riesterRente :: () -- ^ not implemented+                  , vorsorgeaufwendungenAlteRegel  :: () -- ^ not implemented+                  }+              deriving (Bounded,Eq,Ord,Ix,Show)++type VarBetragRO = Variante -> BetragRO+type VarBetrag l w = Variante -> Betrag l w++-- * Berechnung der Einkünfte ++-- firmenWagenNutzung :: +                     +-- | https://www.smartsteuer.de/portal/lexikon/E/Einkuenfte-aus-Kapitalvermoegen.html#D063026000031+--+-- http://de.wikipedia.org/wiki/Sparer-Pauschbetrag+-- einkuenfteAusKapitalvermoegen' = withBody $ \p ->+--   assert (>=0) (positivePart $+--                 haben einkuenfteAusKapitalvermoegen - (splitFaktor p * 801))+--   "Verlust aus Kapitalvermoegen not implemented" ++-- ^ https://www.smartsteuer.de/portal/lexikon/E/Einkuenfte-aus-nichtselbststaendiger-Arbeit.html+--+-- https://www.smartsteuer.de/portal/lexikon/E/Entfernungspauschale.html+--+-- https://www.smartsteuer.de/portal/lexikon/W/Werbungskostenpauschbetrag.html+einkuefteAusNichtSelbststaendigerArbeit' = do+  einkuenfte <- haben arbeitslohn+  entfernungsPauschale' <- readBody $ entfernungsPauschale . pWagen+  sonstige <- soll werbungskostensNichtSelbststaendigeArbeitOhneFahrten+  let werbungskosten = max (entfernungsPauschale' + sonstige)+                       $ min (positivePart einkuenfte) 1000 -- pauschbetrag nur bis zur Höhe der Einküfte+  singleLog "Enternungspauschale: %v\n" entfernungsPauschale'+  singleLog "Werbungskosten (nicht-selbstständige Arbeit): %v\n" werbungskosten+  singleLog "Einkuefte Aus nicht-selbstständiger Arbeit: %v\n" einkuenfte+  return $ einkuenfte - werbungskosten+  +summeDerEinkuenfte :: VarBetragRO+summeDerEinkuenfte v = singleResult "Summe der Einkuenfte" $+                       uebrigeSummeDerEinkuenfte' v+                       + einkuefteAusNichtSelbststaendigerArbeit'+                       + haben ausserordentlicheEinkuenfte+-- http://www.gesetze-im-internet.de/estg/__3c.html                +-- 3 Nr. 40 EStG+uebrigeSummeDerEinkuenfte' :: VarBetragRO+uebrigeSummeDerEinkuenfte' v = singleResult "Summe der übrigen Einkuenfte" $+   haben uebrigeSummeDerEinkuenfte+   + haben sonstigeEinkuenfteAusKapitalvermoegen+   + haben einkuenfteAusBeteiligungenAnKapitalgesellschaften+   + 0.6 * when' (teileinkuenfteVerfahren v)+        (haben einkuenfteAusBeteiligungenAnKapitalgesellschaftenKapEst)++-- * Steuerberechnung++-- | Calculates the Einkommensteuer for a given variant+einkommenSteuerVariante :: GewerbeSteuer -> VarBetragRO+einkommenSteuerVariante gewst v = do+  zvE <- zuVersteuerndesEinkommen v++  tariflicheEinkommensteuer <-+    return $ steuerBetrag zvE++  geminderteTariflicheEinkommensteuer <-+    return tariflicheEinkommensteuer+    -- - Minderungsbetrag nach Punkt 11 Ziffer 2 des Schlussprotokolls zu Art. 23 DBA Belgien+    -- - ausländische Steuer nach § 34c Abs. 1 und 6 EStG, § 12 AStG+    +  festzusetzendeEinkommensteuer <-+    return geminderteTariflicheEinkommensteuer+    - gewStErmaessigung gewst tariflicheEinkommensteuer+    + kinderGeldAnrechnung v++  tell $ logLedger $ printf "festzusetzende Einkommensteuer: %v\n" +    festzusetzendeEinkommensteuer++  return $ festzusetzendeEinkommensteuer++-- | https://www.smartsteuer.de/portal/lexikon/S/Solidaritaetszuschlag.html+--+-- TODO eigentlich unter berücksichtigung von Kinderfreibeträgen?+--+-- https://www.jurion.de/Gesetze/SolZG/3+--+-- http://www.steuerlinks.de/steuerlexikon/lexikon/gewerbesteueranrechnung.html+solidaritaetsZuschlag :: GewerbeSteuer -- ^ GewerbeSteuer+                         -> VarBetragRO+solidaritaetsZuschlag gewst v = singleResult "Solidaritätszuschlag" $ withBody $ \p -> do+  let soliVariante = v{mitKinderFreibetrag=True}+  fEst <- singleResult "Bemessungsgrundlage für Solidaritätszuschlag" $+          mute $ einkommenSteuerVariante gewst soliVariante+                 - kinderGeldAnrechnung soliVariante+  let zuschlag1 = fEst * solidaritaetsFaktor+      zuschlag2 = 0.2 * positivePart (fEst - (splitFaktor p * 972))+  return $ min zuschlag1 zuschlag2++-- | https://www.smartsteuer.de/portal/lexikon/S/Steuerermaessigung-bei-Einkuenften-aus-Gewerbebetrieb.html+--+-- 35 EStG+--+-- http://books.google.de/books?id=NY1oFSCGGM0C&pg=PA123&lpg=PA123&dq=35+estg+verlustausgleich&source=bl&ots=hXL1TzqbyG&sig=cky8FRnixRZVTOMzWWRh7X8A_6Q&hl=en&sa=X&ei=eHUDVODgGcbqaPXMgcAK&ved=0CEoQ6AEwBA#v=onepage&q=35%20estg%20verlustausgleich&f=false+gewStErmaessigung :: GewerbeSteuer -> Amount -- ^ geminderte Einkommensteuer+                  -> BetragRO+gewStErmaessigung gewst est = singleResult "Gewerbesteuerermäßigung" $ do+  einkGew <- positivePart $ haben einkuenfteAusGewerbebetrieb+  when' (einkGew > 0) $ do+    positiveEinkunfte <- sum <$> mapM (positivePart.haben) ertragsKonten+    let hoechstBetrag = einkGew / positiveEinkunfte * est+    return $ max hoechstBetrag $ max (gwSteuer gewst) $ gwMessbetrag gewst * 3.8+                                     +                    +  +-- | Berechnet den Tariflichen Einkommensteuerbetrag für das zu+-- versteuernde Einkommen+steuerBetrag :: Amount -> Amount+steuerBetrag zvE | zvE <= ks !! 0 = 0+                 | zvE <= ks !! 1 = (97458/100*y+1400)*y+                 | zvE <= ks !! 2 = (22874/100*z+2397)*z+971+                 | zvE <= ks !! 3 = 42/100*(zvE-ks !! 2)+13971+                 | True           = 45/100*(zvE-ks !! 3)+97067+  where y = (zvE-ks !! 0)/10000+        z = (zvE-ks !! 1)/10000+        ks = [8354,13469,52881,250730];++testSteuerBetrag = all (\(x,y) ->  y == steuerBetrag x)+                   [(4000,0.0)+                   ,(12000,639.9939990728)+                   ,(24000,3748.9578455914)+                   ,(48000,11975.5534967914)+                   ,(120000,42160.98)]+                  +-- | https://www.smartsteuer.de/portal/lexikon/A/Abgeltungsteuer.html+abgeltungsSteuer :: VarBetragRO+abgeltungsSteuer v = withBody $ \p -> do+  0.25 * positivePart (haben sonstigeEinkuenfteAusKapitalvermoegenKapEst+                       + when' (not $ teileinkuenfteVerfahren v)+                       (haben einkuenfteAusBeteiligungenAnKapitalgesellschaftenKapEst)+                       - (splitFaktor p * 801))+++                       ++-- | Calculate the "zu versteuerndes Einkommen" for a given variant+-- https://www.smartsteuer.de/portal/lexikon/E/Einkommensteuer.html+--+-- more references in the source code...+zuVersteuerndesEinkommen ::  VarBetragRO+zuVersteuerndesEinkommen v = do+  sdE <- summeDerEinkuenfte v++    -- https://www.smartsteuer.de/portal/lexikon/G/Gesamtbetrag-der-Einkuenfte.html+  gesamtbetragderEinkuenfte <-+    return sdE+    -- https://www.smartsteuer.de/portal/lexikon/P/Private-Veraeusserungsgeschaefte.html#D063064400040+    + assert (>= 0) (haben privateVerausserungsgeschaefte)+    "Verlust aus privaten Verausserungsgeschaeften not implemented" +    - altersentlastungsBetrag v+    - entlastungsbetragfuerAlleinerziehende+  +  tell $ logLedger $ printf "Gesamtbetrag der Einkünfte: %s" gesamtbetragderEinkuenfte++  einkommen <-+    return gesamtbetragderEinkuenfte+    -- https://www.smartsteuer.de/portal/lexikon/G/Gesamtbetrag-der-Einkuenfte.html+    -- https://www.smartsteuer.de/portal/lexikon/S/Sonderausgaben.html#D063071200006+    + haben erstattungsUeberhaenge+    -- vorzutragender verlust nur bei negativen SdE (§ 10d Abs.1 EStG )+    - verlustAbzug "ESt" (if sdE < 0 then sdE else positivePart gesamtbetragderEinkuenfte)+    - sonderAusgaben+    - aussergewoehnlicheBelastungen+    -- - sonderAfA+    -- + zuzurechnendes Einkommen gem. § 15 Abs. 1 AStG++  zvE <-+    return einkommen+    -- https://www.smartsteuer.de/portal/lexikon/K/Kinderfreibetrag.html+    - kinderFreibetrag v+    -- - Härteausgleich+    +  tell $ logLedger $ printf "zu versteuerndes Einkommen: %v" zvE++  return zvE++mute = censor (const mempty)++  +-- * Sonderausgaben und Belastungen+                                                 +aussergewoehnlicheBelastungen = ausbildungsKosten++-- § 10 EStG (3)+-- http://de.wikipedia.org/wiki/Sonderausgabe_(Steuerrecht)+-- https://www.smartsteuer.de/portal/lexikon/S/Sonderausgaben.html+sonderAusgaben :: BetragRO+sonderAusgaben = allgemeineSonderAusgaben+                 + altersvorsorgeAufwendungen+                 + sonstigeVorsorgeAufwendung+  +-- | allgemeine Sonderausgaben (z.B. auch die+-- Kirchensteuer beinhalten)+--+-- Aktuel nur Pauschbetrag implementiert+allgemeineSonderAusgaben :: BetragRO+allgemeineSonderAusgaben =  singleResult "allg. Sonderaushaben" $+                            readBody $ \p -> 36 * splitFaktor p +  +-- | http://www.ruv.de/de/r_v_ratgeber/altersvorsorge/foerderung_staat/5_basisrente_sonderausgabenabzug.jsp+--+-- http://www.steuernetz.de/aav_steuernetz/lexikon/K-23995.xhtml?currentModule=home+altersvorsorgeAufwendungen :: BetragRO+altersvorsorgeAufwendungen = singleResult "Altersvorsorgeaufwendungen" $ withBody $ \p -> do+  eigenanteil <- soll altersvorsorgeAufwendungenEigenanteil+  arbeitgeber <- soll altersvorsorgeAufwendungenArbeitgeberAnteil+  jahr <- reader $ fromIntegral . getYear . eDate +  let satz = min 1 $ 0.76 + (jahr - 2013) * 0.02+  return $ satz * (min (splitFaktor p * 20000) $ eigenanteil + arbeitgeber)+    - arbeitgeber++-- | http://www.steuernetz.de/aav_steuernetz/lexikon/K-24101.xhtml?currentModule=home+--+-- TODO bis 2019 möglicher Günsitgervergleich mit Berechnungsmethode bis 2004?+sonstigeVorsorgeAufwendung :: BetragRO+sonstigeVorsorgeAufwendung = singleResult "sonstige Vorsorgeaufwendungen" $ withBody f+  where f p = do+          basis    <- soll krankenundPflegeversicherungBasisbeitraege+          sonstige <- soll sonstigeVorsorgeAufwendungOhneBasisbeitraege+          return $ max basis -- mindestens die Basisbeiträge+            $ min (hbSelbst + hbPartner) -- höchstens die Höchstebeträge+            $ basis + sonstige+          where hoechstBetrag ohneZus = if ohneZus then 2800 else 1900+                hbSelbst = hoechstBetrag $ krankenUndPflegeOhneZuschuesse p+                hbPartner = when' (pSplitting p) $ hoechstBetrag $+                            fromMaybe (error $ "krankenUndPflegeOhneZuschuessePartner is nothing")+                            $ krankenUndPflegeOhneZuschuessePartner p+  +-- * Sonstiges++entlastungsbetragfuerAlleinerziehende :: BetragRO+entlastungsbetragfuerAlleinerziehende = withBody $ \p -> do+  singleResult "Entlastungsbetrag für Alleinerziehende" $+    when' (kinderMitKindergeldImHaushalt p && not (pSplitting p)) 1308+                                                   ++kinderFreibetrag :: VarBetragRO+kinderFreibetrag v = singleResult "Kinderfreibetrag" $+                     when' (mitKinderFreibetrag v) (fst <$> kinderFreibetragUndGeld)++kinderGeldAnrechnung :: VarBetragRO+kinderGeldAnrechnung v = singleResult "Kindergeld Anrechnung" $+                         when' (mitKinderFreibetrag v) (snd <$> kinderFreibetragUndGeld)+  +ausbildungsKosten :: BetragRO+ausbildungsKosten = singleResult "Ausbildungskosten" $ readBody $ \p ->+                         splitFaktor p * 924/2 * pAuswaertigeKinderInBerufsausbildung p++                         +-- | Berechnet den anzurechnenden Freibetrag und das abzuziehende Kindergeld+kinderFreibetragUndGeld :: Monoid w => Acc NatuerlichePerson l w (Amount, Amount)+kinderFreibetragUndGeld  = withBody $ \p -> do+  let faktor = if pSplitting p || kinderFreibetragsVerdopplung p+               then 1 else 0.5+  return (faktor * kinderMitKindergeld p * 7008, 12 * faktor * kinderGeld p)+++-- | <https://www.smartsteuer.de/portal/lexikon/A/Altersentlastungsbetrag.html>+--+-- <https://www.jurion.de/Gesetze/EStG/24a>+altersentlastungsBetrag :: VarBetragRO+altersentlastungsBetrag v = withBody $ \p -> do+  grundlage <- haben arbeitslohn+               + positivePart (mute $ uebrigeSummeDerEinkuenfte' v)+  when (pSplitting p) $ error "altersentlastungsBetrag für Splitting not implemented"+  -- Im Fall der Zusammenveranlagung von Ehegatten ist die Anwendung+  -- des Altersentlastungsbetrages für jeden Ehegatten gesondert zu+  -- prüfen (H 24a [Altersentlastungsbetrag bei Ehegatten] EStH).+  +  (prozent, hoechst) <- eigenerAltersentlastungsSatz <$> jahrInDemManSoAltWird 65+  singleResult "Altersentlastungsbetrag" $+    return $ min hoechst $ grundlage * prozent++  where eigenerAltersentlastungsSatz jahr = if jahr > 2014 then (0,0)+                                            else altersentlastungsSaetze ! jahr+altersentlastungsSaetze :: Array Int (Amount,Amount)+altersentlastungsSaetze = assocArray [( 2005,(  40.0,  1900 )) +                                     ,( 2006,(  38.4,  1824 ))+                                     ,( 2007,(  36.8,  1748 ))+                                     ,( 2008,(  35.2,  1672 ))+                                     ,( 2009,(  33.6,  1596 ))+                                     ,( 2010,(  32.0,  1520 ))+                                     ,( 2011,(  30.4,  1444 ))+                                     ,( 2012,(  28.8,  1368 ))+                                     ,( 2013,(  27.2,  1292 ))+                                     ,( 2014,(  25.6,  1216 ))+                                     ,( 2015,(  24.0,  1140 ))+                                     ,( 2016,(  22.4,  1064 ))+                                     ,( 2017,(  20.8,  988  ))+                                     ,( 2018,(  19.2,  912  ))+                                     ,( 2019,(  17.6,  836  ))+                                     ,( 2020,(  16.0,  760  ))+                                     ,( 2021,(  15.2,  722  ))+                                     ,( 2022,(  14.4,  684  ))+                                     ,( 2023,(  13.6,  646  ))+                                     ,( 2024,(  12.8,  608  ))+                                     ,( 2025,(  12.0,  570  ))+                                     ,( 2026,(  11.2,  532  ))+                                     ,( 2027,(  10.4,  494  ))+                                     ,( 2028,(  9.6,   456  ))+                                     ,( 2029,(  8.8,   418  ))+                                     ,( 2030,(  8.0,   380  ))+                                     ,( 2031,(  7.2,   342  ))+                                     ,( 2032,(  6.4,   304  ))+                                     ,( 2033,(  5.6,   266  ))+                                     ,( 2034,(  4.8,   228  ))+                                     ,( 2035,(  4.0,   190  ))+                                     ,( 2036,(  3.2,   152  ))+                                     ,( 2037,(  2.4,   114  ))+                                     ,( 2038,(  1.6,   76   ))+                                     ,( 2039,(  0.8,   38   ))+                                     ,( 2040,(  0.0,   0    ))+                                     ]+                          
+ src/HAX/Germany/Gewerbe.hs view
@@ -0,0 +1,176 @@+{-# LANGUAGE + NoMonomorphismRestriction+, OverloadedStrings+, TypeSynonymInstances+, FlexibleInstances+, TypeFamilies+, FlexibleContexts+ #-}++-- | This module provides the Gewerbesteuer calculations and+-- 'Gewerbetreibender' type class+--+-- auch interessante Übersicht zum Gesamtverständnis beitragend:+-- http://de.wikipedia.org/wiki/Schachtelprivileg+--+-- http://de.wikipedia.org/wiki/Zinsschranke+-- Zinsschranke greift erst bei 3 Mio negativem Zinssaldo+module HAX.Germany.Gewerbe where++import HAX.Accounting+import HAX.Bookkeeping+import HAX.Common+import Control.Monad.Reader+import HAX.Germany.Subjekte++-- * Gewerbetreibender++class Gewerbetreibender gt where+  gwStFreibetrag :: gt -> Amount+  -- | Nach den Vorschriften des EStG oder KStG ermittelte Gewinn+  --              +  -- https://www.smartsteuer.de/portal/lexikon/E/Einkommensermittlung.html+  -- https://www.smartsteuer.de/portal/lexikon/Z/Zinsschranke.html+  gewerbeGewinn :: AccountingReadOnly (Gewerbe gt) Amount+++-- * Types++type AccGW treib l w = Acc (Gewerbe treib) l w+type BetragGW treib l w = Acc (Gewerbe treib) l w Amount++type AccGWRW treib = AccountingRW (Gewerbe treib)+type BetragGWRW treib = AccountingRW (Gewerbe treib) Amount++type AccGWRO treib = AccountingReadOnly (Gewerbe treib)+type BetragGWRO treib = AccountingReadOnly (Gewerbe treib) Amount++-- * Konten++sonstigerUeberschuss     = "jUeb"+mieteUnbeweglich = "MiU"+mieteBeweglich   = "MiB"+schuldZinsen       = "Zins"+jahresErgebnis  = "jErg"+++gewerbeAccounts = [jahresErgebnis] ++ gewerbeErgebnisAccounts+gewerbeErgebnisAccounts = [sonstigerUeberschuss+                          , mieteUnbeweglich+                          , mieteBeweglich+                          , schuldZinsen+                          ]++-- * Gewerbesteuer++jahresErgebnis' = sum <$> mapM haben gewerbeErgebnisAccounts+                 +                   +-- https://www.smartsteuer.de/portal/lexikon/E/Einkuenfte-aus-Gewerbebetrieb.html+-- auch gut? http://www.recht-finanzen.de/contents/1369-einkommensteuer-verlustrucktrag-und-verlustvortrag++data GewerbeSteuer = GWSt { gwGewinn :: Amount+                          , gwMessbetrag :: Amount+                          , gwSteuer :: Amount+                          }++instance Show GewerbeSteuer where+  show (GWSt g m s) = printf "GWSt {gwGewinn = %v, gwMessbetrag = %v, gwSteuer = %v}"+                      g m s+                              +keineGewerbeSteuer = GWSt 0 0 0 ++gwMesszahl = 0.035+++-- | Bemessungsgrundlage für die GewSt, ist der Gewinn+-- zuzügl. Hinzurechnungen (§ 8 GewStG) und abzügl. Kürzungen (§ 9+-- GewStG)+--+-- https://www.smartsteuer.de/portal/lexikon/G/Gewerbeertrag.html#D063040100012+gewerbeErtrag :: Gewerbetreibender treib => Amount -> BetragGWRO treib+gewerbeErtrag gewinn = singleResult "Gewerbeertrag" $ withBody $ \g -> do+  ertrag <- return gewinn+            + hinzuRechnungen+            - kuerzungen+  abrundenAuf100 <$> nachVerlustAbzug "GewSt" ertrag++abrundenAuf100 x = fromInteger $(100*) $ floor $ x/100+  +-- | Hinzurechnungen (§ 8 GewStG)+--+-- http://de.wikipedia.org/wiki/Gewerbesteuer_(Deutschland)#Hinzurechnungen+hinzuRechnungen :: BetragGWRO treib +hinzuRechnungen = singleResult "Hinzurechnungen" $ do+  summe <- soll schuldZinsen+           + 0.5 * soll mieteUnbeweglich+           + 0.2 * soll mieteBeweglich+  return $ 0.25 * positivePart (summe - 100000)++-- | Kürzungen (§ 9 GewStG)+kuerzungen :: BetragGWRO treib +kuerzungen = return 0++-- | Berechnung und Verrechnung der Relevanten größen+-- +-- https://www.smartsteuer.de/portal/lexikon/G/Gewerbesteuer.html+--+gewerbeSteuer :: Gewerbetreibender treib => AccGWRW treib GewerbeSteuer+gewerbeSteuer = withBody $ \g -> do+  gewinn <- fixed gewerbeGewinn+  ertrag <- fixed $ gewerbeErtrag gewinn+  let verbleibenderErtrag =  ertrag - min (positivePart ertrag)+                             (gwStFreibetrag $ gwTreibender g)+      result = GWSt gewinn (positivePart $ gwMesszahl * verbleibenderErtrag)+               $ gwMessbetrag result * (gwHebesatz g)+  +  logLedger $ printf "%v\n" (show result)  ++  closingTx gewerbeErgebnisAccounts $ BalancingTx "Gewerbesteuer" +    jahresErgebnis [(giroKonto, negate $ gwSteuer result)]++  return result+++runGewerbe :: Gewerbetreibender treib => AccGWRW treib a -- ^ action to run +              -> Gewerbe treib+              -> AccountingRW treib a+runGewerbe f gewerbe = withRWT (\e -> e{eBody=gewerbe}) f++                      +-- * Monatliche Actions+++-- | Berechnet die Lohnkosten für einen Angestellten+--+-- http://de.wikipedia.org/wiki/Lohnnebenkosten+lohnKostenMtl :: AccGWRW treib ()+lohnKostenMtl = withBody $ \g -> do+  let lohn p = let gehalt = pBruttoGehaltMtl p in+          gehalt + when' (pVersicherungsPflicht p)+          (gehalt * (0.073 -- kranken+                     + 0.0945 -- renten+                     + 0.01025 -- pflege+                     + 0.015 -- arbeitslosen+                     + 0.016 -- unfall+                     + 0.0015 -- insolvenz+                     -- TODO mehr ?+                    ))+      kosten = sum $ lohn <$> gwAngestellte g+  fromTo kosten  "Lohnkosten" giroKonto sonstigerUeberschuss++-- | monatliche Action eines Gewerbes+gewerbeMonatlich :: AccGWRW treib ()+gewerbeMonatlich = withBody $ \g -> do+  lohnKostenMtl+  autoGewerbeMtl+  fromTo (gwMonatlMietkosten g) "Miete" giroKonto mieteUnbeweglich++                           +-- | Accountiung Action für das Gewerbe+autoGewerbeMtl :: AccGWRW treib ()+autoGewerbeMtl = withBody $ \g ->+  let k = kostenAutoMonatl <$> gwWaegen g in+  tx $ BalancingTx "Autokosten" giroKonto+  [(mieteBeweglich,sum $ akGewerbeLeasing <$> k)+  ,(sonstigerUeberschuss,sum $ akGewerbe <$> k)]
+ src/HAX/Germany/GmbH.hs view
@@ -0,0 +1,81 @@+{-# LANGUAGE + NoMonomorphismRestriction+, OverloadedStrings+, TypeSynonymInstances+, FlexibleInstances+, TypeFamilies+, FlexibleContexts+ #-}++-- | This module provides the 'Body' for German GmbH+-- as required to calculate income and business tax.+--+-- 'Body's are defined in "Accounting".+--+-- It uses the 'Auto' type from "Germany.NatuerlichePerson"+module HAX.Germany.GmbH where++import HAX.Accounting+import HAX.Bookkeeping+import HAX.Common+import HAX.Germany.Subjekte+import HAX.Germany.Gewerbe++-- * GmbH ('Body' und 'Gewerbetreibender' Instances)++instance Body GmbH where+    nominalAccounts _ = gewerbeAccounts ++ verlustVortragsKonten ++ +                        [ verdeckteGewinnausschuettungen+                        , verdeckteEinlagen+                        , giroKonto+                        , gewinnVerlustVortraege+                        ]+    bodyMonthly action = do+      runGewerbe gewerbeMonatlich =<< readBody gGewerbe+      action+      atYearEnd $ do+        kst <- koerperschaftSteuer+        closingTx [ verdeckteGewinnausschuettungen+                  , verdeckteEinlagen+                  , jahresErgebnis+                  ]+          $ BalancingTx "GmbH KSt und Abschluss"+          gewinnVerlustVortraege+          [(giroKonto, -kst)]++instance Gewerbetreibender GmbH where+  gwStFreibetrag _ = 0+  gewerbeGewinn = jahresErgebnis'+  -- nicht abziebares+  +  -- https://www.smartsteuer.de/portal/lexikon/N/Nichtabziehbare-Aufwendungen-gem_-10-KStG.html+  -- bezieht sich glaube ich hauptsächlich auf gewinne/verluste von+  -- santeilen, die die körperschaft an anderen gesellschaften hält.+                    + haben verdeckteGewinnausschuettungen+                    - soll verdeckteEinlagen++-- * Konten++verdeckteGewinnausschuettungen = "vGA"+verdeckteEinlagen = "vElg"+gewinnVerlustVortraege = "GVV"++-- * koerperschaftSteuer+--+-- https://www.smartsteuer.de/portal/lexikon/E/Einkommensermittlung.html+-- +-- https://www.smartsteuer.de/portal/lexikon/K/Koerperschaftsteuertarif.html+koerperschaftSteuer :: AccountingRW GmbH Amount+koerperschaftSteuer = do+  gewinn <- gwGewinn <$> (runGewerbe gewerbeSteuer =<< readBody gGewerbe )+  -- -abzugsfähige Spenden und Beiträge (§ 9 Abs. 1 Nr. 2 KStG)+  -- -abzugsfähige Großspenden (§ 9 Abs. 1 Nr. 2 Satz 4 und 5 KStG)+  -- +/- Einkommensteuerzurechnung in Organschaftsfällen++  kst <- fixed $ singleResult "KoerperschaftSteuer + Soli" $+         0.15 * (1+solidaritaetsFaktor) * nachVerlustAbzug "KSt" gewinn++  return kst+++
+ src/HAX/Germany/NatuerlichePerson.hs view
@@ -0,0 +1,110 @@+{-# LANGUAGE + NoMonomorphismRestriction+, OverloadedStrings+, TypeSynonymInstances+, FlexibleInstances+, TypeFamilies+, FlexibleContexts+ #-}++-- | This module provides the 'Body' instances for German Natürliche+-- Person as required to calculate income and business tax.+--+-- 'Body's are defined in "Accounting".+module HAX.Germany.NatuerlichePerson where++import HAX.Accounting+import HAX.Bookkeeping+import HAX.Common+import Control.Monad.Reader+import HAX.Germany.Subjekte+import HAX.Germany.Gewerbe+import HAX.Germany.Einkommensteuer+++    +-- * Gewerbetreibender Instance++instance Gewerbetreibender NatuerlichePerson where+  gwStFreibetrag _ = 24500+  gewerbeGewinn = jahresErgebnis'+             ++-- * Natürliche Person ('Body' und 'Gewerbetreibender' Instances)++instance Body NatuerlichePerson where+    nominalAccounts p = maybe [] (const gewerbeAccounts) (pGewerbe p) +                        ++ verlustVortragsKonten +                        ++ aufwandsKonten ++ ertragsKonten ++ +                        [privatVermoegen+                        ,giroKonto+                        ]+    bodyMonthly action = withBody $ \p -> do+      gehalt+      sozialAbgaben+      autoPrivatMtl+      fromTo (kinderGeld p) "Kindergeld" steuerfreieEinnahmen giroKonto+      maybe (return ())+        (runGewerbe gewerbeMonatlich) $ pGewerbe p++      action++      atYearEnd $ do+      -- Gewerbe+      -- TODO Thesaurierungsbegünstigung 34a EStG+      -- https://www.smartsteuer.de/portal/lexikon/T/Tarifbegrenzung-fuer-nicht-entnommene-Gewinne-bei-der-Einkommensteuer.html+        gewerbeSteuer <- maybe (return keineGewerbeSteuer)+                         (runGewerbe gewerbeSteuer) $ pGewerbe p+        when (gwGewinn gewerbeSteuer /=0) $+          fromTo (gwGewinn gewerbeSteuer) "Einkünfte aus Gewerbebetrieb"+            uebrigeSummeDerEinkuenfte privatVermoegen+    +        fromTo 100 "Arbeitsmittel und übrige Werbungskosten" privatVermoegen+          werbungskostensNichtSelbststaendigeArbeitOhneFahrten++        est <- einkommenSteuer gewerbeSteuer+        lst <- soll lohnSteuer+        closingTx (maybe [] (const [jahresErgebnis]) (pGewerbe p)+                   ++ ertragsKonten ++ aufwandsKonten)+          $ BalancingTx "Steuern und Abschluss"+          privatVermoegen+          [(giroKonto,lst - est)]+          +        ++-- http://de.wikipedia.org/wiki/Lohnnebenkosten+sozialAbgaben :: AccNatRW ()+sozialAbgaben = withBody $ \p -> when (pVersicherungsPflicht p) $ do+  let brutto = pBruttoGehaltMtl p+  tx $ BalancingTx "Sozialabgaben"+    giroKonto+    [(krankenundPflegeversicherungBasisbeitraege,+      brutto * (0.082  -- Krankenversicherung+                -- TODO  0.04 davon sind KrankengeldAnspruch = Privatausgaben+                + 0.01025))  -- Pflegeversicherung+    ,(altersvorsorgeAufwendungenEigenanteil, brutto * 0.0945) -- gesetzl. Rentenversicherung+    ,(sonstigeVorsorgeAufwendungOhneBasisbeitraege, brutto * 0.015 ) --arbeitslosenversicherung+    ]++  fromTo (brutto * 0.0945) "Arbeitgeberanteil Rentenversicherung"+    steuerfreieEinnahmen altersvorsorgeAufwendungenArbeitgeberAnteil        ++gehalt :: AccNatRW ()+gehalt = withBody $ \p -> do+  let geldwerterVorteil = geldwerterVorteilMonatl $ pWagen p+      brutto = pBruttoGehaltMtl p+      lohnsteuer = pLohnsteuerMtl p+  tx $ BalancingTx "Gehalt und evtl. Firmenwagen"+    arbeitslohn+    [(giroKonto, brutto - lohnsteuer)+    ,(privatAusgaben, geldwerterVorteil)+    ,(lohnSteuer, lohnsteuer)+    ]++                     +-- | Accountiung Action für die Privatperson+autoPrivatMtl :: AccNatRW ()+autoPrivatMtl = withBody $ \p -> +  let kosten = akPrivat $ kostenAutoMonatl $ pWagen p in+  when (kosten /= 0) $ fromTo kosten "Autokosten" giroKonto privatAusgaben+                     
+ src/HAX/Germany/Subjekte.hs view
@@ -0,0 +1,160 @@+{-# LANGUAGE + NoMonomorphismRestriction+, OverloadedStrings+ #-}++module HAX.Germany.Subjekte where++import HAX.Accounting+import HAX.Bookkeeping+import HAX.Common++solidaritaetsFaktor = 0.055++-- * Rechtssubjekte und Gewerbebetrieb+  +data NatuerlichePerson = NatuerlichePerson+     { --  pSteuerklasse -- KEINEN EINFLUSS AUF STEUERLAST, nur auf Zeitpunkt der Zahlung+     pGeburt :: ADate+     , pGewerbe :: Maybe (Gewerbe NatuerlichePerson)+     , kinderMitKindergeldImHaushalt :: Bool+     , kinderMitKindergeld :: Amount+     , landOderForstwirt :: Bool+     , pSplitting :: Bool+     , krankenUndPflegeOhneZuschuesse :: Bool+       -- ^ Müssen Aufwendungen für die Krankenversicherung alleine+       -- getragen werden?+       -- +       -- http://www.steuernetz.de/aav_steuernetz/lexikon/K-24101.xhtml?currentModule=home+     , krankenUndPflegeOhneZuschuessePartner :: Maybe Bool+     , kinderFreibetragsVerdopplung :: Bool+     , pWagen :: Auto+     , pBruttoGehaltMtl :: Decimal+     , pLohnsteuerMtl :: Decimal+     , pVersicherungsPflicht :: Bool+     -- ^ führt auch zur automatischen Abbuchung der+     -- Sozialversicherungsbeiträge+     , pAuswaertigeKinderInBerufsausbildung :: Decimal+     -- ^ in Berufsausbildung befindende, auswärtig untergebrachte,+     -- volljährigen Kinder, für die Anspruch auf einen Freibetrag+     -- oder Kindergeld besteht (33a Abs. 2)+     }+                       deriving Show+++data GmbH = GmbH { gGewerbe :: Gewerbe GmbH+                 }++data Gewerbe body = Gewerbe { -- gwName :: String+                             gwAngestellte :: [NatuerlichePerson]+                            , gwTreibender :: body+                              -- ^ auf wessen Rechnung wird das Gewerbe betrieben+                            , gwHebesatz :: Amount+                            , gwMonatlMietkosten :: Amount+                            , gwWaegen :: [Auto]+                            }+          deriving Show++-- * Konten++giroKonto = "Giro" ++++-- * Auto++data Auto = AutoMonatl { aKmPrivatOhneArbeitsfahrten :: Decimal+                         -- ^ ohne Fahrten zur Arbeit, welche auch zu den+                         -- Privaten Fahrten zählen, aber über+                         -- die anderen Felder berechnet werden ('privatKmMonatl').+                       , aKmArbeitsstaette :: Decimal -- ^ Entfernung Wohnort Arbeitsstätte +                       , aKmGeschaeftl :: Decimal+                       , aArbeitsTage :: Decimal+                       , aFixKosten :: Amount+                       , aLeasing :: Amount+                       , aSpritKostenProKm :: Amount+                       , aListenPreis :: Amount+                       , aFirmenWagen :: FirmenWagen+                       }+          deriving Show+              +data FirmenWagen = Pauschal | Fahrtenbuch | Privat+     deriving Show+       ++-- | Zusammensetzung der Kosten für ein 'Auto'. Dieses Object wirh+-- berechnet abhängig von Firmenwagennutzung etc. von+-- 'kostenAutoMonatl'+data AutoKosten = AutoKosten { akPrivat :: Amount,+                               akGewerbe :: Amount,+                               akGewerbeLeasing :: Amount+                             }++entfernungsPauschale :: Auto -> Amount+entfernungsPauschale a = 0.3 * 12 * aArbeitsTage a * aKmArbeitsstaette a+              +kmPrivatMonatl :: Auto -> Amount +kmPrivatMonatl a = aKmPrivatOhneArbeitsfahrten a ++                   aArbeitsTage a * 2 * aKmArbeitsstaette a++              +-- | http://de.wikipedia.org/wiki/Fahrtenbuch+geldwerterVorteilMonatl :: Auto -> Amount+geldwerterVorteilMonatl a = g $ aFirmenWagen a+  where g Privat = 0+        g Pauschal = aListenPreis a * (+          0.01 + when' (12 * aArbeitsTage a > 46) 0.0003 * aKmArbeitsstaette a)+        g Fahrtenbuch = gesamtKostenAuto a * privat / (privat + aKmGeschaeftl a)+        privat = kmPrivatMonatl a++gesamtKostenAuto :: Auto -> Amount+gesamtKostenAuto = g . kostenAutoMonatl+  where g (AutoKosten a b c) = a+b+c++kostenAutoMonatl :: Auto -> AutoKosten+kostenAutoMonatl a = g  $ aFirmenWagen a+  where g Privat = AutoKosten+                   (aLeasing a + aFixKosten a + aSpritKostenProKm a * kmPrivatMonatl a)+                   (aSpritKostenProKm a * aKmGeschaeftl a)+                   0+        g _      = AutoKosten+                   0+                   (aFixKosten a + aSpritKostenProKm a * (+                       aKmGeschaeftl a + kmPrivatMonatl a))+                   $ aLeasing a++-- * Verlustvortäge und -abzüge++verlustVortragsKonten = [virtuell,+                         steuerlicheVerlustVortraege+                         ]+virtuell = "virt"+steuerlicheVerlustVortraege = "stVV"++-- | Gibt Verlustabzug zurück und registriert Verlustabzug und+-- -vortrag.+-- +-- Berechnet wird nach den übereinstimmenden Regeln des EStG, KStG, GewStG+--+-- https://www.smartsteuer.de/portal/lexikon/G/Gewerbeverlust.html+--+-- https://www.smartsteuer.de/portal/lexikon/V/Verlustabzug.html+-- https://www.smartsteuer.de/portal/lexikon/G/Gesamtbetrag-der-Einkuenfte.html+--+-- https://www.smartsteuer.de/portal/lexikon/V/Verlustvortrag-und-ruecktrag.html#D063100100038+verlustAbzug :: String -- ^ Type+                -> Amount -- ^ ertrag+                -> AccountingReadOnly b Amount+verlustAbzug vType ertrag = do+  vortraege <- soll steuerlicheVerlustVortraege+  let vorabVerrechenbar = min 1000000 ertrag+      abzug = min vortraege $ vorabVerrechenbar + 0.6*(ertrag - vorabVerrechenbar)+  when (abzug /= 0) $ tell $ +    fromTo abzug (vType ++ "Verlustvorträge") steuerlicheVerlustVortraege virtuell+  return $ positivePart abzug++-- | Gibt den Ertrag nach Abzug zurück. Ansonsten identisch 'verlustAbzug'+nachVerlustAbzug :: String -- ^ Type+                -> Amount -- ^ ertrag+                -> AccountingReadOnly b Amount+nachVerlustAbzug vType ertrag = (ertrag - ) <$> verlustAbzug vType ertrag
+ src/HAX/Report.hs view
@@ -0,0 +1,206 @@+{-# LANGUAGE FlexibleInstances+, OverloadedStrings+ #-}++-- | This module allows to convert the final ledgers generated by+-- "Accounting" into strings for display.+--+-- This module is ditry and work in progress. __Look at the source to use it__+module HAX.Report where++import           HAX.Accounting+import           HAX.Bookkeeping+import           HAX.Common hiding ((!))+import           Data.Aeson hiding (Array)+import qualified Data.ByteString.Lazy as BL+import           Data.List+import           Data.List.Split+import           Data.Text (Text)+import qualified Data.Map as M+import           Data.Maybe+import           Data.Ord+import           System.Directory+import           System.FilePath+import           Text.Blaze hiding (text)+import           Text.Blaze.Html.Renderer.Utf8+import qualified Text.Blaze.Html5 as H+import           Text.Blaze.Html5 hiding (head,map,object,text)+import qualified Text.Blaze.Html5.Attributes as A+import           Text.Blaze.Html5.Attributes hiding (id,title)+import qualified Text.PrettyPrint.Boxes as P+import           Text.PrettyPrint.Boxes hiding (left,(<>))+-- import           Network.Wai+-- import           Network.HTTP.Types.Status+-- import           Network.Wai.Handler.Warp++-- * HTML++-- | Write the ledger to html files+writeHtml :: FilePath -- ^ output dir+          -> World -> IO ()+writeHtml dir world = do+  -- copyFile "code.js" $ dir </> "code.js"+  createDirectoryIfMissing True dir+  res <- generate world+  let resHtml = toHtmlTable res+  BL.writeFile (dir </> "header.html") $ indexPage $ resHtml+  BL.writeFile (dir </> "index.html") $ indexPage $ do+    iframe ! src "header.html" ! width "100%" !+      customAttribute "scrolling" "no" ! A.style "position:fixed; border: 0px" ! height "37px"  $ ""+    resHtml+  BL.writeFile (dir </> "data.js") . toVar "data" . encode $ res++-- | convert ledger to Html Markup+toHtmlTable (FullLedger (FixedLedger balances logEntries) accounts) = do+  table ! A.id "data" $ do+    -- header+    tr $ mapM_ headerColumn headerFANs+    sequence_ $ zipWith f (elems logEntries) $ to2DTable balances+  where headerColumn (FAN entity acc) = th ! class_ (fromString entity)+                                        $ toMarkup entity >> br >> toMarkup acc+        f logs (d,row) = do+          mapM_ (logHtml headerFANs d $ length row) $ reverse logs+          tr ! (monthClass "regular" d) $ rowHtml headerFANs show "" (d,row)+        headerFANs = FAN "" "Month" : sortedAccountNames accounts ++ [FAN "" "Comment"]+  +monthClass cl d = class_ $ fromString $ cl ++" "++ if getMonth d == 12 then "endOfYear" else "duringYear"++-- | create one html row with comment and date+rowHtml headerFANs f comment drow = zipWithM_ g headerFANs $ toRow show (f.scale) drow ++ [comment]+  where g (FAN entity _) x = td ! class_ (fromString entity) $ toMarkup x++-- | convert log entries to markup+logHtml :: [FullAccountName] -> ADate -> Int -- ^ number of columns present+           -> EntityLogEntry -> Html+logHtml _ d len (ent,LComment s) = tr  ! (monthClass ("comment " ++ ent) d) $ do+                            td $ toMarkup $ show d+                            td ! colspan (toValue $ len + 1) $ fromString s+logHtml headerFANs d len (ent,LTx tx) = tr ! (monthClass ("transaction " ++ ent) d) $+                                    rowHtml headerFANs+                                    (\x -> if x == 0 then "" else show x) comment (d,row)+  where (row,comment) = transactionRow (1,len) tx+  +css x = link ! rel "stylesheet" ! type_ "text/css" ! href x+js x = script ! src x $ ""++-- | the html skeletton+indexPage htmlBody = renderHtml $ docTypeHtml $ do+     H.head $ do+         meta ! charset "UTF-8"+         css "static/style.css"+         js "http://code.jquery.com/jquery-1.11.1.min.js"+         -- js "htpp://ajax.googleapis.com/ajax/libs/webfont/1.4.7/webfont.js"+         -- js "http://d3js.org/d3.v3.min.js" ! charset "utf-8"+         css "http://cdn.datatables.net/1.10.2/css/jquery.dataTables.min.css"+         js "http://cdn.datatables.net/1.10.2/js/jquery.dataTables.min.js"+         js "data.js"+         js "static/code.js"+         title "finmod results" +     body $ do H.table ! A.id "example" $ ""+               htmlBody+++-- * JSON instances++toVar :: (Monoid a,IsString a) => a -> a -> a+toVar name value = "var " <> name <> " = " <> value+                   +instance ToJSON FullLedger where+  toJSON (FullLedger (FixedLedger balances logEntries) accounts) =+    object [ "accounts" .= toJSON (FAN "" "Month" : accs)+           , "balances" .=  (toRow toJSON (toJSON.scale) <$> to2DTable balances)+           , "dates"    .= range (bounds logEntries)+           , "log"    .= elems logEntries+           , "entities" .= ((\x -> fromString (fEntity $ head x) .= (fAccount <$> x))+                            <$> groupBy ((==) `on` fEntity) accs  :: [(Text, Value)])+           ]+    where accs = sortedAccountNames accounts++instance ToJSON FullAccountName+instance ToJSON LogEntry+instance ToJSON Tx+  +instance ToJSON Decimal where+  toJSON d = toJSON ( conv d :: Double)++instance ToJSON ADate where+  toJSON = toJSON . show+++-- * String++scale = (roundTo 2.(/1000))++-- zeigeTransaktionen = True+-- zwischenlinien = zeigeTransaktionen++-- -- | Pretty-print the ledger of the world+-- showWorld :: World -> IO String+-- showWorld world = showLedger zeigeTransaktionen+--                   zwischenlinien scale <$> generate world +    +-- -- | Pretty-print the results of 'Accounting.generate'.+-- showLedger :: (Show b) => Bool -> Bool ->+--               Scale b -> (AccountsMap,FixedLedger) -> String+-- showLedger showTxns showSeps scale (accounts,ledger) =+--   ("\n"++) $ renderTable $ (header:) $ seps $+--   toGroups showTxns scale ledger+--   where header :: [Row String]+--         header = [h False $ fEntity,h True $ fAccount]+--           where h m a = (if m then "mm/yy" else ""): +--                        (a <$> sortedAccountNames accounts)+--                        ++ [if m then "Comment" else ""]+--         seps = if not showSeps then return.concat else id +        ++-- toGroups:: (Show b) => Bool -> Scale b -> +--            FixedLedger -> [Group String]+-- toGroups showTxns scale (FixedLedger bals txns) =+--   zipWith groups txnGroups $ toRow show (show.scale) <$> to2DTable bals+--   where groups :: [LogEntry] -> Row String -> Group String+--         groups txns row =  (show <$> (txnRow =<< reverse txns)) ++ [row ++ ["Endsaldo"]]+--         (dateRange,accBounds) = (range1 $ bounds bals, bounds2 $ bounds bals)+--         txnGroups :: [[LogEntry]]+--         txnGroups = if showTxns then elems txns else repeat []+--         txnRow :: LogEntry -> [Row Amount]+--         txnRow (LTx txn) = [transactionRow accBounds txn]+--         txnRow (LComment txn) = [] -- impossible???++-- | generates a row containing the posting's amount at the column+-- corresponding to the posting's account+transactionRow :: (AccountNumber,AccountNumber) -- ^ account number bounds+                  -> Tx -> (Row Amount,Comment)+transactionRow accBounds txn = (postings,tComment txn)+  where postings = elems $ accumArray (+) 0 accBounds+                   $ tPostings txn+        col x = if x == 0 then ""+                else ("       "++) $ show $ scale x+        +-- * Helpers ++type Row e = [e]+type Group e = [Row e]+type Scale b = (Amount -> b)++-- | Renders a list of grouped rows+renderTable :: [Group String] -> String+renderTable groups = render $ punctuateH top seps+                     $ fmap renderCol (transpose $ intercalate  [vertSep] $ groups)+  where seps = vcat P.left $ map text $ replicate+               (length groups - 1 + length (head combined)) " | "+        renderCol xs = vcat P.left $ map text xs+          where rH h = [h,replicate (maximum $ length <$> (h:xs)) '-']+        combined = transpose $ concat groups+        vertSep = map (\c -> replicate (maximum $ length <$> c) '-') combined+++toRow :: (r -> e) -> (a -> e) -> (r,Row a) -> Row e+toRow conv1 conv2 (d,row) = conv1 d : fmap conv2 row++-- | Convert a 2D array into a list of pairs, where the first+-- component contains the first index and the second the coresponding+-- row o the array+to2DTable :: (Ix r,Ix c) => Array (r,c) a -> [(r,Row a)]+to2DTable array = zip (range (r1,r2))+                    $ chunksOf (rangeSize (c1,c2)) $ elems array+  where ((r1,c1),(r2,c2)) = bounds array
+ src/main.hs view
@@ -0,0 +1,11 @@+{-# LANGUAGE OverloadedStrings+ #-}++ +import HAX.Report+import HAX.Example (example1)++++main = do+  writeHtml "html" example1
+ stack.yaml view
@@ -0,0 +1,4 @@+resolver: lts-7.16+packages:+- '.'+pvp-bounds: both
+ static/Chart.js view
@@ -0,0 +1,3379 @@+/*!+ * Chart.js+ * http://chartjs.org/+ * Version: 1.0.1-beta.4+ *+ * Copyright 2014 Nick Downie+ * Released under the MIT license+ * https://github.com/nnnick/Chart.js/blob/master/LICENSE.md+ */+++(function(){++	"use strict";++	//Declare root variable - window in the browser, global on the server+	var root = this,+		previous = root.Chart;++	//Occupy the global variable of Chart, and create a simple base class+	var Chart = function(context){+		var chart = this;+		this.canvas = context.canvas;++		this.ctx = context;++		//Variables global to the chart+		var width = this.width = context.canvas.width;+		var height = this.height = context.canvas.height;+		this.aspectRatio = this.width / this.height;+		//High pixel density displays - multiply the size of the canvas height/width by the device pixel ratio, then scale.+		helpers.retinaScale(this);++		return this;+	};+	//Globally expose the defaults to allow for user updating/changing+	Chart.defaults = {+		global: {+			// Boolean - Whether to animate the chart+			animation: true,++			// Number - Number of animation steps+			animationSteps: 60,++			// String - Animation easing effect+			animationEasing: "easeOutQuart",++			// Boolean - If we should show the scale at all+			showScale: true,++			// Boolean - If we want to override with a hard coded scale+			scaleOverride: false,++			// ** Required if scaleOverride is true **+			// Number - The number of steps in a hard coded scale+			scaleSteps: null,+			// Number - The value jump in the hard coded scale+			scaleStepWidth: null,+			// Number - The scale starting value+			scaleStartValue: null,++			// String - Colour of the scale line+			scaleLineColor: "rgba(0,0,0,.1)",++			// Number - Pixel width of the scale line+			scaleLineWidth: 1,++			// Boolean - Whether to show labels on the scale+			scaleShowLabels: true,++			// Interpolated JS string - can access value+			scaleLabel: "<%=value%>",++			// Boolean - Whether the scale should stick to integers, and not show any floats even if drawing space is there+			scaleIntegersOnly: true,++			// Boolean - Whether the scale should start at zero, or an order of magnitude down from the lowest value+			scaleBeginAtZero: false,++			// String - Scale label font declaration for the scale label+			scaleFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",++			// Number - Scale label font size in pixels+			scaleFontSize: 12,++			// String - Scale label font weight style+			scaleFontStyle: "normal",++			// String - Scale label font colour+			scaleFontColor: "#666",++			// Boolean - whether or not the chart should be responsive and resize when the browser does.+			responsive: false,++                        // Boolean - whether to maintain the starting aspect ratio or not when responsive, if set to false, will take up entire container+                        maintainAspectRatio: true,++			// Boolean - Determines whether to draw tooltips on the canvas or not - attaches events to touchmove & mousemove+			showTooltips: true,++			// Array - Array of string names to attach tooltip events+			tooltipEvents: ["mousemove", "touchstart", "touchmove", "mouseout"],++			// String - Tooltip background colour+			tooltipFillColor: "rgba(0,0,0,0.8)",++			// String - Tooltip label font declaration for the scale label+			tooltipFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",++			// Number - Tooltip label font size in pixels+			tooltipFontSize: 14,++			// String - Tooltip font weight style+			tooltipFontStyle: "normal",++			// String - Tooltip label font colour+			tooltipFontColor: "#fff",++			// String - Tooltip title font declaration for the scale label+			tooltipTitleFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",++			// Number - Tooltip title font size in pixels+			tooltipTitleFontSize: 14,++			// String - Tooltip title font weight style+			tooltipTitleFontStyle: "bold",++			// String - Tooltip title font colour+			tooltipTitleFontColor: "#fff",++			// Number - pixel width of padding around tooltip text+			tooltipYPadding: 6,++			// Number - pixel width of padding around tooltip text+			tooltipXPadding: 6,++			// Number - Size of the caret on the tooltip+			tooltipCaretSize: 8,++			// Number - Pixel radius of the tooltip border+			tooltipCornerRadius: 6,++			// Number - Pixel offset from point x to tooltip edge+			tooltipXOffset: 10,++			// String - Template string for single tooltips+			tooltipTemplate: "<%if (label){%><%=label%>: <%}%><%= value %>",++			// String - Template string for single tooltips+			multiTooltipTemplate: "<%= value %>",++			// String - Colour behind the legend colour block+			multiTooltipKeyBackground: '#fff',++			// Function - Will fire on animation progression.+			onAnimationProgress: function(){},++			// Function - Will fire on animation completion.+			onAnimationComplete: function(){}++		}+	};++	//Create a dictionary of chart types, to allow for extension of existing types+	Chart.types = {};++	//Global Chart helpers object for utility methods and classes+	var helpers = Chart.helpers = {};++		//-- Basic js utility methods+	var each = helpers.each = function(loopable,callback,self){+			var additionalArgs = Array.prototype.slice.call(arguments, 3);+			// Check to see if null or undefined firstly.+			if (loopable){+				if (loopable.length === +loopable.length){+					var i;+					for (i=0; i<loopable.length; i++){+						callback.apply(self,[loopable[i], i].concat(additionalArgs));+					}+				}+				else{+					for (var item in loopable){+						callback.apply(self,[loopable[item],item].concat(additionalArgs));+					}+				}+			}+		},+		clone = helpers.clone = function(obj){+			var objClone = {};+			each(obj,function(value,key){+				if (obj.hasOwnProperty(key)) objClone[key] = value;+			});+			return objClone;+		},+		extend = helpers.extend = function(base){+			each(Array.prototype.slice.call(arguments,1), function(extensionObject) {+				each(extensionObject,function(value,key){+					if (extensionObject.hasOwnProperty(key)) base[key] = value;+				});+			});+			return base;+		},+		merge = helpers.merge = function(base,master){+			//Merge properties in left object over to a shallow clone of object right.+			var args = Array.prototype.slice.call(arguments,0);+			args.unshift({});+			return extend.apply(null, args);+		},+		indexOf = helpers.indexOf = function(arrayToSearch, item){+			if (Array.prototype.indexOf) {+				return arrayToSearch.indexOf(item);+			}+			else{+				for (var i = 0; i < arrayToSearch.length; i++) {+					if (arrayToSearch[i] === item) return i;+				}+				return -1;+			}+		},+		where = helpers.where = function(collection, filterCallback){+			var filtered = [];++			helpers.each(collection, function(item){+				if (filterCallback(item)){+					filtered.push(item);+				}+			});++			return filtered;+		},+		findNextWhere = helpers.findNextWhere = function(arrayToSearch, filterCallback, startIndex){+			// Default to start of the array+			if (!startIndex){+				startIndex = -1;+			}+			for (var i = startIndex + 1; i < arrayToSearch.length; i++) {+				var currentItem = arrayToSearch[i];+				if (filterCallback(currentItem)){+					return currentItem;+				}+			};+		},+		findPreviousWhere = helpers.findPreviousWhere = function(arrayToSearch, filterCallback, startIndex){+			// Default to end of the array+			if (!startIndex){+				startIndex = arrayToSearch.length;+			}+			for (var i = startIndex - 1; i >= 0; i--) {+				var currentItem = arrayToSearch[i];+				if (filterCallback(currentItem)){+					return currentItem;+				}+			};+		},+		inherits = helpers.inherits = function(extensions){+			//Basic javascript inheritance based on the model created in Backbone.js+			var parent = this;+			var ChartElement = (extensions && extensions.hasOwnProperty("constructor")) ? extensions.constructor : function(){ return parent.apply(this, arguments); };++			var Surrogate = function(){ this.constructor = ChartElement;};+			Surrogate.prototype = parent.prototype;+			ChartElement.prototype = new Surrogate();++			ChartElement.extend = inherits;++			if (extensions) extend(ChartElement.prototype, extensions);++			ChartElement.__super__ = parent.prototype;++			return ChartElement;+		},+		noop = helpers.noop = function(){},+		uid = helpers.uid = (function(){+			var id=0;+			return function(){+				return "chart-" + id++;+			};+		})(),+		warn = helpers.warn = function(str){+			//Method for warning of errors+			if (window.console && typeof window.console.warn == "function") console.warn(str);+		},+		amd = helpers.amd = (typeof define == 'function' && define.amd),+		//-- Math methods+		isNumber = helpers.isNumber = function(n){+			return !isNaN(parseFloat(n)) && isFinite(n);+		},+		max = helpers.max = function(array){+			return Math.max.apply( Math, array );+		},+		min = helpers.min = function(array){+			return Math.min.apply( Math, array );+		},+		cap = helpers.cap = function(valueToCap,maxValue,minValue){+			if(isNumber(maxValue)) {+				if( valueToCap > maxValue ) {+					return maxValue;+				}+			}+			else if(isNumber(minValue)){+				if ( valueToCap < minValue ){+					return minValue;+				}+			}+			return valueToCap;+		},+		getDecimalPlaces = helpers.getDecimalPlaces = function(num){+			if (num%1!==0 && isNumber(num)){+				return num.toString().split(".")[1].length;+			}+			else {+				return 0;+			}+		},+		toRadians = helpers.radians = function(degrees){+			return degrees * (Math.PI/180);+		},+		// Gets the angle from vertical upright to the point about a centre.+		getAngleFromPoint = helpers.getAngleFromPoint = function(centrePoint, anglePoint){+			var distanceFromXCenter = anglePoint.x - centrePoint.x,+				distanceFromYCenter = anglePoint.y - centrePoint.y,+				radialDistanceFromCenter = Math.sqrt( distanceFromXCenter * distanceFromXCenter + distanceFromYCenter * distanceFromYCenter);+++			var angle = Math.PI * 2 + Math.atan2(distanceFromYCenter, distanceFromXCenter);++			//If the segment is in the top left quadrant, we need to add another rotation to the angle+			if (distanceFromXCenter < 0 && distanceFromYCenter < 0){+				angle += Math.PI*2;+			}++			return {+				angle: angle,+				distance: radialDistanceFromCenter+			};+		},+		aliasPixel = helpers.aliasPixel = function(pixelWidth){+			return (pixelWidth % 2 === 0) ? 0 : 0.5;+		},+		splineCurve = helpers.splineCurve = function(FirstPoint,MiddlePoint,AfterPoint,t){+			//Props to Rob Spencer at scaled innovation for his post on splining between points+			//http://scaledinnovation.com/analytics/splines/aboutSplines.html+			var d01=Math.sqrt(Math.pow(MiddlePoint.x-FirstPoint.x,2)+Math.pow(MiddlePoint.y-FirstPoint.y,2)),+				d12=Math.sqrt(Math.pow(AfterPoint.x-MiddlePoint.x,2)+Math.pow(AfterPoint.y-MiddlePoint.y,2)),+				fa=t*d01/(d01+d12),// scaling factor for triangle Ta+				fb=t*d12/(d01+d12);+			return {+				inner : {+					x : MiddlePoint.x-fa*(AfterPoint.x-FirstPoint.x),+					y : MiddlePoint.y-fa*(AfterPoint.y-FirstPoint.y)+				},+				outer : {+					x: MiddlePoint.x+fb*(AfterPoint.x-FirstPoint.x),+					y : MiddlePoint.y+fb*(AfterPoint.y-FirstPoint.y)+				}+			};+		},+		calculateOrderOfMagnitude = helpers.calculateOrderOfMagnitude = function(val){+			return Math.floor(Math.log(val) / Math.LN10);+		},+		calculateScaleRange = helpers.calculateScaleRange = function(valuesArray, drawingSize, textSize, startFromZero, integersOnly){++			//Set a minimum step of two - a point at the top of the graph, and a point at the base+			var minSteps = 2,+				maxSteps = Math.floor(drawingSize/(textSize * 1.5)),+				skipFitting = (minSteps >= maxSteps);++			var maxValue = max(valuesArray),+				minValue = min(valuesArray);++			// We need some degree of seperation here to calculate the scales if all the values are the same+			// Adding/minusing 0.5 will give us a range of 1.+			if (maxValue === minValue){+				maxValue += 0.5;+				// So we don't end up with a graph with a negative start value if we've said always start from zero+				if (minValue >= 0.5 && !startFromZero){+					minValue -= 0.5;+				}+				else{+					// Make up a whole number above the values+					maxValue += 0.5;+				}+			}++			var	valueRange = Math.abs(maxValue - minValue),+				rangeOrderOfMagnitude = calculateOrderOfMagnitude(valueRange),+				graphMax = Math.ceil(maxValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude),+				graphMin = (startFromZero) ? 0 : Math.floor(minValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude),+				graphRange = graphMax - graphMin,+				stepValue = Math.pow(10, rangeOrderOfMagnitude),+				numberOfSteps = Math.round(graphRange / stepValue);++			//If we have more space on the graph we'll use it to give more definition to the data+			while((numberOfSteps > maxSteps || (numberOfSteps * 2) < maxSteps) && !skipFitting) {+				if(numberOfSteps > maxSteps){+					stepValue *=2;+					numberOfSteps = Math.round(graphRange/stepValue);+					// Don't ever deal with a decimal number of steps - cancel fitting and just use the minimum number of steps.+					if (numberOfSteps % 1 !== 0){+						skipFitting = true;+					}+				}+				//We can fit in double the amount of scale points on the scale+				else{+					//If user has declared ints only, and the step value isn't a decimal+					if (integersOnly && rangeOrderOfMagnitude >= 0){+						//If the user has said integers only, we need to check that making the scale more granular wouldn't make it a float+						if(stepValue/2 % 1 === 0){+							stepValue /=2;+							numberOfSteps = Math.round(graphRange/stepValue);+						}+						//If it would make it a float break out of the loop+						else{+							break;+						}+					}+					//If the scale doesn't have to be an int, make the scale more granular anyway.+					else{+						stepValue /=2;+						numberOfSteps = Math.round(graphRange/stepValue);+					}++				}+			}++			if (skipFitting){+				numberOfSteps = minSteps;+				stepValue = graphRange / numberOfSteps;+			}++			return {+				steps : numberOfSteps,+				stepValue : stepValue,+				min : graphMin,+				max	: graphMin + (numberOfSteps * stepValue)+			};++		},+		/* jshint ignore:start */+		// Blows up jshint errors based on the new Function constructor+		//Templating methods+		//Javascript micro templating by John Resig - source at http://ejohn.org/blog/javascript-micro-templating/+		template = helpers.template = function(templateString, valuesObject){+			 // If templateString is function rather than string-template - call the function for valuesObject+			if(templateString instanceof Function){+			 	return templateString(valuesObject);+		 	}++			var cache = {};+			function tmpl(str, data){+				// Figure out if we're getting a template, or if we need to+				// load the template - and be sure to cache the result.+				var fn = !/\W/.test(str) ?+				cache[str] = cache[str] :++				// Generate a reusable function that will serve as a template+				// generator (and which will be cached).+				new Function("obj",+					"var p=[],print=function(){p.push.apply(p,arguments);};" +++					// Introduce the data as local variables using with(){}+					"with(obj){p.push('" +++					// Convert the template into pure JavaScript+					str+						.replace(/[\r\t\n]/g, " ")+						.split("<%").join("\t")+						.replace(/((^|%>)[^\t]*)'/g, "$1\r")+						.replace(/\t=(.*?)%>/g, "',$1,'")+						.split("\t").join("');")+						.split("%>").join("p.push('")+						.split("\r").join("\\'") ++					"');}return p.join('');"+				);++				// Provide some basic currying to the user+				return data ? fn( data ) : fn;+			}+			return tmpl(templateString,valuesObject);+		},+		/* jshint ignore:end */+		generateLabels = helpers.generateLabels = function(templateString,numberOfSteps,graphMin,stepValue){+			var labelsArray = new Array(numberOfSteps);+			if (labelTemplateString){+				each(labelsArray,function(val,index){+					labelsArray[index] = template(templateString,{value: (graphMin + (stepValue*(index+1)))});+				});+			}+			return labelsArray;+		},+		//--Animation methods+		//Easing functions adapted from Robert Penner's easing equations+		//http://www.robertpenner.com/easing/+		easingEffects = helpers.easingEffects = {+			linear: function (t) {+				return t;+			},+			easeInQuad: function (t) {+				return t * t;+			},+			easeOutQuad: function (t) {+				return -1 * t * (t - 2);+			},+			easeInOutQuad: function (t) {+				if ((t /= 1 / 2) < 1) return 1 / 2 * t * t;+				return -1 / 2 * ((--t) * (t - 2) - 1);+			},+			easeInCubic: function (t) {+				return t * t * t;+			},+			easeOutCubic: function (t) {+				return 1 * ((t = t / 1 - 1) * t * t + 1);+			},+			easeInOutCubic: function (t) {+				if ((t /= 1 / 2) < 1) return 1 / 2 * t * t * t;+				return 1 / 2 * ((t -= 2) * t * t + 2);+			},+			easeInQuart: function (t) {+				return t * t * t * t;+			},+			easeOutQuart: function (t) {+				return -1 * ((t = t / 1 - 1) * t * t * t - 1);+			},+			easeInOutQuart: function (t) {+				if ((t /= 1 / 2) < 1) return 1 / 2 * t * t * t * t;+				return -1 / 2 * ((t -= 2) * t * t * t - 2);+			},+			easeInQuint: function (t) {+				return 1 * (t /= 1) * t * t * t * t;+			},+			easeOutQuint: function (t) {+				return 1 * ((t = t / 1 - 1) * t * t * t * t + 1);+			},+			easeInOutQuint: function (t) {+				if ((t /= 1 / 2) < 1) return 1 / 2 * t * t * t * t * t;+				return 1 / 2 * ((t -= 2) * t * t * t * t + 2);+			},+			easeInSine: function (t) {+				return -1 * Math.cos(t / 1 * (Math.PI / 2)) + 1;+			},+			easeOutSine: function (t) {+				return 1 * Math.sin(t / 1 * (Math.PI / 2));+			},+			easeInOutSine: function (t) {+				return -1 / 2 * (Math.cos(Math.PI * t / 1) - 1);+			},+			easeInExpo: function (t) {+				return (t === 0) ? 1 : 1 * Math.pow(2, 10 * (t / 1 - 1));+			},+			easeOutExpo: function (t) {+				return (t === 1) ? 1 : 1 * (-Math.pow(2, -10 * t / 1) + 1);+			},+			easeInOutExpo: function (t) {+				if (t === 0) return 0;+				if (t === 1) return 1;+				if ((t /= 1 / 2) < 1) return 1 / 2 * Math.pow(2, 10 * (t - 1));+				return 1 / 2 * (-Math.pow(2, -10 * --t) + 2);+			},+			easeInCirc: function (t) {+				if (t >= 1) return t;+				return -1 * (Math.sqrt(1 - (t /= 1) * t) - 1);+			},+			easeOutCirc: function (t) {+				return 1 * Math.sqrt(1 - (t = t / 1 - 1) * t);+			},+			easeInOutCirc: function (t) {+				if ((t /= 1 / 2) < 1) return -1 / 2 * (Math.sqrt(1 - t * t) - 1);+				return 1 / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1);+			},+			easeInElastic: function (t) {+				var s = 1.70158;+				var p = 0;+				var a = 1;+				if (t === 0) return 0;+				if ((t /= 1) == 1) return 1;+				if (!p) p = 1 * 0.3;+				if (a < Math.abs(1)) {+					a = 1;+					s = p / 4;+				} else s = p / (2 * Math.PI) * Math.asin(1 / a);+				return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p));+			},+			easeOutElastic: function (t) {+				var s = 1.70158;+				var p = 0;+				var a = 1;+				if (t === 0) return 0;+				if ((t /= 1) == 1) return 1;+				if (!p) p = 1 * 0.3;+				if (a < Math.abs(1)) {+					a = 1;+					s = p / 4;+				} else s = p / (2 * Math.PI) * Math.asin(1 / a);+				return a * Math.pow(2, -10 * t) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) + 1;+			},+			easeInOutElastic: function (t) {+				var s = 1.70158;+				var p = 0;+				var a = 1;+				if (t === 0) return 0;+				if ((t /= 1 / 2) == 2) return 1;+				if (!p) p = 1 * (0.3 * 1.5);+				if (a < Math.abs(1)) {+					a = 1;+					s = p / 4;+				} else s = p / (2 * Math.PI) * Math.asin(1 / a);+				if (t < 1) return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p));+				return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) * 0.5 + 1;+			},+			easeInBack: function (t) {+				var s = 1.70158;+				return 1 * (t /= 1) * t * ((s + 1) * t - s);+			},+			easeOutBack: function (t) {+				var s = 1.70158;+				return 1 * ((t = t / 1 - 1) * t * ((s + 1) * t + s) + 1);+			},+			easeInOutBack: function (t) {+				var s = 1.70158;+				if ((t /= 1 / 2) < 1) return 1 / 2 * (t * t * (((s *= (1.525)) + 1) * t - s));+				return 1 / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2);+			},+			easeInBounce: function (t) {+				return 1 - easingEffects.easeOutBounce(1 - t);+			},+			easeOutBounce: function (t) {+				if ((t /= 1) < (1 / 2.75)) {+					return 1 * (7.5625 * t * t);+				} else if (t < (2 / 2.75)) {+					return 1 * (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75);+				} else if (t < (2.5 / 2.75)) {+					return 1 * (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375);+				} else {+					return 1 * (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375);+				}+			},+			easeInOutBounce: function (t) {+				if (t < 1 / 2) return easingEffects.easeInBounce(t * 2) * 0.5;+				return easingEffects.easeOutBounce(t * 2 - 1) * 0.5 + 1 * 0.5;+			}+		},+		//Request animation polyfill - http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/+		requestAnimFrame = helpers.requestAnimFrame = (function(){+			return window.requestAnimationFrame ||+				window.webkitRequestAnimationFrame ||+				window.mozRequestAnimationFrame ||+				window.oRequestAnimationFrame ||+				window.msRequestAnimationFrame ||+				function(callback) {+					return window.setTimeout(callback, 1000 / 60);+				};+		})(),+		cancelAnimFrame = helpers.cancelAnimFrame = (function(){+			return window.cancelAnimationFrame ||+				window.webkitCancelAnimationFrame ||+				window.mozCancelAnimationFrame ||+				window.oCancelAnimationFrame ||+				window.msCancelAnimationFrame ||+				function(callback) {+					return window.clearTimeout(callback, 1000 / 60);+				};+		})(),+		animationLoop = helpers.animationLoop = function(callback,totalSteps,easingString,onProgress,onComplete,chartInstance){++			var currentStep = 0,+				easingFunction = easingEffects[easingString] || easingEffects.linear;++			var animationFrame = function(){+				currentStep++;+				var stepDecimal = currentStep/totalSteps;+				var easeDecimal = easingFunction(stepDecimal);++				callback.call(chartInstance,easeDecimal,stepDecimal, currentStep);+				onProgress.call(chartInstance,easeDecimal,stepDecimal);+				if (currentStep < totalSteps){+					chartInstance.animationFrame = requestAnimFrame(animationFrame);+				} else{+					onComplete.apply(chartInstance);+				}+			};+			requestAnimFrame(animationFrame);+		},+		//-- DOM methods+		getRelativePosition = helpers.getRelativePosition = function(evt){+			var mouseX, mouseY;+			var e = evt.originalEvent || evt,+				canvas = evt.currentTarget || evt.srcElement,+				boundingRect = canvas.getBoundingClientRect();++			if (e.touches){+				mouseX = e.touches[0].clientX - boundingRect.left;+				mouseY = e.touches[0].clientY - boundingRect.top;++			}+			else{+				mouseX = e.clientX - boundingRect.left;+				mouseY = e.clientY - boundingRect.top;+			}++			return {+				x : mouseX,+				y : mouseY+			};++		},+		addEvent = helpers.addEvent = function(node,eventType,method){+			if (node.addEventListener){+				node.addEventListener(eventType,method);+			} else if (node.attachEvent){+				node.attachEvent("on"+eventType, method);+			} else {+				node["on"+eventType] = method;+			}+		},+		removeEvent = helpers.removeEvent = function(node, eventType, handler){+			if (node.removeEventListener){+				node.removeEventListener(eventType, handler, false);+			} else if (node.detachEvent){+				node.detachEvent("on"+eventType,handler);+			} else{+				node["on" + eventType] = noop;+			}+		},+		bindEvents = helpers.bindEvents = function(chartInstance, arrayOfEvents, handler){+			// Create the events object if it's not already present+			if (!chartInstance.events) chartInstance.events = {};++			each(arrayOfEvents,function(eventName){+				chartInstance.events[eventName] = function(){+					handler.apply(chartInstance, arguments);+				};+				addEvent(chartInstance.chart.canvas,eventName,chartInstance.events[eventName]);+			});+		},+		unbindEvents = helpers.unbindEvents = function (chartInstance, arrayOfEvents) {+			each(arrayOfEvents, function(handler,eventName){+				removeEvent(chartInstance.chart.canvas, eventName, handler);+			});+		},+		getMaximumWidth = helpers.getMaximumWidth = function(domNode){+			var container = domNode.parentNode;+			// TODO = check cross browser stuff with this.+			return container.clientWidth;+		},+		getMaximumHeight = helpers.getMaximumHeight = function(domNode){+			var container = domNode.parentNode;+			// TODO = check cross browser stuff with this.+			return container.clientHeight;+		},+		getMaximumSize = helpers.getMaximumSize = helpers.getMaximumWidth, // legacy support+		retinaScale = helpers.retinaScale = function(chart){+			var ctx = chart.ctx,+				width = chart.canvas.width,+				height = chart.canvas.height;++			if (window.devicePixelRatio) {+				ctx.canvas.style.width = width + "px";+				ctx.canvas.style.height = height + "px";+				ctx.canvas.height = height * window.devicePixelRatio;+				ctx.canvas.width = width * window.devicePixelRatio;+				ctx.scale(window.devicePixelRatio, window.devicePixelRatio);+			}+		},+		//-- Canvas methods+		clear = helpers.clear = function(chart){+			chart.ctx.clearRect(0,0,chart.width,chart.height);+		},+		fontString = helpers.fontString = function(pixelSize,fontStyle,fontFamily){+			return fontStyle + " " + pixelSize+"px " + fontFamily;+		},+		longestText = helpers.longestText = function(ctx,font,arrayOfStrings){+			ctx.font = font;+			var longest = 0;+			each(arrayOfStrings,function(string){+				var textWidth = ctx.measureText(string).width;+				longest = (textWidth > longest) ? textWidth : longest;+			});+			return longest;+		},+		drawRoundedRectangle = helpers.drawRoundedRectangle = function(ctx,x,y,width,height,radius){+			ctx.beginPath();+			ctx.moveTo(x + radius, y);+			ctx.lineTo(x + width - radius, y);+			ctx.quadraticCurveTo(x + width, y, x + width, y + radius);+			ctx.lineTo(x + width, y + height - radius);+			ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);+			ctx.lineTo(x + radius, y + height);+			ctx.quadraticCurveTo(x, y + height, x, y + height - radius);+			ctx.lineTo(x, y + radius);+			ctx.quadraticCurveTo(x, y, x + radius, y);+			ctx.closePath();+		};+++	//Store a reference to each instance - allowing us to globally resize chart instances on window resize.+	//Destroy method on the chart will remove the instance of the chart from this reference.+	Chart.instances = {};++	Chart.Type = function(data,options,chart){+		this.options = options;+		this.chart = chart;+		this.id = uid();+		//Add the chart instance to the global namespace+		Chart.instances[this.id] = this;++		// Initialize is always called when a chart type is created+		// By default it is a no op, but it should be extended+		if (options.responsive){+			this.resize();+		}+		this.initialize.call(this,data);+	};++	//Core methods that'll be a part of every chart type+	extend(Chart.Type.prototype,{+		initialize : function(){return this;},+		clear : function(){+			clear(this.chart);+			return this;+		},+		stop : function(){+			// Stops any current animation loop occuring+			helpers.cancelAnimFrame.call(root, this.animationFrame);+			return this;+		},+		resize : function(callback){+			this.stop();+			var canvas = this.chart.canvas,+				newWidth = getMaximumWidth(this.chart.canvas),+				newHeight = this.options.maintainAspectRatio ? newWidth / this.chart.aspectRatio : getMaximumHeight(this.chart.canvas);++			canvas.width = this.chart.width = newWidth;+			canvas.height =  this.chart.height = newHeight;++			retinaScale(this.chart);++			if (typeof callback === "function"){+				callback.apply(this, Array.prototype.slice.call(arguments, 1));+			}+			return this;+		},+		reflow : noop,+		render : function(reflow){+			if (reflow){+				this.reflow();+			}+			if (this.options.animation && !reflow){+				helpers.animationLoop(+					this.draw,+					this.options.animationSteps,+					this.options.animationEasing,+					this.options.onAnimationProgress,+					this.options.onAnimationComplete,+					this+				);+			}+			else{+				this.draw();+				this.options.onAnimationComplete.call(this);+			}+			return this;+		},+		generateLegend : function(){+			return template(this.options.legendTemplate,this);+		},+		destroy : function(){+			this.clear();+			unbindEvents(this, this.events);+			delete Chart.instances[this.id];+		},+		showTooltip : function(ChartElements, forceRedraw){+			// Only redraw the chart if we've actually changed what we're hovering on.+			if (typeof this.activeElements === 'undefined') this.activeElements = [];++			var isChanged = (function(Elements){+				var changed = false;++				if (Elements.length !== this.activeElements.length){+					changed = true;+					return changed;+				}++				each(Elements, function(element, index){+					if (element !== this.activeElements[index]){+						changed = true;+					}+				}, this);+				return changed;+			}).call(this, ChartElements);++			if (!isChanged && !forceRedraw){+				return;+			}+			else{+				this.activeElements = ChartElements;+			}+			this.draw();+			if (ChartElements.length > 0){+				// If we have multiple datasets, show a MultiTooltip for all of the data points at that index+				if (this.datasets && this.datasets.length > 1) {+					var dataArray,+						dataIndex;++					for (var i = this.datasets.length - 1; i >= 0; i--) {+						dataArray = this.datasets[i].points || this.datasets[i].bars || this.datasets[i].segments;+						dataIndex = indexOf(dataArray, ChartElements[0]);+						if (dataIndex !== -1){+							break;+						}+					}+					var tooltipLabels = [],+						tooltipColors = [],+						medianPosition = (function(index) {++							// Get all the points at that particular index+							var Elements = [],+								dataCollection,+								xPositions = [],+								yPositions = [],+								xMax,+								yMax,+								xMin,+								yMin;+							helpers.each(this.datasets, function(dataset){+								dataCollection = dataset.points || dataset.bars || dataset.segments;+								if (dataCollection[dataIndex] && dataCollection[dataIndex].hasValue()){+									Elements.push(dataCollection[dataIndex]);+								}+							});++							helpers.each(Elements, function(element) {+								xPositions.push(element.x);+								yPositions.push(element.y);+++								//Include any colour information about the element+								tooltipLabels.push(helpers.template(this.options.multiTooltipTemplate, element));+								tooltipColors.push({+									fill: element._saved.fillColor || element.fillColor,+									stroke: element._saved.strokeColor || element.strokeColor+								});++							}, this);++							yMin = min(yPositions);+							yMax = max(yPositions);++							xMin = min(xPositions);+							xMax = max(xPositions);++							return {+								x: (xMin > this.chart.width/2) ? xMin : xMax,+								y: (yMin + yMax)/2+							};+						}).call(this, dataIndex);++					new Chart.MultiTooltip({+						x: medianPosition.x,+						y: medianPosition.y,+						xPadding: this.options.tooltipXPadding,+						yPadding: this.options.tooltipYPadding,+						xOffset: this.options.tooltipXOffset,+						fillColor: this.options.tooltipFillColor,+						textColor: this.options.tooltipFontColor,+						fontFamily: this.options.tooltipFontFamily,+						fontStyle: this.options.tooltipFontStyle,+						fontSize: this.options.tooltipFontSize,+						titleTextColor: this.options.tooltipTitleFontColor,+						titleFontFamily: this.options.tooltipTitleFontFamily,+						titleFontStyle: this.options.tooltipTitleFontStyle,+						titleFontSize: this.options.tooltipTitleFontSize,+						cornerRadius: this.options.tooltipCornerRadius,+						labels: tooltipLabels,+						legendColors: tooltipColors,+						legendColorBackground : this.options.multiTooltipKeyBackground,+						title: ChartElements[0].label,+						chart: this.chart,+						ctx: this.chart.ctx+					}).draw();++				} else {+					each(ChartElements, function(Element) {+						var tooltipPosition = Element.tooltipPosition();+						new Chart.Tooltip({+							x: Math.round(tooltipPosition.x),+							y: Math.round(tooltipPosition.y),+							xPadding: this.options.tooltipXPadding,+							yPadding: this.options.tooltipYPadding,+							fillColor: this.options.tooltipFillColor,+							textColor: this.options.tooltipFontColor,+							fontFamily: this.options.tooltipFontFamily,+							fontStyle: this.options.tooltipFontStyle,+							fontSize: this.options.tooltipFontSize,+							caretHeight: this.options.tooltipCaretSize,+							cornerRadius: this.options.tooltipCornerRadius,+							text: template(this.options.tooltipTemplate, Element),+							chart: this.chart+						}).draw();+					}, this);+				}+			}+			return this;+		},+		toBase64Image : function(){+			return this.chart.canvas.toDataURL.apply(this.chart.canvas, arguments);+		}+	});++	Chart.Type.extend = function(extensions){++		var parent = this;++		var ChartType = function(){+			return parent.apply(this,arguments);+		};++		//Copy the prototype object of the this class+		ChartType.prototype = clone(parent.prototype);+		//Now overwrite some of the properties in the base class with the new extensions+		extend(ChartType.prototype, extensions);++		ChartType.extend = Chart.Type.extend;++		if (extensions.name || parent.prototype.name){++			var chartName = extensions.name || parent.prototype.name;+			//Assign any potential default values of the new chart type++			//If none are defined, we'll use a clone of the chart type this is being extended from.+			//I.e. if we extend a line chart, we'll use the defaults from the line chart if our new chart+			//doesn't define some defaults of their own.++			var baseDefaults = (Chart.defaults[parent.prototype.name]) ? clone(Chart.defaults[parent.prototype.name]) : {};++			Chart.defaults[chartName] = extend(baseDefaults,extensions.defaults);++			Chart.types[chartName] = ChartType;++			//Register this new chart type in the Chart prototype+			Chart.prototype[chartName] = function(data,options){+				var config = merge(Chart.defaults.global, Chart.defaults[chartName], options || {});+				return new ChartType(data,config,this);+			};+		} else{+			warn("Name not provided for this chart, so it hasn't been registered");+		}+		return parent;+	};++	Chart.Element = function(configuration){+		extend(this,configuration);+		this.initialize.apply(this,arguments);+		this.save();+	};+	extend(Chart.Element.prototype,{+		initialize : function(){},+		restore : function(props){+			if (!props){+				extend(this,this._saved);+			} else {+				each(props,function(key){+					this[key] = this._saved[key];+				},this);+			}+			return this;+		},+		save : function(){+			this._saved = clone(this);+			delete this._saved._saved;+			return this;+		},+		update : function(newProps){+			each(newProps,function(value,key){+				this._saved[key] = this[key];+				this[key] = value;+			},this);+			return this;+		},+		transition : function(props,ease){+			each(props,function(value,key){+				this[key] = ((value - this._saved[key]) * ease) + this._saved[key];+			},this);+			return this;+		},+		tooltipPosition : function(){+			return {+				x : this.x,+				y : this.y+			};+		},+		hasValue: function(){+			return isNumber(this.value);+		}+	});++	Chart.Element.extend = inherits;+++	Chart.Point = Chart.Element.extend({+		display: true,+		inRange: function(chartX,chartY){+			var hitDetectionRange = this.hitDetectionRadius + this.radius;+			return ((Math.pow(chartX-this.x, 2)+Math.pow(chartY-this.y, 2)) < Math.pow(hitDetectionRange,2));+		},+		draw : function(){+			if (this.display){+				var ctx = this.ctx;+				ctx.beginPath();++				ctx.arc(this.x, this.y, this.radius, 0, Math.PI*2);+				ctx.closePath();++				ctx.strokeStyle = this.strokeColor;+				ctx.lineWidth = this.strokeWidth;++				ctx.fillStyle = this.fillColor;++				ctx.fill();+				ctx.stroke();+			}+++			//Quick debug for bezier curve splining+			//Highlights control points and the line between them.+			//Handy for dev - stripped in the min version.++			// ctx.save();+			// ctx.fillStyle = "black";+			// ctx.strokeStyle = "black"+			// ctx.beginPath();+			// ctx.arc(this.controlPoints.inner.x,this.controlPoints.inner.y, 2, 0, Math.PI*2);+			// ctx.fill();++			// ctx.beginPath();+			// ctx.arc(this.controlPoints.outer.x,this.controlPoints.outer.y, 2, 0, Math.PI*2);+			// ctx.fill();++			// ctx.moveTo(this.controlPoints.inner.x,this.controlPoints.inner.y);+			// ctx.lineTo(this.x, this.y);+			// ctx.lineTo(this.controlPoints.outer.x,this.controlPoints.outer.y);+			// ctx.stroke();++			// ctx.restore();++++		}+	});++	Chart.Arc = Chart.Element.extend({+		inRange : function(chartX,chartY){++			var pointRelativePosition = helpers.getAngleFromPoint(this, {+				x: chartX,+				y: chartY+			});++			//Check if within the range of the open/close angle+			var betweenAngles = (pointRelativePosition.angle >= this.startAngle && pointRelativePosition.angle <= this.endAngle),+				withinRadius = (pointRelativePosition.distance >= this.innerRadius && pointRelativePosition.distance <= this.outerRadius);++			return (betweenAngles && withinRadius);+			//Ensure within the outside of the arc centre, but inside arc outer+		},+		tooltipPosition : function(){+			var centreAngle = this.startAngle + ((this.endAngle - this.startAngle) / 2),+				rangeFromCentre = (this.outerRadius - this.innerRadius) / 2 + this.innerRadius;+			return {+				x : this.x + (Math.cos(centreAngle) * rangeFromCentre),+				y : this.y + (Math.sin(centreAngle) * rangeFromCentre)+			};+		},+		draw : function(animationPercent){++			var easingDecimal = animationPercent || 1;++			var ctx = this.ctx;++			ctx.beginPath();++			ctx.arc(this.x, this.y, this.outerRadius, this.startAngle, this.endAngle);++			ctx.arc(this.x, this.y, this.innerRadius, this.endAngle, this.startAngle, true);++			ctx.closePath();+			ctx.strokeStyle = this.strokeColor;+			ctx.lineWidth = this.strokeWidth;++			ctx.fillStyle = this.fillColor;++			ctx.fill();+			ctx.lineJoin = 'bevel';++			if (this.showStroke){+				ctx.stroke();+			}+		}+	});++	Chart.Rectangle = Chart.Element.extend({+		draw : function(){+			var ctx = this.ctx,+				halfWidth = this.width/2,+				leftX = this.x - halfWidth,+				rightX = this.x + halfWidth,+				top = this.base - (this.base - this.y),+				halfStroke = this.strokeWidth / 2;++			// Canvas doesn't allow us to stroke inside the width so we can+			// adjust the sizes to fit if we're setting a stroke on the line+			if (this.showStroke){+				leftX += halfStroke;+				rightX -= halfStroke;+				top += halfStroke;+			}++			ctx.beginPath();++			ctx.fillStyle = this.fillColor;+			ctx.strokeStyle = this.strokeColor;+			ctx.lineWidth = this.strokeWidth;++			// It'd be nice to keep this class totally generic to any rectangle+			// and simply specify which border to miss out.+			ctx.moveTo(leftX, this.base);+			ctx.lineTo(leftX, top);+			ctx.lineTo(rightX, top);+			ctx.lineTo(rightX, this.base);+			ctx.fill();+			if (this.showStroke){+				ctx.stroke();+			}+		},+		height : function(){+			return this.base - this.y;+		},+		inRange : function(chartX,chartY){+			return (chartX >= this.x - this.width/2 && chartX <= this.x + this.width/2) && (chartY >= this.y && chartY <= this.base);+		}+	});++	Chart.Tooltip = Chart.Element.extend({+		draw : function(){++			var ctx = this.chart.ctx;++			ctx.font = fontString(this.fontSize,this.fontStyle,this.fontFamily);++			this.xAlign = "center";+			this.yAlign = "above";++			//Distance between the actual element.y position and the start of the tooltip caret+			var caretPadding = 2;++			var tooltipWidth = ctx.measureText(this.text).width + 2*this.xPadding,+				tooltipRectHeight = this.fontSize + 2*this.yPadding,+				tooltipHeight = tooltipRectHeight + this.caretHeight + caretPadding;++			if (this.x + tooltipWidth/2 >this.chart.width){+				this.xAlign = "left";+			} else if (this.x - tooltipWidth/2 < 0){+				this.xAlign = "right";+			}++			if (this.y - tooltipHeight < 0){+				this.yAlign = "below";+			}+++			var tooltipX = this.x - tooltipWidth/2,+				tooltipY = this.y - tooltipHeight;++			ctx.fillStyle = this.fillColor;++			switch(this.yAlign)+			{+			case "above":+				//Draw a caret above the x/y+				ctx.beginPath();+				ctx.moveTo(this.x,this.y - caretPadding);+				ctx.lineTo(this.x + this.caretHeight, this.y - (caretPadding + this.caretHeight));+				ctx.lineTo(this.x - this.caretHeight, this.y - (caretPadding + this.caretHeight));+				ctx.closePath();+				ctx.fill();+				break;+			case "below":+				tooltipY = this.y + caretPadding + this.caretHeight;+				//Draw a caret below the x/y+				ctx.beginPath();+				ctx.moveTo(this.x, this.y + caretPadding);+				ctx.lineTo(this.x + this.caretHeight, this.y + caretPadding + this.caretHeight);+				ctx.lineTo(this.x - this.caretHeight, this.y + caretPadding + this.caretHeight);+				ctx.closePath();+				ctx.fill();+				break;+			}++			switch(this.xAlign)+			{+			case "left":+				tooltipX = this.x - tooltipWidth + (this.cornerRadius + this.caretHeight);+				break;+			case "right":+				tooltipX = this.x - (this.cornerRadius + this.caretHeight);+				break;+			}++			drawRoundedRectangle(ctx,tooltipX,tooltipY,tooltipWidth,tooltipRectHeight,this.cornerRadius);++			ctx.fill();++			ctx.fillStyle = this.textColor;+			ctx.textAlign = "center";+			ctx.textBaseline = "middle";+			ctx.fillText(this.text, tooltipX + tooltipWidth/2, tooltipY + tooltipRectHeight/2);+		}+	});++	Chart.MultiTooltip = Chart.Element.extend({+		initialize : function(){+			this.font = fontString(this.fontSize,this.fontStyle,this.fontFamily);++			this.titleFont = fontString(this.titleFontSize,this.titleFontStyle,this.titleFontFamily);++			this.height = (this.labels.length * this.fontSize) + ((this.labels.length-1) * (this.fontSize/2)) + (this.yPadding*2) + this.titleFontSize *1.5;++			this.ctx.font = this.titleFont;++			var titleWidth = this.ctx.measureText(this.title).width,+				//Label has a legend square as well so account for this.+				labelWidth = longestText(this.ctx,this.font,this.labels) + this.fontSize + 3,+				longestTextWidth = max([labelWidth,titleWidth]);++			this.width = longestTextWidth + (this.xPadding*2);+++			var halfHeight = this.height/2;++			//Check to ensure the height will fit on the canvas+			//The three is to buffer form the very+			if (this.y - halfHeight < 0 ){+				this.y = halfHeight;+			} else if (this.y + halfHeight > this.chart.height){+				this.y = this.chart.height - halfHeight;+			}++			//Decide whether to align left or right based on position on canvas+			if (this.x > this.chart.width/2){+				this.x -= this.xOffset + this.width;+			} else {+				this.x += this.xOffset;+			}+++		},+		getLineHeight : function(index){+			var baseLineHeight = this.y - (this.height/2) + this.yPadding,+				afterTitleIndex = index-1;++			//If the index is zero, we're getting the title+			if (index === 0){+				return baseLineHeight + this.titleFontSize/2;+			} else{+				return baseLineHeight + ((this.fontSize*1.5*afterTitleIndex) + this.fontSize/2) + this.titleFontSize * 1.5;+			}++		},+		draw : function(){+			drawRoundedRectangle(this.ctx,this.x,this.y - this.height/2,this.width,this.height,this.cornerRadius);+			var ctx = this.ctx;+			ctx.fillStyle = this.fillColor;+			ctx.fill();+			ctx.closePath();++			ctx.textAlign = "left";+			ctx.textBaseline = "middle";+			ctx.fillStyle = this.titleTextColor;+			ctx.font = this.titleFont;++			ctx.fillText(this.title,this.x + this.xPadding, this.getLineHeight(0));++			ctx.font = this.font;+			helpers.each(this.labels,function(label,index){+				ctx.fillStyle = this.textColor;+				ctx.fillText(label,this.x + this.xPadding + this.fontSize + 3, this.getLineHeight(index + 1));++				//A bit gnarly, but clearing this rectangle breaks when using explorercanvas (clears whole canvas)+				//ctx.clearRect(this.x + this.xPadding, this.getLineHeight(index + 1) - this.fontSize/2, this.fontSize, this.fontSize);+				//Instead we'll make a white filled block to put the legendColour palette over.++				ctx.fillStyle = this.legendColorBackground;+				ctx.fillRect(this.x + this.xPadding, this.getLineHeight(index + 1) - this.fontSize/2, this.fontSize, this.fontSize);++				ctx.fillStyle = this.legendColors[index].fill;+				ctx.fillRect(this.x + this.xPadding, this.getLineHeight(index + 1) - this.fontSize/2, this.fontSize, this.fontSize);+++			},this);+		}+	});++	Chart.Scale = Chart.Element.extend({+		initialize : function(){+			this.fit();+		},+		buildYLabels : function(){+			this.yLabels = [];++			var stepDecimalPlaces = getDecimalPlaces(this.stepValue);++			for (var i=0; i<=this.steps; i++){+				this.yLabels.push(template(this.templateString,{value:(this.min + (i * this.stepValue)).toFixed(stepDecimalPlaces)}));+			}+			this.yLabelWidth = (this.display && this.showLabels) ? longestText(this.ctx,this.font,this.yLabels) : 0;+		},+		addXLabel : function(label){+			this.xLabels.push(label);+			this.valuesCount++;+			this.fit();+		},+		removeXLabel : function(){+			this.xLabels.shift();+			this.valuesCount--;+			this.fit();+		},+		// Fitting loop to rotate x Labels and figure out what fits there, and also calculate how many Y steps to use+		fit: function(){+			// First we need the width of the yLabels, assuming the xLabels aren't rotated++			// To do that we need the base line at the top and base of the chart, assuming there is no x label rotation+			this.startPoint = (this.display) ? this.fontSize : 0;+			this.endPoint = (this.display) ? this.height - (this.fontSize * 1.5) - 5 : this.height; // -5 to pad labels++			// Apply padding settings to the start and end point.+			this.startPoint += this.padding;+			this.endPoint -= this.padding;++			// Cache the starting height, so can determine if we need to recalculate the scale yAxis+			var cachedHeight = this.endPoint - this.startPoint,+				cachedYLabelWidth;++			// Build the current yLabels so we have an idea of what size they'll be to start+			/*+			 *	This sets what is returned from calculateScaleRange as static properties of this class:+			 *+				this.steps;+				this.stepValue;+				this.min;+				this.max;+			 *+			 */+			this.calculateYRange(cachedHeight);++			// With these properties set we can now build the array of yLabels+			// and also the width of the largest yLabel+			this.buildYLabels();++			this.calculateXLabelRotation();++			while((cachedHeight > this.endPoint - this.startPoint)){+				cachedHeight = this.endPoint - this.startPoint;+				cachedYLabelWidth = this.yLabelWidth;++				this.calculateYRange(cachedHeight);+				this.buildYLabels();++				// Only go through the xLabel loop again if the yLabel width has changed+				if (cachedYLabelWidth < this.yLabelWidth){+					this.calculateXLabelRotation();+				}+			}++		},+		calculateXLabelRotation : function(){+			//Get the width of each grid by calculating the difference+			//between x offsets between 0 and 1.++			this.ctx.font = this.font;++			var firstWidth = this.ctx.measureText(this.xLabels[0]).width,+				lastWidth = this.ctx.measureText(this.xLabels[this.xLabels.length - 1]).width,+				firstRotated,+				lastRotated;+++			this.xScalePaddingRight = lastWidth/2 + 3;+			this.xScalePaddingLeft = (firstWidth/2 > this.yLabelWidth + 10) ? firstWidth/2 : this.yLabelWidth + 10;++			this.xLabelRotation = 0;+			if (this.display){+				var originalLabelWidth = longestText(this.ctx,this.font,this.xLabels),+					cosRotation,+					firstRotatedWidth;+				this.xLabelWidth = originalLabelWidth;+				//Allow 3 pixels x2 padding either side for label readability+				var xGridWidth = Math.floor(this.calculateX(1) - this.calculateX(0)) - 6;++				//Max label rotate should be 90 - also act as a loop counter+				while ((this.xLabelWidth > xGridWidth && this.xLabelRotation === 0) || (this.xLabelWidth > xGridWidth && this.xLabelRotation <= 90 && this.xLabelRotation > 0)){+					cosRotation = Math.cos(toRadians(this.xLabelRotation));++					firstRotated = cosRotation * firstWidth;+					lastRotated = cosRotation * lastWidth;++					// We're right aligning the text now.+					if (firstRotated + this.fontSize / 2 > this.yLabelWidth + 8){+						this.xScalePaddingLeft = firstRotated + this.fontSize / 2;+					}+					this.xScalePaddingRight = this.fontSize/2;+++					this.xLabelRotation++;+					this.xLabelWidth = cosRotation * originalLabelWidth;++				}+				if (this.xLabelRotation > 0){+					this.endPoint -= Math.sin(toRadians(this.xLabelRotation))*originalLabelWidth + 3;+				}+			}+			else{+				this.xLabelWidth = 0;+				this.xScalePaddingRight = this.padding;+				this.xScalePaddingLeft = this.padding;+			}++		},+		// Needs to be overidden in each Chart type+		// Otherwise we need to pass all the data into the scale class+		calculateYRange: noop,+		drawingArea: function(){+			return this.startPoint - this.endPoint;+		},+		calculateY : function(value){+			var scalingFactor = this.drawingArea() / (this.min - this.max);+			return this.endPoint - (scalingFactor * (value - this.min));+		},+		calculateX : function(index){+			var isRotated = (this.xLabelRotation > 0),+				// innerWidth = (this.offsetGridLines) ? this.width - offsetLeft - this.padding : this.width - (offsetLeft + halfLabelWidth * 2) - this.padding,+				innerWidth = this.width - (this.xScalePaddingLeft + this.xScalePaddingRight),+				valueWidth = innerWidth/(this.valuesCount - ((this.offsetGridLines) ? 0 : 1)),+				valueOffset = (valueWidth * index) + this.xScalePaddingLeft;++			if (this.offsetGridLines){+				valueOffset += (valueWidth/2);+			}++			return Math.round(valueOffset);+		},+		update : function(newProps){+			helpers.extend(this, newProps);+			this.fit();+		},+		draw : function(){+			var ctx = this.ctx,+				yLabelGap = (this.endPoint - this.startPoint) / this.steps,+				xStart = Math.round(this.xScalePaddingLeft);+			if (this.display){+				ctx.fillStyle = this.textColor;+				ctx.font = this.font;+				each(this.yLabels,function(labelString,index){+					var yLabelCenter = this.endPoint - (yLabelGap * index),+						linePositionY = Math.round(yLabelCenter);++					ctx.textAlign = "right";+					ctx.textBaseline = "middle";+					if (this.showLabels){+						ctx.fillText(labelString,xStart - 10,yLabelCenter);+					}+					ctx.beginPath();+					if (index > 0){+						// This is a grid line in the centre, so drop that+						ctx.lineWidth = this.gridLineWidth;+						ctx.strokeStyle = this.gridLineColor;+					} else {+						// This is the first line on the scale+						ctx.lineWidth = this.lineWidth;+						ctx.strokeStyle = this.lineColor;+					}++					linePositionY += helpers.aliasPixel(ctx.lineWidth);++					ctx.moveTo(xStart, linePositionY);+					ctx.lineTo(this.width, linePositionY);+					ctx.stroke();+					ctx.closePath();++					ctx.lineWidth = this.lineWidth;+					ctx.strokeStyle = this.lineColor;+					ctx.beginPath();+					ctx.moveTo(xStart - 5, linePositionY);+					ctx.lineTo(xStart, linePositionY);+					ctx.stroke();+					ctx.closePath();++				},this);++				each(this.xLabels,function(label,index){+					var xPos = this.calculateX(index) + aliasPixel(this.lineWidth),+						// Check to see if line/bar here and decide where to place the line+						linePos = this.calculateX(index - (this.offsetGridLines ? 0.5 : 0)) + aliasPixel(this.lineWidth),+						isRotated = (this.xLabelRotation > 0);++					ctx.beginPath();++					if (index > 0){+						// This is a grid line in the centre, so drop that+						ctx.lineWidth = this.gridLineWidth;+						ctx.strokeStyle = this.gridLineColor;+					} else {+						// This is the first line on the scale+						ctx.lineWidth = this.lineWidth;+						ctx.strokeStyle = this.lineColor;+					}+					ctx.moveTo(linePos,this.endPoint);+					ctx.lineTo(linePos,this.startPoint - 3);+					ctx.stroke();+					ctx.closePath();+++					ctx.lineWidth = this.lineWidth;+					ctx.strokeStyle = this.lineColor;+++					// Small lines at the bottom of the base grid line+					ctx.beginPath();+					ctx.moveTo(linePos,this.endPoint);+					ctx.lineTo(linePos,this.endPoint + 5);+					ctx.stroke();+					ctx.closePath();++					ctx.save();+					ctx.translate(xPos,(isRotated) ? this.endPoint + 12 : this.endPoint + 8);+					ctx.rotate(toRadians(this.xLabelRotation)*-1);+					ctx.font = this.font;+					ctx.textAlign = (isRotated) ? "right" : "center";+					ctx.textBaseline = (isRotated) ? "middle" : "top";+					ctx.fillText(label, 0, 0);+					ctx.restore();+				},this);++			}+		}++	});++	Chart.RadialScale = Chart.Element.extend({+		initialize: function(){+			this.size = min([this.height, this.width]);+			this.drawingArea = (this.display) ? (this.size/2) - (this.fontSize/2 + this.backdropPaddingY) : (this.size/2);+		},+		calculateCenterOffset: function(value){+			// Take into account half font size + the yPadding of the top value+			var scalingFactor = this.drawingArea / (this.max - this.min);++			return (value - this.min) * scalingFactor;+		},+		update : function(){+			if (!this.lineArc){+				this.setScaleSize();+			} else {+				this.drawingArea = (this.display) ? (this.size/2) - (this.fontSize/2 + this.backdropPaddingY) : (this.size/2);+			}+			this.buildYLabels();+		},+		buildYLabels: function(){+			this.yLabels = [];++			var stepDecimalPlaces = getDecimalPlaces(this.stepValue);++			for (var i=0; i<=this.steps; i++){+				this.yLabels.push(template(this.templateString,{value:(this.min + (i * this.stepValue)).toFixed(stepDecimalPlaces)}));+			}+		},+		getCircumference : function(){+			return ((Math.PI*2) / this.valuesCount);+		},+		setScaleSize: function(){+			/*+			 * Right, this is really confusing and there is a lot of maths going on here+			 * The gist of the problem is here: https://gist.github.com/nnnick/696cc9c55f4b0beb8fe9+			 *+			 * Reaction: https://dl.dropboxusercontent.com/u/34601363/toomuchscience.gif+			 *+			 * Solution:+			 *+			 * We assume the radius of the polygon is half the size of the canvas at first+			 * at each index we check if the text overlaps.+			 *+			 * Where it does, we store that angle and that index.+			 *+			 * After finding the largest index and angle we calculate how much we need to remove+			 * from the shape radius to move the point inwards by that x.+			 *+			 * We average the left and right distances to get the maximum shape radius that can fit in the box+			 * along with labels.+			 *+			 * Once we have that, we can find the centre point for the chart, by taking the x text protrusion+			 * on each side, removing that from the size, halving it and adding the left x protrusion width.+			 *+			 * This will mean we have a shape fitted to the canvas, as large as it can be with the labels+			 * and position it in the most space efficient manner+			 *+			 * https://dl.dropboxusercontent.com/u/34601363/yeahscience.gif+			 */+++			// Get maximum radius of the polygon. Either half the height (minus the text width) or half the width.+			// Use this to calculate the offset + change. - Make sure L/R protrusion is at least 0 to stop issues with centre points+			var largestPossibleRadius = min([(this.height/2 - this.pointLabelFontSize - 5), this.width/2]),+				pointPosition,+				i,+				textWidth,+				halfTextWidth,+				furthestRight = this.width,+				furthestRightIndex,+				furthestRightAngle,+				furthestLeft = 0,+				furthestLeftIndex,+				furthestLeftAngle,+				xProtrusionLeft,+				xProtrusionRight,+				radiusReductionRight,+				radiusReductionLeft,+				maxWidthRadius;+			this.ctx.font = fontString(this.pointLabelFontSize,this.pointLabelFontStyle,this.pointLabelFontFamily);+			for (i=0;i<this.valuesCount;i++){+				// 5px to space the text slightly out - similar to what we do in the draw function.+				pointPosition = this.getPointPosition(i, largestPossibleRadius);+				textWidth = this.ctx.measureText(template(this.templateString, { value: this.labels[i] })).width + 5;+				if (i === 0 || i === this.valuesCount/2){+					// If we're at index zero, or exactly the middle, we're at exactly the top/bottom+					// of the radar chart, so text will be aligned centrally, so we'll half it and compare+					// w/left and right text sizes+					halfTextWidth = textWidth/2;+					if (pointPosition.x + halfTextWidth > furthestRight) {+						furthestRight = pointPosition.x + halfTextWidth;+						furthestRightIndex = i;+					}+					if (pointPosition.x - halfTextWidth < furthestLeft) {+						furthestLeft = pointPosition.x - halfTextWidth;+						furthestLeftIndex = i;+					}+				}+				else if (i < this.valuesCount/2) {+					// Less than half the values means we'll left align the text+					if (pointPosition.x + textWidth > furthestRight) {+						furthestRight = pointPosition.x + textWidth;+						furthestRightIndex = i;+					}+				}+				else if (i > this.valuesCount/2){+					// More than half the values means we'll right align the text+					if (pointPosition.x - textWidth < furthestLeft) {+						furthestLeft = pointPosition.x - textWidth;+						furthestLeftIndex = i;+					}+				}+			}++			xProtrusionLeft = furthestLeft;++			xProtrusionRight = Math.ceil(furthestRight - this.width);++			furthestRightAngle = this.getIndexAngle(furthestRightIndex);++			furthestLeftAngle = this.getIndexAngle(furthestLeftIndex);++			radiusReductionRight = xProtrusionRight / Math.sin(furthestRightAngle + Math.PI/2);++			radiusReductionLeft = xProtrusionLeft / Math.sin(furthestLeftAngle + Math.PI/2);++			// Ensure we actually need to reduce the size of the chart+			radiusReductionRight = (isNumber(radiusReductionRight)) ? radiusReductionRight : 0;+			radiusReductionLeft = (isNumber(radiusReductionLeft)) ? radiusReductionLeft : 0;++			this.drawingArea = largestPossibleRadius - (radiusReductionLeft + radiusReductionRight)/2;++			//this.drawingArea = min([maxWidthRadius, (this.height - (2 * (this.pointLabelFontSize + 5)))/2])+			this.setCenterPoint(radiusReductionLeft, radiusReductionRight);++		},+		setCenterPoint: function(leftMovement, rightMovement){++			var maxRight = this.width - rightMovement - this.drawingArea,+				maxLeft = leftMovement + this.drawingArea;++			this.xCenter = (maxLeft + maxRight)/2;+			// Always vertically in the centre as the text height doesn't change+			this.yCenter = (this.height/2);+		},++		getIndexAngle : function(index){+			var angleMultiplier = (Math.PI * 2) / this.valuesCount;+			// Start from the top instead of right, so remove a quarter of the circle++			return index * angleMultiplier - (Math.PI/2);+		},+		getPointPosition : function(index, distanceFromCenter){+			var thisAngle = this.getIndexAngle(index);+			return {+				x : (Math.cos(thisAngle) * distanceFromCenter) + this.xCenter,+				y : (Math.sin(thisAngle) * distanceFromCenter) + this.yCenter+			};+		},+		draw: function(){+			if (this.display){+				var ctx = this.ctx;+				each(this.yLabels, function(label, index){+					// Don't draw a centre value+					if (index > 0){+						var yCenterOffset = index * (this.drawingArea/this.steps),+							yHeight = this.yCenter - yCenterOffset,+							pointPosition;++						// Draw circular lines around the scale+						if (this.lineWidth > 0){+							ctx.strokeStyle = this.lineColor;+							ctx.lineWidth = this.lineWidth;++							if(this.lineArc){+								ctx.beginPath();+								ctx.arc(this.xCenter, this.yCenter, yCenterOffset, 0, Math.PI*2);+								ctx.closePath();+								ctx.stroke();+							} else{+								ctx.beginPath();+								for (var i=0;i<this.valuesCount;i++)+								{+									pointPosition = this.getPointPosition(i, this.calculateCenterOffset(this.min + (index * this.stepValue)));+									if (i === 0){+										ctx.moveTo(pointPosition.x, pointPosition.y);+									} else {+										ctx.lineTo(pointPosition.x, pointPosition.y);+									}+								}+								ctx.closePath();+								ctx.stroke();+							}+						}+						if(this.showLabels){+							ctx.font = fontString(this.fontSize,this.fontStyle,this.fontFamily);+							if (this.showLabelBackdrop){+								var labelWidth = ctx.measureText(label).width;+								ctx.fillStyle = this.backdropColor;+								ctx.fillRect(+									this.xCenter - labelWidth/2 - this.backdropPaddingX,+									yHeight - this.fontSize/2 - this.backdropPaddingY,+									labelWidth + this.backdropPaddingX*2,+									this.fontSize + this.backdropPaddingY*2+								);+							}+							ctx.textAlign = 'center';+							ctx.textBaseline = "middle";+							ctx.fillStyle = this.fontColor;+							ctx.fillText(label, this.xCenter, yHeight);+						}+					}+				}, this);++				if (!this.lineArc){+					ctx.lineWidth = this.angleLineWidth;+					ctx.strokeStyle = this.angleLineColor;+					for (var i = this.valuesCount - 1; i >= 0; i--) {+						if (this.angleLineWidth > 0){+							var outerPosition = this.getPointPosition(i, this.calculateCenterOffset(this.max));+							ctx.beginPath();+							ctx.moveTo(this.xCenter, this.yCenter);+							ctx.lineTo(outerPosition.x, outerPosition.y);+							ctx.stroke();+							ctx.closePath();+						}+						// Extra 3px out for some label spacing+						var pointLabelPosition = this.getPointPosition(i, this.calculateCenterOffset(this.max) + 5);+						ctx.font = fontString(this.pointLabelFontSize,this.pointLabelFontStyle,this.pointLabelFontFamily);+						ctx.fillStyle = this.pointLabelFontColor;++						var labelsCount = this.labels.length,+							halfLabelsCount = this.labels.length/2,+							quarterLabelsCount = halfLabelsCount/2,+							upperHalf = (i < quarterLabelsCount || i > labelsCount - quarterLabelsCount),+							exactQuarter = (i === quarterLabelsCount || i === labelsCount - quarterLabelsCount);+						if (i === 0){+							ctx.textAlign = 'center';+						} else if(i === halfLabelsCount){+							ctx.textAlign = 'center';+						} else if (i < halfLabelsCount){+							ctx.textAlign = 'left';+						} else {+							ctx.textAlign = 'right';+						}++						// Set the correct text baseline based on outer positioning+						if (exactQuarter){+							ctx.textBaseline = 'middle';+						} else if (upperHalf){+							ctx.textBaseline = 'bottom';+						} else {+							ctx.textBaseline = 'top';+						}++						ctx.fillText(this.labels[i], pointLabelPosition.x, pointLabelPosition.y);+					}+				}+			}+		}+	});++	// Attach global event to resize each chart instance when the browser resizes+	helpers.addEvent(window, "resize", (function(){+		// Basic debounce of resize function so it doesn't hurt performance when resizing browser.+		var timeout;+		return function(){+			clearTimeout(timeout);+			timeout = setTimeout(function(){+				each(Chart.instances,function(instance){+					// If the responsive flag is set in the chart instance config+					// Cascade the resize event down to the chart.+					if (instance.options.responsive){+						instance.resize(instance.render, true);+					}+				});+			}, 50);+		};+	})());+++	if (amd) {+		define(function(){+			return Chart;+		});+	} else if (typeof module === 'object' && module.exports) {+		module.exports = Chart;+	}++	root.Chart = Chart;++	Chart.noConflict = function(){+		root.Chart = previous;+		return Chart;+	};++}).call(this);++(function(){+	"use strict";++	var root = this,+		Chart = root.Chart,+		helpers = Chart.helpers;+++	var defaultConfig = {+		//Boolean - Whether the scale should start at zero, or an order of magnitude down from the lowest value+		scaleBeginAtZero : true,++		//Boolean - Whether grid lines are shown across the chart+		scaleShowGridLines : true,++		//String - Colour of the grid lines+		scaleGridLineColor : "rgba(0,0,0,.05)",++		//Number - Width of the grid lines+		scaleGridLineWidth : 1,++		//Boolean - If there is a stroke on each bar+		barShowStroke : true,++		//Number - Pixel width of the bar stroke+		barStrokeWidth : 2,++		//Number - Spacing between each of the X value sets+		barValueSpacing : 5,++		//Number - Spacing between data sets within X values+		barDatasetSpacing : 1,++		//String - A legend template+		legendTemplate : "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<datasets.length; i++){%><li><span style=\"background-color:<%=datasets[i].fillColor%>\"></span><%if(datasets[i].label){%><%=datasets[i].label%><%}%></li><%}%></ul>"++	};+++	Chart.Type.extend({+		name: "Bar",+		defaults : defaultConfig,+		initialize:  function(data){++			//Expose options as a scope variable here so we can access it in the ScaleClass+			var options = this.options;++			this.ScaleClass = Chart.Scale.extend({+				offsetGridLines : true,+				calculateBarX : function(datasetCount, datasetIndex, barIndex){+					//Reusable method for calculating the xPosition of a given bar based on datasetIndex & width of the bar+					var xWidth = this.calculateBaseWidth(),+						xAbsolute = this.calculateX(barIndex) - (xWidth/2),+						barWidth = this.calculateBarWidth(datasetCount);++					return xAbsolute + (barWidth * datasetIndex) + (datasetIndex * options.barDatasetSpacing) + barWidth/2;+				},+				calculateBaseWidth : function(){+					return (this.calculateX(1) - this.calculateX(0)) - (2*options.barValueSpacing);+				},+				calculateBarWidth : function(datasetCount){+					//The padding between datasets is to the right of each bar, providing that there are more than 1 dataset+					var baseWidth = this.calculateBaseWidth() - ((datasetCount - 1) * options.barDatasetSpacing);++					return (baseWidth / datasetCount);+				}+			});++			this.datasets = [];++			//Set up tooltip events on the chart+			if (this.options.showTooltips){+				helpers.bindEvents(this, this.options.tooltipEvents, function(evt){+					var activeBars = (evt.type !== 'mouseout') ? this.getBarsAtEvent(evt) : [];++					this.eachBars(function(bar){+						bar.restore(['fillColor', 'strokeColor']);+					});+					helpers.each(activeBars, function(activeBar){+						activeBar.fillColor = activeBar.highlightFill;+						activeBar.strokeColor = activeBar.highlightStroke;+					});+					this.showTooltip(activeBars);+				});+			}++			//Declare the extension of the default point, to cater for the options passed in to the constructor+			this.BarClass = Chart.Rectangle.extend({+				strokeWidth : this.options.barStrokeWidth,+				showStroke : this.options.barShowStroke,+				ctx : this.chart.ctx+			});++			//Iterate through each of the datasets, and build this into a property of the chart+			helpers.each(data.datasets,function(dataset,datasetIndex){++				var datasetObject = {+					label : dataset.label || null,+					fillColor : dataset.fillColor,+					strokeColor : dataset.strokeColor,+					bars : []+				};++				this.datasets.push(datasetObject);++				helpers.each(dataset.data,function(dataPoint,index){+					//Add a new point for each piece of data, passing any required data to draw.+					datasetObject.bars.push(new this.BarClass({+						value : dataPoint,+						label : data.labels[index],+						datasetLabel: dataset.label,+						strokeColor : dataset.strokeColor,+						fillColor : dataset.fillColor,+						highlightFill : dataset.highlightFill || dataset.fillColor,+						highlightStroke : dataset.highlightStroke || dataset.strokeColor+					}));+				},this);++			},this);++			this.buildScale(data.labels);++			this.BarClass.prototype.base = this.scale.endPoint;++			this.eachBars(function(bar, index, datasetIndex){+				helpers.extend(bar, {+					width : this.scale.calculateBarWidth(this.datasets.length),+					x: this.scale.calculateBarX(this.datasets.length, datasetIndex, index),+					y: this.scale.endPoint+				});+				bar.save();+			}, this);++			this.render();+		},+		update : function(){+			this.scale.update();+			// Reset any highlight colours before updating.+			helpers.each(this.activeElements, function(activeElement){+				activeElement.restore(['fillColor', 'strokeColor']);+			});++			this.eachBars(function(bar){+				bar.save();+			});+			this.render();+		},+		eachBars : function(callback){+			helpers.each(this.datasets,function(dataset, datasetIndex){+				helpers.each(dataset.bars, callback, this, datasetIndex);+			},this);+		},+		getBarsAtEvent : function(e){+			var barsArray = [],+				eventPosition = helpers.getRelativePosition(e),+				datasetIterator = function(dataset){+					barsArray.push(dataset.bars[barIndex]);+				},+				barIndex;++			for (var datasetIndex = 0; datasetIndex < this.datasets.length; datasetIndex++) {+				for (barIndex = 0; barIndex < this.datasets[datasetIndex].bars.length; barIndex++) {+					if (this.datasets[datasetIndex].bars[barIndex].inRange(eventPosition.x,eventPosition.y)){+						helpers.each(this.datasets, datasetIterator);+						return barsArray;+					}+				}+			}++			return barsArray;+		},+		buildScale : function(labels){+			var self = this;++			var dataTotal = function(){+				var values = [];+				self.eachBars(function(bar){+					values.push(bar.value);+				});+				return values;+			};++			var scaleOptions = {+				templateString : this.options.scaleLabel,+				height : this.chart.height,+				width : this.chart.width,+				ctx : this.chart.ctx,+				textColor : this.options.scaleFontColor,+				fontSize : this.options.scaleFontSize,+				fontStyle : this.options.scaleFontStyle,+				fontFamily : this.options.scaleFontFamily,+				valuesCount : labels.length,+				beginAtZero : this.options.scaleBeginAtZero,+				integersOnly : this.options.scaleIntegersOnly,+				calculateYRange: function(currentHeight){+					var updatedRanges = helpers.calculateScaleRange(+						dataTotal(),+						currentHeight,+						this.fontSize,+						this.beginAtZero,+						this.integersOnly+					);+					helpers.extend(this, updatedRanges);+				},+				xLabels : labels,+				font : helpers.fontString(this.options.scaleFontSize, this.options.scaleFontStyle, this.options.scaleFontFamily),+				lineWidth : this.options.scaleLineWidth,+				lineColor : this.options.scaleLineColor,+				gridLineWidth : (this.options.scaleShowGridLines) ? this.options.scaleGridLineWidth : 0,+				gridLineColor : (this.options.scaleShowGridLines) ? this.options.scaleGridLineColor : "rgba(0,0,0,0)",+				padding : (this.options.showScale) ? 0 : (this.options.barShowStroke) ? this.options.barStrokeWidth : 0,+				showLabels : this.options.scaleShowLabels,+				display : this.options.showScale+			};++			if (this.options.scaleOverride){+				helpers.extend(scaleOptions, {+					calculateYRange: helpers.noop,+					steps: this.options.scaleSteps,+					stepValue: this.options.scaleStepWidth,+					min: this.options.scaleStartValue,+					max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth)+				});+			}++			this.scale = new this.ScaleClass(scaleOptions);+		},+		addData : function(valuesArray,label){+			//Map the values array for each of the datasets+			helpers.each(valuesArray,function(value,datasetIndex){+				//Add a new point for each piece of data, passing any required data to draw.+				this.datasets[datasetIndex].bars.push(new this.BarClass({+					value : value,+					label : label,+					x: this.scale.calculateBarX(this.datasets.length, datasetIndex, this.scale.valuesCount+1),+					y: this.scale.endPoint,+					width : this.scale.calculateBarWidth(this.datasets.length),+					base : this.scale.endPoint,+					strokeColor : this.datasets[datasetIndex].strokeColor,+					fillColor : this.datasets[datasetIndex].fillColor+				}));+			},this);++			this.scale.addXLabel(label);+			//Then re-render the chart.+			this.update();+		},+		removeData : function(){+			this.scale.removeXLabel();+			//Then re-render the chart.+			helpers.each(this.datasets,function(dataset){+				dataset.bars.shift();+			},this);+			this.update();+		},+		reflow : function(){+			helpers.extend(this.BarClass.prototype,{+				y: this.scale.endPoint,+				base : this.scale.endPoint+			});+			var newScaleProps = helpers.extend({+				height : this.chart.height,+				width : this.chart.width+			});+			this.scale.update(newScaleProps);+		},+		draw : function(ease){+			var easingDecimal = ease || 1;+			this.clear();++			var ctx = this.chart.ctx;++			this.scale.draw(easingDecimal);++			//Draw all the bars for each dataset+			helpers.each(this.datasets,function(dataset,datasetIndex){+				helpers.each(dataset.bars,function(bar,index){+					if (bar.hasValue()){+						bar.base = this.scale.endPoint;+						//Transition then draw+						bar.transition({+							x : this.scale.calculateBarX(this.datasets.length, datasetIndex, index),+							y : this.scale.calculateY(bar.value),+							width : this.scale.calculateBarWidth(this.datasets.length)+						}, easingDecimal).draw();+					}+				},this);++			},this);+		}+	});+++}).call(this);+(function(){+	"use strict";++	var root = this,+		Chart = root.Chart,+		//Cache a local reference to Chart.helpers+		helpers = Chart.helpers;++	var defaultConfig = {+		//Boolean - Whether we should show a stroke on each segment+		segmentShowStroke : true,++		//String - The colour of each segment stroke+		segmentStrokeColor : "#fff",++		//Number - The width of each segment stroke+		segmentStrokeWidth : 2,++		//The percentage of the chart that we cut out of the middle.+		percentageInnerCutout : 50,++		//Number - Amount of animation steps+		animationSteps : 100,++		//String - Animation easing effect+		animationEasing : "easeOutBounce",++		//Boolean - Whether we animate the rotation of the Doughnut+		animateRotate : true,++		//Boolean - Whether we animate scaling the Doughnut from the centre+		animateScale : false,++		//String - A legend template+		legendTemplate : "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<segments.length; i++){%><li><span style=\"background-color:<%=segments[i].fillColor%>\"></span><%if(segments[i].label){%><%=segments[i].label%><%}%></li><%}%></ul>"++	};+++	Chart.Type.extend({+		//Passing in a name registers this chart in the Chart namespace+		name: "Doughnut",+		//Providing a defaults will also register the deafults in the chart namespace+		defaults : defaultConfig,+		//Initialize is fired when the chart is initialized - Data is passed in as a parameter+		//Config is automatically merged by the core of Chart.js, and is available at this.options+		initialize:  function(data){++			//Declare segments as a static property to prevent inheriting across the Chart type prototype+			this.segments = [];+			this.outerRadius = (helpers.min([this.chart.width,this.chart.height]) -	this.options.segmentStrokeWidth/2)/2;++			this.SegmentArc = Chart.Arc.extend({+				ctx : this.chart.ctx,+				x : this.chart.width/2,+				y : this.chart.height/2+			});++			//Set up tooltip events on the chart+			if (this.options.showTooltips){+				helpers.bindEvents(this, this.options.tooltipEvents, function(evt){+					var activeSegments = (evt.type !== 'mouseout') ? this.getSegmentsAtEvent(evt) : [];++					helpers.each(this.segments,function(segment){+						segment.restore(["fillColor"]);+					});+					helpers.each(activeSegments,function(activeSegment){+						activeSegment.fillColor = activeSegment.highlightColor;+					});+					this.showTooltip(activeSegments);+				});+			}+			this.calculateTotal(data);++			helpers.each(data,function(datapoint, index){+				this.addData(datapoint, index, true);+			},this);++			this.render();+		},+		getSegmentsAtEvent : function(e){+			var segmentsArray = [];++			var location = helpers.getRelativePosition(e);++			helpers.each(this.segments,function(segment){+				if (segment.inRange(location.x,location.y)) segmentsArray.push(segment);+			},this);+			return segmentsArray;+		},+		addData : function(segment, atIndex, silent){+			var index = atIndex || this.segments.length;+			this.segments.splice(index, 0, new this.SegmentArc({+				value : segment.value,+				outerRadius : (this.options.animateScale) ? 0 : this.outerRadius,+				innerRadius : (this.options.animateScale) ? 0 : (this.outerRadius/100) * this.options.percentageInnerCutout,+				fillColor : segment.color,+				highlightColor : segment.highlight || segment.color,+				showStroke : this.options.segmentShowStroke,+				strokeWidth : this.options.segmentStrokeWidth,+				strokeColor : this.options.segmentStrokeColor,+				startAngle : Math.PI * 1.5,+				circumference : (this.options.animateRotate) ? 0 : this.calculateCircumference(segment.value),+				label : segment.label+			}));+			if (!silent){+				this.reflow();+				this.update();+			}+		},+		calculateCircumference : function(value){+			return (Math.PI*2)*(value / this.total);+		},+		calculateTotal : function(data){+			this.total = 0;+			helpers.each(data,function(segment){+				this.total += segment.value;+			},this);+		},+		update : function(){+			this.calculateTotal(this.segments);++			// Reset any highlight colours before updating.+			helpers.each(this.activeElements, function(activeElement){+				activeElement.restore(['fillColor']);+			});++			helpers.each(this.segments,function(segment){+				segment.save();+			});+			this.render();+		},++		removeData: function(atIndex){+			var indexToDelete = (helpers.isNumber(atIndex)) ? atIndex : this.segments.length-1;+			this.segments.splice(indexToDelete, 1);+			this.reflow();+			this.update();+		},++		reflow : function(){+			helpers.extend(this.SegmentArc.prototype,{+				x : this.chart.width/2,+				y : this.chart.height/2+			});+			this.outerRadius = (helpers.min([this.chart.width,this.chart.height]) -	this.options.segmentStrokeWidth/2)/2;+			helpers.each(this.segments, function(segment){+				segment.update({+					outerRadius : this.outerRadius,+					innerRadius : (this.outerRadius/100) * this.options.percentageInnerCutout+				});+			}, this);+		},+		draw : function(easeDecimal){+			var animDecimal = (easeDecimal) ? easeDecimal : 1;+			this.clear();+			helpers.each(this.segments,function(segment,index){+				segment.transition({+					circumference : this.calculateCircumference(segment.value),+					outerRadius : this.outerRadius,+					innerRadius : (this.outerRadius/100) * this.options.percentageInnerCutout+				},animDecimal);++				segment.endAngle = segment.startAngle + segment.circumference;++				segment.draw();+				if (index === 0){+					segment.startAngle = Math.PI * 1.5;+				}+				//Check to see if it's the last segment, if not get the next and update the start angle+				if (index < this.segments.length-1){+					this.segments[index+1].startAngle = segment.endAngle;+				}+			},this);++		}+	});++	Chart.types.Doughnut.extend({+		name : "Pie",+		defaults : helpers.merge(defaultConfig,{percentageInnerCutout : 0})+	});++}).call(this);+(function(){+	"use strict";++	var root = this,+		Chart = root.Chart,+		helpers = Chart.helpers;++	var defaultConfig = {++		///Boolean - Whether grid lines are shown across the chart+		scaleShowGridLines : true,++		//String - Colour of the grid lines+		scaleGridLineColor : "rgba(0,0,0,.05)",++		//Number - Width of the grid lines+		scaleGridLineWidth : 1,++		//Boolean - Whether the line is curved between points+		bezierCurve : true,++		//Number - Tension of the bezier curve between points+		bezierCurveTension : 0.4,++		//Boolean - Whether to show a dot for each point+		pointDot : true,++		//Number - Radius of each point dot in pixels+		pointDotRadius : 4,++		//Number - Pixel width of point dot stroke+		pointDotStrokeWidth : 1,++		//Number - amount extra to add to the radius to cater for hit detection outside the drawn point+		pointHitDetectionRadius : 20,++		//Boolean - Whether to show a stroke for datasets+		datasetStroke : true,++		//Number - Pixel width of dataset stroke+		datasetStrokeWidth : 2,++		//Boolean - Whether to fill the dataset with a colour+		datasetFill : true,++		//String - A legend template+		legendTemplate : "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<datasets.length; i++){%><li><span style=\"background-color:<%=datasets[i].strokeColor%>\"></span><%if(datasets[i].label){%><%=datasets[i].label%><%}%></li><%}%></ul>"++	};+++	Chart.Type.extend({+		name: "Line",+		defaults : defaultConfig,+		initialize:  function(data){+			//Declare the extension of the default point, to cater for the options passed in to the constructor+			this.PointClass = Chart.Point.extend({+				strokeWidth : this.options.pointDotStrokeWidth,+				radius : this.options.pointDotRadius,+				display: this.options.pointDot,+				hitDetectionRadius : this.options.pointHitDetectionRadius,+				ctx : this.chart.ctx,+				inRange : function(mouseX){+					return (Math.pow(mouseX-this.x, 2) < Math.pow(this.radius + this.hitDetectionRadius,2));+				}+			});++			this.datasets = [];++			//Set up tooltip events on the chart+			if (this.options.showTooltips){+				helpers.bindEvents(this, this.options.tooltipEvents, function(evt){+					var activePoints = (evt.type !== 'mouseout') ? this.getPointsAtEvent(evt) : [];+					this.eachPoints(function(point){+						point.restore(['fillColor', 'strokeColor']);+					});+					helpers.each(activePoints, function(activePoint){+						activePoint.fillColor = activePoint.highlightFill;+						activePoint.strokeColor = activePoint.highlightStroke;+					});+					this.showTooltip(activePoints);+				});+			}++			//Iterate through each of the datasets, and build this into a property of the chart+			helpers.each(data.datasets,function(dataset){++				var datasetObject = {+					label : dataset.label || null,+					fillColor : dataset.fillColor,+					strokeColor : dataset.strokeColor,+					pointColor : dataset.pointColor,+					pointStrokeColor : dataset.pointStrokeColor,+					points : []+				};++				this.datasets.push(datasetObject);+++				helpers.each(dataset.data,function(dataPoint,index){+					//Add a new point for each piece of data, passing any required data to draw.+					datasetObject.points.push(new this.PointClass({+						value : dataPoint,+						label : data.labels[index],+						datasetLabel: dataset.label,+						strokeColor : dataset.pointStrokeColor,+						fillColor : dataset.pointColor,+						highlightFill : dataset.pointHighlightFill || dataset.pointColor,+						highlightStroke : dataset.pointHighlightStroke || dataset.pointStrokeColor+					}));+				},this);++				this.buildScale(data.labels);+++				this.eachPoints(function(point, index){+					helpers.extend(point, {+						x: this.scale.calculateX(index),+						y: this.scale.endPoint+					});+					point.save();+				}, this);++			},this);+++			this.render();+		},+		update : function(){+			this.scale.update();+			// Reset any highlight colours before updating.+			helpers.each(this.activeElements, function(activeElement){+				activeElement.restore(['fillColor', 'strokeColor']);+			});+			this.eachPoints(function(point){+				point.save();+			});+			this.render();+		},+		eachPoints : function(callback){+			helpers.each(this.datasets,function(dataset){+				helpers.each(dataset.points,callback,this);+			},this);+		},+		getPointsAtEvent : function(e){+			var pointsArray = [],+				eventPosition = helpers.getRelativePosition(e);+			helpers.each(this.datasets,function(dataset){+				helpers.each(dataset.points,function(point){+					if (point.inRange(eventPosition.x,eventPosition.y)) pointsArray.push(point);+				});+			},this);+			return pointsArray;+		},+		buildScale : function(labels){+			var self = this;++			var dataTotal = function(){+				var values = [];+				self.eachPoints(function(point){+					values.push(point.value);+				});++				return values;+			};++			var scaleOptions = {+				templateString : this.options.scaleLabel,+				height : this.chart.height,+				width : this.chart.width,+				ctx : this.chart.ctx,+				textColor : this.options.scaleFontColor,+				fontSize : this.options.scaleFontSize,+				fontStyle : this.options.scaleFontStyle,+				fontFamily : this.options.scaleFontFamily,+				valuesCount : labels.length,+				beginAtZero : this.options.scaleBeginAtZero,+				integersOnly : this.options.scaleIntegersOnly,+				calculateYRange : function(currentHeight){+					var updatedRanges = helpers.calculateScaleRange(+						dataTotal(),+						currentHeight,+						this.fontSize,+						this.beginAtZero,+						this.integersOnly+					);+					helpers.extend(this, updatedRanges);+				},+				xLabels : labels,+				font : helpers.fontString(this.options.scaleFontSize, this.options.scaleFontStyle, this.options.scaleFontFamily),+				lineWidth : this.options.scaleLineWidth,+				lineColor : this.options.scaleLineColor,+				gridLineWidth : (this.options.scaleShowGridLines) ? this.options.scaleGridLineWidth : 0,+				gridLineColor : (this.options.scaleShowGridLines) ? this.options.scaleGridLineColor : "rgba(0,0,0,0)",+				padding: (this.options.showScale) ? 0 : this.options.pointDotRadius + this.options.pointDotStrokeWidth,+				showLabels : this.options.scaleShowLabels,+				display : this.options.showScale+			};++			if (this.options.scaleOverride){+				helpers.extend(scaleOptions, {+					calculateYRange: helpers.noop,+					steps: this.options.scaleSteps,+					stepValue: this.options.scaleStepWidth,+					min: this.options.scaleStartValue,+					max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth)+				});+			}+++			this.scale = new Chart.Scale(scaleOptions);+		},+		addData : function(valuesArray,label){+			//Map the values array for each of the datasets++			helpers.each(valuesArray,function(value,datasetIndex){+				//Add a new point for each piece of data, passing any required data to draw.+				this.datasets[datasetIndex].points.push(new this.PointClass({+					value : value,+					label : label,+					x: this.scale.calculateX(this.scale.valuesCount+1),+					y: this.scale.endPoint,+					strokeColor : this.datasets[datasetIndex].pointStrokeColor,+					fillColor : this.datasets[datasetIndex].pointColor+				}));+			},this);++			this.scale.addXLabel(label);+			//Then re-render the chart.+			this.update();+		},+		removeData : function(){+			this.scale.removeXLabel();+			//Then re-render the chart.+			helpers.each(this.datasets,function(dataset){+				dataset.points.shift();+			},this);+			this.update();+		},+		reflow : function(){+			var newScaleProps = helpers.extend({+				height : this.chart.height,+				width : this.chart.width+			});+			this.scale.update(newScaleProps);+		},+		draw : function(ease){+			var easingDecimal = ease || 1;+			this.clear();++			var ctx = this.chart.ctx;++			// Some helper methods for getting the next/prev points+			var hasValue = function(item){+				return item.value !== null;+			},+			nextPoint = function(point, collection, index){+				return helpers.findNextWhere(collection, hasValue, index) || point;+			},+			previousPoint = function(point, collection, index){+				return helpers.findPreviousWhere(collection, hasValue, index) || point;+			};++			this.scale.draw(easingDecimal);+++			helpers.each(this.datasets,function(dataset){+				var pointsWithValues = helpers.where(dataset.points, hasValue);++				//Transition each point first so that the line and point drawing isn't out of sync+				//We can use this extra loop to calculate the control points of this dataset also in this loop++				helpers.each(dataset.points, function(point, index){+					if (point.hasValue()){+						point.transition({+							y : this.scale.calculateY(point.value),+							x : this.scale.calculateX(index)+						}, easingDecimal);+					}+				},this);+++				// Control points need to be calculated in a seperate loop, because we need to know the current x/y of the point+				// This would cause issues when there is no animation, because the y of the next point would be 0, so beziers would be skewed+				if (this.options.bezierCurve){+					helpers.each(pointsWithValues, function(point, index){+						var tension = (index > 0 && index < pointsWithValues.length - 1) ? this.options.bezierCurveTension : 0;+						point.controlPoints = helpers.splineCurve(+							previousPoint(point, pointsWithValues, index),+							point,+							nextPoint(point, pointsWithValues, index),+							tension+						);++						// Prevent the bezier going outside of the bounds of the graph++						// Cap puter bezier handles to the upper/lower scale bounds+						if (point.controlPoints.outer.y > this.scale.endPoint){+							point.controlPoints.outer.y = this.scale.endPoint;+						}+						else if (point.controlPoints.outer.y < this.scale.startPoint){+							point.controlPoints.outer.y = this.scale.startPoint;+						}++						// Cap inner bezier handles to the upper/lower scale bounds+						if (point.controlPoints.inner.y > this.scale.endPoint){+							point.controlPoints.inner.y = this.scale.endPoint;+						}+						else if (point.controlPoints.inner.y < this.scale.startPoint){+							point.controlPoints.inner.y = this.scale.startPoint;+						}+					},this);+				}+++				//Draw the line between all the points+				ctx.lineWidth = this.options.datasetStrokeWidth;+				ctx.strokeStyle = dataset.strokeColor;+				ctx.beginPath();++				helpers.each(pointsWithValues, function(point, index){+					if (index === 0){+						ctx.moveTo(point.x, point.y);+					}+					else{+						if(this.options.bezierCurve){+							var previous = previousPoint(point, pointsWithValues, index);++							ctx.bezierCurveTo(+								previous.controlPoints.outer.x,+								previous.controlPoints.outer.y,+								point.controlPoints.inner.x,+								point.controlPoints.inner.y,+								point.x,+								point.y+							);+						}+						else{+							ctx.lineTo(point.x,point.y);+						}+					}+				}, this);++				ctx.stroke();++				if (this.options.datasetFill && pointsWithValues.length > 0){+					//Round off the line by going to the base of the chart, back to the start, then fill.+					ctx.lineTo(pointsWithValues[pointsWithValues.length - 1].x, this.scale.endPoint);+					ctx.lineTo(pointsWithValues[0].x, this.scale.endPoint);+					ctx.fillStyle = dataset.fillColor;+					ctx.closePath();+					ctx.fill();+				}++				//Now draw the points over the line+				//A little inefficient double looping, but better than the line+				//lagging behind the point positions+				helpers.each(pointsWithValues,function(point){+					point.draw();+				});+			},this);+		}+	});+++}).call(this);+(function(){+	"use strict";++	var root = this,+		Chart = root.Chart,+		//Cache a local reference to Chart.helpers+		helpers = Chart.helpers;++	var defaultConfig = {+		//Boolean - Show a backdrop to the scale label+		scaleShowLabelBackdrop : true,++		//String - The colour of the label backdrop+		scaleBackdropColor : "rgba(255,255,255,0.75)",++		// Boolean - Whether the scale should begin at zero+		scaleBeginAtZero : true,++		//Number - The backdrop padding above & below the label in pixels+		scaleBackdropPaddingY : 2,++		//Number - The backdrop padding to the side of the label in pixels+		scaleBackdropPaddingX : 2,++		//Boolean - Show line for each value in the scale+		scaleShowLine : true,++		//Boolean - Stroke a line around each segment in the chart+		segmentShowStroke : true,++		//String - The colour of the stroke on each segement.+		segmentStrokeColor : "#fff",++		//Number - The width of the stroke value in pixels+		segmentStrokeWidth : 2,++		//Number - Amount of animation steps+		animationSteps : 100,++		//String - Animation easing effect.+		animationEasing : "easeOutBounce",++		//Boolean - Whether to animate the rotation of the chart+		animateRotate : true,++		//Boolean - Whether to animate scaling the chart from the centre+		animateScale : false,++		//String - A legend template+		legendTemplate : "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<segments.length; i++){%><li><span style=\"background-color:<%=segments[i].fillColor%>\"></span><%if(segments[i].label){%><%=segments[i].label%><%}%></li><%}%></ul>"+	};+++	Chart.Type.extend({+		//Passing in a name registers this chart in the Chart namespace+		name: "PolarArea",+		//Providing a defaults will also register the deafults in the chart namespace+		defaults : defaultConfig,+		//Initialize is fired when the chart is initialized - Data is passed in as a parameter+		//Config is automatically merged by the core of Chart.js, and is available at this.options+		initialize:  function(data){+			this.segments = [];+			//Declare segment class as a chart instance specific class, so it can share props for this instance+			this.SegmentArc = Chart.Arc.extend({+				showStroke : this.options.segmentShowStroke,+				strokeWidth : this.options.segmentStrokeWidth,+				strokeColor : this.options.segmentStrokeColor,+				ctx : this.chart.ctx,+				innerRadius : 0,+				x : this.chart.width/2,+				y : this.chart.height/2+			});+			this.scale = new Chart.RadialScale({+				display: this.options.showScale,+				fontStyle: this.options.scaleFontStyle,+				fontSize: this.options.scaleFontSize,+				fontFamily: this.options.scaleFontFamily,+				fontColor: this.options.scaleFontColor,+				showLabels: this.options.scaleShowLabels,+				showLabelBackdrop: this.options.scaleShowLabelBackdrop,+				backdropColor: this.options.scaleBackdropColor,+				backdropPaddingY : this.options.scaleBackdropPaddingY,+				backdropPaddingX: this.options.scaleBackdropPaddingX,+				lineWidth: (this.options.scaleShowLine) ? this.options.scaleLineWidth : 0,+				lineColor: this.options.scaleLineColor,+				lineArc: true,+				width: this.chart.width,+				height: this.chart.height,+				xCenter: this.chart.width/2,+				yCenter: this.chart.height/2,+				ctx : this.chart.ctx,+				templateString: this.options.scaleLabel,+				valuesCount: data.length+			});++			this.updateScaleRange(data);++			this.scale.update();++			helpers.each(data,function(segment,index){+				this.addData(segment,index,true);+			},this);++			//Set up tooltip events on the chart+			if (this.options.showTooltips){+				helpers.bindEvents(this, this.options.tooltipEvents, function(evt){+					var activeSegments = (evt.type !== 'mouseout') ? this.getSegmentsAtEvent(evt) : [];+					helpers.each(this.segments,function(segment){+						segment.restore(["fillColor"]);+					});+					helpers.each(activeSegments,function(activeSegment){+						activeSegment.fillColor = activeSegment.highlightColor;+					});+					this.showTooltip(activeSegments);+				});+			}++			this.render();+		},+		getSegmentsAtEvent : function(e){+			var segmentsArray = [];++			var location = helpers.getRelativePosition(e);++			helpers.each(this.segments,function(segment){+				if (segment.inRange(location.x,location.y)) segmentsArray.push(segment);+			},this);+			return segmentsArray;+		},+		addData : function(segment, atIndex, silent){+			var index = atIndex || this.segments.length;++			this.segments.splice(index, 0, new this.SegmentArc({+				fillColor: segment.color,+				highlightColor: segment.highlight || segment.color,+				label: segment.label,+				value: segment.value,+				outerRadius: (this.options.animateScale) ? 0 : this.scale.calculateCenterOffset(segment.value),+				circumference: (this.options.animateRotate) ? 0 : this.scale.getCircumference(),+				startAngle: Math.PI * 1.5+			}));+			if (!silent){+				this.reflow();+				this.update();+			}+		},+		removeData: function(atIndex){+			var indexToDelete = (helpers.isNumber(atIndex)) ? atIndex : this.segments.length-1;+			this.segments.splice(indexToDelete, 1);+			this.reflow();+			this.update();+		},+		calculateTotal: function(data){+			this.total = 0;+			helpers.each(data,function(segment){+				this.total += segment.value;+			},this);+			this.scale.valuesCount = this.segments.length;+		},+		updateScaleRange: function(datapoints){+			var valuesArray = [];+			helpers.each(datapoints,function(segment){+				valuesArray.push(segment.value);+			});++			var scaleSizes = (this.options.scaleOverride) ?+				{+					steps: this.options.scaleSteps,+					stepValue: this.options.scaleStepWidth,+					min: this.options.scaleStartValue,+					max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth)+				} :+				helpers.calculateScaleRange(+					valuesArray,+					helpers.min([this.chart.width, this.chart.height])/2,+					this.options.scaleFontSize,+					this.options.scaleBeginAtZero,+					this.options.scaleIntegersOnly+				);++			helpers.extend(+				this.scale,+				scaleSizes,+				{+					size: helpers.min([this.chart.width, this.chart.height]),+					xCenter: this.chart.width/2,+					yCenter: this.chart.height/2+				}+			);++		},+		update : function(){+			this.calculateTotal(this.segments);++			helpers.each(this.segments,function(segment){+				segment.save();+			});+			this.render();+		},+		reflow : function(){+			helpers.extend(this.SegmentArc.prototype,{+				x : this.chart.width/2,+				y : this.chart.height/2+			});+			this.updateScaleRange(this.segments);+			this.scale.update();++			helpers.extend(this.scale,{+				xCenter: this.chart.width/2,+				yCenter: this.chart.height/2+			});++			helpers.each(this.segments, function(segment){+				segment.update({+					outerRadius : this.scale.calculateCenterOffset(segment.value)+				});+			}, this);++		},+		draw : function(ease){+			var easingDecimal = ease || 1;+			//Clear & draw the canvas+			this.clear();+			helpers.each(this.segments,function(segment, index){+				segment.transition({+					circumference : this.scale.getCircumference(),+					outerRadius : this.scale.calculateCenterOffset(segment.value)+				},easingDecimal);++				segment.endAngle = segment.startAngle + segment.circumference;++				// If we've removed the first segment we need to set the first one to+				// start at the top.+				if (index === 0){+					segment.startAngle = Math.PI * 1.5;+				}++				//Check to see if it's the last segment, if not get the next and update the start angle+				if (index < this.segments.length - 1){+					this.segments[index+1].startAngle = segment.endAngle;+				}+				segment.draw();+			}, this);+			this.scale.draw();+		}+	});++}).call(this);+(function(){+	"use strict";++	var root = this,+		Chart = root.Chart,+		helpers = Chart.helpers;++++	Chart.Type.extend({+		name: "Radar",+		defaults:{+			//Boolean - Whether to show lines for each scale point+			scaleShowLine : true,++			//Boolean - Whether we show the angle lines out of the radar+			angleShowLineOut : true,++			//Boolean - Whether to show labels on the scale+			scaleShowLabels : false,++			// Boolean - Whether the scale should begin at zero+			scaleBeginAtZero : true,++			//String - Colour of the angle line+			angleLineColor : "rgba(0,0,0,.1)",++			//Number - Pixel width of the angle line+			angleLineWidth : 1,++			//String - Point label font declaration+			pointLabelFontFamily : "'Arial'",++			//String - Point label font weight+			pointLabelFontStyle : "normal",++			//Number - Point label font size in pixels+			pointLabelFontSize : 10,++			//String - Point label font colour+			pointLabelFontColor : "#666",++			//Boolean - Whether to show a dot for each point+			pointDot : true,++			//Number - Radius of each point dot in pixels+			pointDotRadius : 3,++			//Number - Pixel width of point dot stroke+			pointDotStrokeWidth : 1,++			//Number - amount extra to add to the radius to cater for hit detection outside the drawn point+			pointHitDetectionRadius : 20,++			//Boolean - Whether to show a stroke for datasets+			datasetStroke : true,++			//Number - Pixel width of dataset stroke+			datasetStrokeWidth : 2,++			//Boolean - Whether to fill the dataset with a colour+			datasetFill : true,++			//String - A legend template+			legendTemplate : "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<datasets.length; i++){%><li><span style=\"background-color:<%=datasets[i].strokeColor%>\"></span><%if(datasets[i].label){%><%=datasets[i].label%><%}%></li><%}%></ul>"++		},++		initialize: function(data){+			this.PointClass = Chart.Point.extend({+				strokeWidth : this.options.pointDotStrokeWidth,+				radius : this.options.pointDotRadius,+				display: this.options.pointDot,+				hitDetectionRadius : this.options.pointHitDetectionRadius,+				ctx : this.chart.ctx+			});++			this.datasets = [];++			this.buildScale(data);++			//Set up tooltip events on the chart+			if (this.options.showTooltips){+				helpers.bindEvents(this, this.options.tooltipEvents, function(evt){+					var activePointsCollection = (evt.type !== 'mouseout') ? this.getPointsAtEvent(evt) : [];++					this.eachPoints(function(point){+						point.restore(['fillColor', 'strokeColor']);+					});+					helpers.each(activePointsCollection, function(activePoint){+						activePoint.fillColor = activePoint.highlightFill;+						activePoint.strokeColor = activePoint.highlightStroke;+					});++					this.showTooltip(activePointsCollection);+				});+			}++			//Iterate through each of the datasets, and build this into a property of the chart+			helpers.each(data.datasets,function(dataset){++				var datasetObject = {+					label: dataset.label || null,+					fillColor : dataset.fillColor,+					strokeColor : dataset.strokeColor,+					pointColor : dataset.pointColor,+					pointStrokeColor : dataset.pointStrokeColor,+					points : []+				};++				this.datasets.push(datasetObject);++				helpers.each(dataset.data,function(dataPoint,index){+					//Add a new point for each piece of data, passing any required data to draw.+					var pointPosition;+					if (!this.scale.animation){+						pointPosition = this.scale.getPointPosition(index, this.scale.calculateCenterOffset(dataPoint));+					}+					datasetObject.points.push(new this.PointClass({+						value : dataPoint,+						label : data.labels[index],+						datasetLabel: dataset.label,+						x: (this.options.animation) ? this.scale.xCenter : pointPosition.x,+						y: (this.options.animation) ? this.scale.yCenter : pointPosition.y,+						strokeColor : dataset.pointStrokeColor,+						fillColor : dataset.pointColor,+						highlightFill : dataset.pointHighlightFill || dataset.pointColor,+						highlightStroke : dataset.pointHighlightStroke || dataset.pointStrokeColor+					}));+				},this);++			},this);++			this.render();+		},+		eachPoints : function(callback){+			helpers.each(this.datasets,function(dataset){+				helpers.each(dataset.points,callback,this);+			},this);+		},++		getPointsAtEvent : function(evt){+			var mousePosition = helpers.getRelativePosition(evt),+				fromCenter = helpers.getAngleFromPoint({+					x: this.scale.xCenter,+					y: this.scale.yCenter+				}, mousePosition);++			var anglePerIndex = (Math.PI * 2) /this.scale.valuesCount,+				pointIndex = Math.round((fromCenter.angle - Math.PI * 1.5) / anglePerIndex),+				activePointsCollection = [];++			// If we're at the top, make the pointIndex 0 to get the first of the array.+			if (pointIndex >= this.scale.valuesCount || pointIndex < 0){+				pointIndex = 0;+			}++			if (fromCenter.distance <= this.scale.drawingArea){+				helpers.each(this.datasets, function(dataset){+					activePointsCollection.push(dataset.points[pointIndex]);+				});+			}++			return activePointsCollection;+		},++		buildScale : function(data){+			this.scale = new Chart.RadialScale({+				display: this.options.showScale,+				fontStyle: this.options.scaleFontStyle,+				fontSize: this.options.scaleFontSize,+				fontFamily: this.options.scaleFontFamily,+				fontColor: this.options.scaleFontColor,+				showLabels: this.options.scaleShowLabels,+				showLabelBackdrop: this.options.scaleShowLabelBackdrop,+				backdropColor: this.options.scaleBackdropColor,+				backdropPaddingY : this.options.scaleBackdropPaddingY,+				backdropPaddingX: this.options.scaleBackdropPaddingX,+				lineWidth: (this.options.scaleShowLine) ? this.options.scaleLineWidth : 0,+				lineColor: this.options.scaleLineColor,+				angleLineColor : this.options.angleLineColor,+				angleLineWidth : (this.options.angleShowLineOut) ? this.options.angleLineWidth : 0,+				// Point labels at the edge of each line+				pointLabelFontColor : this.options.pointLabelFontColor,+				pointLabelFontSize : this.options.pointLabelFontSize,+				pointLabelFontFamily : this.options.pointLabelFontFamily,+				pointLabelFontStyle : this.options.pointLabelFontStyle,+				height : this.chart.height,+				width: this.chart.width,+				xCenter: this.chart.width/2,+				yCenter: this.chart.height/2,+				ctx : this.chart.ctx,+				templateString: this.options.scaleLabel,+				labels: data.labels,+				valuesCount: data.datasets[0].data.length+			});++			this.scale.setScaleSize();+			this.updateScaleRange(data.datasets);+			this.scale.buildYLabels();+		},+		updateScaleRange: function(datasets){+			var valuesArray = (function(){+				var totalDataArray = [];+				helpers.each(datasets,function(dataset){+					if (dataset.data){+						totalDataArray = totalDataArray.concat(dataset.data);+					}+					else {+						helpers.each(dataset.points, function(point){+							totalDataArray.push(point.value);+						});+					}+				});+				return totalDataArray;+			})();+++			var scaleSizes = (this.options.scaleOverride) ?+				{+					steps: this.options.scaleSteps,+					stepValue: this.options.scaleStepWidth,+					min: this.options.scaleStartValue,+					max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth)+				} :+				helpers.calculateScaleRange(+					valuesArray,+					helpers.min([this.chart.width, this.chart.height])/2,+					this.options.scaleFontSize,+					this.options.scaleBeginAtZero,+					this.options.scaleIntegersOnly+				);++			helpers.extend(+				this.scale,+				scaleSizes+			);++		},+		addData : function(valuesArray,label){+			//Map the values array for each of the datasets+			this.scale.valuesCount++;+			helpers.each(valuesArray,function(value,datasetIndex){+				var pointPosition = this.scale.getPointPosition(this.scale.valuesCount, this.scale.calculateCenterOffset(value));+				this.datasets[datasetIndex].points.push(new this.PointClass({+					value : value,+					label : label,+					x: pointPosition.x,+					y: pointPosition.y,+					strokeColor : this.datasets[datasetIndex].pointStrokeColor,+					fillColor : this.datasets[datasetIndex].pointColor+				}));+			},this);++			this.scale.labels.push(label);++			this.reflow();++			this.update();+		},+		removeData : function(){+			this.scale.valuesCount--;+			this.scale.labels.shift();+			helpers.each(this.datasets,function(dataset){+				dataset.points.shift();+			},this);+			this.reflow();+			this.update();+		},+		update : function(){+			this.eachPoints(function(point){+				point.save();+			});+			this.reflow();+			this.render();+		},+		reflow: function(){+			helpers.extend(this.scale, {+				width : this.chart.width,+				height: this.chart.height,+				size : helpers.min([this.chart.width, this.chart.height]),+				xCenter: this.chart.width/2,+				yCenter: this.chart.height/2+			});+			this.updateScaleRange(this.datasets);+			this.scale.setScaleSize();+			this.scale.buildYLabels();+		},+		draw : function(ease){+			var easeDecimal = ease || 1,+				ctx = this.chart.ctx;+			this.clear();+			this.scale.draw();++			helpers.each(this.datasets,function(dataset){++				//Transition each point first so that the line and point drawing isn't out of sync+				helpers.each(dataset.points,function(point,index){+					if (point.hasValue()){+						point.transition(this.scale.getPointPosition(index, this.scale.calculateCenterOffset(point.value)), easeDecimal);+					}+				},this);++++				//Draw the line between all the points+				ctx.lineWidth = this.options.datasetStrokeWidth;+				ctx.strokeStyle = dataset.strokeColor;+				ctx.beginPath();+				helpers.each(dataset.points,function(point,index){+					if (index === 0){+						ctx.moveTo(point.x,point.y);+					}+					else{+						ctx.lineTo(point.x,point.y);+					}+				},this);+				ctx.closePath();+				ctx.stroke();++				ctx.fillStyle = dataset.fillColor;+				ctx.fill();++				//Now draw the points over the line+				//A little inefficient double looping, but better than the line+				//lagging behind the point positions+				helpers.each(dataset.points,function(point){+					if (point.hasValue()){+						point.draw();+					}+				});++			},this);++		}++	});++++++}).call(this);
+ static/charts-code.js view
@@ -0,0 +1,107 @@++Chart.defaults.global.animation = false;++var accuracy = 3;++var baseScale = 1000;+var accountScales = {+    'default'       : 1,+};+function accountScale(account) {+    return baseScale * (accountScales[account] || accountScales['default']);+}++var accountColors = {+    'Verm'          : "rgba(150, 0, 0, 1)",+    'Giro'          : "rgba(150, 0, 0, 0.5)",+    'Lohn'          : "rgba(255, 0, 0, 1)",+    'LSt'           : "rgba(255, 0, 0, 0.5)",+    'StFrei'        : "rgba(255, 0, 0, 0.2)",+    'default'       : "rgba(150, 150, 150, 0.5)",+};+function accountColor(account) {+    return accountColors[account] || accountColors['default'];+}++function arrayMult(arr, m) {+    var a = arr.slice(0);+    for(var i=0; i<a.length; i++) {+        a[i] *= m;+    }+    return a;+}+function arrayAbsSum(arr) {+    return arr.reduce(function(prev, current) {+        return Math.abs(current) + prev;+    }, 0);+}++var months = [], accountBalances = {}, plotData = {};+$(document).ready(function(j) {++    var ctx = $("#myChart")[0].getContext("2d");+    var myNewChart;+    for(var a in data.accounts) {+        var account = data.accounts[a].fAccount;+        if("Month" != account) {+            accountBalances[account] = [];+        }+    }+    var i = 0;+    for(var b in data.balances) {+        if(i++ % accuracy != 0) continue;+        var balance = data.balances[b];+        for(var a in data.accounts) {+            var account = data.accounts[a].fAccount;+            if("Month" == account) months.push(balance[a]);+            else accountBalances[account].push(balance[a]);+        }+    }+    for(var a in data.accounts) {+        var account = data.accounts[a].fAccount;+        if("Month" != account) {+            $("<label>").css({'color': accountColor(account)})+                .text(" "+account + " ("+Math.round(arrayAbsSum(accountBalances[account]))+")")+                .prepend(+                    $("<input>", { type: "checkbox", name: account})+                        .prop('checked', false)+                        .prop('data-account', account)+                        .click(function() {+                            redrawChart();+                        })+                ).appendTo("#accountSelectorForm");+        }+    }+    $("#accountSelectorForm #checkAll").click(function() {+        $("#accountSelectorForm input:checkbox:not(#checkAll)")+            .prop('checked', $(this).is(":checked"));+        redrawChart();+    });++    function redrawChart() {+        if(myNewChart) myNewChart.destroy();+        plotData.labels = months;+        plotData.datasets = [];+        $("#accountSelectorForm input:checkbox:not(#checkAll)").each(function() {+            if(!$(this).is(":checked")) return true;+            var account = $(this).prop('data-account');+            plotData.datasets.push(+                {+                    label: account,+                    data: arrayMult(accountBalances[account], accountScale(account)),+                    strokeColor: accountColor(account),+                    pointColor: accountColor(account),+                    pointStrokeColor: "#fff",+                    pointHighlightFill: "#fff",+                    pointHighlightStroke: accountColor(account),+                }+            );+        });+        if(plotData.datasets.length>0)+            myNewChart = new Chart(ctx).Line(plotData, {+                datasetFill: false+            });+    }+++});
+ static/charts.html view
@@ -0,0 +1,33 @@+<!DOCTYPE html>+<html lang="en">+<head>+    <meta charset="UTF-8">+    <title></title>+    <script src="http://code.jquery.com/jquery-1.11.1.min.js"></script>+    <script src="Chart.js"></script>+    <script src="../html/data.js"></script>+    <script src="charts-code.js"></script>+    <style>+#myChart {+}+#accountSelectorForm {+    width: 200px;+    vertical-align: top;+    display: inline-block;+}+#accountSelectorForm label {+    display: block;+    font-weight: bold;+}+    </style>+</head>+<body>+<canvas id="myChart" width="800" height="400"></canvas>+<form id="accountSelectorForm">+    <label>+        <input id="checkAll" type="checkbox" name="checkAll">+        All/None+    </label>+</form>+</body>+</html>
+ static/code.js view
@@ -0,0 +1,110 @@+/*+http://datatables.net/reference/option/+*/++function sep(e)+{+		$("table *:nth-child("+e+") ").css("border-right","1px solid black");+}+function hsep(e)+{+		// hsep(1);+		// for(var i in data.dates){+		// 		if(/12\/.*/.test(data.dates[i])){+		// 				hsep(parseInt(i)+2);+		// 		}+		// }+		$("table tr:nth-child("+e+") td,th").css("border-bottom","1px solid black");+}++$(document).ready(function() {+		keys();+		$('.regular').css("background","#eeeeee");+		$('body').css({"font-size":"12px",+									"margin":"0px"});+		$('#data').addClass("cell-border display compact");+		$('#data').css({"text-align" : "right"});+		var i = 1;+		sep(i);+		for (var key in data.entities){+				i += data.entities[key][1].length;+				sep(i);+		}+		$(".regular.endOfYear td,th").css("border-bottom","1px solid black");+    $('#data').dataTable({+		});+		+		if(false){ //alternative: generate dynamically using JSON data+				$('#example').addClass("cell-border display compact");+				+				var header = data.accounts.map(function(e){+						return {"title":e.fEntity +"\n"+ e.fAccount};+				});+				+				$('#example').dataTable( +						{"data": data.balances+						 ,"columns": header+						 , "paging": false+						 , "ordering": false,+						} );    +		}++} );+++state = { transaction : true,+					comment : true,+					duringYear : true,+					Innk: true,+				  Mo: true};++function keys()+{+		function s(a){+				if(state[a])+						$('.'+a).show();+		}+		function h(a){+				if(!state[a])+						$('.'+a).hide();+		}+		function apply(a){+				h(a);s(a);+		}+		function tog(a){+				state[a]=!state[a];+				s('duringYear');+				s('comment');+				s('transaction');+				apply('Mo');+				apply('Innk');+				h('transaction');+				h('comment');+				h('duringYear');+		}++$(document).keypress(function(e) {+		// console.log(e);+		// console.log(String.fromCharCode(e.charCode));+		// console.log(e.key);+		switch(String.fromCharCode(e.charCode)){+				case "t":+				tog("transaction");+				break;+				case "c":+				tog("comment");+				break;+				case "y":+				tog("duringYear");+				break;+				case "i":+				tog("Innk");+				break;+				case "m":+				tog("Mo");+				break;+		}+});+++}