ここにいくつかのスターターJSLがあります。 (ありがとう@txnelson .) 必要なことを実行するには、いくらかの愛と注意が必要ですが、問題と解決策を示していると思います。 このコードは、JMP が SVG ファイルにコマンドを書き込む順序に (とりわけ) 依存しているため、JMP が新しいメジャー リリースを持ったときに壊れることは間違いありません。この順序は、将来さまざまな理由で変更される可能性があります。 (また、それで遊んだ後、少なくともユーザーインターフェイスについてもう少し考えなければ、それが本当にあなたが望むものであるかどうかはわかりません。)
編集 1: JSL は JMP 15 および 16 で実行する必要があります...
編集 2: try(...) を追加しようとしています
// *** choose big class or random data, below ***
//
// example JSL for auto-zoom on constellation plot
//
// because the constellation plot uses its own internal coords for the diagram and does
// not expose them, this code may break at any time in the future. Or past. Or now.
// This JSL saves the graph as SVG and attempts to load the SVG as XML and discover
// where the node IDs are plotted. It uses that info to reposition the axes when rows
// are selected. This JSL uses some global flags to decide if the graph is being used
// for row selection, in which case the axis update is delayed so the data does not
// jump around under the selection rectangle. Part of that fix means an extra mouse
// move is required to trigger the redraw. This JSL also uses some features of XML
// import to deal with the SVG pattern. If the SVG writer changes the way it handles
// text or ellipses, it will need rework.
If( 0, // 1 for big class, 0 for big data
dt = Open( "$sample_data/big class.jmp" );
dt << Graph Builder( Size( 525, 454 ), Show Control Panel( 0 ), Variables( X( :weight ), Y( :height ) ), Elements( Points( X, Y, Legend( 3 ) ) ) );
zoom = 10;//<<<<<<<<<<<< adjust for number of nodes
hc = dt << Hierarchical Cluster( Y( :height, :weight ), Color Clusters( 1 ), Method( "single" ), Standardize By( "Columns" ),
Show Dendrogram( 0 ), Dendrogram Scale( "Distance Scale" ), Number of Clusters( 5 ), Constellation Plot( 1 ) );//
,// else make some random data with clusters. 3000 is beginning to lag enough to not be fun, but still usable...
nObs = 2000;
dt = New Table( "Untitled",
New Column( "name", values( 1 :: nObs ) ), // in this table, or bigclass, name is the id
New Column( "x", Numeric, "Continuous", Format( "Best", 12 ), values( J( nObs, 1, Random normal(random integer(1,3),.25 ) ) )),
New Column( "y", Numeric, "Continuous", Format( "Best", 12 ), values( J( nObs, 1, Random normal(random integer(1,3),.25 ) ) ) ),
Set Label Columns( :name )
);
dt:name << datatype( "character" );// it needs to match the character data from XML, like bigclass
dt << Graph Builder( Size( 525, 454 ), Show Control Panel( 0 ), Variables( X( :x ), Y( :y ) ), Elements( Points( X, Y, Legend( 3 ) ) ) );
zoom = 100;//<<<<<<<<<<<< adjust for number of nodes
hc = dt << Hierarchical Cluster( Y( :x, :y ), Color Clusters( 1 ), Method( "FastWard" ), Standardize By( "Columns" ),
Show Dendrogram( 0 ), Dendrogram Scale( "Distance Scale" ), Number of Clusters( 9 ), Constellation Plot( 1 ) );
);
// make an SVG picture of the constellation, name it with an XML extension
CP = Report( hc )[Outline Box( "Constellation Plot" )];
(CP[framebox( 1 )]) << savepicture( "$temp/constellationPlot.svg", "svg" );
try( deletefile("$temp/constellationPlot.svg.xml") );
renamefile("$temp/constellationPlot.svg","constellationPlot.svg.xml");
// open it once to get the constellation coordinates for each row
// this is complicated because one row is at zero degrees and becomes a special case.
// "ellipse" is the marker's center for either case, but text is nested deeper (or not) for
// the two cases, and needs a separate row-maker. By forcing both text fields to be named
// "nodename" they are put in the same column.
// the coords are typically 0..500, see bounding box below.
dtPositions = Open(
"$TEMP/constellationPlot.svg.xml",
XML Settings(
Stack( 1 ),
Row( "/svg/g/g/g/g/g/g/g" ),
Row( "/svg/g/g/g/g/g/text" ),
Col( "/svg/g/g/g/g/g/text", Column Name( "nodename" ), Fill( "Use Once" ), Type( "Character" ) ),
Col( "/svg/g/g/g/g/g/ellipse/@cx", Column Name( "xx" ), Fill( "+", "/svg/g/g/g/g/g/ellipse/@cx" ), Type( "Numeric" ) ),
Col( "/svg/g/g/g/g/g/ellipse/@cy", Column Name( "yy" ), Fill( "+", "/svg/g/g/g/g/g/ellipse/@cy" ), Type( "Numeric" ) ),
// above: ellipse is before text. There are many unlabelled ellipse, the +...@ drops old ones as new ones show up.
// most recent ellipse center is combined with the text ID for the nodename.
// either nodename goes in the nodename col; one happens sometimes when a label is horizontal
// (0 degrees leave out the transform, see alice single-link case).
Col( "/svg/g/g/g/g/g/g/g/text", Column Name( "nodename" ), Fill( "Use Once" ), Type( "Character" ) )
),
XML Wizard( 0 )// use 1 to reopen wizard if something needs to change for SVG in the future
);
// open it again to get the bounding box coordinates (typ 0..500). They are needed for scaling the data above into 0..1 coords.
dtSize = Open(
"$temp/constellationPlot.svg.xml",
XML Settings(
Stack( 1 ),
Row( "/svg" ),
Col( "/svg/g/g/rect/@x", Column Name( "xmin" ), Fill( "Use Once" ), Type( "Numeric" ) ),
Col( "/svg/g/g/rect/@y", Column Name( "ymin" ), Fill( "Use Once" ), Type( "Numeric" ) ),
Col( "/svg/g/g/rect/@width", Column Name( "xwide" ), Fill( "Use Once" ), Type( "Numeric" ) ),
Col( "/svg/g/g/rect/@height", Column Name( "yhigh" ), Fill( "Use Once" ), Type( "Numeric" ) )
),
XML Wizard( 0 )
);
// add the xx and yy cols back into the source table. You probably don't want to save these later. Maybe you'd rather update to the temp file.
dt << Update( With( dtPositions ), Match Columns( :name = :nodename ), Add Columns from Update Table( :xx, :yy ) );
Close( dtPositions, nosave );
// Jim Nelson's code for the row state handler started this...modified a bit. Capture the original axis range.
maxX = CP[axisbox( 2 )] << get Max;
minX = CP[axisbox( 2 )] << get Min;
maxY = CP[axisbox( 1 )] << get Max;
minY = CP[axisbox( 1 )] << get Min;
f = Function( {a}, // this "a" is only the rows that just changed state, sel or unsel
If( outside, // the mouse is outside of the graph, OK to update the axes
// for zooming, we need *all* the selected rows to get a bounding box
a = (dt << getselectedrows);
If( N Rows( a ) > 0,
minSelectedX = 99e99;// the loop below captures min and max for x and y
maxSelectedX = -99e99;
minSelectedY = 99e99;
maxSelectedY = -99e99;
For( i = 1, i <= N Rows( a ), i += 1,
If( Selected( Row State( dt, a[i] ) ),
xxx = (dt:xx[a[i]] - dtsize:xmin[1]) / dtsize:xwide[1]; // SVG 0..500 -> unit space 0..1
yyy = (dt:yy[a[i]] - dtsize:ymin[1]) / dtsize:yhigh[1];
// the axis range is typically -10..10, and the 0..500 SVG range has to be mapped back to the axis
xxx = minX + xxx * (maxX - minX); // 0..1 -> axis range. This is the Constellation Plot's internal coords
yyy = minY + (1 - yyy) * (maxY - minY);
minSelectedX = Min( minSelectedX, xxx ); // capture the range of the selected rows
maxSelectedX = Max( maxSelectedX, xxx );
minSelectedY = Min( minSelectedY, yyy );
maxSelectedY = Max( maxSelectedY, yyy );
)
);
If( minSelectedX <= maxSelectedX & minSelectedY <= maxSelectedY, //if we got something captured, zoom in
z = max( (maxX - minX) / zoom, (maxY - minY) / zoom );
CP[axisbox( 2 )] << Min( minSelectedX - z ); // the zoom is <= than zoom/2 depending on range of selection
CP[axisbox( 2 )] << Max( maxSelectedX + z );
CP[axisbox( 1 )] << Min( minSelectedY -z);
CP[axisbox( 1 )] << Max( maxSelectedY +z);
);//
, // unzoom, handle nothing selected
CP[axisbox( 2 )] << Min( MinX );
CP[axisbox( 2 )] << Max( MaxX );
CP[axisbox( 1 )] << Min( MinY );
CP[axisbox( 1 )] << Max( MaxY );
);//
, //
selectionNeedsUpdating = 1;// remember something changed while the cursor was inside the graph
)
);
rs = dt << make row state handler( f );
// use mousebox to detect if the cursor is over the graph and the axis changes need to be locked out
CPP = CP << parent;
Insert Into( CPP, mb = MouseBox( Remove From( cPP, 1 ) ) );
// mostly need to prevent moving the axes during a rectangle select...
outside = 1;// global, is the cursor outside the graph?
selectionNeedsUpdating = 0; // anything happed recently?
mb << setTrack(
Function( {this, clickpt}, // in the graph, the mouse is not pressed
outside = (clickpt[1] < 0);// clickpt is -1,-1 when leaving the graph box
If( selectionNeedsUpdating,
selectionNeedsUpdating = 0;
outside = 1;// allow the update to happen, pretend the mouse is out of the graph
temp = Row State( dt, 1 );
Row State( dt, 1 ) = Selected State( 1 )/*tickle the handler to trigger the update*/;
Row State( dt, 1 ) = Selected State( 0 );
Row State( dt, 1 ) = temp; /* restore*/
);
)
) << setTrackEnable( 1 );
// hook into the graph's update by adding a graphic script.
// use that hook to show the selected points...more
(CP[framebox( 1 )]) << addgraphicsScript(
//"back", // color goes under the black dot
"front",// color goes on top
a = (dt << getselectedrows);
Fill Color( RGB Color( .2, .8, .8 ) ); // medium cyan
transparency(.5);// for "front" fill, solid is better for "back"
For( i = 1, i <= N Rows( a ), i += 1,
xxx = (dt:xx[a[i]] - dtsize:xmin[1]) / dtsize:xwide[1];
yyy = (dt:yy[a[i]] - dtsize:ymin[1]) / dtsize:yhigh[1];
xxx = minX + xxx * (maxX - minX); // same conversion described earlier
yyy = minY + (1 - yyy) * (maxY - minY);
// circle's radius is 1/50 of the graph's height
size = ((CP[axisbox( 1 )] << get Max) - (CP[axisbox( 1 )] << get Min)) / 50;
Circle( {xxx, yyy}, size, "FILL" );
);
);