TweetFollow Us on Twitter

Contour Plotting in Java

Volume Number: 13 (1997)
Issue Number: 9
Column Tag: Javatech

Contour Plotting In Java

by David Rand

Developing a simple, useful applet using Metrowerks CodeWarrior Java

Introduction

This article discusses a Java applet which plots contours based on a given matrix of floating-point values. The idea is to present an algorithm which is simple enough to be implemented in a few hundred lines of code yet which performs a useful, non-trivial operation. Java is of course multi-platform, but this article focuses on development of the applet on the Macintosh, using Metrowerks CodeWarrior Java (specifically, CW11, which includes version 1.0.2 of the JDK). It is assumed that the reader is at least somewhat familiar with the Java language and the CodeWarrior environment. See, for example, Dave Mark's series of "Getting Started" articles [1] in MacTech magazine.

A contour plot is a convenient way of representing three-dimensional data on a two-dimensional surface such as a map or a computer screen. If you have ever gone hiking and took with you a topographical map indicating the lay of the land, you have used a contour plot. The lines of constant elevation are the contours. If the lines are close together then we have a region of steep slope, whereas widely spaced lines indicate more gradual slope. A closed contour looping back on itself indicates the existence of an extremum - a peak or a depression - somewhere inside the loop. If the extremum is pronounced, i.e. a sharp peak or a deep depression, then several such loops will be nested together concentrically.

The Plotting Algorithm

The algorithm used for contour plotting is taken from a 1978 article by W. V. Snyder [2]. The Java implementation was created by first translating Snyder's Fortran source code manually into C, then reworking the C code to remove the many convoluted goto constructs, and finally manually translating this "unravelled" C code into Java. The second step, necessary because Java does not support goto statements, proved to be the most laborious. This incompatibility should not be considered a limitation of Java; on the contrary, it is a result of the lack of appropriate block structures in old versions of Fortran.

The plotting algorithm can be summarized thus:

a) Given a matrix of floating point values which are the values of a function z = f(x,y) given at the nodes of a grid of x and y values (the grid values are assumed equally spaced, although the horizontal and vertical spacing may differ), the program determines the minimum and maximum values of z and then computes a number of contour values (in this implementation, 10 values) by linear or logarithmic interpolation between the extrema.

b) The program "walks" about the grid of points looking for any segment (i.e. a line joining two adjacent nodes in the grid) which must be crossed by one of the contours because some contour value lies between the values of z at the nodes.

c) Having found such a segment, it finds the intersection point of the contour and the segment by linear interpolation between the nodes. It also stores the information that the current contour value has been located on the current segment, so that this operation will not be repeated.

d) The program then attempts to locate a neighbouring segment having a similar property - that is, crossed by the same contour. If it finds one, it determines the intersection point as in step c) and then draws a straight line segment joining the previous intersection point with the current one. This step is repeated until no such neighbour can be found, taking care to exclude any segment which has already been dealt with.

e) Steps b), c) and d) are repeated until no segment can be found whose intersection with any contour value has not already been processed.

For a more detailed description of steps b) through e) of the algorithm, consult Snyder's article. Note that, as stated in step d), the path of each contour is constructed of linear segments, the simplest method possible. A more sophisticated algorithm, based on bicubic Hermite polynomials, may be found in [3].

The CodeWarrior Project

The Java applet discussed in this article was developed using Metrowerks CodeWarrior Java on a Power Macintosh. Figure 1 shows the window corresponding to the project file named ContourPlotApplet.µ. Figure 2 illustrates the project settings.

Figure 1. The CodeWarrior Project.

Figure 2. The Project Settings.

The project includes an html file, four java files (one for each new class) and the file classes.zip. The settings indicate that the project will generate an applet which will be run using the Metrowerks Java interpreter, and the classes (the applet as well as the three classes which it uses) will have their object code stored in class files in a folder called Classes inside the same folder as the project file. When the project is run, the interpreter will look in the html file for an <APPLET> tag from which it will read the dimensions and parameters of the applet. The html file can also be opened by a Java-enabled web browser to run the applet from within the browser; this will display the entire contents of the html file which may include instructions for using the applet or any related documentation.

Figure 3. The ContourPlotApplet running in the Metrowerks Java interpreter.

Figure 4. The ContourPlotApplet running in Netscape Navigator 3.0 Gold on a Macintosh.

Figure 3 shows the applet running in the Metrowerks Java interpreter. Figure 4 shows the same applet (although using different data) running as part of a page at the web site of the CRM, Université de Montreal; the web server is a unix platform and the web client, as shown in the figure, is Netscape Navigator running on a Power Macintosh. To serve the applet in this way, all that is required in the unix file system is to create a directory called Classes in the same directory as that containing the html file and then to ftp (as raw data) the four class files from the Classes directory on the Macintosh where they were developed to the Classes directory on the unix machine.

Figure 5. The ContourPlotApplet running in Netscape Navigator 3.01 on a unix platform.

As an illustration of platform independence, Figure 5 also shows the applet running in Netscape Navigator on a unix platform. The data here are the same as in Figure 3 except that logarithmic interpolation has been chosen. In addition, the eagle-eyed reader will notice that the character strings - such as the prompt in the upper left corner of the applet, the button, etc. - are in French. This is easily accomplished without modifying the applet, simply by changing the parameters in the <APPLET> tag in the html file. This requires, of course, that any language-dependent elements not be hard-coded in the Java source code. This approach is familiar to Macintosh programmers, who know that strings and such should be stored in an application's resource fork and not hard-coded in the application.

As shown in Figures 3, 4 and 5, there are six user-interface elements in the applet, each implemented as a Java object of some type derived from the Component class. The contour plot is the largest component and is located in the right-hand part of the applet's rectangle. The other five components, all located in the left-hand part, are

  1. The prompt "Matrix of z values:".
  2. The data area in which the user enters the matrix of z values. Each row of the matrix is a list of floating-point values separated by commas and enclosed in brace brackets. Such as {0.5, 1.1, 1.5}. The rows also are separated by commas and the list of rows is enclosed in brace brackets. This format is similar to Mathematica syntax, for example. The rows need not all be of the same length; the program will complete any short rows with the appropriate number of zeroes.
  3. The check box for choosing logarithmic instead of linear interpolation for the computation of the contour values. Logarithmic interpolation is possible only if all the z values are positive. In particular, logarithmic interpolation is unavailable if the rows of the matrix are not all of the same length, because short rows are filled with zeroes.
  4. The "Draw" button. When the user clicks this button, the program parses the matrix in component #2, draws the contour plot if the data is valid, and finally shows some results (or an error message) in component #5.
  5. The results area in which the applet displays some information about the plot just drawn. If the data are not valid for some reason, an error message will appear here. Otherwise, this area will display the number of rows and columns in the grid, the matrix of z values (with some rows extended by zeroes if necessary to make the matrix rectangular), and finally the ten contour values, numbered from 0 through 9.

The Source Code

The source code consists of an html file and four java files, one for each newly defined class. Each file has the same name as the class whose source code it contains, with the extension java appended. Thus the file ContourPlotApplet.java contains the class ContourPlotApplet, etc. Flanagan [4] is an invaluable reference for information about Java classes predefined in the Java API version 1.0, from which the classes discussed here are derived.

ContourPlotApplet is the main class of the four and is derived from java.applet.Applet. The class ContourPlotLayout is used to layout the user-interface items in the applet's rectangle. ContourPlot contains the code, adapted from Snyder [2], whose task is to draw the applet's most important component, the contour plot itself. Finally, ParseMatrixException is used to signal error conditions corresponding to invalid data.

ContourPlotApplet.html

This html file contains the <APPLET> tag which declares the CODEBASE (that is, the folder where class files are located), the CODE (that is, the name of the applet class), the graphical dimensions of the applet and its parameters. This <APPLET> tag is shown in Listing 1; for brevity, some of the parameters are omitted from the listing.

Listing 1

The <APPLET> tag in ContourPlotApplet.html
<APPLET CODEBASE="./Classes/"
    CODE="ContourPlotApplet.class" WIDTH=715 HEIGHT=460>
<PARAM NAME="stringX"          VALUE="Number of rows:">
<PARAM NAME="stringY"          VALUE="Number of columns:">
<PARAM NAME="stringZ"          VALUE="Matrix of z values:">
<PARAM NAME="stringBox"        VALUE="Log interpolation">
<PARAM NAME="stringButton"      VALUE="Draw">
<PARAM NAME="stringResults"    VALUE="Contour values:">
<!- Other parameters here ->
</APPLET>

ContourPlotApplet.java

ContourPlotApplet is a container for all the user-interface elements. Its source code is shown in Listing 2. The class starts with a few final static variables (constants) the keyword final indicating that the variable's value may not be changed and the keyword static indicating that it is a class variable, not an instance variable. These are followed by the six user-interface components shown in Figures 3, 4 and 5, and finally a number of static (class) variables which are String objects used to store strings read from the <APPLET> tag and subsequently to display messages in the results area. These data members are followed by the class' three methods init(), handleEvent(Event e) and DrawTheContourPlot() which are explained in the comments in the source code. This last method is the most important and calls several key methods in the ContourPlot object, especially thePlot.paint(Graphics g).

Listing 2
ContourPlotApplet.java
// "ContourPlotApplet" is the main class, that is, the applet,
// which is a container for all the user-interface elements.

import java.awt.*;
import java.io.*;

public class ContourPlotApplet extends java.applet.Applet {

  // Below, constants, i.e. "final static" data members:
  final static int  NUMBER_COMPONENTS =  6;
  final static int  MIN_X_STEPS  =   2,
              MIN_Y_STEPS  =   2,
              MAX_X_STEPS  =  100,
              MAX_Y_STEPS  =  100;
  final static String EOL  =
    System.getProperty("line.separator");
  final static String DEFAULT_Z  =
    "{{0.5,1.1,1.5,1,2.0,3,3,2,1,0.1}," + EOL +
    " {1.0,1.5,3.0,5,6.0,2,1,1.2,1,4}," + EOL +
    " {0.9,2.0,2.1,3,6.0,7,3,2,1,1.4}," + EOL +
    " {1.0,1.5,3.0,4,6.0,5,2,1.5,1,2}," + EOL +
    " {0.8,2.0,3.0,3,4.0,4,3,2.4,2,3}," + EOL +
    " {0.6,1.1,1.5,1,4.0,3.5,3,2,3,4}," + EOL +
    " {1.0,1.5,3.0,5,6.0,2,1,1.2,2.7,4}," + EOL +
    " {0.8,2.0,3.0,3,5.5,6,3,2,1,1.4}," + EOL +
    " {1.0,1.5,3.0,4,6.0,5,2,1,0.5,0.2}}";

  // Below, the six user-interface components:
  ContourPlot thePlot  =
    new ContourPlot(MIN_X_STEPS, MIN_Y_STEPS);
  Label       zPrompt  =  new Label("", Label.LEFT);
  TextArea   zField    =  new TextArea(DEFAULT_Z,30,6);
  Checkbox   interBox  =  new Checkbox();
  Button      drawBtn  =  new Button();
  TextArea   results   =  new TextArea();

  // Below, class data members read from the <APPLET> tag:
  static String  contourValuesTitle,infoStrX,infoStrY,
            errParse,errLog,errComp,errEqual,
            errExpect,errEOF,errBounds;

  //----------------------------------
  // "init" overrides "super.init()" and initializes the applet by:
  // 1.  getting parameters from the <APPLET> tag;
  // 2.  setting layout to instance of "ContourPlotLayout";
  // 3.  initializing and adding the six user-interface
  //      components, using the method "add()" which will
  //      also call "ContourPlotLayout.addLayoutComponent()".
  //----------------------------------
  public void init() {
    infoStrX   =  getParameter("stringX");
    infoStrY   =  getParameter("stringY");

    setLayout(new ContourPlotLayout());
    add("thePlot", thePlot);
    zPrompt.setText(getParameter("stringZ"));
    add("zPrompt", zPrompt);
    zField.setBackground(Color.white);
    add("zField",  zField);
    interBox.setLabel(getParameter("stringBox"));
    interBox.setState(false);
    add("interBox",  interBox);
    drawBtn.setLabel(getParameter("stringButton"));
    drawBtn.setFont(new Font("Helvetica", Font.BOLD, 10));
    drawBtn.setBackground(Color.white);
    add("drawBtn", drawBtn);
    results.setEditable(false);
    results.setFont(new Font("Courier", Font.PLAIN, 9));
    results.setBackground(Color.white);
    add("results", results);
    contourValuesTitle = getParameter("stringResults");
    errParse   =  getParameter("stringErrParse");
    errLog      =  getParameter("stringErrLog1") + EOL +
                getParameter("stringErrLog2") + EOL +
                getParameter("stringErrLog3");
    errComp    =  getParameter("stringErrComp");
    errEqual   =  getParameter("stringErrEqual");
    errExpect  =  getParameter("stringErrExpect");
    errEOF      =  getParameter("stringErrEOF");
    errBounds  =  getParameter("stringErrBounds");
  }

  //----------------------------------
  // Handle events. The only event not handled by the superclass 
  // is a mouse hit (i.e. "Event.ACTION_EVENT") in the "Draw" button.
  //----------------------------------
  public boolean handleEvent(Event e) {
    if ((e != null) &&
       (e.id == Event.ACTION_EVENT) &&
       (e.target == drawBtn)) {
      DrawTheContourPlot();
      return true;
    }
    else return super.handleEvent(e);
  }
  //----------------------------------
  // "DrawTheContourPlot" does what its name says (in reaction to a hit on the 
  // "Draw" button). The guts of this method are in the "try" block which:
  // 1.  gets the interpolation flag (for contour values);
  // 2.  parses the data, i.e. the matrix of z values;
  // 3.  draws the contour plot by calling the "paint()"
  //      method of the component "thePlot";
  //   4.  displays the results, i.e. the number of rows and columns in the grid, 
  //      an echo of the matrix of z values, and the list of contour values.
  // This method catches 2 exceptions, then finally (i.e. regardless of exceptions) 
  // sends a completion message to the Java console using "System.out.println()".
  //----------------------------------
  public void DrawTheContourPlot() {
    String    s;

    try {
      s = zField.getText();
      thePlot.logInterpolation = interBox.getState();
      thePlot.ParseZedMatrix(s);
      thePlot.paint(thePlot.getGraphics());
      s = thePlot.ReturnZedMatrix() +
        contourValuesTitle + EOL +
        thePlot.GetContourValuesString();
      results.setText(s);
    }
    catch(ParseMatrixException e) {
      thePlot.repaint();
      results.setText(e.getMessage());
    }
    catch(IOException e) {
      thePlot.repaint();
      results.setText(e.getMessage());
    }
    finally {
      System.out.println("Exiting DrawTheContourPlot");
    }
  }
 }

ContourPlotLayout.java

ContourPlotLayout is derived directly from java.lang.Object and implements the interface java.awt.LayoutManager. Its source code is shown in Listing 3. Recall that an "interface" in Java is an abstract class in which all methods are abstract, and is Java's limited way of implementing mix-in classes, that is, allowing a very restricted degree of multiple inheritance. Since an interface is completely abstract, all its methods must be overridden in any class which implements it, and that is the case here: ContourPlotLayout contains implementations of all five methods - addLayoutComponent, layoutContainer, minimumLayoutSize, preferredLayoutSize and removeLayoutComponent - declared abstractly in java.awt.LayoutManager.

The purpose of ContourPlotLayout is to lay out the six user-interface components inside our applet's rectangle. The Java API includes several layout managers, such as FlowLayout, BorderLayout, GridLayout, etc. (again, see Dave Mark's series of Getting Started articles in MacTech), but none was deemed appropriate here because it was desired to assign special fixed values to most (but not all) of the positions and dimensions of the components. The six components are stored in an instance variable, an array k, whose values k[0] through k[5] correspond to the applet's instance variables thePlot, zPrompt, zField, interBox, drawBtn and results. k[1] through k[4] are of fixed position and dimension. The other two components, that is, the contour plot and the results, also have fixed position, but may change in size as the applet's dimensions change (for example, if the applet's window is resized in the Java interpreter). The contour plot will be made as large as possible while remaining square, while its size never falls below a certain minimum, the constant (that is, static final) MIN_PLOT_DIMEN. The results area's width never changes, but its height expands to fill the available space while never falling below the minimum MIN_RES_HEIGHT.

Listing 3

ContourPlotLayout.java
// ContourPlotLayout implements the interface LayoutManager
// & is used by ContourPlotApplet to lay out its components.

import java.awt.*;
import java.io.*;

public class ContourPlotLayout
  extends    java.lang.Object
  implements  java.awt.LayoutManager {

  // Below, constant data members:
  private static final int  COUNT =
    ContourPlotApplet.NUMBER_COMPONENTS;
  private static final int
    MARGIN          =   5,
    MIN_PLOT_DIMEN  =  300,
    LEFT_WIDTH      =  250,
    CBOX_WIDTH      =  130,
    BUTTON_H_POS    =  MARGIN + CBOX_WIDTH + MARGIN,
    BUTTON_WIDTH    =  LEFT_WIDTH - CBOX_WIDTH - MARGIN,
    LINE_HEIGHT     =   25,
    DATA_HEIGHT     =  105,
    MIN_RES_HEIGHT  =   50,
    DATA_V_POS      =  MARGIN + MARGIN + LINE_HEIGHT,
    BUTTON_V_POS    =  DATA_V_POS + MARGIN + DATA_HEIGHT,
    RESULTS_V_POS    =  BUTTON_V_POS + MARGIN + LINE_HEIGHT;

  // Below, data members: the array of components, the dimensions of 
  // the contour  plot component and the height of the results area.
  Component  k[]  = new Component[COUNT];
  Dimension  d    = new Dimension(  MIN_PLOT_DIMEN,
                                MIN_PLOT_DIMEN);
  int  results_height = MIN_RES_HEIGHT;

  //----------------------------------
  // "addLayoutComponent" is necessary to override the
  // corresponding abstract method in "LayoutManager".
  //----------------------------------
  public void addLayoutComponent(String name, Component c)
  {
    if (name.equals("thePlot")) {
      c.reshape(  2*MARGIN+LEFT_WIDTH, MARGIN,
                d.width, d.height);
      addComponentNumber(0,c);
    }
    else if (name.equals("zPrompt")) {
      c.reshape(  MARGIN, MARGIN,
                LEFT_WIDTH, LINE_HEIGHT);
      addComponentNumber(1,c);
    }
    else if (name.equals("zField")) {
      c.reshape(  MARGIN, DATA_V_POS,
                LEFT_WIDTH, DATA_HEIGHT);
      addComponentNumber(2,c);
    }
    else if (name.equals("interBox")) {
      c.reshape(  MARGIN, BUTTON_V_POS,
                CBOX_WIDTH, LINE_HEIGHT);
      addComponentNumber(3,c);
    }
    else if (name.equals("drawBtn")) {
      c.reshape(  BUTTON_H_POS, BUTTON_V_POS,
                BUTTON_WIDTH, LINE_HEIGHT);
      addComponentNumber(4,c);
    }
    else if (name.equals("results")) {
      c.reshape(  MARGIN, RESULTS_V_POS,
              LEFT_WIDTH, results_height);
      addComponentNumber(5,c);
    }
//   throw new SomeKindOfException(
//     "Attempt to add an invalid component");
  }

  //----------------------------------
  // "GetDimensions" computes the data members "d" and  "results_height" 
  // which are  the only dimensions in the layout which are not fixed.
  //----------------------------------
  public void GetDimensions(Container parent) {
    d = parent.size();
    d.width = d.width - LEFT_WIDTH - 3*MARGIN;
    d.height = d.height - 2*MARGIN;
    if (d.width < MIN_PLOT_DIMEN)
      d.width = MIN_PLOT_DIMEN;
    if (d.height < MIN_PLOT_DIMEN)
      d.height = MIN_PLOT_DIMEN;
    if (d.width > d.height) d.width = d.height;
    else if (d.height > d.width) d.height = d.width;
    results_height = d.height + MARGIN - RESULTS_V_POS;
    if (results_height < MIN_RES_HEIGHT)
      results_height = MIN_RES_HEIGHT;
  }

  //----------------------------------
  // "addComponentNumber" adds a component given its index
  // and is a utility routine used by "addLayoutComponent".
  //----------------------------------
  public void addComponentNumber(int i, Component c) {
    if ((i < 0) || (i >= COUNT)) {
      throw new ArrayIndexOutOfBoundsException();
    }
    else if (k[i] != null) {
//   throw new SomeKindOfException(
//     "Attempt to add a component already added");
    }
    else k[i] = c;
  }

  //----------------------------------
  // "layoutContainer" is necessary to override the
  // corresponding abstract method in "LayoutManager".
  //----------------------------------
  public void layoutContainer(Container parent) {
    GetDimensions(parent);
    if (k[0] != null) k[0].reshape
      (2*MARGIN+LEFT_WIDTH,MARGIN,d.width,d.height);
    if (k[1] != null) k[1].reshape
      (MARGIN,MARGIN,LEFT_WIDTH,LINE_HEIGHT);
    if (k[2] != null) k[2].reshape
      (MARGIN,DATA_V_POS,LEFT_WIDTH,DATA_HEIGHT);
    if (k[3] !=null) k[3].reshape
      (MARGIN,BUTTON_V_POS,CBOX_WIDTH,LINE_HEIGHT);
    if (k[4] != null) k[4].reshape
      (BUTTON_H_POS,BUTTON_V_POS,
       BUTTON_WIDTH,LINE_HEIGHT);
    if (k[5] != null) k[5].reshape
      (MARGIN,RESULTS_V_POS,LEFT_WIDTH,results_height);
  }

  //----------------------------------
  // "minimumLayoutSize" is necessary to override the
  // corresponding abstract method in "LayoutManager".
  //----------------------------------
  public Dimension minimumLayoutSize(Container parent) {
    return new Dimension(
      3*MARGIN + LEFT_WIDTH + MIN_PLOT_DIMEN,
      2*MARGIN + MIN_PLOT_DIMEN);
  }

  //----------------------------------
  // "preferredLayoutSize" is necessary to override the
  // corresponding abstract method in "LayoutManager".
  //----------------------------------
  public Dimension preferredLayoutSize(Container parent) {
    GetDimensions(parent);
    return new Dimension(  3*MARGIN + d.width + LEFT_WIDTH,
                        2*MARGIN + d.height);
  }

  //----------------------------------
  // "removeLayoutComponent" is necessary to override the
  // corresponding abstract method in "LayoutManager".
  //----------------------------------
  public void removeLayoutComponent(Component c) {
    for (int i = 0; i < COUNT; i++)
      if (c == k[i]) k[i] = null;
  }
}

ContourPlot.java

ContourPlot, part of whose source code is shown in Listing 4, is derived from the class java.awt.Canvas. An instance of it is used by the applet as the user-interface component which parses the data, draws the contour plot, and returns a string of results. This class begins with a number of constants: note, for example, the characters OPEN_SUITE and CLOSE_SUITE specifying delimiters in the matrix to be parsed and BETWEEN_ARGS which specifies the data separator between values in the matrix; note also the platform-independent way of assigning a value to EOL, as recommended in [5].

The data members xSteps and ySteps are used to hold the number of horizontal and vertical steps, respectively, in the grid.

The matrix z will contain values of type float and is declared to have two indices but the number of components in each dimension is initially unspecified. The number of rows in z and the length of each row will be incremented as the data are read. The matrix will be made rectangular only after all data are parsed; in fact, Java syntax allows one to use an array of arrays (such as z here) in which the "inner" arrays need not have the same length. For example, the length of the xth row of z is given by z[x].length. Notice that, according to standard Java practice, the memory allocated for the matrix z is never disposed, even when new data are parsed, since garbage collection is performed automatically by the Java interpreter. Or to express this in different words, the contents of z may be "disposed" by simply performing the assignment z = null which has the effect that any previous contents of z are no longer referenced (unless they have been assigned to some other variable other than z) and may thus be garbage-collected by the interpreter at its convenience.

The data members d, deltaX, deltaY are measurements, in pixels, of the dimensions of the contour plot and the distance between grid lines horizontally and vertically.

Most of the remaining data members are variables adapted from Snyder's Fortran subroutine GCONTR. See [2] for a discussion of their meaning.

The various methods in the class ContourPlot are explained briefly by comments in the full source code included with the project. The most important are paint(Graphics g) and ContourPlotKernel(Graphics g, boolean workSpace[]). The former is called directly by the applet and in turn calls the latter which corresponds to the "outer" level of the plotting algorithm adapted from Synder's subroutine GCONTR. (For brevity, some methods have been omitted from Listing 4, in particular a few methods which are called directly or indirectly only by ContourPlotKernel and thus include only code adapted from Synder.)

Listing 4

Selections from ContourPlot.java
// "ContourPlot" is the most important class. It is a user-interface component which 
// parses the data, draws the contour plot, and returns a string of results.

import java.awt.*;
import java.io.*;

public class ContourPlot extends Canvas {

  // Below, constant data members:
  final static boolean SHOW_NUMBERS = true;
  final static int  BLANK       =  32,
              OPEN_SUITE      =  (int)'{',
              CLOSE_SUITE     =  (int)'}',
              BETWEEN_ARGS    =  (int)',',
              N_CONTOURS      =  10,
              PLOT_MARGIN     =  20,
              WEE_BIT        =   3,
              NUMBER_LENGTH    =   3;
  final static double    Z_MAX_MAX  =  1.0E+10,
                      Z_MIN_MIN  = -Z_MAX_MAX;
  final static String EOL  =
    System.getProperty("line.separator");

  // Below, data members which store the grid steps,
  // the z values, the interpolation flag, the dimensions
  // of the contour plot and the increments in the grid:
  int        xSteps, ySteps;
  float      z[][];
  boolean    logInterpolation = false;
  Dimension  d;
  double      deltaX, deltaY;

  // Below, data members, most of which are adapted from
  // Fortran variables in Snyder's code:
  int    ncv = N_CONTOURS;
  int    l1[] = new int[4];
  int    l2[] = new int[4];
  int    ij[] = new int[2];
  int    i1[] = new int[2];
  int    i2[] = new int[2];
  int    i3[] = new int[6];
  int    ibkey,icur,jcur,ii,jj,elle,ix,iedge,iflag,ni,ks;
  int    cntrIndex,prevIndex;
  int    idir,nxidir,k;
  double    z1,z2,cval,zMax,zMin;
  double    intersect[]    = new double[4];
  double    xy[]          = new double[2];
  double    prevXY[]      = new double[2];
  float     cv[]          = new float[ncv];
  boolean  jump;

  //----------------------------------
  // A constructor method.
  //----------------------------------
  public ContourPlot(int x, int y) {
    super();
    xSteps = x;
    ySteps = y;
    setForeground(Color.black);
    setBackground(Color.white);
  }

  //----------------------------------
  // The following routines are omitted from this listing.
  // See the full source code included with the project.
  //
  // int sign(int a, int b)
  // void InvalidData()
  // void GetExtremes()
  // void SetMeasurements()
  // void DetectBoundary()
  // boolean Routine_label_020()
  // boolean Routine_label_050()
  // boolean Routine_label_150()
  // short Routine_label_200(  Graphics g,
  //                          boolean workSpace[])
  // void ContinueContour()
  //----------------------------------
  // "AssignContourValues" interpolates between "zMin" and "zMax", either
  // logarithmically or linearly, in order to assign contour values to the array "cv".
  //----------------------------------
  void AssignContourValues() throws ParseMatrixException {
    int    i;
    double  delta;

    if ((logInterpolation) && (zMin <= 0.0)) {
      InvalidData();
      throw new
        ParseMatrixException(ContourPlotApplet.errLog);
    }
    if (logInterpolation) {
      double  temp = Math.log(zMin);

      delta = (Math.log(zMax)-temp) / ncv;
      for (i = 0; i < ncv; i++)
        cv[i] = (float)Math.exp(temp + (i+1)*delta);
    }
    else {
      delta = (zMax-zMin) / ncv;
      for (i = 0; i < ncv; i++)
        cv[i] = (float)(zMin + (i+1)*delta);
    }
  }

  //----------------------------------
  // "GetContourValuesString" returns a list of the
  // contour values for display in the results area.
  //----------------------------------
  String GetContourValuesString() {
    String  s = new String();
    int    i;

    for (i = 0; i < ncv; i++)
      s = s  + "[" +  Integer.toString(i)
            + "] " + Float.toString(cv[i]) + EOL;
    return s;
  }

  //----------------------------------
  // "DrawGrid" draws the rectangular grid of gray lines
  // on top of which the contours will later be drawn.
  //----------------------------------
  void DrawGrid(Graphics g) {
    int  i,j,kx,ky;

    // Interchange horizontal & vertical
    g.clearRect(0, 0,    d.height+2*PLOT_MARGIN,
                      d.width +2*PLOT_MARGIN);
    g.setColor(Color.gray);
    for (i = 0; i < xSteps; i++) {
      kx = (int)((float)i * deltaX);
      g.drawLine(  PLOT_MARGIN,
                  PLOT_MARGIN+kx,
                  PLOT_MARGIN+d.height,
                  PLOT_MARGIN+kx);
    }
    for (j = 0; j < ySteps; j++) {
      ky = (int)((float)j * deltaY);
      g.drawLine(  PLOT_MARGIN+ky,
                  PLOT_MARGIN,
                  PLOT_MARGIN+ky,
                  PLOT_MARGIN+d.width);
    }
    g.setColor(Color.black);
  }

  //----------------------------------
  // "SetColour" sets the colour of the graphics object, given the contour 
  // index, by interpolating linearly between "Color.blue" & "Color.red".
  //----------------------------------
  void SetColour(Graphics g) {
    Color c = new Color(
      ((ncv-cntrIndex)  * Color.blue.getRed()  +
             cntrIndex  * Color.red.getRed())/ncv,
      ((ncv-cntrIndex)  * Color.blue.getGreen() +
             cntrIndex  * Color.red.getGreen())/ncv,
      ((ncv-cntrIndex)  * Color.blue.getBlue() +
             cntrIndex  * Color.red.getBlue())/ncv);
    g.setColor(c);
  }
  //----------------------------------
  // "DrawKernel" is the guts of drawing and is called directly or indirectly by 
  // "ContourPlotKernel" in order to draw a segment of a contour or to set the pen
  // position "prevXY". Its action depends on "iflag":
  //
  // iflag == 1 means Continue a contour
  // iflag == 2 means Start a contour at a boundary
  // iflag == 3 means Start a contour not at a boundary
  // iflag == 4 means Finish contour at a boundary
  // iflag == 5 means Finish closed contour not at boundary
  // iflag == 6 means Set pen position
  //
  // If the constant "SHOW_NUMBERS" is true, then the contour index is drawn 
  // adjacent to where the contour ends when completing a contour (iflag == 4 or 5).
  //----------------------------------
  void DrawKernel(Graphics g) {
    int  prevU,prevV,u,v;

    if ((iflag == 1) || (iflag == 4) || (iflag == 5)) {
      if (cntrIndex != prevIndex) { // Must change colour
        SetColour(g);
        prevIndex = cntrIndex;
      }
      prevU = (int)((prevXY[0] - 1.0) * deltaX);
      prevV = (int)((prevXY[1] - 1.0) * deltaY);
      u = (int)((xy[0] - 1.0) * deltaX);
      v = (int)((xy[1] - 1.0) * deltaY);

      // Interchange horizontal & vertical
      g.drawLine( PLOT_MARGIN+prevV,PLOT_MARGIN+prevU,
              PLOT_MARGIN+v,   PLOT_MARGIN+u);
      if ((SHOW_NUMBERS) && ((iflag==4) || (iflag==5))) {
        if       (u == 0)        u = u - WEE_BIT;
        else if  (u == d.width)  u = u + PLOT_MARGIN/2;
        else if  (v == 0)        v = v - PLOT_MARGIN/2;
        else if  (v == d.height)  v = v + WEE_BIT;
        g.drawString(Integer.toString(cntrIndex),
          PLOT_MARGIN+v, PLOT_MARGIN+u);
      }
    }
    prevXY[0] = xy[0];
    prevXY[1] = xy[1];
  }

  //----------------------------------
  // "CrossedByContour" is true iff the current segment inthe grid is crossed by 
  // one of the contour values and has not already been processed for that value.
  //----------------------------------
  boolean CrossedByContour(boolean workSpace[]) {
    ii = ij[0] + i1[elle];
    jj = ij[1] + i1[1-elle];
    z1 = z[ij[0]-1][ij[1]-1];
    z2 = z[ii-1][jj-1];
    for (cntrIndex = 0; cntrIndex < ncv; cntrIndex++) {
      int  i = 2*(xSteps*(ySteps*cntrIndex+ij[1]-1)
                  +ij[0]-1) + elle;

      if (!workSpace[i]) {
        float x = cv[cntrIndex];
        if ((x>Math.min(z1,z2)) && (x<=Math.max(z1,z2)))
        {
          workSpace[i] = true;
          return true;
        }
      }
    }
    return false;
  }

  //----------------------------------
  // "ContourPlotKernel" is the guts of this class and
  // corresponds to Synder's subroutine "GCONTR".
  //----------------------------------
  void ContourPlotKernel(Graphics g,  boolean workSpace[])
  {
    short val_label_200;

    l1[0] = xSteps;    l1[1] = ySteps;
    l1[2] = -1;       l1[3] = -1;
    i1[0] =  1; i1[1] =  0;
    i2[0] =  1; i2[1] = -1;
    i3[0] =  1; i3[1] =  0; i3[2] =  0;
    i3[3] =  1; i3[4] =  1; i3[5] =  0;
    prevXY[0]  = 0.0; prevXY[1] = 0.0;
    xy[0]       = 1.0; xy[1] = 1.0;
    cntrIndex  = 0;
    prevIndex  = -1;
    iflag      = 6;
    DrawKernel(g);
    icur = Math.max(1,
      Math.min((int)Math.floor(xy[0]), xSteps));
    jcur = Math.max(1,
      Math.min((int)Math.floor(xy[1]), ySteps));
    ibkey = 0;
    ij[0] = icur;
    ij[1] = jcur;
    if (  Routine_label_020() &&
        Routine_label_150()) return;
    if (  Routine_label_050()) return;
    while (true) {
      DetectBoundary();
      if (jump)  {
        if (ix != 0)
          iflag = 4; // Finish contour at boundary
        iedge = ks + 2;
        if (iedge > 4) iedge = iedge - 4;
        intersect[iedge-1] = intersect[ks-1];
        val_label_200 = Routine_label_200(g,workSpace);
        if (val_label_200 == 1) {
          if (  Routine_label_020() &&
               Routine_label_150()) return;
          if (  Routine_label_050()) return;
          continue;
        }
        if (val_label_200 == 2) continue;
        return;
      }
      if ((ix != 3) && (ix+ibkey != 0) &&
        CrossedByContour(workSpace)) {
        //
        // An acceptable line segment has been found.
        // Follow contour until it hits a boundary or closes.
        //
        iedge = elle + 1;
        cval = cv[cntrIndex];
        if (ix != 1) iedge = iedge + 2;
        iflag = 2 + ibkey;
        intersect[iedge-1] = (cval - z1) / (z2 - z1);
        val_label_200 = Routine_label_200(g,workSpace);
        if (val_label_200 == 1) {
          if (  Routine_label_020() &&
               Routine_label_150()) return;
          if (  Routine_label_050()) return;
          continue;
        }
        if (val_label_200 == 2) continue;
        return;
      }
      if (++elle > 1) {
        elle = idir % 2;
        ij[elle] = sign(ij[elle],l1[k-1]);
        if (Routine_label_150()) return;
      }
      if (Routine_label_050()) return;
    }
  }

  //----------------------------------
  // "paint" overrides the superclass' "paint()" method. This method draws the grid and 
  // then the contours, provided that the first two contour values are not equal 
  // (which would indicate invalid data). The "workSpace" is used to remember which 
  // segments in the grid have been crossed by which contours.
  //----------------------------------
  public void paint(Graphics g)
  {
    int    workLength = 2 * xSteps * ySteps * ncv;
    boolean  workSpace[]; // Allocate below if data valid

    SetMeasurements();
    DrawGrid(g);
    if (cv[0] != cv[1]) { // Valid data
      workSpace = new boolean[workLength];
      ContourPlotKernel(g, workSpace);
    }
  }

  //----------------------------------
  // "ParseZedMatrix" parses the matrix of z values
  // which it expects to find in the string "s".
  //----------------------------------
  public void ParseZedMatrix(String s)
    throws ParseMatrixException, IOException
  {
    StringBufferInputStream i;
    StreamTokenizer      t;

    i = new StringBufferInputStream(s);
    t = new StreamTokenizer(i);

    z = null; // Junk any existing matrix
    EatCharacter(t,OPEN_SUITE);
    do ParseRowVector(t);
    while (t.nextToken() == BETWEEN_ARGS);
    if (t.ttype != CLOSE_SUITE) {
      InvalidData();
      throw new ParseMatrixException(
        ContourPlotApplet.errParse + EOL +
        ContourPlotApplet.errExpect+(char)CLOSE_SUITE);
    }
    if (t.nextToken() != t.TT_EOF) {
      InvalidData();
      throw new ParseMatrixException(
        ContourPlotApplet.errParse + EOL +
        ContourPlotApplet.errEOF);
    }
    MakeMatrixRectangular();
    GetExtremes();
    if (zMax > Z_MAX_MAX) zMax = Z_MAX_MAX;
    if (zMin < Z_MIN_MIN) zMin = Z_MIN_MIN;
    AssignContourValues();
  }

  //----------------------------------
  // "ParseRowVector" parses a row of data from the stream.
  //----------------------------------
  public void ParseRowVector(StreamTokenizer t)
    throws ParseMatrixException, IOException
  {  // Parse a row of float's and
    // insert them in a new row of z[][]
    if (z == null) z = new float[1][];
    else AddRow();
    EatCharacter(t,OPEN_SUITE);
    do {
      if (t.nextToken() == t.TT_NUMBER) {
        int x = z.length - 1;

        if (z[x] == null) {
          z[x] = new float[1];
          z[x][0] = (float)t.nval;
        }
        else AddColumn((float)t.nval);
      }
      else {
        int x = z.length - 1;
        int y = z[x].length - 1;

        InvalidData();
        throw new ParseMatrixException(
          ContourPlotApplet.errParse + EOL +
          ContourPlotApplet.errComp + " [" +
          Integer.toString(x) + "," +
          Integer.toString(y) + "]");
      }
    } while (t.nextToken() == BETWEEN_ARGS);
    if (t.ttype != CLOSE_SUITE) {
      InvalidData();
      throw new ParseMatrixException(
        ContourPlotApplet.errParse + EOL +
        ContourPlotApplet.errExpect+(char)CLOSE_SUITE);
    }
  }

  //----------------------------------
  // "AddRow" appends a new empty row to the end of "z"
  //----------------------------------
  public void AddRow() throws ParseMatrixException {
    int leng = z.length;
    float temp[][];

    if (leng >= ContourPlotApplet.MAX_X_STEPS)
      throw new ParseMatrixException(
        ContourPlotApplet.errParse + EOL +
        ContourPlotApplet.errBounds);
    temp = new float[leng+1][];
    System.arraycopy(z, 0, temp, 0, leng);
    z = temp;
  }

  //----------------------------------
  // "AddColumn" appends "val" to end of last row in "z"
  //----------------------------------
  public void AddColumn(float val)
    throws ParseMatrixException
  {
    int i = z.length - 1;
    int leng = z[i].length;
    float temp[];

    if (leng >= ContourPlotApplet.MAX_Y_STEPS)
      throw new ParseMatrixException(
        ContourPlotApplet.errParse + EOL +
        ContourPlotApplet.errBounds);
    temp = new float[leng+1];
    System.arraycopy(z[i], 0, temp, 0, leng);
    temp[leng] = val;
    z[i] = temp;
  }

  //----------------------------------
  // "MakeMatrixRectangular" appends zero(s) to the end of
  // any row of "z" which is shorter than the longest row.
  //----------------------------------
  public void MakeMatrixRectangular() {
    int  i,y,leng;

    xSteps = z.length;
    ySteps = ContourPlotApplet.MIN_Y_STEPS;
    for (i = 0; i < xSteps; i++) {
      y = z[i].length;
      if (ySteps < y) ySteps = y;
    }
    for (i = 0; i < xSteps; i++) {
      leng = z[i].length;
      if (leng < ySteps) {
        float temp[] = new float[ySteps];

        System.arraycopy(z[i], 0, temp, 0, leng);
        while (leng < ySteps) temp[leng++] = 0;
        z[i] = temp;
      }
    }
  }

  //----------------------------------
  // "ReturnZedMatrix" returns a string containing the
  // values in "z" for display in the results area.
  //----------------------------------
  public String ReturnZedMatrix() {
    String  s,oneValue;
    int    i,j;
    s = new String(
      ContourPlotApplet.infoStrX + xSteps + EOL +
      ContourPlotApplet.infoStrY + ySteps + EOL);
    for (i = 0; i < xSteps; i++) {
      for (j = 0; j < ySteps; j++) {
        oneValue = Double.toString(z[i][j]);
        while (oneValue.length() < NUMBER_LENGTH)
          oneValue = " " + oneValue;
        s = s + oneValue;
        if (j < ySteps-1) s = s + " ";
      }
      s = s + EOL;
    }
    return s;
  }
  //----------------------------------
  // "EatCharacter" skips any BLANK's in the stream and
  // expects the character "c", throwing an exception if
  // the next non-BLANK character is not "c".
  //----------------------------------
  public void EatCharacter(StreamTokenizer t, int c)
    throws ParseMatrixException, IOException
  {
    while (t.nextToken() == BLANK) ;
    if (t.ttype != c) {
      InvalidData();
      throw new ParseMatrixException(
        ContourPlotApplet.errParse + EOL +
        ContourPlotApplet.errExpect + (char)c);
    }
  }
}

ParseMatrixException.java

ParseMatrixException, a very small class whose source code is shown in Listing 5, extends java.lang.Exception and is used to throw exceptions when any error occurs during parsing of the matrix of z values. It contains no new data members and its only method is a constructor taking a single String argument whose contents explain the error. The applet catches this exception and displays the string in the results box. The various explanatory strings are built from arguments read from the <APPLET> tag in the html file and stored as static String objects in ContourPlotApplet.

Listing 5

ParseMatrixException.java
// Class "ParseMatrixException" is used to signal an error corresponding to invalid 
// data encounteredwhen parsing the matrix of z values.

public class ParseMatrixException extends Exception {

  public ParseMatrixException(String message) {
    super(message);
  }
}

Conclusion

This article has presented a reasonably simple Java applet which nevertheless performs a useful function. It illustrates a variety of features of the Java language, such as:

  • constants, that is, final static data members;
  • class (static) methods (See, for example, Float.toString called by GetContourValuesString or Math.log() called by AssignContourValues, in Listing 4.);
  • manipulation of characters strings using the String object;
  • parsing data by breaking it into tokens (see ParseZedMatrix() in Listing 4);
  • sending output to the Java console (see DrawTheContourPlot in Listing 2);
  • several user-interface components (see the data members in Listing 2);
  • interfaces and a custom layout (see Listing 3);
  • a custom component (see Listing 4);
  • arrays of one or two dimensions (see, for example, data member z in Listing 4);
  • applet parameters (see Listing 1 and init() in Listing 2);
  • a little colour (see DrawGrid and SetColour in Listing 4);
  • throwing and catching exceptions (see ParseZedMatrix() in Listing 4 and DrawTheContourPlot in Listing 2); custom exceptions (see Listing 5); etc.

For the reader who would like to experiment with possible improvements to this applet, here are a few suggestions:

  • allow user-input of the number of contour values, or of the contour values themselves;
  • implement a file dialogue so the user can read a matrix of data from a disk file;
  • shade the regions between contours;
  • allow the option of keeping grid cells square when the number of rows does not equal the number of columns - thus requiring a non-rectangular drawing area;
  • allow user-input of the grid values - i.e. x and y values - so that the grid lines need not be equally spaced.
  • for the ambitious: parse a closed-form expression such as z = sin(x y), then generate the grid values - choosing the fineness of the grid according to the absolute values of the partial derivatives of z - and finally plot the result;
  • again, for the ambitious: implement Preusser's algorithm, for nice smooth curves!

The contour plotting applet (not necessarily the version described in this article, but similar) may be viewed by pointing your web browser to http://www.CRM.UMontreal.CA/Galerie/ContourPlotApplet_Eng.html.

References

  1. Dave Mark, "Java Break," MacTech Magazine, 12, 5 (May 1996), 7-12. (and subsequent months)
  2. W. V. Snyder, "Algorithm 531, Contour plotting [J6]," ACM Trans. Math. Softw. 4, 3 (Sept. 1978), 290-294.
  3. A. Preusser, "Algorithm 671, FARB-E-2D: Fill Area with Bicubics on Rectangles-A Contour Plot Program," ACM Trans. Math. Softw. 15, 1 (March 1989), 79-89.
  4. D. Flanagan, Java in a Nutshell, O'Reilly & Associates (1996).
  5. 100% Pure Java Cookbook, Version 5.01.97, Sun Microsystems (1997).

David Rand works at the Centre de recherches mathématiques (CRM) at the Université de Montréal where he manages the CRM's web site. He has developed a number of Macintosh applications such as the concordance-editor Concorder 3 and the shareware text- and list-editor Zephyr 1.1.

 
AAPL
$98.91
Apple Inc.
+1.24
MSFT
$44.22
Microsoft Corpora
-0.29
GOOG
$591.51
Google Inc.
+2.49

MacTech Search:
Community Search:

Software Updates via MacUpdate

Bartender 1.2.20 - Organize your menu ba...
Bartender lets you organize your menu bar apps. Features: Lets you tidy your menu bar apps how you want. See your menu bar apps when you want. Hide the apps you need to run, but do not need to... Read more
TotalFinder 1.6.2 - Adds tabs, hotkeys,...
TotalFinder is a universally acclaimed navigational companion for your Mac. Enhance your Mac's Finder with features so smart and convenient, you won't believe you ever lived without them. Tab-based... Read more
Vienna 3.0.0 RC 2 :be5265e: - RSS and At...
Vienna is a freeware and Open-Source RSS/Atom newsreader with article storage and management via a SQLite database, written in Objective-C and Cocoa, for the OS X operating system. It provides... Read more
VLC Media Player 2.1.5 - Popular multime...
VLC Media Player is a highly portable multimedia player for various audio and video formats (MPEG-1, MPEG-2, MPEG-4, DivX, MP3, OGG, ...) as well as DVDs, VCDs, and various streaming protocols. It... Read more
Default Folder X 4.6.7 - Enhances Open a...
Default Folder X attaches a toolbar to the right side of the Open and Save dialogs in any OS X-native application. The toolbar gives you fast access to various folders and commands. You just click... Read more
TinkerTool 5.3 - Expanded preference set...
TinkerTool is an application that gives you access to additional preference settings Apple has built into Mac OS X. This allows to activate hidden features in the operating system and in some of the... Read more
Audio Hijack Pro 2.11.0 - Record and enh...
Audio Hijack Pro drastically changes the way you use audio on your computer, giving you the freedom to listen to audio when you want and how you want. Record and enhance any audio with Audio Hijack... Read more
Intermission 1.1.1 - Pause and rewind li...
Intermission allows you to pause and rewind live audio from any application on your Mac. Intermission will buffer up to 3 hours of audio, allowing users to skip through any assortment of audio... Read more
Autopano Giga 3.6 - Stitch multiple imag...
Autopano Giga allows you to stitch 2, 20, or 2,000 images. Version 3.0 integrates impressive new features that will definitely make you adopt Autopano Pro or Autopano Giga: Choose between 9... Read more
Airfoil 4.8.7 - Send audio from any app...
Airfoil allows you to send any audio to AirPort Express units, Apple TVs, and even other Macs and PCs, all in sync! It's your audio - everywhere. With Airfoil you can take audio from any... Read more

Latest Forum Discussions

See All

The Phantom PI Mission Apparition Review
The Phantom PI Mission Apparition Review By Jordan Minor on July 28th, 2014 Our Rating: :: GHOSTS BUSTEDUniversal App - Designed for iPhone and iPad The Phantom PI is an exceedingly clever and well-crafted adventure game.   | Read more »
More Stubies Are Coming Your Way in a Ne...
More Stubies Are Coming Your Way in a New Update Posted by Jessica Fisher on July 28th, 2014 [ permalink ] Universal App - Designed for iPhone and iPad | Read more »
The Great Prank War Review
The Great Prank War Review By Nadia Oxford on July 28th, 2014 Our Rating: :: PRANKING IS SERIOUS BUSINESSUniversal App - Designed for iPhone and iPad Though short, The Great Prank War offers an interesting and fun mix of action and... | Read more »
Marvel Contest of Champions Announced at...
Marvel Contest of Champions Announced at Comic-Con Posted by Jennifer Allen on July 28th, 2014 [ permalink ] Announced over the weekend at San Diego Comic-Con was the fairly exciting looking Marvel Contest of Champions. | Read more »
Teenage Mutant Ninja Turtles Review
Teenage Mutant Ninja Turtles Review By Jennifer Allen on July 28th, 2014 Our Rating: :: DULL SWIPINGUniversal App - Designed for iPhone and iPad The pizza power is weak when it comes to this Teenage Mutant Ninja Turtles game.   | Read more »
Exploration Focused Puzzle Game Beatbudd...
Exploration Focused Puzzle Game Beatbuddy Set to Make Transition from PC to iOS this September Posted by Jennifer Allen on July 28th, 2014 [ permalink ] | Read more »
PlanetHD
PlanetHD By Nadia Oxford on July 28th, 2014 Our Rating: :: SPACE MADNESSUniversal App - Designed for iPhone and iPad PlanetHD will keep players busy for a while, though its unpredictable physics are a handful to deal with.   | Read more »
This Week at 148Apps: July 21-25, 2014
Another Week of Expert App Reviews   At 148Apps, we help you sort through the great ocean of apps to find the ones we think you’ll like and the ones you’ll need. Our top picks become Editor’s Choice, our stamp of approval for apps with that little... | Read more »
Reddme for iPhone - The Reddit Client (...
Reddme for iPhone - The Reddit Client 1.0 Device: iOS iPhone Category: News Price: $.99, Version: 1.0 (iTunes) Description: Reddme for iPhone is an iOS 7-optimized Reddit client that offers a refreshing new way to experience Reddit... | Read more »
Jacob Jones and the Bigfoot Mystery : Ep...
Jacob Jones and the Bigfoot Mystery : Episode 2 1.0 Device: iOS Universal Category: Games Price: $1.99, Version: 1.0 (iTunes) Description: Jacob Jones is back in Episode 2 of one of Apples 'Best of 2013' games and an App Store... | Read more »

Price Scanner via MacPrices.net

13-inch 2.5GHz MacBook Pro on sale for $1099,...
Best Buy has the 13″ 2.5GHz MacBook Pro available for $1099.99 on their online store. Choose free shipping or free instant local store pickup (if available). Their price is $100 off MSRP. Price is... Read more
Roundup of Apple refurbished MacBook Pros, th...
The Apple Store has Apple Certified Refurbished 13″ and 15″ MacBook Pros available for up to $400 off the cost of new models. Apple’s one-year warranty is standard, and shipping is free. Their prices... Read more
Record Mac Shipments In Q2/14 Confound Analys...
A Seeking Alpha Trefis commentary notes that Apple’s fiscal Q3 2014 results released July 22, beat market predictions on earnings, although revenues were slightly lower than anticipated. Apple’s Mac’... Read more
Intel To Launch Core M Silicon For Use In Not...
Digitimes’ Monica Chen and Joseph Tsai, report that Intel will launch 14nm-based Core M series processors specifically for use in fanless notebook/tablet 2-in-1 models in Q4 2014, with many models to... Read more
Apple’s 2014 Back to School promotion: $100 g...
 Apple’s 2014 Back to School promotion includes a free $100 App Store Gift Card with the purchase of any new Mac (Mac mini excluded), or a $50 Gift Card with the purchase of an iPad or iPhone,... Read more
iMacs on sale for $150 off MSRP, $250 off for...
Best Buy has iMacs on sale for up to $160 off MSRP for a limited time. Choose free home shipping or free instant local store pickup (if available). Prices are valid for online orders only, in-store... Read more
Mac minis on sale for $100 off MSRP, starting...
Best Buy has Mac minis on sale for $100 off MSRP. Choose free shipping or free instant local store pickup. Prices are for online orders only, in-store prices may vary: 2.5GHz Mac mini: $499.99 2.3GHz... Read more
Global Tablet Market Grows 11% in Q2/14 Notwi...
Worldwide tablet sales grew 11.0 percent year over year in the second quarter of 2014, with shipments reaching 49.3 million units according to preliminary data from the International Data Corporation... Read more
New iPhone 6 Models to Have Staggered Release...
Digitimes’ Cage Chao and Steve Shen report that according to unnamed sources in Apple’s upstream iPhone supply chain, the new 5.5-inch iPhone will be released several months later than the new 4.7-... Read more
New iOS App Helps People Feel Good About thei...
Mobile shoppers looking for big savings at their favorite stores can turn to the Goodshop app, a new iOS app with the latest coupons and deals at more than 5,000 online stores. In addition to being a... Read more

Jobs Board

*Apple* Solutions Consultant (ASC) - Apple (...
**Job Summary** The ASC is an Apple employee who serves as an Apple brand ambassador and influencer in a Reseller's store. The ASC's role is to grow Apple Read more
Sr. Product Leader, *Apple* Store Apps - Ap...
**Job Summary** Imagine what you could do here. At Apple , great ideas have a way of becoming great products, services, and customer experiences very quickly. Bring Read more
*Apple* Solutions Consultant (ASC) - Apple (...
**Job Summary** The ASC is an Apple employee who serves as an Apple brand ambassador and influencer in a Reseller's store. The ASC's role is to grow Apple Read more
*Apple* Solutions Consultant (ASC) - Apple (...
**Job Summary** The ASC is an Apple employee who serves as an Apple brand ambassador and influencer in a Reseller's store. The ASC's role is to grow Apple Read more
WW Sales Program Manager, *Apple* Online St...
**Job Summary** Imagine what you could do here. At Apple , great ideas have a way of becoming great products, services, and customer experiences very quickly. Bring Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.