//Monitor------------------------------------------------------------------------------------------------------------ //Laptop----------------------------------------------------------------------------------- //Presentation----------------------------------------------------------- //Borders//////////////////////////////////////////////////////////////// //Stefan Nikles////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////// /*Creates an XY plot (aka map) from the current data table showing the regions where a specific threshold condition is met. Here, the absolute value of a test column must be less than a specified value for the condition to be true. It is similar to a XY contour plot with the test column as the Z variable, except here only one specific contour value (aka border) is plotted. However, several maps may be overlaid on a single plot for all values of a 4th variable. Furthermore, multiple plots may be created for all combinations of additional variables (bycol.list). Yet more plots may be generated for multiple test columns. If there are multiple test column values for the same point in XY space, the average is assumed. Use this script to visualize how a specified variable value changes over 3 (or more) other variables. For example, you wish to visualize all the values of F0 & Q were the R2 > 0.99, for a variety of noise levels. Does not work on versions of JMP prior to JMP 12. Inputs: xcol/ycol (string): Column names for X & Y axes of plot. xscale/yscale ("lin"/"log"): type of axis for X & Y. tcol.arr (array): array of lists, with each list containing the test column name (string) and the test limit (number). Values in the column will be evaluated against the test limit to determine the border location. Plots are generated for each test column. ovcol (string): Column name. Separate maps will be created and overlaid for each value in ovcol. bycol.list (list): list of column names (strings). Separate plots will be generated for each combination of the values in these columns. Outputs: Pretty graph(s). Requirements for the current data table: 1. The columns xcol, ycol, and tcol must be numeric. 2. The ovcol column or columns specified in bycol.list may be either numeric or character. 3. Missing values of tcol will not get plotted. 4. Points are plotted for every possible pair of the values in xcol and ycol. If a specific pair doesn't exist in the actual data table, the value of tcol is assumed to be missing/outside of the test threshold. 5. If an xcol/ycol pair is present more than once, the average of the tcol values is used. 6. Excluded rows are ignored. */ ///////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////// Names Default to Here(1); Write("\!N"); If(N Table() == 0, New Window("Borders - Alert", << Modal, Textbox("No tables are currently open. Script terminated.") ); Write("No tables are currently open. Script terminated.\!N"); Stop(); ); //Default Inputs--------------------------------------------------------- GUI.flag = 1; //1: use GUI. OW, use default vars below. xcol_def = "f0_nom"; //coords for X axis ycol_def = "Q_nom"; //coords for Y axis //test column and limit for which the border is determined tcol.arr_def = { {"δ(F0)", 0.01}, {"testa", 0.5}, {"testb", 0.5} }; //mult. maps overlaid for each val in ovcol. ovcol_def = "Noise_Fract"; //mult. plots will be created for each combination of values in //bycol.list. bycol.list_def = { "fp_nom", "Bogus" }; //test condition: //1: tcol <= tlim //2: tcol >= tlim //3: |tcol| <= tlim //4: |tcol| >= tlim tcon = 3; //Graph Settings--------------------------------------------------------- xframe = 300; //Frame size (pixels) yframe = 300; pad = "Y"; //Adds padding to scale auto = "Y"; //Y: autogen axes values; N: manual override (below) xmin = .; //Set equal to . for auto for just this one xmax = .; //Set equal to . for auto for just this one xinc = .; //Set equal to . for auto for just this one ymin = .; //Set equal to . for auto for just this one ymax = .; //Set equal to . for auto for just this one yinc = .; //Set equal to . for auto for just this one //Border option: 1=no borders, 2=show borders, 3=show borders w/ arrows border.opt = 2; fill.opt = 1; //fill option: 0=no polygons, 1=show polygons pts.opt = 0; //points option: 0=no points, 1=show points xsc.opt = 1; //X Scale type (1: Lin, 2: Log) ysc.opt = 1; //Y Scale type (1: Lin, 2: Log) xmagd = 1; //X major grid (0: off, 1: on) xmigd = 0; //X minor grid (0: off, 1: on) ymagd = 1; //Y major grid (0: off, 1: on) ymigd = 0; //Y minor grid (0: off, 1: on) //Colors: Add more colors for each map plotted: //linecolor.list = {"light blue", "light green", "light blue", "light orange"}; linecolor.list = {"red", "green", "blue", "orange", "BlueGreen", "Purple", "Yellow","Cyan", "Magenta", "YellowGreen", "BlueCyan", "Fuchsia"}; //Function Definitions/////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////// //CompIntPoints---------------------------------------------------------- /*Returns a list containing the x,y coordinates for an interpolated point between the current point in value.arr and the closest point in the specified direction. Also returns the x,y coordinates for the origin of an arrow pointing towards the interpolated point. If the current location is at the edge of the data set, the interpolated point is just set to equal the current point and the arrow origin is set to be 1/5 the distance between that point and the next closest point in the opposite direction. If the closest point in the specified direction is missing (but whose coordinates are still within the data set), a point halfway between the 2 points is returned as the interpolated point. -Inputs: i,j: indices for the xcol and ycol values, respectively k: direction of border for element ij (1: East, 2: North, etc.) value.arr: Contains info for an entire map. See description later in script for more detail. tlim: value used for interpolation -Outputs: A list containing the following: {interpolated x coord, arrow origin x coord, interpolated y coord, arrow origin y coord} */ CompIntPoints = Function({i, j, k, value.arr, tlim}, {Default Local}, dir.vec = [1 0 -1 0]; di = dir.vec[k]; dj = dir.vec[Mod((k+2),4)+1]; curval = value.arr[i][j][5]; //current value //Loop for x & y axes output.list = {}; For(axis = 6, axis <=7, axis++, //6: xcoord; 7: ycoord curloc = value.arr[i][j][axis]; //curr coordinate //Get distance to adjacent outer element Try( //"Try" bc not sure if axis reaches out that far posloc = value.arr[i+di][j+dj][axis]; pdist = posloc - curloc, pdist = 0; ); //Get distances to adjacent inner element Try( //"Try" bc not sure if axis reaches out that far negloc = value.arr[i-di][j-dj][axis]; ndist = curloc - negloc, ndist = 0; ); //Compute coordinates Try( //"Try" bc not sure if axis reaches out that far. //Also, have to eval pdist & ndist first. posval = value.arr[i+di][j+dj][5]; posloc = value.arr[i+di][j+dj][axis]; If(!IsMissing(posval), intploc = Interpolate(tlim, curval, curloc, posval, posloc); ,//Else if posval is missing intploc = curloc + pdist/2; ); arloc = (curloc + intploc)/2; ,//Else it's a border intploc = curloc; arloc = curloc - ndist/5; ); //Output InsertInto(output.list, EvalList({intploc, arloc})); ); //Return results output.list ); /*CountDB---------------------------------------------------------------- Counts the number of child displayboxes in the parent displaybox -Inputs: DB, the parent display box -Outputs: the number of child displayboxes in DB -External Variables required: None -User Functions called: None */ CountDB = function({DB}, {Default Local}, i=0; Lastflag = "false"; While(Lastflag == "false", i++; Try( DB_child = DB[i] << Class Name, Lastflag = "true" ); ); i - 1; ); //GetColUnits------------------------------------------------------------ /*Returns a string containing a column value with units. If units property doesn't exist for the column or = "-", no units are returned. If column is formatted as a percentage, "%" is returned for the units. -Inputs: colname: column name. val: value to be formatted -Outputs: val.str: concatenated value with units */ GetColUnits = Function({colname, val}, {Default Local}, //Get current table dt = Current Data Table(); col.unit = Column(dt,colname) << Get Property(Units); If(IsMissing(col.unit) | col.unit == "-", col.unit = "" ); col.lfmt = Char(Column(dt,colname) << Get Format); fmt.pat = "Format(\!"" + PatArb() >? col.fmt + "\!"" + PatArb(); PatMatch(col.lfmt, fmt.pat, ""); If(col.fmt == "Percent", col.unit = "%"; val = 100*val ); val.str = Char(val)||col.unit; ); //GetColVals------------------------------------------------------------- /*Returns a sorted column vector or list of all values from a column in the current data table, with duplicates and missings removed. If column is numeric, a vector is returned. If non-numeric, a list is returned. -Inputs: colname: name of column to be gotten. -Outputs: col.vec: column vector of each column value, sorted in asc. order. col.lis: list of each column value, sorted in asc. order. */ GetColVals = Function({colname}, {Default Local}, dt = Current Data Table(); dtsum = dt << Summary( //Removes duplicates and sorts Group(colname), Link to original data table( 0 ) ); output = Column(dtsum, 1) << Get Values; Close(dtsum, No Save); output ); //GetNext---------------------------------------------------------------- /*Gets the next border element from value.arr by "following" the current border in the CCW direction. Returns indices of next border element. -Inputs: value.arr: array containing information about each element. k: current index for column (x value) l: current index for row (y value) m: current orientation of border to be checked (1:Facing East, 2: Facing North, Etc.) -Outputs: k: column index of next adjacent border element in the given direction (x) l: row index of next adjacent border element in the given direction (y) m: orientation of next adjacent border element (1:Facing East, 2: Facing North, Etc.) */ GetNext = Function({value.arr, k, l, m}, {Default Local}, //list of increments to travel in X direction dk.list = {1,1,0,-1,-1,-1,0,1}; //list of increments to travel in Y direction dl.list = {0,1,1,1,0,-1,-1,-1}; mp = Mod(m,4)+1; //CCW value of m mn = Mod(m+2,4)+1; //CW value of m //Determine next location //incr. to travel in X (if going to diag element) dk = dk.list[2*m]; //incr. to travel in Y (if going to diag element) dl = dl.list[2*m]; //incr. to travel in X (if going to adj element) dk2 = dk.list[2*mp-1]; //incr. to travel in Y (if going to adj element) dl2 = dl.list[2*mp-1]; If( //Diag CCW item has adj border Try(value.arr[k+dk][l+dl][mn][1]==1,0), k = k + dk; l = l + dl; m = mn, //Dir. CCW item has adj Border Try(value.arr[k+dk2][l+dl2][m][1] == 1,0), k = k + dk2; l = l + dl2, //Curr item has CCW Border value.arr[k][l][mp][1] == 1, m = mp; ); //Return output output.list = EvalList({k, l, m}); ); //Input Add Row---------------------------------------------------------- /*Adds row to the specified lineupbox containing the next test column, test limit, and remove-checkbox. The lineup box is assumed to have 3 columns, and a single header row. -Inputs: LUB (displaybox): LineUpBox to be appended to. colname (string): name of column to be added to leftmost column in lineup box colval (number): Optional. If present, will be added to the middle column of the lineup box. -Outputs: The LineUpBox LUB will have a row added to it for each item in col.list. Each row will consist of the column name, a number edit box, and a checkbox. The checkbox has the function of removing it's row when clicked. -Functions Called: CountDB */ InputAddRow = Function({LUB, colname, colval=.}, {Default Local}, firsttest = LUB[4] << Get Text; If(firsttest == "required numeric", LUB[6] << Delete; //BALEETED! LUB[5] << Delete; LUB[4] << Delete; ); LUB << Append(TextBox(colname)); //Test name LUB << Append(NumberEditBox(colval)); //Test limit LUB << Append( //Remove checkbox CheckBox("", << Set Function(Function({This}, LUB = This << Parent; ntcol = CountDB(LUB)/3 - 1; For(i=ntcol, i>=1, i--, //query LUB for which box is checked chbx = LUB[i*3+3] << Get; If(chbx == 1, //Don't delete first row If(ntcol > 1, LUB[i*3+3] << Delete; //BALDETED! LUB[i*3+2] << Delete; LUB[i*3+1] << Delete; ,//Else, replace first row with placeholder LUB[i*3+3] << Set(1,0); LUB[i*3+2] << Set(.); LUB[i*3+2] << Enable(0); LUB[i*3+1] << Set Text("required numeric"); LUB[i*3+1] << Set Font Style("Italic"); ) ) ) ))) ) ); //Input Check------------------------------------------------------------ /*Checks if all inputs are correct in order for script to proceed. If so, leaves the ready.flag set to 1. Otherwise ready.flag is set to zero. Steps: 1. Checks if all required columns are present. 2. Checks if xcol, ycol, and tcols are numeric. 3. Checks if tcol limits are present and numeric. 4. Checks if any columns were used more than once. Variables Read: xcol, ycol, tcol.arr, ovcol, bycol.list, GUI.flag, Variables Set/Created: ready.flag, allcol.list, numcol.list, checkid, error.str, nonnumcol.list, colname, col.type, missnum.list, dup.list */ inputcheck.expr = Expr( ready.flag = 1; //Check if all required columns have been assigned (and collect //list of column names being used) If(ready.flag == 1, allcol.list = {}; numcol.list = {}; testcol.list = {}; If(xcol == "", ready.flag = 0, InsertInto(allcol.list, xcol); InsertInto(numcol.list, xcol); ); If(ycol == "", ready.flag = 0, InsertInto(allcol.list, ycol); InsertInto(numcol.list, ycol) ); If(tcol.arr == {}, ready.flag = 0 ,//else tcol.arr isn't empty For(checkid = 1, checkid <= NItems(tcol.arr), checkid++, If( tcol.arr[checkid][1] == "" | tcol.arr[checkid][1] == "required numeric", ready.flag = 0 ,//else tcol.arr contains actual items If(!Contains(testcol.list, tcol.arr[checkid][1]), InsertInto(testcol.list, tcol.arr[checkid][1]) ); InsertInto(numcol.list, tcol.arr[checkid][1]); ); ); ); If(ovcol != "", InsertInto(allcol.list, ovcol)); If(bycol.list != {}, InsertInto(allcol.list, bycol.list)); If(ready.flag == 0, error.str = "All required columns must be specified: X Axis, Y Axis, Test Column(s)."; ReportError(error.str, GUI.flag) ) ); //Check if xcol, ycol, and tcols are numeric format. Already //taken care of by GUI, but not if GUI is disabled. If(ready.flag == 1, nonnumcol.list = {}; For(checkid = 1, checkid <= NItems(numcol.list), checkid++, colname = numcol.list[checkid]; col.type = Column(dt, colname) << Get Data Type; If(col.type != "Numeric", InsertInto(nonnumcol.list, colname) ); ); If(nonnumcol.list != {}, ready.flag = 0; error.str = "The x column, y column, and test columns must all be numeric: "; ReportError(error.str, GUI.flag, nonnumcol.list) ) ); //Check if tcol limits are present and numeric If(ready.flag == 1, missnum.list = {}; For(checkid = 1, checkid <= NItems(tcol.arr), checkid++, If( IsMissing(tcol.arr[checkid][2]) | !IsNumber(tcol.arr[checkid][2]), InsertInto(missnum.list, tcol.arr[checkid][1]) ); ); If(missnum.list != {}, ready.flag = 0; error.str = "Each test column must have a numeric test limit assigned: "; ReportError(error.str, GUI.flag, missnum.list) ) ); //Check if any columns are duplicated If(ready.flag == 1, allcol.list = Sort List(allcol.list || testcol.list); dup.list = {}; For(checkid = 2, checkid <= NItems(allcol.list), checkid++, If(allcol.list[checkid] == allcol.list[checkid - 1], If(!Contains(dup.list, allcol.list[checkid]), InsertInto(dup.list, allcol.list[checkid]) ) ) ); If(dup.list != {}, ready.flag = 0; error.str = "Each column may be used only once: "; ReportError(error.str, GUI.flag, dup.list) ) ) ); //Input Read------------------------------------------------------------- /*Reads all inputs from the GUI -Variables read: CLB_xcol, CLB_ycol, LUB_tcol, CLB_ovcol, CLB_bycol, RB_testcon, RB_xscale, RB_yscale, CB_diag -Variables set/created: xcol, ycol, tcol.arr, ovcol, bycol.list, tcon, xsc.opt, ysc.opt, diag.flag, ntcol, i, tname, tval */ inputread.expr = Expr( Try(xcol = (CLB_xcol << Get Items)[1], xcol = ""); Try(ycol = (CLB_ycol << Get Items)[1], ycol = ""); tcol.arr = {}; ntcol = CountDB(LUB_tcol)/3 - 1; //don't count header row For(i=1, i<=ntcol, i++, tname = LUB_tcol[i*3+1] << Get Text; tval = LUB_tcol[i*3+2] << Get; InsertInto(tcol.arr, EvalList({EvalList({tname, tval})})); ); Try(ovcol = (CLB_ovcol << Get Items)[1], ovcol = ""); bycol.list = CLB_bycol << Get Items; tcon = RB_testcon << Get; border.opt = RB_brd << Get; pts.opt = CB_disp << Get(1); fill.opt = CB_disp << Get(2); xsc.opt = RB_xscale << Get; xmagd = CB_xgrid << Get(1); xmigd = CB_xgrid << Get(2); ysc.opt = RB_yscale << Get; ymagd = CB_ygrid << Get(1); ymigd = CB_ygrid << Get(2); diag.flag = CB_diag << Get(1); ); //Input Save------------------------------------------------------------- /*Inserts the current input selections in the GUI into an array and saves to a text file for recall later. The saved text file is called "BordersSelections.txt" and saved to the Temp directory. -Inputs: settingname: name current settings will be saved by. settings.arr: the array of settings to which the new settings will be saved. -Outputs: A list containing settingname.list and settings.arr: -settingname.list: a list of the setting names in settings.arr. -settings.arr: updated array containing the new settings with the rest, sorted alphatically, with "Last" settings in the first position. A text file called "BordersSelections.txt" is created or overwritten, containing all the settings. -User Scripts/Functions called: inputread.expr: reads the inputs from the rest of the GUI. inputsort: sorts the items in settings.arr, leaving the setting named "Last" first in the first position, and all subsequent settings alphabetical order. -Other Requirements: None */ inputsave = Function({settingname, settings.arr}, {Default Local}, //Create setting entry inputread.expr; savey.jstr = JSLQuote( xcol = "xcol.expr"; ycol = "ycol.expr"; tcol.arr = tcol.expr; ovcol = "ovcol.expr"; bycol.list = by_col.expr; tcon = tcon.expr; border.opt = border.expr; pts.opt = pts.expr; fill.opt = fill.expr; xsc.opt = xscale.expr; xmagd = xmagd.expr; xmigd = xmigd.expr; ysc.opt = yscale.expr; ymagd = ymagd.expr; ymigd = ymigd.expr; diag.flag = diag.expr; ); SubstituteInto(savey.jstr, Expr(xcol.expr), Char(xcol), Expr(ycol.expr), Char(ycol), Expr(tcol.expr), Char(tcol.arr), Expr(ovcol.expr), Char(ovcol), Expr(by_col.expr), Char(bycol.list), //careful: ycol.expr will substitute into bycol.expr Expr(tcon.expr), Char(tcon), Expr(border.expr), Char(border.opt), Expr(pts.expr), Char(pts.opt), Expr(fill.expr), Char(fill.opt), Expr(xscale.expr), Char(xsc.opt), Expr(xmagd.expr), Char(xmagd), Expr(xmigd.expr), Char(xmigd), Expr(yscale.expr), Char(ysc.opt), Expr(ymagd.expr), Char(ymagd), Expr(ymigd.expr), Char(ymigd), Expr(diag.expr), Char(diag.flag) ); newitem = EvalList({settingname, savey.jstr}); //Get current list of setting names and determine whether to add //or overwrite output = inputsort(settings.arr); settingname.list = output[1]; settingname.pos = Contains(settingname.list, settingname); If(settingname.pos == 0, //If setting doesn't already exist InsertInto(settings.arr, EvalList({newitem})), //Else, setting exists... settings.arr[settingname.pos-1] = newitem //Ignore "Select..." ); //Re-sort list, update, and save output = inputsort(settings.arr); settingname.list = output[1]; settings.arr = output[2]; settings.jstr = Char(settings.arr); Save Text File("$TEMP/BordersSelections.txt", settings.jstr, Replace ); EvalList({settingname.list, settings.arr}); ); //Input Set-------------------------------------------------------------- /*Sets GUI inputs. Used when reading in saved input sets. -Variables read: xcol, ycol, tcol.arr, InputAddRow, LUB_tcol, ovcol, bycol.list, tcon, xsc.opt, ysc.opt, diag.flag -Variables set: CLB_xcol, CLB_ycol, i, LUB_tcol, CLB_ovcol, CLB_bycol, RB_testcon, RB_xscale, RB_yscale, CB_diag */ inputset.expr = Expr( BB_res << Click; //Clear all existing inputs CLB_xcol << Set Items(xcol); CLB_ycol << Set Items(ycol); For(i=1, i<=NItems(tcol.arr), i++, InputAddRow(LUB_tcol, tcol.arr[i][1], tcol.arr[i][2]); ); CLB_ovcol << Set Items(ovcol); CLB_bycol << Set Items(bycol.list); RB_testcon << Set(tcon); RB_brd << Set(border.opt); CB_disp << Set(1, pts.opt); CB_disp << Set(2, fill.opt); RB_xscale << Set(xsc.opt); CB_xgrid << Set(1, xmagd); CB_xgrid << Set(2, xmigd); RB_yscale << Set(ysc.opt); CB_ygrid << Set(1, ymagd); CB_ygrid << Set(2, ymigd); CB_diag << Set(1, diag.flag); ); //Input Sort------------------------------------------------------------- /*Sorts the items in settings.arr alphetically by their name. After sorting, the item named "Last" is moved to the first position. Returns the sorted array, plus a list containing the sorted names. The list with the sorted names has a prompt "Select..." inserted as the first item. -Inputs: settings.arr: array containing the name and settings for each item. -Outputs: A list containing settingname.list and settings.arr: -settingname.list: a list of the setting names in settings.arr. -settings.arr: updated array containing the new settings with the rest, sorted alphatically, with "Last" settings in the first position. -User Scripts/Functions called: None -Other Requirements: None */ inputsort = Function({settings.arr}, {Default Local}, //Create and alphabetize a list of just the setting names settingname.list = {}; For(i=1, i<=NItems(settings.arr), i++, InsertInto(settingname.list, settings.arr[i][1]) ); sortedsettingname.list = SortList(settingname.list); //Move the "Last" setting name to the first position lastpos = Contains(sortedsettingname.list, "Last"); //There might not be a "last" setting if this is the first time //using, so: If(lastpos != 0, InsertInto(sortedsettingname.list, sortedsettingname.list[lastpos], 1 ); RemoveFrom(sortedsettingname.list, lastpos+1, 1) ); //Use the sorted setting names to sort the actual setting array. sortedsettings.arr = {}; For(i=1, i<=NItems(sortedsettingname.list), i++, currpos = Contains(settingname.list, sortedsettingname.list[i]); InsertInto(sortedsettings.arr, EvalList({settings.arr[currpos]}) ); ); //Clean up. settingname.list = sortedsettingname.list; settings.arr = sortedsettings.arr; InsertInto(settingname.list, "Select...", 1); //add prompt EvalList({settingname.list, settings.arr}); ); //Reporterror------------------------------------------------------------ /*Generic function for reporting errors to log as well as via GUI window. If col.list argument is provided, the list of names in col.list will be appended to the end of error.str. -Inputs: error.str (string): message that is reported. Offending column names are appended to this. GUI.flag (0,1): determines if window is displayed. col.list (list of strings): (optional) list containing the offending column names. -Outputs: error message to log If GUI.flag = 1, window containing error message */ ReportError = Function({error.str, GUI.flag, col.list = {}}, {Default Local}, col.str = ""; VLB = VListBox(); For(i = 1, i <= Nitems(col.list), i++, col.str = col.str||", "||col.list[i]; VLB << Append(TextBox(" "||col.list[i])) ); RemoveFrom(col.str,1,2); Write(error.str||col.str||"\!N"); If(GUI.flag == 1, New Window("Borders - Alert", << Modal, TextBox(error.str, Set Font Style("Bold")), VLB, SpacerBox(Size(5,5)), HListBox(SpacerBox(), ButtonBox("OK")) ) ) ); //Input Window Definitions/////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////// input.expr = Expr( //Display Box Definitions-------------------------------------------- //Title Boxes wrap.len = 800; TBtit = Textbox("Borders Input", Set Font Style("Bold"), Set Font Size(12) ); TBsubtit1 = TextBox("Creates an XY plot showing the regions where a specific test condition is met.", Set Wrap(wrap.len)); TBsubtit2 = TextBox("Test Columns: columns used to evaluate the test condition and determine the borders.", Set Wrap(wrap.len), Bullet Point(1)); TBsubtit3 = TextBox("Overlay Column: multiple borders may be overlaid on single plot.", Set Wrap(wrap.len), Bullet Point(1)); TBsubtit4 = TextBox("By Columns: multiple plots may be generated for each column.", Set Wrap(wrap.len), Bullet Point(1)); //X Axis Boxes BB_xcol = ButtonBox("X Axis", var.list = FCS << Get Selected; CLB_xcol << Remove All; CLB_xcol << Append(var.list); ); CLB_xcol = ColListBox(NLines(2), dt, MinItems(1), MaxItems(1), Numeric, << Set Size(250,50)); //Y Axis Boxes BB_ycol = ButtonBox("Y Axis", var.list = FCS << Get Selected; CLB_ycol << Remove All; CLB_ycol << Append(var.list); ); CLB_ycol = ColListBox(NLines(2), dt, MinItems(1), MaxItems(1), Numeric); //Test Column Boxes BB_tcol = ButtonBox("Test Columns", //Get selected items frim filter column list var.list = FCS << Get Selected; tcol.list = {}; ntcol = CountDB(LUB_tcol)/3 - 1; //don't count header row //Generate list of already-existing tcols For(i=1, i<=ntcol, i++, curcol = LUB_tcol[i*3+1] << Get Text; InsertInto(tcol.list, curcol) ); //Loop through all items selected in the filter column list nonnum.list = {}; For(i=1, i<=NItems(var.list), i++, var.type = Column(dt, var.list[i]) << Get Data Type; If(var.type == "Numeric", //Confirm column is numeric //Checks if new selections aren't already in existing //tcols //If(!Contains(tcol.list, var.list[i]), InputAddRow(LUB_tcol, var.list[i]); //) ,//else data type is not numeric Insertinto(nonnum.list, var.list[i]) ); ); //end looping through all selected columns ); //End of BB_tcol def BB_tcol << Set Tip("Columns whose values will be used to compute borders"); LUB_tcol = Lineupbox(NCol(3), Spacing(5,2), TextBox("Test Column", Set Font Style("Bold")), TextBox("Test Limit", Set Font Style("Bold")), TextBox("Remove", Set Font Style("Bold")), TextBox("required numeric", Set Font Style("Italic")), NEB = NumberEditBox(), CB_rem = CheckBox("") ); NEB << Enable(0); CB_rem << EnableItem(1,0); //Overlay (aka Map) Boxes BB_ovcol = ButtonBox("Overlay Column", var.list = FCS << Get Selected; CLB_ovcol << Remove All; CLB_ovcol << Append(var.list) ); BB_ovcol << Set Tip("A map in each plot will be created for each value in this column."); CLB_ovcol = ColListBox(NLines(2), dt, MaxItems(1)); //By Column Boxes BB_bycol = ButtonBox("By Columns", var.list = FCS << Get Selected; CLB_bycol << Append(var.list) ); BB_bycol << Set Tip("Separate plots will be created for each value in this column (or combinations of values in these columns)."); CLB_bycol = ColListBox(NLines(5), dt); //Test condition Box VLB_testcon = VListBox( TextBox("Test Condition:", Set Font Style("Bold")), RB_testcon = RadioBox( {"Test Col ≤ Test Lim", "Test Col ≥ Test Lim", "|Test Col| ≤ Test Lim", "|Test Col| ≥ Test Lim"} ), SpacerBox(5,.) ); RB_testcon << Set(tcon); //Border display box VLB_brd = VListBox( TextBox("Borders:", Set Font Style("Bold")), RB_brd = RadioBox( {"None", "Show", "Show w/Arrows"} ), SpacerBox(Size(5,.)) ); RB_brd << Set(border.opt); //Display options box VLB_disp = VListBox( TextBox("Display:", Set Font Style("Bold")), CB_disp = CheckBox({"Points", "Filled Areas"}), SpacerBox(Size(5,.)) ); CB_disp << Set(1, pts.opt); CB_disp << Set(2, fill.opt); //X Scale Box VLB_xaxis = VListBox( TextBox("X-Axis:", Set Font Style("Bold")), RB_xscale = RadioBox({"Lin", "Log"}), CB_xgrid = CheckBox({"Major Gridlines", "Minor Gridlines"}), SpacerBox(5,.) ); RB_xscale << Set(xsc.opt); CB_xgrid << Set(1, xmagd); CB_xgrid << Set(2, xmigd); //Y Scale Box VLB_yaxis = VListBox( TextBox("Y-Axis:", Set Font Style("Bold")), RB_yscale = RadioBox({"Lin", "Log"}), CB_ygrid = CheckBox({"Major Gridlines", "Minor Gridlines"}), SpacerBox(5,.) ); RB_yscale << Set(ysc.opt); CB_ygrid << Set(1, ymagd); CB_ygrid << Set(2, ymigd); //Keep diag open check box CB_diag = Checkbox("Keep dialog open"); //OK Button BB_Ok = ButtonBox("OK", inputread.expr; inputcheck.expr; //Save new inputs to file for later use output = inputsave("Last", settings.arr); settingname.list = output[1]; //update list of settings settings.arr = output[2]; //update array of settings If(ready.flag == 1, If(diag.flag != 1, InputWin << Close Window); main.expr; plot.expr; ); ); //Cancel Button BB_Canc = ButtonBox("Cancel", Write("\!NUser Cancel. Exiting script.\!N"); InputWin << Close Window; ); //Reset all inputs button BB_Res = ButtonBox("Reset", FCS << Clear Selection; CLB_xcol << Remove All; CLB_ycol << Remove All; nboxes = CountDB(LUB_tcol); For(i=nboxes, i>=4, i--, LUB_tcol[i] << Delete; ); LUB_tcol << Append( TextBox("required numeric", Set Font Style("Italic")) ); LUB_tcol << Append(NumberEditBox()); LUB_tcol << Append(CheckBox("")); LUB_tcol[5] << Enable(0); LUB_tcol[6] << EnableItem(1,0); CLB_ovcol << Remove All; CLB_bycol << Remove All; RB_testcon << Set(tcon); RB_xscale << Set(xsc.opt); RB_yscale << Set(ysc.opt); CB_diag << Set(1, 0); ); BB_Res << Set Tip("Reset all inputs"); //Recall last button BB_Rec = ButtonBox("Recall Last", Try( //Might not be an existing settings.arr If(settings.arr[1][1] == "Last", Eval(Parse(EvalList(settings.arr[1][2]))); inputset.expr ) ) ); BB_Rec << Set Tip("Recall the last settings used"); If(Contains(settingname.list, "Last")==0, BB_Rec << Enable(0)); //Save inputs button BB_Save = ButtonBox("Save Inputs", inputread.expr; inputcheck.expr; If(ready.flag == 1, //Save inputs only if inputs make sense. saveinp.win = New Window("Borders - Save Inputs", << Modal, TextBox("Current inputs will be saved and can be recalled later.", << Set Font Style("Bold")), Spacerbox(Size(5,5)), HListBox( TextBox("Name for saved inputs:"), TEBsave = TextEditBox("", Set Width(150)) ), HListBox( Spacerbox(), ButtonBox("OK", settingname = TEBsave << Get Text), ButtonBox("Cancel") ) ); If(saveinp.win == {Button(1)} & settingname != "", output = inputsave(settingname, settings.arr); settingname.list = output[1]; settings.arr = output[2]; CB_Saved << Set Items(settingname.list); ); ) ); BB_Save << Set Tip("Save the current settings for recall later"); //Remove a saved input BB_Rem = ButtonBox("Delete Input", selectid = (CB_Saved << Get) - 1; //-1 bc 1st item is "Select" If(selectid > 1, // > 1 bc can't remove "Last" RemoveFrom(settingname.list, selectid+1); RemoveFrom(settings.arr, selectid) ); CB_Saved << Set Items(settingname.list); settings.jstr = Char(settings.arr); Save Text File("$TEMP/BordersSelections.txt", settings.jstr, Replace); ); BB_Rem << Set Tip("Remove the currently selected setting in the \!"Load Inputs\!" drop down box."); //Saved inputs selection TB_Saved = TextBox("Load Inputs:"); CB_Saved = ComboBox(settingname.list, << Enable Item(1,0), selectid = (CB_Saved << Get) - 1; //-1 bc 1st item is "Select" Eval(Parse(EvalList(settings.arr[selectid][2]))); inputset.expr ); CB_Saved << Set Tip("Load previously saved settings."); //Input Window------------------------------------------------------- Try(Window("Borders Input") << Close Window); InputWin = New Window("Borders Input", BorderBox(Left(10), Right(10), Top(5), Bottom(10), Sides(0), VlistBox( TBtit, TBsubtit1, TBsubtit2, TBsubtit3, TBsubtit4, SpacerBox(size(5,5)), HListBox( PanelBox("Select Columns", FCS = FilterColSelector(dt, NLines(25), << Set Size(250, 500)), SpacerBox() ), LineUpBox(NCol(1), PanelBox("Cast Selected Columns into Roles", LineUpBox(NCol(2), Spacing(5,5), BB_xcol, CLB_xcol, BB_ycol, CLB_ycol, BB_tcol, LUB_tcol, BB_ovcol, CLB_ovcol, BB_bycol, CLB_bycol ) ), PanelBox("Options", Vlistbox( LineUpBox(NCol(3), Spacing(15,2), VLB_testcon, VLB_brd, VLB_disp, VLB_xaxis, VLB_yaxis, ), SpacerBox(Size(5,10)), CB_diag, ) ) ), PanelBox("Action", LineUpBox(NCol(1), BB_Ok, BB_Canc, SpacerBox(size(5, 20)), BB_Res, BB_Rec, BB_Save, BB_Rem, SpacerBox(size(5, 20)), TB_Saved, CB_Saved ) ) ) ) ) ); Write("\!N"); ); //End of input expression //Main/////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////// main.expr = Expr( winname = tablename||" - Threshold Plot of "||xcol||" vs "||ycol; If(NItems(bycol.list) > 0, winname = winname||", by "||Char(bycol.list)); //Try(Window(winname) << Close Window); //for debug only //Create working table of unexcluded rows only dt << Clear Select; dt << Select Excluded << Invert Row Selection; dtwrk = dt << Subset( Selected Rows, All Columns, Output Table("Working") ); Current Data Table(dtwrk); //Get column values from current data table xcol.vec = GetColVals(xcol); //Sorted vect. of all values in xcol ycol.vec = GetColVals(ycol); // " ycol If(ovcol != "", // " ovcol ovcol.list = AsList(GetColVals(ovcol)), ovcol.list = {1} //just a garbage value to force >=1 loop ); //Summary table for bycol values dtsum = dtwrk << Summary(Group(bycol.list)); //Loop through each value of bycol to create allplots.arr------------ allplots.arr = {}; /*allplots.arr is a master array containing all the information needed for all maps in all plots. It gets passed to the Results Window that is created when the script is complete. This allows the user to run the script repeatedly with previous results windows open, and not have them crash due to local variables being overwitten. Each item of allplots.arr has the following general structure: allplots.arr[byid][mpid][1][bdid] = plot.arr[mpid][1][bdid] = map.arr[1][bdid] = border.arr allplots.arr[byid][mpid][2] = plot.arr[mpid][2] = map.arr[2] = poly.arr Where: byid = the plot #; a single number representing the specific combination of values for the columns in bycol.list for which each plot is generated. mpid = the "map" #, representing each ovcol value. A map consists two parts. The first part consists of the boundaries (aka "borders") surrounding all the regions in XY-space where the threshold conditions are met for a given ovcol val. The second part consists of the polygons that can be used to represent the regions alternatively. A map of borders/polygons will be plotted in each plot for each mpid-th value of ovcol. bdid = the border #. In a single map, there may be multiple, discrete regions where the test threshold condition is met, each surrounded by a border. Additionally, a single region may have multiple borders (e.g. "donut" shaped region). This number identifies each border independently. border.arr = an array containing all the X,Y information needed to create a single border (for a given map and plot). border.arr has the following stucture: border.arr = { {segment id1, border interpolated xcoord, border interpolated ycoord, arrow origin xcoord, arrow origin ycoord}, {segment id2, border interpolated xcoord, border interpolated ycoord, arrow origin xcoord, arrow origin ycoord}, ... {segment idN, border interpolated xcoord, border interpolated ycoord, arrow origin xcoord, arrow origin ycoord} } poly.arr = a list containing all the information needed to create a single complex polygon representing the regions in a given map and plot where the test conditions are met. poly.arr has the following structure: poly.arr = { polygon xcoord column vector, polygon ycoord column vector } */ For(byid = 1, byid <= NRows(dtsum), byid++, For(tsid = 1, tsid <= NItems(tcol.arr), tsid++, tcol = tcol.arr[tsid][1]; //test column If(tcon == 1 | tcon == 3, //test limit for that column tlim = tcol.arr[tsid][2], //for testing if tcol <= tlim tlim = -tcol.arr[tsid][2] //for testing if tcol >= tlim ); //Create plot.arr by looping through all maps (aka all //values in ovcol).------------------------------------------ plot.arr = {}; /*Plot.arr is an array containing all the information needed to create the region borders for all overlaid maps within a single plot. This is inserted into allplots.arr for each plot and used in the subsequent plotting section. Each item of plot.arr has the following general structure: plot.arr[mpid][1][bdid] = map.arr[1][bdid] = border.arr plot.arr[mpid][2] = map.arr[2] = poly.arr See section above for more info. */ For(mpid=1, mpid<=NItems(ovcol.list), mpid++, //Select Rows within the current bycol combination for //the current map dtwrk << Clear Select; dtsum << Select Rows(byid); If(ovcol != "", dtwrk << Select Where( dtwrk:ovcol == ovcol.list[mpid], Current Selection("restrict") ); //versions JMP12 and greater only ); dtwrk << Clear Column Selection; dtsub = dtwrk << Subset(Selected Rows); //Take average of any xcol/ycol pair duplicates grp.list = EvalList({xcol, ycol, ovcol}||bycol.list); dtsum2 = dtsub << Summary( Group(grp.list), Mean( Column(dtsub,tcol) ), Freq( "None" ), Weight( "None" ), statistics column name format( "column" ), Link to original data table( 0 ) ); Close(dtsub, NoSave); //Build value.arr for x and y columns.------------------- /*Each item in value.arr represents info about one XY element, containing its location, whether it is adjacent to a border, which direction the border is facing, among other things. Note: a single element may have borders on multiple sides. Structure: value.arr = { {item11, item12, ..., item1N}, {item21, item22, ..., item2N}, ... {itemM1, itemM2, ..., itemMN} } value.arr[i][j] = itemij = item with the ith xcoord and jth ycoord Where: itemij.arr = { 1: E.list, 2: N.list, 3: W.list, 4: S.list, 5: element value (tcol), 6: element xcoord (xcol), 7: element ycoord (ycol) } And further: [E,N,W,S].list = { 1: Is a border on this element's [E,N,W,S] side? (1: Yes, 0: No), 2: Border ID (If brd exists): Multiple borders may exist in a given map, 3: Segment ID (If brd exists): for each border, this is an id for each line segment, 4: Interp. xcoord between curr. X and adj. X where tcol = tlim, 5: Interp. ycoord between curR. Y and adj. Y where tcol = tlim, 6: xcoord for starting point of arrow pointing towards border, 7: ycoord for starting point of arrow pointing towards border } */ //Initialize value.arr item.arr = { {0,.,.,.,.,.,.}, //E border: exists?, border id, //segment id, intpx, intpy, //arx, ary {0,.,.,.,.,.,.}, //N border: " {0,.,.,.,.,.,.}, //W border: " {0,.,.,.,.,.,.}, //S border: " ., //element value (tcol) ., //element x coord (xcol) . //element y coord (ycol) }; blankcol = Repeat(EvalList({item.arr}), NRows(ycol.vec)); value.arr = Repeat(EvalList({blankcol}), NRows(xcol.vec) ); //Populate value.arr with vals from tcol, xcol, and ycol Current Data Table(dtsum2); For Each Row( xval = Column(dtsum2, xcol)[]; yval = Column(dtsum2, ycol)[]; i = Contains(xcol.vec, xval); //row num (x) j = Contains(ycol.vec, yval); //col num (y) If( tcon == 1, //tcol <= tlim value.arr[i][j][5] =Column(dtsum2,tcol)[], tcon == 2, //tcol >= tlim value.arr[i][j][5] =-Column(dtsum2,tcol)[], tcon == 3, //|tcol| <= tlim value.arr[i][j][5] =Abs(Column(dtsum2,tcol)[]), tcon == 4, //|tcol| >= tlim value.arr[i][j][5] =-Abs(Column(dtsum2,tcol)[]) ); ); Close(dtsum2, NoSave); Current Data Table(dtwrk); For(i=1, i<=NRows(xcol.vec), i++, For(j=1, j<=NRows(ycol.vec), j++, value.arr[i][j][6] = xcol.vec[i]; //ele. xcoord value.arr[i][j][7] = ycol.vec[j]; //ele. ycoord ); ); //Determine if array item has a border, and if so, what //orientation(s)----------------------------------------- For(i=1, i<=NRows(xcol.vec), i++, For(j=1, j<=NRows(ycol.vec), j++, //Check if value exists and meets test condition currval = value.arr[i][j][5]; //ele. tcol value //missing values = failing the test cond. If(!IsMissing(currval) & currval <= tlim, //Inspect adj. elements in all 4 directions dir.vec = [1 0 -1 0]; For(k=1, k<=4, k++, di = dir.vec[k]; dj = dir.vec[Mod((k-1+3),4)+1]; //adjacent element tcol value a_val =Try(value.arr[i+di][j+dj][5],.); If(a_val > tlim | IsMissing(a_val), value.arr[i][j][k][1] = 1; //set border flag = yes ); ); //End looping through all directions ); //End if statement ); //End looping through j ); //End looping through i //Identify elements with adjacent borders---------------- /*Cycle through value.arr again to determine which elements having borders are adjacent. If adjacent elements have borders, assign a border id and segment id. Count up the number of regions and segments.*/ //Number of separate borders detected in the map //(initialized at 1) nborders = 1; //loop through x values For(i=1, i<=NRows(xcol.vec), i++, //loop through y values For(j=1, j<=NRows(ycol.vec), j++, //for each x,y value, loop thru 4 dir (E,N,W,S) For(k=1, k<=4, k++, //If element has a border... If(value.arr[i][j][k][1] == 1, //If segment id not assigned... If(IsMissing(value.arr[i][j][k][3]), //(a missing value indicates it has not //yet been assigned by a prior loop //through i,j,k) //Initialize loop values------------- counter = 0; stop.flag = 0; assigned.flag = 0; nsegs = 0; icurr = i; jcurr = j; kcurr = k; iinit = i; jinit = j; kinit = k; //"Follow" border until it hits the //edge of the data set, or it loops //back on itself--------------------- While(stop.flag == 0 & counter < 1000, counter++; //just for protection against runaways //Get new coords newcoord.list = GetNext(value.arr, icurr, jcurr, kcurr); inext = newcoord.list[1]; jnext = newcoord.list[2]; knext = newcoord.list[3]; //Init Check: seg id already assigned? If( !IsMissing(value.arr[icurr][jcurr][kcurr][3]), assigned.flag = 1 ); //Update segment number //curr region id value.arr[icurr][jcurr][kcurr][2] = nborders; nsegs++; //update seg id value.arr[icurr][jcurr][kcurr][3] = nsegs; //Compute interpolated points and arrow origins graph.list = CompIntPoints(icurr, jcurr, kcurr, value.arr, tlim); value.arr[icurr][jcurr][kcurr][4] = graph.list[1]; //interp xcoord value.arr[icurr][jcurr][kcurr][5] = graph.list[3]; //interp ycoord value.arr[icurr][jcurr][kcurr][6] = graph.list[2]; //arrow orig x value.arr[icurr][jcurr][kcurr][7] = graph.list[4]; //arrow orig y /* //For Debug write("\!Nmpid, nborders: "||char(EvalList({mpid,nborders})) ||"\!NInit i,j,k: "||Char(EvalList({i,j,k})) ||"\!NInit Segment #: "||Char(value.arr[i][j][k][3]) ||"\!NCurr i,j,k,val,intpx,intpy: "||Char(EvalList({icurr, jcurr, kcurr, value.arr[icurr][jcurr][5], value.arr[icurr][jcurr][kcurr][4], value.arr[icurr][jcurr][kcurr][5]})) ||"\!NCurr Segment #: "||Char(value.arr[icurr][jcurr][kcurr][3]) ||"\!NNext i,j,k,val: "||Char(EvalList({inext, jnext, knext, value.arr[inext][jnext][5]})) ||"\!NNext Segment #: "||Char(value.arr[inext][jnext][knext][2]) ||"\!Nassigned.flag: "||Char(assigned.flag) ||"\!N" ); */ //Check for stop If(assigned.flag==1, //ass. flag =1 if border loops back on itself stop.flag = 1; nborders++, //incr. # of regions //Else go to next coords icurr = inext; jcurr = jnext; kcurr = knext ); ); //End of while loop following a border ) //Close "if not already assigned..." ) //Close "if not a border element..." ) //End k ) //End j ); //End i //map.arr------------------------------------------------ map.arr = {{} , {}}; /*Map.arr is an array containing all the information needed to create the region borders and polygons for a single map. This is inserted into plot.arr for each map and used in the subsequent plotting section. Each item of map.arr has the following general structure: map.arr[1][bdid] = border.arr map.arr[2] = poly.arr See section above for more info on the structures of border.arr and poly.arr.*/ //Add Borders and Polygons to map.arr //All polys return to these coords. Must != 0 in case //of log plots. polyx.vec = [1]; polyy.vec = [1]; For(bdid=1, bdid<=nborders, bdid++, //Detect and sort all segments in the current region border.arr = {}; For(i=1, i<=NRows(xcol.vec), i++, //ele xcoord For(j=1, j<=NRows(ycol.vec), j++, //ele ycoord For(k=1, k<=4, k++, //direction (E,N,W,S) //element border id & segment id If(value.arr[i][j][k][2]==bdid & !IsMissing(value.arr[i][j][k][3]), InsertInto(border.arr, EvalList( {EvalList( //segment num {value.arr[i][j][k][3], //interp. x value.arr[i][j][k][4], //interp. y value.arr[i][j][k][5], //arrow start x value.arr[i][j][k][6], //arrow start y value.arr[i][j][k][7]} )} ) ) ) ) ) ); //sorts array by segment id number SortListInto(border.arr); //Check & modify for any looped borders, then //append to map array If(border.arr != {}, /*Bc it was sorted, the first segment id should not be 2. This happens bc the border looped back on itself and overwrote segment#1. Copy and insert the last segment back to the first one. */ If(border.arr[1][1] == 2, lastseg = NItems(border.arr); segment.list = EvalList( {1, border.arr[lastseg][2], border.arr[lastseg][3], border.arr[lastseg][4], border.arr[lastseg][5]} ); InsertInto(border.arr, EvalList({segment.list}), 1); ); InsertInto(map.arr[1], EvalList({border.arr})); ); //Build up poly.arr For(sgid = 1, sgid <= NItems(border.arr), sgid++, polyx.vec = polyx.vec|/border.arr[sgid][2]; polyy.vec = polyy.vec|/border.arr[sgid][3]; ); polyx.vec = polyx.vec|/[1]; polyy.vec = polyy.vec|/[1]; ); //End of looping through borders (bdid) poly.arr = EvalList({polyx.vec, polyy.vec}); InsertInto(map.arr[2], EvalList(poly.arr)); InsertInto(plot.arr, EvalList({map.arr})); ); //End of looping through maps, aka ovcol vals (mpid) InsertInto(allplots.arr, EvalList({plot.arr})); ); //End of looping through all tcols (tsid) ); //End of looping through all bycol vals (byid) ); //End of main.expr def //Create Plot//////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////// plot.expr = Expr( //Reset current data table to orig. so plots will be assoc. with it. Current Data Table(dt); //Display Boxes for Plot Title--------------------------------------- TB_Title = Textbox("Threshold Plot for "||ycol||" vs. "||xcol); TB_Title << Set Font("Segoe UI"); TB_Title << Set Font Size(13); TB_Title << Set Font Style("Bold"); TB_Title << Set Wrap((xframe+200)*NItems(tcol.arr)); TB_subtit = TextBox("All areas within the drawn borders/polygons meet the specified test criteria for the specified value of "||ovcol); TB_subtit << Set Wrap((xframe+200)*NItems(tcol.arr)); //Display Boxes for Plot Legend-------------------------------------- If(ovcol != "", VLB_Leg = VListBox( TextBox("Map Legend:", Set Font Style("Bold")), LUB_Leg = LineUpBox(NCol(2), Spacing(5, 1)) ); For(i=1, i<=NItems(ovcol.list), i++, ovval.str = GetColUnits(ovcol, ovcol.list[i]); LUB_Leg << Append( VCenterBox(SpacerBox(Size(20,2), Color(linecolor.list[i]))) ); LUB_Leg << Append( TextBox(ovcol||" = "||ovval.str) ); ), VLB_Leg = SpacerBox(Size(1,1)) ); //Display Boxes for Plot Options------------------------------------- VLB_brd = VListBox( TextBox("Borders:", Set Font Style("Bold")), RB_brd = RadioBox({"None", "Show", "Show with Arrows"}, border.opt = RB_brd << Get; LUB_plots << Reshow; ), SpacerBox(Size(5,.)) ); RB_brd << Set(border.opt); VLB_disp = VListBox( TextBox("Display:", Set Font Style("Bold")), CB_disp = CheckBox({"Points", "Filled Areas"}, pts.opt = CB_disp << Get(1); //List of all bivplot refs in LUB_plots db.list = LUB_plots << Xpath("//LineUpBox/ListBox/OutlineBox"); //converts list to actual scriptable bivplots bvp.list = db.list << Get Scriptable Object; //For some reason, I can't use 'bvp.list << Show Point(pts)' If(pts.opt == 0, bvp.list << Show Points(0), bvp.list << Show Points(1) ); fill.opt = CB_disp << Get(2); LUB_plots << Reshow; ), SpacerBox(Size(5,.)) ); CB_disp << Set(1, pts.opt); CB_disp << Set(2, fill.opt); VLB_xaxis = VListBox( TextBox("X Axis:", Set Font Style("Bold")), RB_xsc = RadioBox({"Lin", "Log"}, xsc.str = RB_xsc << Get Selected; //List of all xaxisbox refs in LUB_plots db.list = LUB_plots << Xpath("//AxisBox[@charID='1']"); db.list << Scale(xsc.str); //Changing scale makes grid go away, so I need to regen db.list << Show Major Grid(xmagd); db.list << Show Minor Grid(xmigd); ), CB_xgr = CheckBox({"Major Gridlines", "Minor Gridlines"}, xmagd = CB_xgr << Get(1); xmigd = CB_xgr << Get(2); //List of all xaxisbox refs in LUB_plots db.list = LUB_plots << Xpath("//AxisBox[@charID='1']"); db.list << Show Major Grid(xmagd); db.list << Show Minor Grid(xmigd); ), SpacerBox(Size(5,.)) ); RB_xsc << Set(xsc.opt); CB_xgr << Set(1, xmagd); CB_xgr << Set(2, xmigd); VLB_yaxis = VListBox( TextBox("Y Axis:", Set Font Style("Bold")), RB_ysc = RadioBox({"Lin", "Log"}, ysc.str = RB_ysc << Get Selected; //List of all yaxisbox refs in LUB_plots db.list = LUB_plots << Xpath("//AxisBox[@charID='2']"); db.list << Scale(ysc.str); //Changing scale makes grid go away, so I need to regen db.list << Show Major Grid(ymagd); db.list << Show Minor Grid(ymigd); ), CB_ygr = CheckBox({"Major Gridlines", "Minor Gridlines"}, ymagd = CB_ygr << Get(1); ymigd = CB_ygr << Get(2); //List of all yaxisbox refs in LUB_plots db.list = LUB_plots << Xpath("//AxisBox[@charID='2']"); db.list << Show Major Grid(ymagd); db.list << Show Minor Grid(ymigd); ), SpacerBox(Size(5,.)) ); RB_ysc << Set(ysc.opt); CB_ygr << Set(1, ymagd); CB_ygr << Set(2, ymigd); //JSL Quote append plots to window.---------------------------------- /*A JSL quote is necessary because of the graphing commands needed to draw the borders. Bivvy.jstr is the generic command to append each Bivariate plot, and its associated display boxes (e.g. VLB_leg) to the Lineup Box LUB. At the end is a "grphplaceholder" which will be substituted with the code for the borders and arrows commands (see below). */ //Converts *.opt to a string that will be read in the jstr below xsc.str = {"Lin", "Log"}[xsc.opt]; ysc.str = {"Lin", "Log"}[ysc.opt]; LUB_plots = LineUpBox(Ncol(Nitems(tcol.arr))); bivvy.jstr = JSLQuote( LUB_plots << Append( HListBox( bvp = Bivariate( SendToByGroup(Bygroup Default), Y(Column(dt, ycol)), X(Column(dt, xcol)), Show Points(pts.opt), SendToReport( Dispatch( {}, "1", ScaleBox, { Scale(xsc.str), Min(xmin), Max(xmax), Inc(xinc), Minor Ticks(1), Label Row( {Show Major Grid( xmagd ), Show Minor Grid( xmigd )} ) } ), Dispatch( {}, "2", ScaleBox, { Scale(ysc.str), Min(ymin), Max(ymax), Inc(yinc), Minor Ticks(1), Label Row( {Show Major Grid( ymagd ), Show Minor Grid( ymigd )} ) } ), Dispatch( {}, "Bivar Plot", FrameBox, { Frame Size(xframe, yframe), //Marker Drawing Mode("Fast"), Marker Drawing Mode("Normal"), //Marker Size(2), Grid Line Order(3), Reference Line Order(2) } ), //placeholder for graph commands grphplaceholder ) ); ) ); //Tucks the legend under the outline for each plot //Sets listbox to be horizontal LUB_plots[plid][1][1] << Set Horizontal(1); LUB_plots[plid][1][1] << Append( VListBox( VLB_Leg, SpacerBox(Size(5,5)), ) ); ); //End of bivvy.jstr def //JSL Quote to add line segments and arrows-------------------------- /*This is a template jstr used to create the borders and arrows. The appropriate values for mpid (map ID) and bdid (border ID) are substituted below to create the desired expression. The variables "linecolor.list" and "allplots.arr" are scoped to the Window in order to prevent conflicts when multiple windows are open.*/ threshy.jstr = JSL Quote( Dispatch( {}, "Bivar Plot", FrameBox, { Add Graphics Script( Pen Color(Window:linecolor.list[mpid]); For(i=1, i<=NItems(Window:allplots.arr[plid][mpid][1][bdid])-1, i++, //Arrows If(Window:border.opt == 3, Pen Size(1); Transparency(0.5); Arrow( {Window:allplots.arr[plid][mpid][1][bdid][i][4], Window:allplots.arr[plid][mpid][1][bdid][i][5]}, {Window:allplots.arr[plid][mpid][1][bdid][i][2], Window:allplots.arr[plid][mpid][1][bdid][i][3]} ); ); //Segments If(Window:border.opt == 2 | Window:border.opt == 3, Pen Size(2); Transparency(0.5); Line( {Window:allplots.arr[plid][mpid][1][bdid][i][2], Window:allplots.arr[plid][mpid][1][bdid][i][3]}, {Window:allplots.arr[plid][mpid][1][bdid][i+1][2], Window:allplots.arr[plid][mpid][1][bdid][i+1][3]}, ); ); ); //Polygons If(Window:fill.opt == 1, Fill Color(Window:linecolor.list[mpid]); tfact = 0.1 / Nitems(Window:allplots.arr[plid][mpid][1]); //reduce tx by # brdrs Transparency(tfact); Polygon( Window:allplots.arr[plid][mpid][2][1], Window:allplots.arr[plid][mpid][2][2] ) ) ) } )); //end of threshy.jstr //Loop through all plots to be created (byid x tsid)----------------- For(byid = 1, byid <= NRows(dtsum), byid++, For(tsid = 1, tsid <= NItems(tcol.arr), tsid++, tcol = tcol.arr[tsid][1]; //test column tlim = tcol.arr[tsid][2]; //test limit plid = NItems(tcol.arr)*(byid - 1) + tsid; plot.arr = allplots.arr[plid]; //Scale the axis ranges to rounded off values if autograph //is on.----------------------------------------------------- /*This creates a set of variables "X_min", "X_max", "X_inc" and "Y_min", "Y_max", "Y_inc". These values are computed based on the actual ranges of X, Y, but have been rounded to multiples that make sense (e.g. digits ending in 1, 2, 5). These will get used in the plot below.*/ If(auto == "Y", autoaxis.jstr = JSL Quote( axis_min = Col Min(Column(dtwrk, axis_col)); axis_max = Col Max(Column(dtwrk, axis_col)); axis_rng = axis_max - axis_min; axis_msd = Floor(Log(axis_rng,10)); axis_rmd = axis_rng*10^(-axis_msd); If( axis_rmd <= 2, axis_inc = 0.2, axis_rmd <= 5, axis_inc = 0.5, axis_inc = 1 ); axis_inc = axis_inc*(10^axis_msd); If(axis_sc.opt == 2, //Log scale axis_inc = 1; If(pad == "Y", axis_min = axis_min/1.2; axis_max = axis_max*1.2; ), //Else linear If(pad == "Y", axis_min = axis_min - axis_rng*0.05; axis_max = axis_max + axis_rng*0.05; ) ) ); autoX.jstr = Substitute(autoaxis.jstr, "axis_", "x"); autoY.jstr = Substitute(autoaxis.jstr, "axis_", "y"); Eval(Parse(autoX.jstr)); Eval(Parse(autoY.jstr)) ); //Build up newbivvy.jstr------------------------------------- If(plot.arr != {}, newbivvy.jstr = bivvy.jstr; For(mpid=1, mpid<=NItems(ovcol.list), mpid++, Try(nborders = NItems(plot.arr[mpid][1]), nborders = 0); For(bdid=1, bdid<=nborders, bdid++, //Sub in the specific vals of mpid (map id) and //bdid (border id) to get specific jstr. curthreshy.jstr = Substitute(threshy.jstr, Expr(mpid), Char(mpid), Expr(bdid), Char(bdid) ); //Build up grphplaceholder jstr containing all //combinations of mpid and bdid values. SubstituteInto(newbivvy.jstr, "grphplaceholder", curthreshy.jstr||",grphplaceholder" ); ) ) ); SubstituteInto(newbivvy.jstr, ",grphplaceholder", "", "plid", Char(plid), "pts.opt", Char(pts.opt) //for some reason, can't just use variable as is in jstr. ); Eval(Parse(newbivvy.jstr)); //Set titles to individual plots bycol.str = ""; For(i=1, i<=NItems(bycol.list), i++, val.str = GetColUnits(bycol.list[i], Column(dtsum,i)[byid]); bycol.str = bycol.str||", "||bycol.list[i]||" = "||val.str; ); RemoveFrom(bycol.str, 1, 2); tval.str = GetColUnits(tcol, tlim); If( tcon == 1, plot_tit = "Threshold Plot of "||tcol||" ≤ "||tval.str, tcon == 2, plot_tit = "Threshold Plot of "||tcol||" ≥ "||tval.str, tcon == 3, plot_tit = "Threshold Plot of |"||tcol||"| ≤ "||tval.str, tcon == 4, plot_tit = "Threshold Plot of |"||tcol||"| ≥ "||tval.str ); If(NItems(bycol.list) > 0, plot_tit = plot_tit||" for "||bycol.str); LUB_plots[plid][1] << Set Title(plot_tit); ); //End of looping through all tcols (tsid) ); //End of looping through all bycol vals (byid) Close(dtsum, No Save); Close(dtwrk, No Save); //Create Results Window---------------------------------------------- /*Some variables are copied over to the Window namespace so that multiple windows may exist simultaneously without conflicts.*/ winny = New Window(winname, Window:linecolor.list = linecolor.list; Window:allplots.arr = allplots.arr; Window:border.opt = border.opt; Window:fill.opt = fill.opt; Window:xsc.opt = xsc.opt; Window:xmagd = xmagd; Window:xmigd = xmigd; Window:ysc.opt = ysc.opt; Window:ymagd = ymagd; Window:ymigd = ymigd; edge = 5; Brdbox = Border Box( Left(edge), Right(edge), Top(edge), Bottom(edge), VListBox( TB_Title, TB_subtit, SpacerBox(Size(5,10)), LineUpBox(NCol(4), Spacing(15,1), VLB_brd, VLB_disp, VLB_xaxis, VLB_yaxis ), SpacerBox(Size(5,10)), LUB_plots ) ); ); //Housekeeping----------------------------------------------------------- dt << Clear Column Selection; dt << Clear Select; ); //End of plot.expr def //Execute Them All/////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////// //All boils down to this... dt = Current Data Table(); tablename = dt << Get Name; If(GUI.flag==1, //loads all previous input selection sets and creates settings.arr Try( settings.arr = Include("$TEMP/BordersSelections.txt"), settings.arr = {} ); //ensures settings.arr is sorted and also creates two items below output = inputsort(settings.arr); //this list populates the combo box in the GUI settingname.list = output[1]; //this array is available if an input set is selected. settings.arr = output[2]; //this executes GUI, and upon pressing "ok", executes the same //expressions as below. input.expr, //Else (run without GUI) //Write default values that would've been assigned in GUI xcol = xcol_def; ycol = ycol_def; tcol.arr = tcol.arr_def; ovcol = ovcol_def; bycol.list = bycol.list_def; inputcheck.expr; //Check inputs If(ready.flag == 1, main.expr; //Compute value.arr and allplots.arr plot.expr, //plot everything Write("Script terminated.\!N") ) );