cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 
Choose Language Hide Translation Bar
Custom Table Calculator

Problem

You need to build a custom calculator that displays a table of numbers based on several input values.

Solution

Use Number Edit Boxes to get the values and use the Set Function method to attach JSL to the Number Edit Boxes to recalculate the table when the values are changed. Use a Table Box to hold columns of numbers. Rebuild the columns of numbers when the values change.

Payment table built in a Table BoxPayment table built in a Table Box
if( jmpversion()<"13", Open Log(1); throw("requires JMP 13 or greater"));

// need default values to set up the edit boxes
borrow = 1e5;
years = 2;
apr = 15;

cur = "USD"; // "EUR"

New Window( "table demo",
	Border Box( Left( 5 ), Right( 5 ), bottom( 5 ), // this box extends the pale green a few pixels
		V List Box( // border boxes need a single child, like a vlist, to hold everything
			Border Box( Left( 20 ), Right( 20 ), top( 10 ), bottom( 10 ), // the data entry border
				H List Box( // separate the data entry fields from "Interesting" title
					V List Box( // the three vertical entry fields and their labels
						H List Box( // a field and its label
							Number Edit Box( // a field
								borrow,
								<<setformat( 13, "Currency", cur ), // JMP 14 sets the formatM
								<<Set Function( // a callback function for responding when the
									Function( {this}, // entry fields change
										borrow = this << get; // get the current value
										freshen(); // run the common code
									)
								)
							),
							Text Box( " amount" ) // label the entry field, space it over a bit
						),
						H List Box( // ditto
							Number Edit Box(
								years,
								<<setformat( 13, "Best" ),
								<<Set Function(
									Function( {this},
										years = this << get;
										freshen();
									)
								)
							),
							Text Box( " years" )
						),
						H List Box( // ditto
							Number Edit Box(
								apr,
								<<setformat( 13, "Best" ),
								<<Set Function(
									Function( {this},
										apr = this << get;
										freshen();
									)
								)
							),
							Text Box( " apr" )
						)
					),
					Border Box( Left( 100 ), Text Box( "Interesting", <<setfontsize( 40 ) ) ) // label
				)
			),
			Spacer Box( size( 10, 10 ), color( "dark green" ), <<setAutoStretching( 1, 0 ), <<setMaxSize( 1e5, 10 ) ),
			tb = Table Box( // here's the table box. You could put columns here, but
				// the freshen function will do that in a second, using "tb".
				<<setShadeAlternateRows( 1 ), // set the table properties to make a green-bar report
				<<setShadeHeadings( 0 ),
				<<setHeadingColumnBorders( 0 ),
				<<setUnderlineHeadings( 1 ),
				<<setColumnBorders( 0 ),
				<<setRowBorders( 0 ),
				<<setShadeCells( 0 )
			)
		),
		<<backgroundcolor( RGB Color( 245, 255, 245 ) ) // pale green
	)
);


freshen = Function( {},
	{// local variables
	rate = (apr / 100) / 12, day = Min( 28, Day( Today() ) ), month = Month( Today() ), year = Year( Today() ), dates = J( 1 + years * 12, 1, 0 ), i,
	payment = Payment( rate, 12 * years, borrow ), interest = -Interest Payment( rate, 0 :: 12 * years, 12 * years, borrow ), principal =
	-Principal Payment( rate, 0 :: 12 * years, 12 * years, borrow ), payment2 = interest + principal, balance =
	Present Value( rate, 12 * years :: 0, payment, 0 ), CumulativeInterest = Cumulative Sum( interest )}, 

	
	For( i = 1, i <= N Rows( dates ), i++,
		If( month > 12,
			month -= 12;
			year += 1;
		);
		dates[i] = Date DMY( day, month, year );
		month += 1;
	);
	
	While( !Is Empty( tb << child ), (tb << child) << delete ); // remove previous data, if any
	
	// add the new columns to the table
	tb << append( Number Col Box( "Due Date", dates, <<setformat( 11, "yyyy-mm-dd" ) ) );
	tb << append( Number Col Box( "Payment to Principal", principal, <<setformat( 13, "Currency", cur ) ) );
	tb << append( Number Col Box( "Payment to Interest", interest, <<setformat( 13, "Currency", cur ) ) );
	tb << append( Number Col Box( "Cumulative Interest", CumulativeInterest, <<setformat( 13, "Currency", cur ) ) );
	tb << append( Number Col Box( "Monthly Payment", payment2, <<setformat( 13, "Currency", cur ) ) );
	tb << append( Number Col Box( "Outstanding Balance", balance, <<setformat( 13, "Currency", cur ) ) );
	0;// return nothing
);

freshen();

Discussion

I'm not a finance guy. Don't assume I'm using the formulas correctly. I chose shades of green and specified some table box parameters to make the table attractive. I'm not an artist, you can do better. The green spacer box makes a nice bar of color and fills the page without making the page wider by using the AutoStretching and MaxSize options. The freshen() function is called from all three data entry fields and doesn't care which field changed. You could do something more complicated, but unless you have really slow calculations, keep it simple. You can right-click the table and make into data table to further process the data; you might want a way to save the parameters with the data table (another button, perhaps, to make the data table). You can also journal the report which will include the parameters.


See Also

Help->Books->Scripting Guide has more information about Table Boxes

Comments
Ressel

Started scripting early 2021 and this post was really useful. I copied most of the script above and modified it for my own calculator, the output of which I am planning to use in another platform.

 

However, I am not fully satisfied with what I have achieved. When I open and run my script the first time, I get two pre-filled number boxes where I can enter values.

 

Ressel_0-1652810931455.png

 

It is only when I change one of the values in the pre-filled number boxes, that the third box with the result I am actually interested in using further appears.

 

Ressel_2-1652811135800.png

 

I think I ran into this, because what I created is based on the code provided by @Craige_Hales above.

 

  • How can I make it, that the third box (circled in red above) appears from the beginning, but is also updated based on the input values in the other two boxes?
  • Is there a more efficient solution to building a small calculator or am using an unnecessarily complicated approach?

 

At this point I also wanted to mention that, yes, I have been studying the Scripting Guide as well as the Scripting Index, but I am not yet a strong enough scripter to come up with my own solution. (I did not browse through "Jump into JMP Scripting" though, as I currently do not have access to my copy.)

 

Names default to here ( 1 );

// some default values for the first two number edit boxes
CurrentConc_wt% = 45.00;
LowestAcceptConc_wt% = 40.00;


New Window( "Calculating Concentration acceptance limit",
	Border Box(Left( 5 ), Right( 5 ), bottom( 20 ),
		V List Box(
			Text Box("Some comment")
		)
	),
	
	Border Box(Left( 5 ), Right( 5 ), bottom( 20 ),
		V List Box(
			Text Box("Instructions: [add instructions here later]")
		)
	),
	
	Border Box(Left( 5 ), Right( 5 ), bottom( 5 ), // added this because I copied this code and left in there, just in case...
		V List Box( // border boxes need a single child, like a vlist, to hold everything
			Border Box(Left( 5 ), Right( 5 ), bottom( 5 ), // border for data entry boxes
				H List Box(
					V List Box(
						Border Box(Left( 5 ), Right( 5 ), bottom( 5 ),
							H List Box(
								Number Edit Box( 
									CurrentConc_wt%,
									<<setformat ( "Fixed Dec", 12, 2 ),
									<<Set Function(
										Function( {this},
											CurrentConc_wt% = this << get;
											freshen();
										)
									)
								),
								Text Box(" Concentration, current [wt%]") // label for entry field, with a little space between them
							)
						),
					),
					V List Box(
						Border Box(Left( 5 ), Right( 5 ), bottom( 5 ),
							H List Box(
								Number Edit Box( 
									LowestAcceptConc_wt%,
									<<setformat ( "Fixed Dec", 12, 2 ),
									<<Set Function(
										Function( {this},
											LowestAcceptConc_wt% = this << get;
											freshen();
										)
									)
								),
								Text Box(" Concentration, lowest acceptable [wt%]") // label for entry field, ...
							)
						),
						bb = Border Box(Left( 5 ), Right( 5 ), bottom( 5 ),
						),
					),
				)
			)
		)
	)
);

freshen = Function ( {}, 
	{LowestAcceptConc_%rel = LowestAcceptConc_wt%/CurrentConc_wt% * 100},

	While( !Is Empty( bb << child ), (bb << child) << delete );

	bb << append (H List Box(
								Number Edit Box( 
									LowestAcceptConc_%rel,
									<<setformat ( "Fixed Dec", 12, 2 ),
								),
								Text Box(" Acceptance limit [% rel.]") // label ...
							)
			);
);

 

It will be a little while before I can look at this in more depth. But I think you may just be missing the call at the very end of the original JSL, freshen() , which was there to initialize all of the boxes as if something had been clicked already.

Ressel

Thanks, that was an easy fix. Perhaps there's reason to be satisfied after all.

JSL Cookbook

If you’re looking for a code snippet or design pattern that performs a common task for your JSL project, the JSL Cookbook is for you.

This knowledge base contains building blocks of JSL code that you can use to reduce the amount of coding you have to do yourself.

It's also a great place to learn from the experts how to use JSL in new ways, with best practices.