Interactive Function Manipulation for Mathematica 5.2
(and Earlier Versions)

Andreas Lauschke
mooniac@gmail.com

The following Mathematica functions demonstrate the use of methods from Java Swing to create an interactive environment for function manipulation. They are based on the J/Link Java connectivity package and use Java Swing code in Mathematica.

Discussion

The function manipulate takes at least two arguments, the first argument is the expression to be evaluated for various settings of the slider(s), which are defined in the second and possibly more argument(s). The entire function is wrapped in a JavaBlock to ensure that all temporary Java objects that are created for this task are released after manipulate is used. Everything is displayed on two JPanels, the first JPanel holds the slider(s) and the second JPanel (a MathGraphicsJPanel, inherited from JPanel), shows the output. For every slider a horizontal box is created that holds the slider, which is configured by setting various properties such as minimum and maximum tick spacing, etc., and a small border around it.

The manipulate function makes use of the ImplementJavaInterface function that J/Link provides, which is a very convenient way to implement practically every Java interface with method calls into the Mathematica kernel. In this case only the changeListener needs to be registered with the slider for which a stateChange event calls into a handler function (computeexpr). This event handler function then takes the expression to be manipulated and replaces the variables with the corresponding values that the slider(s) are set to, and sets the string representation of the resulting expression to the setMathCommand property of the MathGraphicsJPanel instance.

The code can easily be adapted to suit different needs, e. g. to place multiple sliders in a row/column layout, instead of the default layout that just displays one column of all sliders. Borders, sizes, colors, backgrounds, shapes, labels, etc., can be changed, basically everything that Java Swing supports in a manner that can be scripted in Mathematica can be used. One could add additional Swing components such as push buttons, spinners, radio buttons, drop-down menus, etc., provided the corresponding event listeners are registered and the event handlers are defined with ImplementJavaInterface.

It is highly recommended to download the latest Java runtime and use it instead of the redistributable Java that Mathematica uses by default. Java 5 and Java 6 have much improved user-interface elements and use better internal Java code than the earlier versions 1.4.0 and 1.4.2 used as defaults in Mathematica versions 5.0 through 5.2. In that case change the variable javalocation to the full path to the file java.exe (see example below in the code).

The manipulate Function

Off[General::"spell"];
Off[General::"spell1"];

This loads the package.

<< JLink`

Set javalocation to the full path of the java.exe file of your Java installation if you want to specify a particular Java runtime. For example:

javalocation="E:\\Program Files\\Java\\jre1.5.0_06\\bin\\java.exe";
javalocation="E:\\Program Files\\Java\\jre1.5.0_08\\bin\\java.exe";
javalocation="E:\\Program Files\\Java\\jre1.6.0\\bin\\java.exe";
     
Leave it "" to use the default one.

javalocation = "";

We need the first argument of manipulate in a held form so that the string representation of the expression to be manipulated can be passed to the setMathCommand property.

SetAttributes[manipulate, HoldFirst];

This defines the manipulate main function.

\(\(manipulate[expr_, 
          tranges__] := 
JavaBlock[
Module[{window, topPanel, 
              plotPanel, title, computeexpr, tsliderListener, sliderlist, r1, 
              r2, r3, trow, tslider, 
              ranges = {tranges}}, 

 (*\ 
              Launch\ the\ Java\ runtime\ *) 
If[
              javalocation =!= "", 
              InstallJava[CommandLine -> javalocation], 
              InstallJava[]]; 

 (*\ 
              Create\ the\ window\ and\ position\ \(\(it\)\(.\)\)\ *) 
window = 
              JavaNew["com.wolfram.jlink.MathJFrame"]; 
window@setLocation[1, 1]; 
window@
              setTitle["Manipulate"]; 

 (*\ 
              Create\ the\ two\ panels . \ topPanel\ holds\ the\ sliders . \ 
                  plotPanel\ holds\ the\ \(\(output\)\(.\)\)\ *) 
topPanel = 
              JavaNew["javax.swing.JPanel"]; 
topPanel@
              setLayout[
                JavaNew["javax.swing.BoxLayout", topPanel, 
                  BoxLayout`YUAXIS]]; 
plotPanel = 
              JavaNew["com.wolfram.jlink.MathGraphicsJPanel"]; 
LoadJavaClass["java.awt.Color"]; 
plotPanel@
              setBackground[Color`white]; 
plotPanel@
              setPreferredSize[
                JavaNew["java.awt.Dimension", 500, 
                  500]]; 

 (*\ 
              Add\ the\ two\ panels\ to\ the\ top\ and\ bottom\ of\ the\ \(\(window\)\(.\)\)\ *) 
LoadJavaClass["java.awt.BorderLayout"]; 
window@\(getContentPane[]@
                add[topPanel, 
                  ReturnAsJavaObject[
                    BorderLayout`NORTH]]\); 
window@\(getContentPane[]@
                add[plotPanel, 
                  ReturnAsJavaObject[
                    BorderLayout`SOUTH]]\); 

 (*\ 
              The\ following\ Table\ creates\ one\ slider\ per\ variable\ and\ sets\ properties\ for\ them\ *) 
LoadJavaClass["javax.swing.BorderFactory"]; 
LoadJavaClass["javax.swing.Box"]; 
sliderlist = 
              Table[
trow = 
                  Box`createHorizontalBox[]; 
tslider = 
                  JavaNew["javax.swing.JSlider", 
                    r1 = \(ranges[\([i]\)]\)[\([2]\)], 
                    r2 = \(ranges[\([i]\)]\)[\([3]\)], \(ranges[\([i]\)]\)[\([4]\)]]; 
tslider@
                  setValue[\(ranges[\([i]\)]\)[\([2]\)]]; 
tslider@setSnapToTicks[True]; 
tslider@
                  setPaintLabels[True]; 
tslider@
                  setPaintTicks[True]; 
tslider@
                  setMinorTickSpacing[1]; 
tslider@
                  setMajorTickSpacing[
                    If[\((r3 = r2 - r1)\) ≥ 20, Floor[r3/5], 
                      1]]; 
tslider@
                  setMaximumSize[
                    JavaNew["java.awt.Dimension", 400, 
                      50]]; 
tslider@
                  setBorder[
                    BorderFactory`createEmptyBorder[0, 20, 0, 
                      20]]; 
tsliderListener = 
                  ImplementJavaInterface["javax.swing.event.ChangeListener", {"stateChanged" -> 
                        ToString[computeexpr]}]; 
tslider@
                  addChangeListener[
                    tsliderListener]; 

trow@
                  add[JavaNew["javax.swing.JLabel", "Parameter " <> 
                        ToString[\(ranges[\([i]\)]\)[\([1]\)]]]]; 
trow@add[tslider]; 
topPanel@
                  add[trow]; 
tslider
, {i, Length@ranges}]; 

window@
              pack[]; 

 (*\ 
              Define\ the\ function\ to\ be\ called\ when\ the\ slider\ is\ \(\(dragged\)\(.\)\)\ *) 
computeexpr[___] := 
Module[{func, about, rng, sliderVal, series, 
                  plot}, 
sliderVal = 
                  slider@getValue[]; 

Block[{$DisplayFunction = 
                      Identity}, 
\(plot = \((Unevaluated[
                            expr])\) /. 
                        Table[\(ranges[\([i]\)]\)[\([1]\)] -> 
                            ToExpression[sliderlist[\([i]\)]@getValue[]], {i, 
                            Length@
                              ranges}];\)
]; 
plotPanel@
                  setMathCommand[
                    ToString[plot, 
                      InputForm]]
]; 
 (*\ Now\ display\ the\ window\ in\ modal\ form\ *) 
window@
              setModal[]; 
JavaShow[
              window]; 
computeexpr[]; 
DoModal[];
]
];\)\)

Examples

The following examples demonstrate that almost anything that depends on numeric quantities that can be changed can be displayed with a manipulate. We use Graphics objects, Graphics3D objects, strings, numbers, and TableForm output.

This example shows a simple rectangle with a color that is determined by the sliders.

manipulate[
      Graphics[{{RGBColor[a/100, b/100, c/100], 
            Rectangle[{0, 0}, {3, 1}]}}], {a, 0, 100, 0}, {b, 0, 100, 0}, {c, 
        0, 100, 0}]

Here we rotate a line clockwise around the origin, from 0 through 2 Pi.

manipulate[
      Graphics[{Line[{{0, 0}, {Sin[t/100], Cos[t/100]}}], Red, 
          PointSize[0.02], Point[{Sin[t/100], Cos[t/100]}], Blue, 
          Point[{0, 0}]}, 
        PlotRange -> {{-1.2, 1.2}, {-1.2, 1.2}}, 
        AspectRatio -> 1], {t, 0, Round[2  Pi 100], 1}]

This example shows a Sine curve for which the sliders set frequency, phase shift, number of PlotPoints, and color.

manipulate[
      Plot[Sin[a x + b/10], {x, -Pi, Pi}, PlotPoints -> c, 
        PlotStyle -> Hue[d/100]], {a, 1, 4, 1}, {b, 0, Round[10 2 Pi], 
        1}, {c, 25, 50, 25}, {d, 0, 100, 0}]

This example is a 3-dim curve for which the sliders define frequency and number of PlotPoints.

manipulate[
      Plot3D[Sin[a x y], {x, -Pi, Pi}, {y, -Pi, Pi}, 
        PlotPoints -> b], {a, 1, 4, 1}, {b, 25, 50, 25}]

The following 3-dim Sine curve can be moved in the x-y plane with the first two sliders, the third slider sets the number of PlotPoints.

manipulate[
      Plot3D[Sin[x y], {x, -Pi + Pi a/100, 
          Pi + Pi a/100}, {y, -Pi + Pi b/100, Pi + Pi b/100}, 
        PlotPoints -> c], {a, 0, 100, 1}, {b, 0, 100, 1}, {c, 25, 50, 
        25}]

This is a 3-dim Plot that shows the unit disk at a value of 1 and 0 outside. The volume is Pi when the slider is moved all the way to the right as can be seen with Integrate[Boole[x^2+y^2<1],{x,-1,1},{y,-1,1}]

manipulate[
      Plot3D[Boole[x^2 + y^2 < a/500], {x, -1, 1}, {y, -1, 1}, 
        PlotPoints -> b], {a, 0, 500, 0}, {b, 25, 100, 25}]

This shows a Plot of the Bessel function of the first kind with a parameter determined by the slider.

manipulate[Plot[BesselJ[a, x], {x, 0, 20}], {a, 1, 10, 1}]

This example shows that the output doesn't have to be graphical, it can be a string.

\(manipulate[
      Part[{"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"}, a], {a, 1, 7, 1}]\)

This plot shows in realtime the solution of an ODE which depends on a parameter set by the slider.

manipulate[
      Plot[Evaluate[
          y[x] /. NDSolve[{(y^′)[x] == Sin[x^(a/2)], 
                y[0] == 0}, y[x], {x, 0, 10}]], {x, 0, 10}, 
        PlotPoints -> 125, PlotRange -> All], {a, 0, 7, 
        0}]

Notice how terrible the Taylor polynomial compares against the continued fraction expansion of the ArcTan function. (In fact, to compute Pi as 4 ArcTan[1] to 7 correct digits only 10 continued fraction terms are required, while even 10 million terms are not sufficient to attain the same accuracy when using the Taylor polynomial! This continued fraction expansion is identical to the (n+1,n+1)-Padé expansion at 0.)

arctancf[n_] := 
        x/(1 + 
              Fold[(x #2)^2 /(2 #2 + 1 + #1) &, 0, 
                Reverse@Range@n]);
taylor[n_] := 
        Sum[(-1)^k x^(2  k + 1)/(2  k + 1), {k, 0, 
            n}];
manipulate[
      Plot[Evaluate@{ArcTan@x, arctancf@n, taylor@n}, {x, -2 Pi, 2  Pi}, 
        PlotRange -> {-Pi/2, Pi/2}, 
        PlotStyle -> {Red, Blue, Green}], {n, 1, 20, 1}]

Here we plot a 3dim curve, parametrized by one variable.

manipulate[
      ParametricPlot3D[{Cos[5  t/100], Sin[3  t/100], Sin[t/100]}, {t, 0.01, 
          lim}, PlotPoints -> 300, 
        PlotRange -> {{-1, 1}, {-1, 1}, {-1, 1}}], {lim, 0, 
        Round[100 2 Pi], 1}]

Here we expand n terms of an algebraic expression.

manipulate[Expand[(1 + a)^n], {n, 0, 50, 1}]

Here we factor the expression x^n-1 of up to 50 terms.

manipulate[Factor[x^n - 1], {n, 0, 50, 1}]

This outputs various functions of n in a framed GridBox.

\(manipulate[
      FrameBox@GridBox[
          Prepend[\({#, Fibonacci@#, Prime@#, PrimePi@#, 2^#} &\) /@ 
              Range@n, {"n", "Fibonacci[n]", "Prime[n]", "PrimePi[n]", "2^n"}], RowLines -> True, 
          ColumnLines -> True], {n, 1, 25, 1}]\)

This compares the Herglotz series of Pi Cot[Pi x] with the exact function.

herglotz[deg_] := 
        1/x + Sum[
            1/(x + n) + 1/(x - n), {n, 1, 
              deg}];
manipulate[
      Plot[{Pi Cot[x Pi], herglotz[n]}, {x, -4 Pi, 4  Pi}, 
        PlotStyle -> {Red, Blue}], {n, 1, 10, 1}]

GraphPlot works on disconnected graphs.

<< DiscreteMath`GraphPlot`
manipulate[
      If[n > 0, 
        GraphPlot[Table[i -> Mod[i^2, n], {i, 0, n - 1}]], ""], {n, 
        0, 500, 1}]

This shows the n-Butterfly Graphs.

<< DiscreteMath`GraphPlot`
manipulate[ShowGraph@ButterflyGraph[n], {n, 1, 5, 1}]

This shows the n,k-DeBruijn Graphs.

<< DiscreteMath`GraphPlot`
manipulate[
      ShowGraph@DeBruijnGraph[n, k], {n, 1, 3, 1}, {k, 1, 3, 1}]

By manipulating the ViewPoint with the slider you can effectively rotate the 3d graphic.

Off[Arg::"indet"]; 
    manipulate[
      Plot3D[Arg[x + I y], {x, -Pi, Pi}, {y, -Pi, Pi}, 
        PlotPoints -> b,  
        ViewPoint  ->  {5 Cos[alpha/100],  5 Sin[alpha/100],  2},  
        SphericalRegion -> True,   Axes  ->  False], {alpha, 0, 
        Round[2 Pi 100], 0}, {b, 25, 75, 50}]

This example shows two curves the lengths of which are adjusted with the slider.

warning: can be slow on slower machines!

tfunc[a_, 
          b_] := (
Block[{$DisplayFunction = 
                Identity}, 
pl1 = 
              ParametricPlot3D[{x + 1/10000, y + 1/10000, 
                  Arg[x + I y]}, {x, -Pi, Pi}, {y, -Pi, Pi}, 
                PlotPoints -> b]; 
pl2 = 
              ParametricPlot3D[{Pi (-Cos@t), Pi (-Sin@t), 
                  t - Pi + 0.01}, {t, 0, a + 1/10000}, 
                PlotPoints -> b]; 
pl3 = 
              ParametricPlot3D[{1/2 Pi (-Cos@t), 
                  1/2 Pi (-Sin@t), t - Pi + 0.01}, {t, 0, 
                  a + 1/10000}, 
                PlotPoints -> 
                  b];
]; 
Graphics3D[
            Join[pl1[[1]], {RGBColor[1. , 0. , 0. ], {Thickness[0.005], 
                  pl2[[1]]}}, {RGBColor[1. , 0. , 
                  0. ], {Thickness[0.005], 
                  pl3[[1]]}}]]
);
manipulate[
      tfunc[a/100, b], {a, 0, Floor[2 Pi 100], 1}, {b, 25, 50, 
        25}]

Several Fourier sum expansions of continuous and discontinuous functions.

Off[NIntegrate::"ploss"];
Off[NIntegrate::"pwncvb"];

fsum[f_, {x_Symbol, minusL_?NumericQ, L_?NumericQ}, m_Integer] := 
        If[minusL == -L, 
          Chop[(1/(2*L))*NIntegrate[f, {x, -L, L}] + (1/L)*
                Sum[NIntegrate[f*Cos[n*Pi*x/L], {x, -L, L}]*
                      Cos[n*Pi*x/L] + 
                    NIntegrate[f*Sin[n*Pi*x/L], {x, -L, L}]*
                      Sin[n*Pi*x/L], {n, 1, m}]], 
          Print["Interval must be of the form [-L,L]."]; {}];

With[{func = Abs@x}, 
      manipulate[
        Plot[Evaluate@{func, fsum[func, {x, -5, 5}, n]}, {x, -5, 5}, 
          PlotStyle -> {Red, Blue}], {n, 0, 25, 1}]]

With[{func = Piecewise[{{0, x < 0}}, x]}, 
      manipulate[
        Plot[Evaluate@{func, fsum[func, {x, -5, 5}, n]}, {x, -5, 5}, 
          PlotRange -> All, PlotStyle -> {Red, Blue}], {n, 0, 25, 
          1}]]

With[{func = 
          Piecewise[{{0, x < -1}, {x + 1, x > -1 && x < 0}, {1 - x^2, 
                x > 0 && x < 1}, {0, x > 1}}, x]}, 
      manipulate[
        Plot[Evaluate@{func, fsum[func, {x, -2, 2}, n]}, {x, -2, 2}, 
          PlotRange -> All, PlotStyle -> {Red, Blue}], {n, 0, 25, 
          1}]]

With[{func = 
          Piecewise[{{0, x < -1}, {1 - 2  x, x > -1 && x < 0}, {1, 
                x > 0 && x < 1}, {0, x > 1}}, x]}, 
      manipulate[
        Plot[Evaluate@{func, fsum[func, {x, -2, 2}, n]}, {x, -2, 2}, 
          PlotRange -> All, PlotStyle -> {Red, Blue}], {n, 0, 50, 
          1}]]

Note that due to its construction the Fourier series is extended periodically beyond the interval over which it was created.

With[{func = 
          Piecewise[{{0, x < -1}, {1 - 2  x, x > -1 && x < 0}, {1, 
                x > 0 && x < 1}, {0, x > 1}}, x]}, 
      manipulate[
        Plot[Evaluate@{func, fsum[func, {x, -2, 2}, n]}, {x, -6, 6}, 
          PlotRange -> All, PlotStyle -> {Red, Blue}], {n, 0, 50, 
          1}]]