The Multiple Y-Axis Graph
This is a specialized graph that scales all of the y-axis variables and overlays them and then provides the y-axis scale in the units and range of the variable. This is useful when you want to overlay 3 or 4 variables that are all in different ranges. In the figure above the flow rates are in 0-40 range while power is in the range of 200-1000 and 100 to 500. This type of graph is often used in scientific and engineering applications where multiple sensors are recording data at the same time, but the range of each sensor is quite different. For example, in a fermentation example, glucose is 1-5 g/dl, pH is 6.8 - 7.8, lactic Acid is 10-50 mmol, and cell count or turrbidity is an order of magnitude greater. A simple way to graph the relationship between all the variables over time is very helpful for understanding the kinetics (or detecting unusual patterns) of the process.
Names Default To Here( 1 );
dt = Current Data Table(); //This could be a dialog too, but its set up to make an add-in this way.
//////////////////////////////////////////////////////////////Expressions - I moved these all up here rather than having them scattered through the script
obj = Expr( //the graph builder plot which is gb
gb = dt2 << Graph Builder(
Size( 640, 230 ),
Show Title( 0 ), ////opt
Show Control Panel( 0 ),
Legend Position( "Inside Left" ),
Variables( X( Column( dt2, 1 ) ), Y( :Data ), Overlay( :Column Names ) ), //:Row needs to be dynamic based on x or no x in dialog.
Elements( Line( X, Y, Legend( 7 ) ) ),
SendToReport(Dispatch( {}, "400", LegendBox, {Set Title( "" )} ) ),
SendToReport(Dispatch( {}, "Graph Builder", OutlineBox, {Set Title( "Scaled Response Graph" )} ) ),
SendToReport(Dispatch( {}, "Data", ScaleBox, {Label Row( Show Major Labels( 0 ) )} )),
SendToReport(Dispatch( {}, "Y title", TextEditBox, {Set Text( "" )} ))
)
);
objwin = Expr( //window that everything ends up in
New Window( "Scaled Response Graph with Multiple Y's" )
);
objout = Expr( //outline box
Outline Box( "Y-axis Labels" )
);
objhlb1 = Expr( H List Box( "1" ) ); //this one has the axis plots
objhlb2 = Expr( H List Box( "1" ) ); //this one has the obj graph
objvlb = Expr( V List Box( "1" ) ); //this one has out and
dt2stackcols = Expr( columns() ); //column names to stack
dt2stack = Expr( //the stack expression with arguments. Note: the column argument MUST be first.
stack( Source Label Column( "Column Names" ), Stacked Data Column( "Data" ) )
);
//////////////////////////////////////////////////////////////////// Dialog to get column names
cd = Column Dialog(
ylist = ColList( "Y", Min Col( 2 ), Max Col( 8 ), Data Type( "Numeric" ), Modeling Type( {"Continuous"} ) ),
xlist = ColList( "X", Max Col( 1 ), Modeling Type( {"Continuous", "Multiple Response"} ) )
);
Eval( Eval Expr( cd[1] ) ); //eval expr, returns what is in cd[1], and the eval makes it execute
Eval( Eval Expr( cd[2] ) ); //separately run just cd[1], then run evalexpr)cd[1], this is how I get the ylist and xlist variables
/////////////////////////////////////////////////////////////////// Standardized Data Table, and "Axis Boxes""
dt1 = New Table( "multiple y-axis table" ); //this is the table where the standardized values go
If( N Items( xlist ) == 1, //note that if there is a column selected for xlist, then it doesn't get scaled
Insert Into( ylist, xlist, 1 ), //call me crazy, but the first value in ylist is the "X", if it exists
dt1 << New Column( "Row", formula( Row() ) )
);
For(
If( N Items( xlist ) == 1, //yep, that's an "if" argument inside a "for" loop
i = 2; //if an "x" is specified, then start with the second thing in the list
ycol = As Column( dt, ylist[1] ) << get values;
dt1 << New Column( Munger( ylist[1], 1, ":", "" ), set values( ycol ) );//this is soooo ugly
,i = 1), i <= N Items( ylist ), i++, //end of setup for the For loop
ycol = As Column( dt, ylist[i] ) << get values;
yycol = ((ycol - Min( ycol )) / (Max( ycol ) - Min( ycol )));
dt1 << New Column( Munger( ylist[i], 1, ":", "" ), set values( yycol ) ); //this is soooo ugly, very sorry
amin = Min( ycol ); //this second part of this script is getting values to make the y-axis boxes
amax = Max( ycol ); //I need the min and max values from each of the unscaled columns, along with their names
ymin = amin - ((amax - amin) * .1); //The y-axis box axis range is 10% higher and lower than the min and max.
ymax = amax + ((amax - amin) * .1); //It looks better and is more closely aligned with the scaled data in gb
obj1 = Graph Box( Framesize( 0, 230 ), yName( Char( ylist[i] ) ), Y Scale( ymin, ymax ) ); //its not a true axis box, just a squished graph box. the axis box was free
Insert Into( objhlb1, Name Expr( obj1 ) ); //there is a lot of inserting of name expr() here. its part of building expressions from the inside out.
);
///////////////////////////////////////////////////////////////stack standardized data for graphing
For( If( N Items( xlist ) == 1, i =2,i=1), i <= N Items( ylist ), i++, Insert Into( dt2stackcols, ylist[i] ););
Insert Into( dt2stack, Name Expr( dt2stackcols ) ,1);
dt2 = dt1 << Eval Expr( dt2stack );
eval(substitute(expr(dt2=dt1<<ping),expr(ping),evalexpr(dt2stack) )); //This is tricky!
dt2 << set name( "multiple y-axis stack" );
////////////////////////////////////////////////////////////////Assembling all the parts into the final window that is displayed - order matters a lot
Insert Into( objout, Name Expr( objhlb1 ) );
Insert Into( objvlb, Name Expr( objout ) );
Insert Into( objhlb2, Name Expr( objvlb ) );
Insert Into( objhlb2, Name Expr( obj ) );
Insert Into( objwin, Name Expr( objhlb2 ) );
Eval( Eval Expr( objwin ) ); //Evaluating all the assembled parts after all the expressions are evaluated
gb << size( 600, 285 ); //if you want to format the graph, send messages to gb
Find a more detailed set of notes on how the script works here:
https://community.jmp.com/t5/Byron-Wingerd-s-Blog/Multiple-Y-Axis-Graphs/ba-p/52000