lib.js 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458
  1. var units = {
  2. inv_time: [
  3. 'days',
  4. 'weeks',
  5. 'months',
  6. 'quarters',
  7. 'years',
  8. ],
  9. roof_types: [
  10. 'Metal',
  11. 'Asphalt shingle',
  12. 'Clay tile',
  13. ],
  14. };
  15. $(function() {
  16. $('.tab-group').each(function() {
  17. var grp = $(this);
  18. var bar = $('<div class="tab-bar"></div>');
  19. grp.find('> .tab').each(function() {
  20. var contents = $(this);
  21. var tab = $('<div class="tab-bar-tab">'+$(this).attr('title')+'</div>');
  22. tab.click(function() {
  23. bar.find('.tab-bar-tab').removeClass('active');
  24. tab.addClass('active');
  25. grp.find('> .tab').removeClass('active').hide();
  26. contents.addClass('active').show();
  27. });
  28. bar.append(tab);
  29. });
  30. bar.find('.tab-bar-tab:first').addClass('active');
  31. // handle next/prev links
  32. grp.find('.next-tab').click(function(e) {
  33. e.defaultPrevented = true;
  34. var ct = grp.find('.tab.active');
  35. var nt = ct.next('.tab');
  36. ct.removeClass('active').hide();
  37. nt.addClass('active').show();
  38. ct = bar.find('.tab-bar-tab.active');
  39. nt = ct.next('.tab-bar-tab');
  40. ct.removeClass('active');
  41. nt.addClass('active');
  42. });
  43. grp.find('.prev-tab').click(function(e) {
  44. e.defaultPrevented = true;
  45. var ct = grp.find('.tab.active');
  46. var nt = ct.prev('.tab');
  47. ct.removeClass('active').hide();
  48. nt.addClass('active').show();
  49. ct = bar.find('.tab-bar-tab.active');
  50. nt = ct.prev('.tab-bar-tab');
  51. ct.removeClass('active');
  52. nt.addClass('active');
  53. });
  54. grp.prepend(bar);
  55. });
  56. // magical controls
  57. $('scalar').each(function() {
  58. var elem = $(this);
  59. var name = elem.attr('name');
  60. var internal_type = elem.attr('type');
  61. var def = elem.attr('default');
  62. var control = $('<div class="control"></div>');
  63. control.append('<label>' + elem.html() + '</label>');
  64. control.append('<input type="edit" cname="'+name+'" internal="'+internal_type+'" value="'+def+'" />');
  65. elem.replaceWith(control);
  66. });
  67. // magical unit controls
  68. $('unit').each(function() {
  69. var elem = $(this);
  70. var name = elem.attr('name');
  71. var internal_type = elem.attr('type');
  72. var def = elem.attr('default');
  73. var control = $('<div class="control"></div>');
  74. control.append('<label>' + elem.html() + '</label>');
  75. control.append('<input type="edit" cname="'+name+'" internal="'+internal_type+'" value="'+def+'" />');
  76. var sel = $('<select cname="'+name+'__type">');
  77. sel.append(units[elem.attr('type')].map(function(x) {
  78. return $('<option value="'+x+'">'+x+'</option>');
  79. }));
  80. control.append(sel);
  81. elem.replaceWith(control);
  82. });
  83. // magical selectbox controls
  84. $('choose').each(function() {
  85. var elem = $(this);
  86. var name = elem.attr('name');
  87. var internal_type = elem.attr('type');
  88. var control = $('<div class="control"></div>');
  89. control.append('<label>' + elem.html() + '</label>');
  90. var sel = $('<select cname="'+name+'" internal="'+internal_type+'">');
  91. sel.append(units[elem.attr('type')].map(function(x) {
  92. return $('<option value="'+x+'">'+x+'</option>');
  93. }));
  94. control.append(sel);
  95. elem.replaceWith(control);
  96. });
  97. var radio_counter = 0;
  98. $('radio').each(function() {
  99. radio_counter++;
  100. var elem = $(this);
  101. var name = elem.attr('name');
  102. var control = $('<fieldset class="control radio"></fieldset>');
  103. control.append($('<legend>'+elem.attr('title')+'</legend>'));
  104. var hi = $('<input type="hidden" internal="string" cname="'+elem.attr('name')+'" />');
  105. control.append(hi);
  106. elem.find('> opt').each(function() {
  107. var title = $(this).attr('title');
  108. var opt_container = $('<fieldset class="radio-option unselected"></fieldset>');
  109. var value = $(this).attr('value');
  110. var legend = $('<legend>'+title+'</legend>');
  111. var def = "";
  112. if($(this).attr('default')) {
  113. def = 'checked="checked"';
  114. hi.attr('value', value);
  115. }
  116. var radio_box = $('<input type="radio" name="radio_'+radio_counter+'" '+def+' />');
  117. function clickfn(e) {
  118. control.find('> fieldset.radio-option').addClass('unselected');
  119. opt_container.removeClass('unselected');
  120. hi.attr('value', value);
  121. radio_box.prop('checked', true);
  122. }
  123. radio_box.click(clickfn);
  124. legend.click(clickfn);
  125. opt_container.append(legend.prepend(radio_box));
  126. opt_container.append($(this).html());
  127. control.append(opt_container);
  128. });
  129. elem.replaceWith(control);
  130. });
  131. });
  132. function grab_value(name) {
  133. var n = $('[cname="'+name+'"]');
  134. var n_t = $('[cname="'+name+'__type"]');
  135. if(!n) return NaN;
  136. var val = n.val();
  137. if(n_t.length) {
  138. var t = n_t.val();
  139. return {
  140. scalar: val,
  141. unit: t,
  142. };
  143. }
  144. return val;
  145. };
  146. function mkChart(sel, title, data) {
  147. console.log(data);
  148. var legend = [];
  149. var d2 = data.map(function(ds) {
  150. legend.push(ds[0]);
  151. return ds[1].map(function(x, k) {
  152. return {
  153. period: k,
  154. value: x,
  155. }
  156. });
  157. });
  158. console.log(d2);
  159. // d2 = MG.convert.date(d2, 'date');
  160. MG.data_graphic({
  161. title: title,
  162. data: d2,
  163. width: 1100,
  164. height: 500,
  165. area: false,
  166. right: 100,
  167. left: 70,
  168. target: sel,
  169. x_accessor: 'period',
  170. y_accessor: 'value',
  171. yax_units: '$',
  172. xax_count: 12,
  173. decimals: 0,
  174. legend: legend,
  175. });
  176. // var ctx = $(sel)[0].getContext("2d");
  177. // var chart = new Chart(ctx).Line(chartData, opts);
  178. }
  179. function grabData(parent) {
  180. var data = {};
  181. function asFloat(x) {
  182. var y = parseFloat(x);
  183. return isFinite(y) ? y : 0;
  184. }
  185. var formatters = {
  186. string: function(x) { return x },
  187. pct: function(x) { return asFloat(x) * .01 },
  188. amort: function(x) { return asFloat(x) / 12 },
  189. num: asFloat,
  190. time: asFloat,
  191. dollars: asFloat,
  192. percent: function(x) { return asFloat(x) * .01 },
  193. };
  194. // parent.find('[data-json]').each(function() {
  195. parent.find('[cname]').each(function() {
  196. var n = $(this);
  197. var name = n.attr('cname');
  198. var val = n.val();
  199. var fmt = n.attr('fmt') || n.attr('internal') || 'num';
  200. console.log(name, val);
  201. data[name] = formatters[fmt](val);
  202. });
  203. return data;
  204. };
  205. function accumulate(arr, init, begin) {
  206. var acc = init || 0;
  207. var o = [];
  208. for(var i = 0; i < arr.length; i++) {
  209. if(begin) o.push(acc);
  210. acc += arr[i];
  211. if(!begin) o.push(acc);
  212. }
  213. return o;
  214. }
  215. function sum(a, b) {
  216. var o = [];
  217. for(var i = 0; i < a.length; i++) {
  218. o.push(a[i] + b[i]);
  219. }
  220. return o;
  221. }
  222. function mul(a, b) {
  223. var o = [];
  224. for(var i = 0; i < a.length; i++) {
  225. o.push(a[i] * b[i]);
  226. }
  227. return o;
  228. }
  229. function inchworm(arr, first, fn) {
  230. var o = [];
  231. var last = first;
  232. for(var i = 0; i < arr.length; i++) {
  233. o[i] = fn(last, arr[i]);
  234. last = arr[i];
  235. }
  236. return o;
  237. }
  238. function constantValue(val) {
  239. return function() { return val; }
  240. }
  241. /*
  242. A = periodic payment amount
  243. P = net principle
  244. i = periodic interest rate (mon_interest above)
  245. n = total number of payments (first payment is 30 days after the loan originates)
  246. p(t) = principle remaining at time t
  247. A = P(((i(1_i)^n) / ((1+i)^n - 1))
  248. = (Pi) / (1- (1+i)^-n)
  249. = P(i + ( i / ((1+i)^n - 1) )
  250. p(t) / P = 1 - ( ((1+it^t - 1)) / ((1+i)^n - 1) )
  251. calculation of mortgage stuff:
  252. 1) calculate the monthly payment amount using loan amount and interest
  253. 2) for each pay period,
  254. calc interest i_p = (remaining balance * monthly interest)
  255. calc payment principle = payment - i_p
  256. calc new blance = first balance - payment principle
  257. */
  258. function monthlyPayment(loan_amount, monthly_interest, num_payments) {
  259. return (loan_amount * monthly_interest) / (1 - Math.pow(1 + monthly_interest, -num_payments) );
  260. }
  261. function piRatios(balance, monthly_interest, payment_amount) {
  262. var i_p = balance * monthly_interest;
  263. return {
  264. principle: payment_amount - i_p,
  265. interest: i_p,
  266. new_balance: balance - payment_amount + i_p,
  267. };
  268. }
  269. function annualToMonthlyInterest(annual) {
  270. return Math.pow(1 + annual, 1/12) - 1;
  271. }
  272. // crap
  273. function periodSuccessor(o) {
  274. var n = _.extend({}, o);
  275. var ltv = parseFloat((o.loan_balance / o.purchase_price).toFixed(2));
  276. var pmi = 0;
  277. if(ltv > .80) {
  278. pmi = o.pmi;
  279. }
  280. var app_amt = o.house_value * o.app_rate / 12;
  281. n.house_value += app_amt;
  282. var p_i = piRatios(o.loan_balance, o.monthly_interest, o.payment_amount);
  283. // _total_principle += p_i.principle;
  284. // _total_interest += p_i.interest;
  285. // _total_insurance += insurance;
  286. n.equity += p_i.principle + app_amt;
  287. var _monthly_cost = p_i.interest + o.insurance + pmi + o.overhead
  288. n.acc_cost += _monthly_cost;
  289. n.period = o.period + 1;
  290. n.ltv = ltv;
  291. n.pmi = pmi,
  292. n.interest = p_i.interest;
  293. n.principle = p_i.principle;
  294. n.loan_balance = p_i.new_balance;
  295. n.appreciation = app_amt;
  296. n.equity = n.acc_cost - o.downpayment
  297. n.loan_balance = p_i.new_balance;
  298. return n;
  299. }
  300. // probably has floating point precision problems
  301. function amortTable(loanAmount, apr, months) {
  302. var o = {
  303. principle: [],
  304. interest: [],
  305. payment: [],
  306. period: [],
  307. loan_balance: [],
  308. ending_balance: [],
  309. };
  310. var payment = monthlyPayment(loanAmount, apr / 12, months);
  311. for(var i = 0; i <= months; i++) {
  312. var p_i = piRatios(loanAmount, apr / 12, payment);
  313. o.loan_balance[i] = loanAmount;
  314. loanAmount = p_i.new_balance;
  315. o.payment[i] = payment;
  316. o.principle[i] = p_i.principle;
  317. o.interest[i] = p_i.interest;
  318. o.ending_balance[i] = p_i.new_balance;
  319. o.period[i] = i;
  320. }
  321. return o;
  322. }