In addition to this tutorial, there is a folder of documentation on the source code for the library. Perhaps you do not need certain buttons or GUI components currently available or perhaps you wish to add a button. Consult the JavaDoc documentation on GrapherGUI for an introduction on the source.
Because all the work is done within the GUI class, there is quite a lot of freedom in terms of where you place the graphing utility. We have already gone through an example of how to create an applet containing a grapher. This section describes how to create a standalone application using GrapherGUI. The full source code can be found here.
When creating an application, you must declare the following in your main class. The first thing to do is to create your Function objects, SliderMatrix object, and GUI object. After that, just cut and paste the rest because the rest of the code will remain consistent across any program. If you are curious what the code does, consult Sun's documentation on JFrame.
static public void main(String[] args) { // ... SliderMatrix, Function, and GUI setup here ... // this is the object which will contain the GrapherGUI objects JPanel p = new JPanel(); // add you GUI object to the JPanel p.add(gui); // create an instance of the window JFrame f = new JFrame("Grapher"); // This will shut down the java virtual machine // when you close the program f.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent we) { System.exit(0); } }); // set the size of the main window to 510 by 720 pixels f.setSize(510, 720); // place the window 300 pixels to the right of the left side // and 200 pixels down from the top side f.setLocation(300, 200); f.getContentPane().add(p); f.setVisible(true); f.show(); }
To run an application from the command line, use the following command "java Standalone" If your class name is Standalone. Like the first example, you can name your class whatever you want. This procedure is useful for testing as it is not necessary to make an HTML file or to load a browser every time you wish to test your program.
Because GUI inherits from the JPanel. class, you could even embed whatever grapher you create inside a larger Java program as an internal utility. That is outside the scope of this document but this is in practice not difficult.
Java is regarded as being too slow for mathematically intensive tasks. One way of optimizing the speed of your application is to use C or Fortran for mathematical routines. This is possible through the use of the Java Native Interface (JNI).
We want a Function that uses natively compiled code. An example of such an interface between Java and C is given in NativeFunction.java. This class is very similar to the function classes we defined in earlier examples, most importantly in that it extends the Function class.
The method f() and name() are defined but unlike our other functions, we do not do any calculations. f() grabs the values of two parameters and passes them to a new method called native_f.
public native String name(); public native double native_f(double in, double p1, double p2); static { System.loadLibrary("NativeFunction"); }The native keyword indicates that the function will be implemented natively. At the end of this code snippet, we load a library called "NativeFunction". This means that the program will try to look for the native functions defined in this class (name() and native_f) in a shared library called libNativeFunction.so (Note: on Linux/Unix systems)
Now that we have an interface between Java and C, we need to write the native code. How do we know what to functions to define? After compiling NativeFunction.java, use the javah tool to create a C header file for your native code. Run javah:
$ javah NativeFunctionNote that javah takes a class name and not a filename for an argument. We get the following to prototyped functions:
/* * Class: NativeFunction * Method: name * Signature: ()Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_NativeFunction_name (JNIEnv *, jobject); /* * Class: NativeFunction * Method: native_f * Signature: (DDD)D */ JNIEXPORT jdouble JNICALL Java_NativeFunction_native_1f (JNIEnv *, jobject, jdouble, jdouble, jdouble);The first defines the function which will be used for NativeFunction.name() and the second for NativeFunction.native_f().
At first the huge obfuscated variable names and types might seem daunting, but don't worry -- we won't really need to use many of them.
Each JNI function has at least two arguments. The following arguments are the arguments defined in NativeFunction.java. Our native_f() function is very simple - we want to plot x^2 * (g - h). Note that the native implementation of this (Java_NativeFunction_native_1f) function returns a jdouble and takes three other jdouble arguments in addition to the two that are always there. Remember back to our Java source; the first is the independent variable, the others are parametes passed in by our Java program.
You can do a direct cast from jdouble to double:
return (jdouble) ((double) in * (double) in * ((double) g - (double) h));This simplifies matters a bit. Finally, once you have your dependent variable, just cast it back into a jdouble and return it. Our name() function (JNIEXPORT jstring JNICALL Java_NativeFunction_name) is very straightforward. All we need to do is return a Java string by using the NewStringUTF() method that is part of our env variable:
return (*env)->NewStringUTF(env, "My Function Name");This function should never get more complicated than one line. In fact, you probably don't need to implement a native version of this function, but we do so here to show that it is possible to manipulate more than primitive variable types within your native code.
To compile a shared library using GCC under Linux/Unix, issue the following commands:
$ gcc -c NativeFunction.c -o NativeFunction.o $ gcc -shared -o libNativeFunction.so NativeFunction.oMake sure that the compiler knows where to find jni.h and the Java development libraries. You can use the -I and -L arguments for GCC to set this. Now place libNativeFunction.so in a directory where the linker can find it. Alternatively, you can set the $LD_LIBRARY_PATH to point to the directory the shared library is located and run whatever Java application uses it.
So after everything is compiled, you should set the library path and run the program. Find the path to the shared libraries by using pwd command on Unix systems. If you are using the Bash shell, you would issue the following commands:
$ pwd /path/to/native/library $ LD_LIBRARY_PATH=/path/to/native/library java MyApplicationClassNote that this only temporarily sets the library path. To make the change permanent for the rest of your shell session, use the export command. If you are using the C shell (default on Athena systems), set the environment with the following and run:
$ setenv LD_LIBRARY_PATH /path/to/native/library $ java MyApplicationClass
For more complex functions, you should probably create a separate function(s)/file(s) and call them.
Using the JMI interface is relatively easy when using native C/C++ code. To use native code written in other languages such as Fortran, the process requires the additional step of interfacing C with the third language. Since Fortran is very popular in the scientific community, we will detail the process of using a Fortran subroutine as the workhorse for calculating the coordinate set for a function.
Using a native interface other than Java-C/C++ is very platform specific as Java distributions provide no tools for languages other than C/C++. Our example will use the GNU Compiler Collection (GCC) because it has been ported to many different platforms.
The first few steps are the same as the previous example. We need to create a class which inherits and extends the Function class. The method f() and farray() must be declared to be native. The following lines must be somewhere within the class to indicate which library object file to search for the native functions:
static { System.loadLibrary("myLibName"); }
Like before, use javah to create the correct header file. The C code will be the interface between Java and Fortran and as a consequence should be relatively short. As you can see in the following code, we first declare an external function named gwpakt_(). This refers to the Fortran subroutine of the same name in f77alg.f with an underscore tacked onto the end.
#include <jni.h> #include "Native.h" extern "C" void gwpakt_( double *xv, double *av, double *bv, double *xmax, double *xmin, int *m, double *t, double *h, double *sigma); JNIEXPORT void JNICALL Java_Native__1ncf_1interface ( JNIEnv *env, jobject obj, jdouble minx, jdouble maxx, jint density, jdoubleArray indep, jdoubleArray dep, jdouble t, jdouble h, jdouble sigma) { jdouble *indepPtr = env->GetDoubleArrayElements(indep, 0); jdouble *depPtr = env->GetDoubleArrayElements(dep, 0); jdouble *dummy = new jdouble[density]; gwpakt_(indepPtr, depPtr, dummy, &maxx, &minx, &density, &t, &h, &sigma); env->ReleaseDoubleArrayElements(indep, indepPtr, 0); env->ReleaseDoubleArrayElements(dep, depPtr, 0); delete dummy; }There are a few quirks in this code that are important to note. The first is that we create a dummy array which never seems to be used. The Fortran subroutine is one which was developed before and independently of GrapherGUI. Since GrapherGUI only draws one function per Function object, we needed to adapt the code slightly. A method to conserve wasted computing power would be to separate the subroutine into two subroutines.
And here is the declaration of the Fortran subroutine we wish to use. Again, it is very important that you add an underscore in the extern declaration in the C code at the end of the function name.
subroutine gwpakt(xv,av,bv,xmax,xmin,m,t,h,sigma) IMPLICIT NONE
The following is a complete GNU makefile for this project:
CCFLAGS = -O -shared CPPDEFINES = -D __FORTRAN_BUILD__ FFLAGS = -O -shared -g IDIRS = CCLIBS = -lstdc++ OFILES = f77alg.o libNative.so : $(OFILES) gcc $(CCFLAGS) -o libNative.so $(OFILES) $(CPPDEFINES) $(CCLIBS) $(IDIRS) Native.cpp f77alg.o : f77alg.f g77 $(FFLAGS) f77alg.f -o f77alg.o