Archive

Posts Tagged ‘swing’

Glazed Lists – an essential Java library for lists and tables

May 2, 2012 9 comments

Swing is the built in toolkit for creating user interfaces for Java programs. While these types of standalone desktop applications are becoming less prevalent, perhaps due to increasing functionality of webapps, there are still some industries which are highly reliant on them. If you find yourself creating a Java desktop application, you will probably have to learn Swing, and you will also probably have to learn to display information to the user in list or table form. In standard Java Swing applications, it is difficult, or at least annoying, to do the following three tasks:

  1. Displaying domain specific models
  2. Filtering
  3. Sorting

Glazed Lists is an open source project that makes all three of these tasks trivial. Its primary author, Jesse Wilson, is a current Google employee. Let’s examine each of these aspects in turn.

Provides a simplified API for representing objects within a JTable

Swing uses the Model View Controller paradigm throughout. Thus the table or list merely presents a view for an underlying model data structure. Part of your job in displaying data in a Swing table is to define the TableModel implementation which provides the data for the JTable to display.

Swing provides an AbstractTableModel that does most of the work for you, requiring you only to implement the following methods:

public int getRowCount();
public int getColumnCount();
public Object getValueAt(int row, int column);

Here’s a simple domain model object we might want to visualize in a table:

public class Person {
    int age;
    String name;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public int getAge() { return age; }

    public String getName() { return name; }
}

The logical way of doing that would be two have two columns, one for the age, one for the name. Let’s make a table model for this case:

public class PersonTableModel extends AbstractTableModel {
    private static final String[] columns = {"Name", "Age"};
    private final List people;

    public PersonTableModel(List people) {
        // Make a defensive copy
        this.people = new ArrayList(people);
    }

    public int getRowCount() {
        return people.size();
    }
    public int getColumnCount() {
        return columns.length;
    }
    public Object getValueAt(int row, int column) {
        Person p = people.get(row);
        if (column == 0) {
            return p.getName();
        } else {
            return p.getAge();
        }
    }
}

This certainly works, but it requires a fair bit of boilerplate. Furthermore, the code above does not provide any way of modifying the list of people after it is copied by the TableModel.

Glazed Lists simplifies your life by treating the table not as an arbitrary two dimensional grid, but instead as a collection of rows, where the rows are kept in sync with changes to the domain models that they represent. All you have to do is define how a row is laid out, and Glazed Lists takes care of the rest.

The interface you need to use in order to define how the table looks and which aspects of your model objects are exposed is called [TableFormat][].

The interface is as follows:

  • int getColumnCount() – The number of columns to display.
  • String getColumnName(int column) – Gets the title of the specified column.
  • Object getColumnValue(E baseObject, int column) – Gets the value of the specified field for the specified object.

This should remind you of the TableModel interface presented previously, but note how the getColumnValue method is different – rather than getting a row and column, and forcing you to look up the object corresponding to that row, you are provided the object directly.

Here is a TableFormat which allows Person objects to be easily visible in a JTable:

public class PersonTableFormat implements TableFormat {

    String[] columnNames = {"Name", "Age"};
    private static final int NAME_INDEX = 0;
    private static final int AGE_INDEX = 1;

    public int getColumnCount() { return columnNames.length; }

    public String getColumnName(int column) { return columnNames[i]; }

    public Object getColumnValue(Person baseObject, int column) {
        switch (column) {
            case NAME_INDEX:
                return baseObject.getName();
            case AGE_INDEX:
                return baseObject.getAge();
            default:
                throw new IllegalArgumentException("Expected column 0 or 1, got " + column);
        }
    }
}

While this isn’t too hard to write, it’s still a lot of boilerplate (and not significantly different from the previous example). Glazed Lists makes it even easier than this. The entire class definition above can be replaced with three lines:

TableFormat personTableFormat = GlazedLists.tableFormat(Person.class,
    // Names of the properties to fetch
    new String[] {"name","age"},
    // Names for the columns
    new String[] {"Name", "Age"});

What’s this doing? And how can it do all that I had previously in one line of code? Well, it requires and takes advantage of JavaBeans naming convention. The static function uses reflection to find the methods mapping to properties named “name” and “age”. In this case, it looks for two methods, getName() and getAge(), both of which it finds. (If I didn’t name my methods appropriately, I would get a runtime exception). The second array defines the strings that should be used to identify the corresponding entry in the properties array. In other words, element 0 in the names column is used to identify the property name at index 0.

This TableFormat class alone is insufficient to display data in a table. To do that, you need a class which fulfills the TableModel interface I described previously. Fortunately, Glazed Lists makes this easy.

The fundamental building block of Glazed Lists is the EventList class. It is similar to the ArrayList class in Java, except that it has support for observers. If you’re not familiar with the Observer/Observable design pattern, it allows objects (observers) to register themselves and receive notifications whenever a different object (the observable) is changed. For instance, when a new item is added to the EventList, the UI element representing it on screen automatically refreshes itself.

The EventTableModel class fulfills the TableModel interface, making use of the EventList and TableFormat we described earlier. The EventList is the data provider, and the TableFormat determines how to extract the data from the EventList and display it in the table.

EventList people = new BasicEventList();
// Add all the elements
for (Person p : getPeople()) {
    personList.add(p);
}
TableFormat personTableFormat = GlazedLists.tableFormat(Person.class,
    // Names of the properties to fetch
    new String[] {"name","age"},
    // Names for the columns
    new String[] {"Name", "Age"});
EventTableModel tableModel = new EventTableModel(people, personTableFormat);
JTable table = new JTable(tableModel);
// Any modifications to the ‘people’ list is automatically reflected in the table

Provides a simplified means of filtering a table or list

Perhaps one of the most important features of any interactive table is the ability to filter out extraneous information. Glazed Lists makes this possible by chaining together EventList transformations; these transformations provide a different view of the underlying data. When the original model is modified, the filtered views automatically pick up the changes and update accordingly.

Say we want to provide the ability to filter the list based on people’s names. We will add a listener to a text field which listens for changes (new letters typed or deleted), and filters the list in real time. Once we have an EventList of some sort, it is easy to create a new “view” of that same list, filtering out entries you don’t want to see. You do this by wrapping the list in a FilterList, and then assigning some sort of filter criterion. Let’s start simple with a filtered list which only shows those users whose names start with the letter ‘A’.

EventList personList = new BasicEventList();
personList.add(new Person("Anthony Hopkins", 74));
personList.add(new Person("Barack Obama", 50));
personList.add(new Person("American McGee", 39));

Matcher personFilter = new Matcher() {
    public boolean matches(Person p) {
        return p.getName().startsWith("A");
    }
};
// Create a filtered list
FilterList filteredList = new FilterList(personList, personFilter);
// Displaying the people in a list as opposed to a table; could also create EventTableModel
// as in the last example.
EventListModel filteredListModel = new EventListModel(personList)
JList list = new JList(filteredListModel);
// At this point, shows Anthony Hopkins and American McGee

The filter I’ve defined above is static – once it’s instantiated, its filter condition never changes. Glazed Lists supports dynamic filters as well, through the MatcherEditor interface. We will see how to use a MatcherEditor instance for a text field, but first we need to tell Glazed Lists which strings to use when filtering for a given object. We do this with the TextFilterator interface.

Picture illustrating a FilterList which accepts only those people whose name starts with 'A'

 

public class PersonTextFilterator imlements TextFilterator {
    // Slightly strange interface, but done for efficiency reasons
    public getFilterStrings(List baseList, Person element) {
        baseList.add(element.getName());
        // Allow users to filter by age as well
        baseList.add(String.valueOf(element.getAge()));
    }
}

The MatcherEditor class to use in our case is TextComponentMatcherEditor. We provide it with the text field that it will use as the filter source, as well as an instance of the PersonTextFilterator class we just defined.

EventList personList = new BasicEventList();
personList.add(new Person("Anthony Hopkins", 74));
personList.add(new Person("Barack Obama", 50));
personList.add(new Person("American McGee", 39));

JTextField filterTextField = new JTextField();
// Add the text field to the UI - add to a JPanel

// Hook the text field up to a filter list
MatcherEditor filter = new TextComponentMatcherEditor(filterTextField, new PersonTextFilterator());

// Create a filtered list
FilterList filteredList = new FilterList(personList, filter);
EventListModel filteredListModel = new EventListModel(filteredList)
JList list = new JList(filteredListModel);
// List automatically updates in response to typing in the text field

Each transformed EventList is itself an EventList, meaning it can also be used as the basis of an EventListModel or EventTableModel. This chaining capability is extremely powerful.

Provides sorting capabilities

Finally, Glazed Lists makes it extremely easy to implement rich sorting capabilities in your tables or lists.

As we saw in the last example, it is possible to wrap a given EventList to provide a different view. In this case, we will have a sorted view of the data, which automatically updates whenever the underlying data changes.

To create the SortedList, you need to make your domain object implement Comparable, or create a Comparator. For instance,

public class PersonNameComparator implements Comparator {
    public int compare(Person p1, Person p2) {
        return p1.getName().compareTo(p2.getName());
    }
}
EventList personList = new BasicEventList();
personList.add(new Person("Anthony Hopkins", 74));
personList.add(new Person("Barack Obama", 50));
personList.add(new Person("American McGee", 39));

Comparator nameComparator = new PersonNameComparator();
// Create a sorted list decorator
SortedList sortedList = new SortedList(personList, nameComparator);
EventListModel sortedListModel = new EventListModel(sortedList)
JList list = new JList(filteredListModel);

A SortedList, wrapping a standard EventList

While the above example works for JLists, it’s nice to be able to sort a JTable as well. This is not too hard, either, as long as you have set up a TableFormat instance as described in the first section of this post. In essence, the TableFormat defines the type of each column, which is then used to sort the table whenever the corresponding column header is clicked. This behavior is defined in the TableComparatorChooser class, which exposes a static method to perform the installation on the target JTable. Here’s an example:

Comparator nameComparator = new PersonNameComparator();
// Create a sorted list decorator
SortedList sortedList = new SortedList(personList, nameComparator);
EventTableModel peopleTableModel = new EventTableModel(sortedList, new PersonTableFormat());
JTable peopleTable = new JTable(peopleTableModel);

// Use MULTIPLE_COLUMN_MOUSE to allow sorting by multiple columns, or SINGLE_COLUMN
// to sort by just a single column
TableComparatorChooser tableSorter = TableComparatorChooser.install(
    peopleTable, sortedList, TableComparatorChooser.MULTIPLE_COLUMN_MOUSE);

// At this point, clicking on the table headers will sort by this column

As the more detailed Glazed Lists tutorial warns,

By default, TableComparatorChooser sorts by casting column values to Comparable. If your column’s values are not Comparable, you’ll have to manually remove the default Comparator using TableComparatorChooser.getComparatorsForColumn(column).clear().

As long as your columns are represented by Comparable classes such as Number or String, you shouldn’t have to worry about this caveat.

Conclusion

Glazed Lists is one of the best Java Swing libraries I’ve ever used. It simplifies life for the programmer as well as the end user of the software project, since tables that allow sorting and filtering are far more useful than those which do not. If you do any sort of Swing programming, you owe it to yourself to try this library out. You can find much more information, including the aforementioned tutorial, on the Glazed List website.

 

Chaining together multiple list transformations makes it easy to create powerful programs

P2PU – World of Webcraft

January 13, 2011 1 comment
P2PU (Peer 2 Peer University) is a “grassroots open education project that organizes learning outside of institutional walls and gives learners recognition for their achievements. ”  In particular, they are offering a series of free online classes for web development; you can see the full list at http://p2pu.org/webcraft.  Once you sign up for an account, you can apply to take part in any of the courses offered.  Due to the limited number of slots, you must complete some rudimentary tasks to prove that you are going to be involved in the class, in order to apply for admission. 

I signed up for the Javascript 101 course and was asked to watch a 30 minute introductory video, write a short blog post, and then write a simple javascript program that did some addition and displayed the result as an alert in the browser.  Pretty simple stuff, but it weeds out those who are not willing to do some work on their own and would just slow down the class.

I also applied for, and was accepted into, the Introduction to Ruby and Rails course.  Longtime readers of this blog might remember that I have dabbled in Rails before, and had an initially favorable impression.  But like any skill, you must use your knowledge or it quickly fades away.  I’m basically back to square one with Ruby/Rails due to not having used it in the past year, so I’m looking forward to the added impetus of a class to get me to learn it.

Given that the vast majority of my programming has been on desktop / mobile client applications, I am excited to learn some web programming languages and techniques.  Technology changes quickly, and I can all but guarantee my expertise in Java Swing is not going to count for much in a few years.

I’ll be sure to write about my impressions as I go through the course; in the meantime, I urge you to sign up for these courses before they fill up.

Of Rubber Stamps and CheckBoxes: Why your Android ListView is broken

November 5, 2010 5 comments
There are a lot of tutorials on how to use ListViews, the ubiquitous UI element in which a list of information is presented in a linear, scrolling view.  However, I do not think they do enough at explaining just why they behave in the way they do, especially with respect to stateful UI components. 

An example list view, with a very simple "view" for each row

The problem is, people do not quite understand what it means for views to be reused in the context of a ListView. This blog post is intended to explain what that means, and what consequences that has for your list views and your adapters.

The following description comes from the Swing documentation on how JTables are rendered, but it is instructive for this description, and introduces the central metaphor that helps explain what’s going on with the way ListViews are rendered.

Before you go on to the next few tasks, you need to understand how tables draw their cells. You might expect each cell in a table to be a component. However, for performance reasons, Swing tables are implemented differently.

Instead, a single cell renderer is generally used to draw all of the cells that contain the same type of dataYou can think of the renderer as a configurable ink stamp that the table uses to stamp appropriately formatted data onto each cell.

Why would the designers implement the rendering this way?  Imagine if each row of the table (or list) were a separate object.  Each row would hold references to its own UI widgets, e.g. for TextViews (labels), ImageViews (images).  If there were N rows and each row takes on average M bytes of memory, you’d have an average memory footprint of N*M bytes.  Given that these lists need to be able to display a large number of items, this would be an unacceptable memory footprint.  Furthermore, since most of the list would be offscreen, most of the memory would be completely wasted.  While it would be simpler to implement, it would bring any Android phone to its virtual knees.

The designers, borrowing from the Swing designers, chose instead a much more memory efficient way of rendering the lists – if only X items are visible on the screen, where X is much less than N, the total memory footprint would be X*M bytes.  It takes more work in the ListView class to calculate exactly which rows are visible and to ensure that only their views are rendered, but it’s worthwhile.  Each visible row uses its own “rubber stamp”, filling in the details specific to that row. 

What you do by defining your adapter view in XML is define the raw features that make up your rubber stamp.

This is the basic rubber stamp that might be used for displaying tweets in a Twitter application.  There are four elements: an image, a username label, a paragraph text label, and another label for how the tweet was posted.
Let’s say that at most four tweets can fit on the screen at once.  When the first row needs to be rendered, the view is null (hasn’t been instantiated) and must be inflated from XML.  This process is repeated for the second and third row.  When the next tweet needs to be loaded on the screen, it can reuse the view (rubber stamp) of the view that’s just left the screen at the top.

Row views are reused

This leads us into what trips a lot of Android developers up.  You cannot maintain state within the UI components alone.  What do I mean?  You cannot use, for instance, Checkboxes in each row and hope to have them keep track of which items are checked or not.  Why?  This will work for a small number of rows, but once you get past the number that can fit on one screen, you’ll start to see weird problems.  If you can fit X rows on the screen, then item 1, X+1, 2X+1, … will all share the same checkbox.  Thus by changing the state of item 1, you will change the status of all the rows further down the line that are a multiple of X away from it.  The correct way of handling this is to have each row data object, that which is being visualized by the Adapter, maintain this state.  You can see an example of this in a tutorial on how to use checkboxes in listviews. I see this question come up repeatedly on StackOverflow, and it stems from the programmer not understanding how these views are reused in each row.
The metaphor of the rubber stamp fits more with that of JTable rendering, where there truly is a single component that gets reused over and over again, whereas there are multiple reused row View objects in the case of Android.  Why isn’t the single component approach used for Android?  At first I assumed it was, until I read this great explanation on android.amberfog which illustrates how there are actually as many views inflated as there are potential rows visible at any time.  The reason as far as I can imagine is that the user needs to be able to interact with each rows.  If all that was required was the static view of each row, then it would be fine to use a single rubber stamp, render a row, change an offset in the graphics context, and render the next row.  As it stands, however, each row needs to be able to detect when it is clicked on, long clicked on, etc.  Furthermore, each row could have UI elements which themselves could be interactive.  Thus it is insufficient to present static views of the data; the user would be unable to interact with the rows in the way he expects.
I hope this explanation is helpful, and if I’ve gotten anything wrong, I’d love to hear about it in the comments.

(All images were created with the use of Balsamiq, a great tool for mockups).

EventBus: Introduction and troubleshooting for annotation problems

May 11, 2010 5 comments

Today I ran into a problem with a great project I use called EventBus, so I figured I’d use it as an opportunity to introduce the project, as well as give a solution to the problem I was facing.

EventBus, an open-source Java project, is described on its homepage as

Pub-sub Event broadcasting mechanism, Swing-oriented

In a nutshell, the publication/subscription mechanism is an alternative to explicitly defining listeners and adding them to objects of interest (the Observer/Observable pattern). This is beneficial because it allows for a more modular, late-binding approach to events. As the EventBus page describes,

“It replaces the tight coupling introduced by explicit listeners with a flexible loosely coupled design. ”

Here is the typical MVC type code for having objects notified of changes in state (from my post about creating a solar system model in Java):

public class SolarSystemModel extends Observable {

    // snip

    public void setDay(int day) {
        int oldDay = this.day;
        this.day = clampDay(day);
        if (oldDay != this.day) {
            setChanged();
            notifyObservers();
        }
    }

    public void setHour(int hour) {
        int oldHour = this.hour;
        this.hour = clampHour(hour);
        if (oldHour != this.hour) {
            setChanged();
            notifyObservers();
        }
    }
}
public class  SolarSystemView extends JComponent implements Observer {

    public void update(Observable o, Object arg) {
            repaint();
    }

}

This works fine but note a couple problems.
1) I am forced to either make my domain object extend Observable, in which case I lose out ability to subclass other more useful classes, or forced to roll my own Observer type interfaces, hold a list of listeners, add them all manually.

2) If we were to add another class that visualized the solar system, it would need to get a handle to the solar system model and be tightly coupled with it.

EventBus strives to get away from explicit listeners that the model objects need to keep track of. Instead, there are feeds of objects or topics that interested classes can register their interest in and receive the updated objects. The above example could be redone using EventBus as follows:

public class SolarSystemModel {

    // snip

    public void setDay(int day) {
        int oldDay = this.day;
        this.day = clampDay(day);
        if (oldDay != this.day) {
            EventBus.publish(this);
        }
    }

    public void setHour(int hour) {
        int oldHour = this.hour;
        this.hour = clampHour(hour);
        if (oldHour != this.hour) {
            EventBus.publish(this);
        }
    }
}

public class  SolarSystemView extends JComponent implements EventSubscriber<SolarSystemModel> {

    public SolarSystemView() {
        // Snip
        EventBus.subscribe(SolarSystemModel.class, this);
    }

    public void onEvent(SolarSystemModel m) {
        // Use the updated solar system model to repaint
        repaint();
    }
}

Again, note that the model now is free to extend whatever class it wants, rather than Observable.

This example is a bit too trivial to fully illustrate the power of EventBus; for a more real world example see the excellent article on refactoring a Swing financial app to use EventBus.

In addition to explicitly declaring that you are an EventSubscriber, you can annotate methods in your object to announce the same intent. For instance, rather than doing something like

public class View implements EventSubscriber<ModelClassA>, EventSubscriber<ModelClassB>, EventSubscriber<ModelClassC> {

    public View() {
        EventBus.subscribe(ModelClassA.class, this);
        EventBus.subscribe(ModelClassB.class, this);
        EventBus.subscribe(ModelClassC.class, this);
    }

    public void onEvent(ModelClassA) {
        // Handle this type of object
    }

    public void onEvent(ModelClassB) {
        // Handle this type of object
    }

    public void onEvent(ModelClassC) {
        // Handle this type of object
    }
}

you can instead name the methods however you want, using annotations:

public class View {

    public View() {
        // Important: without this line, the annotations will not be read
        AnnotationProcessor.process(this);
    }

    @EventSubscriber(eventClass=ModelClassA.class)
    public void handleTypeA(ModelClassA) {
        // Handle this type of object
    }

    @EventSubscriber(eventClass=ModelClassB.class)
    public void handleTypeB(ModelClassB) {
        // Handle this type of object
    }

    @EventSubscriber(eventClass=ModelClassC.class)
    public void handleTypeC(ModelClassC) {
        // Handle this type of object
    }
}

This is a good feature, since it allows you to customize the naming of your methods, providing more descriptive names than “onEvent”.

OK, after that rather lengthy introduction, here is the problem I was running into:

“incompatible types
required: java.lang.annotation.Annotation
found: org.bushe.swing.event.EventTopicSubscriber”

It was fairly inscrutable to me, so once I found out what was going on, I figured I had to write about it.

The problem is that there are identically named classes, one for the actually implementation, and one for the annotation. So instead of importing

org.bushe.swing.event.EventTopicSubscriber

you must instead import

org.bushe.swing.event.Annotation.EventSubscriber

If you use the “Fix all imports” in NetBeans, it will automatically choose the wrong one.

Java Swing Drag and Drop Troubleshooting

April 11, 2010 Leave a comment

Dragging domain objects between JTables

Drag and drop Troubleshooting

Drag and drop is an ubiquitous UI metaphor in common desktop/web applications.  I am in the process of finally learning how to accomplish drag and drop in Java through Swing; it hasn’t been an easy process.  I have been keeping track of some of the problems I have run into along the way, and will be updating this post as I find more.  Make sure you read through the whole set of Drag and Drop tutorials before you attempt to implement Drag and Drop; a lot of my mistakes came from jumping the gun and not reading through the whole document.  I will be making a post later with more complete source code illustrating how to drag and drop domain objects between components (the tutorials online usually show text being dragged and dropped rather than class instances).

Problem: When I attempt to drag an item out of the table, nothing happens
Solution:

  • Did you remember to call “component.setDragEnabled(true)” first?
  • Make sure you are not using a DataFlavor constructed with default constructor; if it is the drag action will not occur

Problem: When I complete a drag and drop, I get a class cast exception
Solution: Make sure you have created the DataFlavor correctly; you should define the class of the serialized object within the constructor to the DataFlavor.  E.g. if you are sending an instance of a class named Foo.java, you could create a DataFlavor as follows:

public class Foo implements Transferable, Serializable {

    public static final DataFlavor DATA_FLAVOR = new DataFlavor(Foo.class, "Foo");

    public DataFlavor[] getTransferDataFlavors() {
         return new DataFlavor[]{DATA_FLAVOR};
    }

    public boolean isDataFlavorSupported(DataFlavor flavor) {
        return flavor == FLAVOR;
    }

    public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
        return this;
    }
}

Problem: When I attempt to drop an item onto a table, nothing happens
Solution:
If the table is empty, make sure you called “JTable.setFillsViewportHeight(true)” first?  Explanation:

JTable.setFillsViewportHeight(true)is invoked to set the fillsViewportHeight property. When this property is true the table uses the entire height of the container, even if the table doesn’t have enough rows to use the whole vertical space. This makes it easier to use the table as a drag-and-drop target. (http://java.sun.com/docs/books/tutorial/uiswing/components/table.html)

See more info about and drag and drop with respect to empty tables.

Problem: When I move an item from one table to another, the item isn’t deleted from the first table
Solution: Make sure you do the modification to the underlying table model in the exportDone method.

For instance:

@Override
public void exportDone(JComponent c, Transferable t, int action) {
    if (action == TransferHandler.MOVE) {
        JTable table = (JTable) c;
        list.remove(table.getSelectedRow());
    }
}
Categories: Java, programming, UI Tags: , , ,

How to make a solar system: Introduction to affine transformations and Java 2D

February 2, 2010 5 comments

At the heart of all computer graphics lies linear algebra, and specifically matrix multiplication. One can use matrices to perform all sorts of operations, such as transformations to move from one coordinate system to another, as well as a set known as affine transformations. Affine transformations are those that map lines to lines in the transformed coordinate space, and which preserve the relative distance between points. An affine transformation consists of one or more translation, rotation, scaling, and shearing transformations.

See the following external sites for translation and rotation examples, shearing, scaling.

Java has a class to represent these affine transformations, as well as shorthand methods to apply them to a Graphics2D context.

Rotate about origin
Rotate about a point
Scale x and y axis by given amount
Shear
Translation

If you do any work involving Graphics2D in Java (and if you work with Swing components, you implicitly do), knowing how to use affine transforms is extremely beneficial. With them you can express and code things more succintly, and clearly than is possible without them.

We’ll start with a simple example, once with standard Swing painting code, and once using affine transformations. Finally we will end with a more fully fleshed out example that really illustrates the power of affine transformations, rendering a simplified overhead view of the solar system. This example would be extremely difficult to replicate without affine transformations.

For the simple example, let’s draw dots in a circle pattern. The easiest way to start drawing to the screen is simply to subclass the JComponent class and override the paintComponent(Graphics g) method. Here we go:

   /**
     * Draw a series of dots in a circular pattern
     * @param g
     */
    @Override
    public void paintComponent(Graphics g) {
        // Don't forget to call the super method
        super.paintComponent(g);

        int radius = getWidth() / 2;

        for (int i = 0; i &lt; NUM_DOTS; i++) {
            double theta = 2 * Math.PI * ((double) i / NUM_DOTS);
            int x = (int) (radius * Math.cos(theta));
            int y = (int) (radius * Math.sin(theta));

            // these x and y are relative to center of circle; currently origin
            // is at upper left corner of window.  Add to x and y to
            // translate.
            x += getWidth() / 2;
            y += getHeight() / 2;

            g.drawOval(x, y, 1, 1);
        }

    }

Here’s a picture of the result.

(Full source)

Now, here’s that same code using the implicit affine transformation of the rotate() method of Graphics2D.

/**
     * Draw a series of dots in a circular pattern
     * @param g
     */
    @Override
    public void paintComponent(Graphics g) {
        Graphics2D g2 = (Graphics2D) g;
        // Don't forget to call the super method
        super.paintComponent(g);

        int radius = getWidth()/2;

        // Translate the origin to the center of window
        g2.translate(getWidth() /2, getHeight() /2);
        for (int i = 0; i &lt; NUM_DOTS; i++) {
            g2.rotate(RADIANS_PER_DOT);
            // We have rotated about the origin; draw a ray out along x axis
            // of new coordinate system
            g2.drawOval(radius, 0, 1, 1);

        }
    }

(Full Source)

As you can see from the screenshots, they come out functionally the same. In this case there’s not a huge advantage to using the rotation over the standard method. But what if we weren’t drawing dots along the radius of the circle, but instead were drawing rectangles that laid tangent to the circle? Here’s how simple that is to do using the rotations..

// Define the number of pixels wide each box is
private static final int BOX_SIZE = 5;

// Replace the call to drawOval with fillRect
g2.fillRect(radius, 0, BOX_SIZE, BOX_SIZE);

Here is the result

(Full source)

Think how complicated this would be to accomplish if you were not using affine transforms; you would need to manually calculate the coordinates of each corner of each box, create a polygon from those points, and then call fillShape on the polygon.

The other place where affine transformations shine is when you need to place objects relative to each other. For instance, you might draw a table with a bowl of fruit on it; if your table moves, you would like the bowl to move as well. I will show you how you can render a simplified version of the solar system where the earth revolves around the sun, while at the same time the moon orbits the earth. As you can imagine, implementing this without affine transformations would be absolutely infeasible.

First we separate our model from our view as per the model view controller pattern; the state of the solar system is kept in the model which the view uses to render itself. Since the state of the model will be observed by the view, we make it a subclass of the Java Observable class.

package solarsystem;

import java.util.Observable;

public class SolarSystemModel extends Observable {

    public static final int DAYS_PER_EARTH_REVOLUTION_AROUND_SUN = 365;
    public static final int HOURS_PER_EARTH_REVOLUTION_AROUND_AXIS = 24;

    // <a href="http://en.wikipedia.org/wiki/Orbit_of_the_Moon">http://en.wikipedia.org/wiki/Orbit_of_the_Moon</a>
    // "The orbit of the Moon around the Earth is completed in approximately 27.3 days"
    public static final float DAYS_PER_MOON_ORBIT_AROUND_EARTH = 27.3f;

    private int day;
    private int hour;

    public int getDay() {
        return day;
    }

    public void setDay(int day) {
        int oldDay = this.day;
        this.day = clampDay(day);
        if (oldDay != this.day) {
            setChanged();
            notifyObservers();
        }
    }

    public int getHour() {
        return hour;
    }

    public void setHour(int hour) {
        int oldHour = this.hour;
        this.hour = clampHour(hour);
        if (oldHour != this.hour) {
            setChanged();
            notifyObservers();
        }
    }

    private int clampDay(int day) {
        return day % DAYS_PER_EARTH_REVOLUTION_AROUND_SUN;
    }

    private int clampHour(int hour) {
        return hour % HOURS_PER_EARTH_REVOLUTION_AROUND_AXIS;
    }

}

(Note that we need to call setChanged() before notifyObservers() or our Observers registered with the model will not be updated.)

Now that we have our model defined, we need to make a view to actually render the solar system. Just as in our previous examples, I make the view extend JComponent for ease of display in a JFrame.

public class SolarSystemView extends JComponent implements Observer

The Observer interface allows classes to be notified when an Observable object changes; since we want to keep our view in sync with the model, this is just what we will do.

Here is the meat of the class:

        @Override
        public void paintComponent(Graphics g) {
            Graphics2D g2 = (Graphics2D) g;

            drawSpaceBackdrop(g2);

            // Set the origin to be in the center of the screen
            g2.translate(getWidth()/2, getHeight()/2);

            // Order matters, since the earth placement is dependent upon the sun
            // placement, and the moon placement is dependent upon the earth placement
            drawSun(g2);

            drawEarth(g2);

            drawMoon(g2);
        }

The graphics context is passed into each drawing method, which may or may not modify the context. The drawSpaceBackdrop method merely draws a few random stars on a black background; see the following screenshot:

The code for that is fairly straightforward:

    /**
         * Draws a black backdrop with star field
         * @param g2
         */
        private void drawSpaceBackdrop(Graphics2D g2) {
            // Draw background as black
            g2.setColor(Color.BLACK);
            g2.fillRect(0, 0, getWidth(), getHeight());

            g2.setColor(Color.WHITE);
            for (int i = 0; i &lt; NUM_STARS; i++) {
                g2.fillOval(starX[i], starY[i], starRadius[i], starRadius[i]);
            }

        }

starX, starY, starRadius are parallel int arrays that are initialized earlier in the program by a random int generator.

    /**
     * Creates and populates our arrays of star x values, star y values, and
     * star radii
     * @param width     what is max x value we should consider for star
     * @param height    what is max y value we should consider for star
     */
    private void createStarField(int width, int height, int maxRadius) {
        // Create the arrays
        starX = new int[NUM_STARS];
        starY = new int[NUM_STARS];
        starRadius = new int[NUM_STARS];
        // Fill them in with random values
        for (int i = 0; i &lt; NUM_STARS; i++) {
            starX[i] = random.nextInt(width);
            starY[i] = random.nextInt(height);
            starRadius[i] = random.nextInt(maxRadius);
        }
    }

After initializing the arrays and drawing the stars, we then draw the sun. Note that we translate the origin from the upper left corner to the center of the screen; this allows each of the drawing methods to consider its own local coordinate system and not have to remember to translate from upper left corner of screen. For instance, the center of the sun is at (0,0) in its coordinate system.

        /**
         *
         * @param g2 graphics context with (0,0) in center of screen (where sun will
         * be centered)
         */
        private void drawSun(Graphics2D g2) {
            int sunRadius = (int) (SUN_RADIUS_PROPORTION * getWidth());
            GradientPaint sunColor = new GradientPaint(0, 0, Color.YELLOW, 0, sunRadius, Color.RED);
            g2.setPaint(sunColor);
            g2.fillOval(-sunRadius/2, -sunRadius/2, sunRadius, sunRadius);
        }

We apply a gradient just to make it look slightly nicer than a monochrome sun.

After having drawn the sun, it’s time to draw the earth.

        /**
         * Draws the earth to the screen, whose position is dependent upon the
         * day of the year
         * @param g2 the graphics context with its origin in the center of the sun
         */
        private void drawEarth(Graphics2D g2) {
            // Draw the earth
            // Calculate what portion along its orbit the earth is, and thus how
            // far to rotate about our centerpoint
            double earthTheta = map(model.getDay(), 0, SolarSystemModel.DAYS_PER_EARTH_REVOLUTION_AROUND_SUN, 0, TWO_PI);

            // Rotate our coordinate system by that much
            g2.rotate(earthTheta);
            // Translate the earth
            int distanceFromEarthToSun = (int) (EARTH_DISTANCE_PROPORTION_SCREEN * getWidth());
            g2.translate(distanceFromEarthToSun, 0);

            int earthRadius = (int) (EARTH_RADIUS_PROPORTION * getWidth());
            GradientPaint earthColor = new GradientPaint(0, 0, Color.BLUE, 0, earthRadius, Color.GREEN.darker(), true);
            g2.setPaint(earthColor);

            g2.fillOval(-earthRadius/2, -earthRadius/2, earthRadius, earthRadius);
        }

If you’ve read my earlier blog post on the map function, you know that it maps a value from one range of numbers to another. We must calculate the number of radians to rotate so that we can position our earth correctly along its orbit.

Note that we first rotate and then translate; if we did it in the opposite direction we would see the earth spin about its axis but it would not revolve around the earth.

The drawMoon method is much the same; the main difference is that we calculate its position along its orbit based on its much smaller time to orbit the earth.

        /**
         * Draw the moon to the screen, whose position is dependent upon that of
         * the earth and the day of the year, which dictates its position along
         * its orbit around earth
         * @param g2 the graphics context with its origin in the center of the earth
         */
        private void drawMoon(Graphics2D g2) {
            double moonTheta = map(model.getDay(), 0, SolarSystemModel.DAYS_PER_MOON_ORBIT_AROUND_EARTH, 0, TWO_PI);

            int moonRadius = (int) (MOON_RADIUS_PROPORTION * getWidth());
            g2.setColor(Color.WHITE);
            g2.rotate(moonTheta);
            int distanceFromEarthToMoon = (int) (MOON_DISTANCE_PROPORTION_SCREEN * getWidth());
            // Translate the earth
            g2.translate(distanceFromEarthToMoon, 0);
            g2.fillOval(-moonRadius/2, -moonRadius/2, moonRadius, moonRadius);
        }

Finally all we have to do is create an instance of the model and view, hook them together, and display them in a JFrame.

        public static void main(String[] args) {
            JFrame frame = new JFrame("Solar System");

            final SolarSystemModel model = new SolarSystemModel();
            final SolarSystemView view = new SolarSystemView(model);
            model.addObserver(view);

            JPanel panel = new JPanel();
            panel.add(view);
            frame.add(panel);

            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.pack();
            frame.setVisible(true);
        }
   

If we run it as is, we see the planets aligned, since the model starts off at day zero. It’s a lot more fun to be able to interact with the model. To do that, we add a JSlider that modifies the model.

            final JSlider daySlider = new JSlider(0,SolarSystemModel.DAYS_PER_EARTH_REVOLUTION_AROUND_SUN);
            daySlider.setPaintLabels(true);
            daySlider.setPaintTicks(true);
            daySlider.setMajorTickSpacing(100);
            panel.add(daySlider);
            daySlider.addChangeListener(new ChangeListener() {
                public void stateChanged(ChangeEvent e) {
                    model.setDay(daySlider.getValue());
                }
            });

With that addition, we can move the slider and watch the planets move.

That’s it for this time. Can you figure out how to use the hour field of the model with another slider to make the earth rotate about its axis as it revolves around the sun?

Full model source
Full view source

Categories: Java, programming, regular Tags: , , ,