cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 
Check out the JMP® Marketplace featured Capability Explorer add-in
Choose Language Hide Translation Bar
xxvvcczz
Level III

Is there a way to make the rowstate handler less laggy?

Is it possible to make the rowstate handler only fire on mouse up or after a selection event is completed? 

 

The realtime event causes drag select from my graph to lag and makes the user experience unpleasant.

 

I've gone so far  as to completely rewrite a selection event in the mousetrap thing but I'm having trouble applying it to histograms and I don't even love how it's performing either.

 

It would meet my needs if I could use the selection box the "Arrow(A)" tool at the same time as the mousetrap function so I could simply pick up my selected rows at the end of the moustrap event instead of firing off a million events while dragging the mouse along.

 

Stuff I don't like:
The longer you mouse down move the mouse around while selecting, more and more useless graphics scripts pile into the graph.

I'd like to just have one and only one. I added a loop at the end to kill them all out of the graph which is why they appear to "fade away".
I could just leave them there but they add visual noise to the graph and I don't know what happens when you get too many of them.

 

Edit: when you get too many of them they start to lag. It might be faster to destroy the graph and rebuild it than for looping to remove the left over graphics scripts. Edit 2: It's way faster to destroy the graph and recreate it but then the zoom gets reset, so I'll have to capture more about the state of the graph so I can rebuild it to hide the refresh from the user.

I got the Shift/control click stuff to work it just feels too hacky for production. Especially the control click it has this long lag time after you mouseup and the selection box chases the mouse even when you've already released the button, I think jmp is handling a bunch of stale drag events in the meantime before it stops moving.

 

Or just discard this whole adventure and find a way to have a row state handler that fires on mouse up (that would be ideal).

 

Examples I borrowed code from:
http://www.pega-analytics.co.uk/blog/segmented-regression/

https://community.jmp.com/t5/Discussions/Quick-way-to-compare-two-lists-and-identify-the-uncommon/td...

 

 

Names Default To Here(1);

dt = open("$sample_data\Iris.jmp");
MY_GRAPH_BUILDER = Graph Builder(
	Size( 531, 456 ),
	Show Control Panel( 0 ),
	Variables( X( :Sepal length ), Y( :Sepal width ) ),
	Elements( Points( X, Y, Legend( 3 ) ) ),
		SendToReport(	
		Dispatch({},"Graph Builder",
			FrameBox,
			{Add Graphics Script(MouseTrap( MouseDown(x,y), MouseUp(x,y)))}
			),
			),
);





//MOUSE STUFF
get_box_dims = Function({firstX,lastX,firstY,lastY},
show("get_box_dims");
//show(firstX,lastX,firstY,lastY,modifier_key);

	If (lastX>firstX,
	max_x = lastX; min_x =firstX,
	max_x = firstX;min_x =lastX,
	);

	If (lastY>firstY,
	max_y = lastY; min_y =firstY,
	max_y = firstY;min_y =lastY,
	);


show(min_x,min_y,max_x,max_y);
evallist({min_x,max_x,min_y,max_y});
);

do_normal_select = Function({select_range},
show("normal_select");


dt << Select Where( 

	AsColumn(:Sepal length) >= select_range[1] 
	& AsColumn(:Sepal length) <= select_range[2]
	& AsColumn(:Sepal width) >= select_range[3] 
	& AsColumn(:Sepal width) <= select_range[4]
	
	);
);

do_shift_select = Function({select_range},
	show("shift_select");


	new_rows_to_add = as list(dt << Get Rows Where( 

		AsColumn(:Sepal length) >= select_range[1] 
		& AsColumn(:Sepal length) <= select_range[2]
		& AsColumn(:Sepal width) >= select_range[3] 
		& AsColumn(:Sepal width) <= select_range[4]
		
		)
			
	);

	if (length(new_rows_to_add) > 0,
	curr_rows = as list(dt << Get Selected Rows);
	rows_to_select = curr_rows || new_rows_to_add;
	dt << Select Rows( rows_to_select);
	);
		show(new_rows_to_add,length(new_rows_to_add));
);


do_ctrl_select = Function({select_range},
	show("ctrl_select");
	
	new_rows_to_subtract = associativearray(as list(dt << Get Rows Where( 

		AsColumn(:Sepal length) >= select_range[1] 
		& AsColumn(:Sepal length) <= select_range[2]
		& AsColumn(:Sepal width) >= select_range[3] 
		& AsColumn(:Sepal width) <= select_range[4]
		
		)
			
	));
	
	if (length(new_rows_to_subtract) > 0,
	curr_rows = associativearray(as list(dt << Get Selected Rows));
	show("current_rows_selected",length(curr_rows));
	show("number_rows_to_subtract",length(new_rows_to_subtract));
	intersection=curr_rows;intersection<<Intersect(new_rows_to_subtract);
	uncommon1 = curr_rows; 
	uncommon1<<remove(intersection);
	uncommon1 = uncommon1<<getkeys;

	show(uncommon1);
	show("uncommon array length",length(uncommon1));
	
	uncommon2 = new_rows_to_subtract; 
	uncommon2<<remove(intersection);
	uncommon2 = uncommon2<<getkeys;
	
	new_rows_to_add = as list(uncommon1 || uncommon2);
	
	
	dt << Clear Select;
	dt << Select Rows(new_rows_to_add);
	);

);




lstX = {};
lstY = {};

xpr_select_box = Expr(
	Line Style( "solid" );
	Fill Color( "BlueGreen" );
	Transparency( 0.03 );
	Rect(firstX, firstY, lastX, lastY, 1);
);

 
MouseDown = Function({px,py},


Insert Into(lstX,pX);
Insert Into(lstY,pY);
firstX = lstX[1];
lastX = lstX[NItems(lstX)];	
firstY = lstY[1];
lastY = lstY[NItems(lstY)];	


//show what's being selected
(report(MY_GRAPH_BUILDER)[FrameBox(1)]) << Add Graphics Script(
	Line Style( "solid" );
	Fill Color( "BlueGreen" );
	Transparency( 0.03 );
	Rect(firstX, firstY, lastX, lastY, 1);
);


);

MouseUp = Function({px,py},
		box_dims = get_box_dims(firstX,lastX,firstY,lastY);
		show("mouseUP range",box_dims);
		
		If( Is Shift Key(),
			modifier_key = "shift";do_shift_select(box_dims);,
			Is Control Key(),
			modifier_key = "control";do_ctrl_select(box_dims);,
		//no modifier key
		modifier_key = "none";dt << Clear Select;do_normal_select(box_dims);
		);
		


// I have no idea how this Xpath works but its pretty sweet
	script_list = ((report(MY_GRAPH_BUILDER)[FrameBox(1)]) << XPath("//TopSeg")) << get script;
	for(i=0,i<=length(script_list),i++,
	(report(MY_GRAPH_BUILDER)[FrameBox(1)]) << Remove Graphics Script( 2 );	
	);
	

		lstX = {};
		lstY = {};
		modifier_key = "none";	
);

 

2 ACCEPTED SOLUTIONS

Accepted Solutions
Craige_Hales
Super User

Re: Is there a way to make the rowstate handler less laggy?

Simplify the select where expression as much as possible. I don't see the lag for 150 rows, but for 100K rows it is pretty bad. This gets me a 10X improvement:


do_normal_select = Function( {select_range},
    {start, stop, sr1, sr2, sr3, sr4}, 
//show("normal_select");

    start = HP Time();

    sr1 = select_range[1];
    sr2 = select_range[2];
    sr3 = select_range[3];
    sr4 = select_range[4];
    dt << Select Where( sr1 <= :Sepal length <= sr2 & sr3 <= :Sepal width <= sr4 );
   
//   dt << Select Where(
//	AsColumn(:Sepal length) >= select_range[1] 
//	& AsColumn(:Sepal length) <= select_range[2]
//	& AsColumn(:Sepal width) >= select_range[3] 
//	& AsColumn(:Sepal width) <= select_range[4]
//	);
	
    stop = HP Time();
    Write( Eval Insert( "\!nselect where ^stop-start^" ) );
);

There are three changes: removing AsColumn, lifting the subscripting out of the implicit loop, and chaining the compare operator.

 

@Audrey_Shull  - I wish the profiler would work with this. The profiler stops before the window opens, so I never get to study the time spent in the interactive selection.

Craige

View solution in original post

Craige_Hales
Super User

Re: Is there a way to make the rowstate handler less laggy?

For the multiple scripts, you could do it all in a single script and use a missing value in firstX to turn it off. Here's a q&d hack to only add one copy of a second script, or freshen the graph if not adding, and still use your logic to remove it when done.

 

MouseDown = Function( {px, py}, 
    Insert Into( lstX, pX );
    Insert Into( lstY, pY );
    firstX = lstX[1];
    lastX = lstX[N Items( lstX )];
    firstY = lstY[1];
    lastY = lstY[N Items( lstY )];	

// quick and dirty hack to only add the script once
    If( N Items( ((Report( MY_GRAPH_BUILDER )[FrameBox( 1 )]) << XPath( "//TopSeg" )) << get script ) < 2,
        (Report( MY_GRAPH_BUILDER )[FrameBox( 1 )]) << Add Graphics Script(
            Line Style( "solid" );
            Fill Color( "BlueGreen" );
            Transparency( 0.3 );
            Rect( firstX, firstY, lastX, lastY, 1 );
        );
    , // ELSE make sure to redraw using new values (might work without this)
        (Report( MY_GRAPH_BUILDER )[FrameBox( 1 )]) << inval << updatewindow;
    );
);
Craige

View solution in original post

11 REPLIES 11
jthi
Super User

Re: Is there a way to make the rowstate handler less laggy?

Different state handlers are a mess in JMP and should be in my opinion be avoided if possible . I have created wish list item for them to be improvedImprove JMP's state handlers (hover label, row state handler, filter state handler, scheduler, ...)  

-Jarmo
xxvvcczz
Level III

Re: Is there a way to make the rowstate handler less laggy?

Yeah they've always been hard to use.

 

I have a need for something in order to get realtime response, I'm hoping the row state handler is laggy because it was trying to write to the console and that it might be faster for other tasks.

 

Rewriting the selection criteria works well enough for my scatter chart though it's kind of janky, my Distribution histograms on the other hand don't seem to like it.

 

jthi
Super User

Re: Is there a way to make the rowstate handler less laggy?

Dropping prints should make it faster, hiding data table might also help a little. Also checking that there is some "real" change made between two events can help but depending on application might be difficult to check. 

-Jarmo

Re: Is there a way to make the rowstate handler less laggy?

What are you trying to do by dragging in the Graph Builder frame box? If it is to select rows and then respond, a row state handler associated with the data table should suffice.

xxvvcczz
Level III

Re: Is there a way to make the rowstate handler less laggy?

It's super laggy.

 

I'm trying to stream http requests out from JSL to a websertver to generate a live realtime dashboard based on jmp selections. I thought I could do it with the row state handler but it's so laggy it kills the user experience. Once you scoop up more than 500 points the performance plummets.

 

What I have right now is make a selection on the graph, click a button, then the http report renders.

I want to get rid of that button click.

 

So what I'm trying to do now is upon mouse up, send a batch of data to my http server to generate my dashboard. Just like a javascript event.

 

 

jthi
Super User

Re: Is there a way to make the rowstate handler less laggy?

I would just use the button (that is what I have done), but maybe something could be built with MouseTrap (or MouseBox?)

Names Default To Here(1);
dt = Open("$SAMPLE_DATA/Big Class.jmp");

gb = dt << Graph Builder(
	Size(529, 457),
	Show Control Panel(0),
	Variables(X(:weight), Y(:height)),
	Elements(Points(X, Y, Legend(3)))
);
fb = Report(gb)[FrameBox(1)];

i = 0;
fb << Add Graphics Script(
	Mousetrap(
		If(i == 0,
			x0 = x; y0 = y;
		);
		exx = x; exy = y;
		i++;
	,	
		i = 0;
		show("RELEASED");
	);
	Rect(x0, y0, exx, exy);
);
-Jarmo
xxvvcczz
Level III

Re: Is there a way to make the rowstate handler less laggy?

Tried this but getting.

Names Default To Here(1);
dt = Open("$SAMPLE_DATA/Big Class.jmp");

gb = dt << Graph Builder(
	Size(529, 457),
	Show Control Panel(0),
	Variables(X(:weight), Y(:height)),
	Elements(Points(X, Y, Legend(3)))
);
fb = Report(gb)[FrameBox(1)];

i = 0;
fb << Add Graphics Script(
	Mousetrap(
		If(i == 0,
			x0 = x; y0 = y;
		);
		exx = x; exy = y;
		i++;
	,	
		i = 0;
		show("RELEASED");
	);
	Rect(x0, y0, exx, exy);
);

 

Name Unresolved: x0 in access or evaluation of 'x0' , x0/*###*/

at line 24 in Script 2.jsl

 

 

I'm not super familiar with the mousetrap(); function,  doesn't it need 2x internal functions?
MouseTrap( MouseDown(x,y), MouseUp(x,y)));

 

I do want to figure this out though because I think a lot of my lag is coming from streaming the points into 2x lists and then selecting the first and last from those lists.

 

 

Craige_Hales
Super User

Re: Is there a way to make the rowstate handler less laggy?

I doubt this is documented very well, anywhere... @Audrey_Shull 

MouseTrap and the Handle function with similar behavior are not executed in-line like Rect() and the other normal graphics script functions like Rect().

When the flow of control in your graphics script goes through Handle() or MouseTrap(), the function does nothing in the case of MouseTrap, or draws a tiny rectangle at the handle position for Handle. When you click on a graph with a graphic script, JMP looks through the script for MouseTrap or Handle functions and calls them, in order, until one of them takes the point.

So, in your example, until you click, the code that sets x0 has never run. But the Rect code will run.

I believe, without trying it, you could have several Handles() followed by a MouseTrap(), and JMP would take the click point and pass it to each Handle() until one of them takes it, or the MouseTrap() which will always take it.

Rect() can be used in a for loop to draw many rectangles. You can't make multiple handles by putting Handle(...) in a loop. If you needed to create a variable number of handles, you could either use MouseTrap() and a list of x,y locations to search, or you could use some form of expression manipulation to build multiple Handle() calls.

This also means MouseTrap/Handle must be in the Graphics Script, not in a user function the graphics script calls. Rect, on the other hand, can be in a user function called from the graphics script.

If you put two MouseTraps in a graphics script, I believe the 2nd one can never be reached because the 1st will always accept the click.

New Window( "Example",
    exx = 20,
    exy = 50;
    Graph Box(
        Frame Size( 200, 200 ),
        If( 0, // note that it still works
            Mousetrap(
                exx = x;
                exy = y;
            )
        );
        Circle( {0, 0}, Sqrt( exx * exx + exy * exy ) );
    );
);
Craige
Craige_Hales
Super User

Re: Is there a way to make the rowstate handler less laggy?

Simplify the select where expression as much as possible. I don't see the lag for 150 rows, but for 100K rows it is pretty bad. This gets me a 10X improvement:


do_normal_select = Function( {select_range},
    {start, stop, sr1, sr2, sr3, sr4}, 
//show("normal_select");

    start = HP Time();

    sr1 = select_range[1];
    sr2 = select_range[2];
    sr3 = select_range[3];
    sr4 = select_range[4];
    dt << Select Where( sr1 <= :Sepal length <= sr2 & sr3 <= :Sepal width <= sr4 );
   
//   dt << Select Where(
//	AsColumn(:Sepal length) >= select_range[1] 
//	& AsColumn(:Sepal length) <= select_range[2]
//	& AsColumn(:Sepal width) >= select_range[3] 
//	& AsColumn(:Sepal width) <= select_range[4]
//	);
	
    stop = HP Time();
    Write( Eval Insert( "\!nselect where ^stop-start^" ) );
);

There are three changes: removing AsColumn, lifting the subscripting out of the implicit loop, and chaining the compare operator.

 

@Audrey_Shull  - I wish the profiler would work with this. The profiler stops before the window opens, so I never get to study the time spent in the interactive selection.

Craige