Archive

Archive for February, 2010

Touch screen interface considerations

February 24, 2010 5 comments
When moving to a new hardware platform such as a touch sensitive mobile device, it is important to design the UI with the constraints of the device in mind.  Early Windows tablets failed in large part due to the OS not being suited to touch interaction; icons were too tiny to press on without a stylus.  I have heard the same complaint about early generation Windows Mobile phones; the user in question said he had to literally press tiny squares on the screen where the up and down arrows were in the scroll bar.  The UI widgets from desktop programs were ported over to these new platforms without giving thought to the decreased screen resolution, screen size, and finger dexterity that a mobile platform provides.

The iPhone and its multitouch gestures provide very intuitive, natural ways of interacting with the reduced screen real estate.  Scroll bars are no longer controls that must be held and dragged; rather they are small, unobtrusive widgets that appear only while a page is being dragged.  The entire page becomes the draggable area, in keeping with the idea of maximizing the size of items that need to be interacted with.  Coupled with the pinch gestures for zooming in and out of webpages, the browsing experience on the iPhone is almost flawless.  There are two main problems stemming from the metaphor of dragging the entire webpage: the lack of hover and drag and drop.

Hover behavior

Given the fact that touch screens only pick up your inputs when you are touching the screen, there is no notion of hovering (the browser doesn’t display a cursor where you last touched).  Hovering over elements in a webpage usually has one of two effects:

1) Showing a tool tip

2) Displaying additional information in a section of the browser; e.g. mousing over an embedded link reveals the target of the link in the lower left corner while using firefox.

In very few cases, mousing over or hovering over elements actually acts like a click; see dontclick.it for an example.

Number 2 is not a big concern; when you long press on an embedded link, a screen pops up revealing the target of the link:

Number 1 is more of a concern.  If your site is designed to be viewed on a mobile browser, you should ensure that that you have a version of the CSS that does not require you to mouse over or hover over elements to reveal information.

Drag and drop

Drag and drop is a common desktop OS UI metaphor.  It can be slightly tricky for users to handle due to the dexterity required, especially if the drag and drop targets are small; most digital natives don’t have any problems with it.  It is less common on the web than traditional desktop fat clients, but there are certainly webpages that use it.  For instance, WordPress uses drag and drop to configure sidebar widgets:

The problem is that these movable elements cannot be selected and moved on the iPhone; they select when pressed but as soon as you move your finger, they deselect and instead the page moves.  While this would be a problem in and of itself, drag and drop is also used in common UI widgets such as sliders.

Sliders

Sliders are an excellent UI controller for selecting an approximate value from a bounded range of continuous values.  Look at Endless.com‘s elegant dual slider control for narrowing down price:
With a single control, we get two functions:
  1. Displaying the current range of values
  2. Allowing user to narrow down that range into a smaller subset.

For instance, if user only wants sneakers that cost between 50 and 100 dollars he just drags both ends of the slider bar:

This illustrates a drawback of the slider approach: it is imprecise when the range of available values is large and the control is small on the page; the slider would have to have subpixel precision in order to make user

The frustrating thing is that the iPhone has BRILLIANT slider support in its iPod app.  They get around the problem of precision by making use of the vertical dimension that normally is ignored.  When your finger is right on the slider, the slider follows your finger one to one.  When you move your finger down the screen, the sensitivity is turned down and it takes a greater horizontal change to elicit a change in the position of the slider (changing from “Hi-Speed Scrubbing” to half speed to quarter speed.)

White dot represents where the finger is held down

The drawback of this interaction is that it is novel; I discovered it by accident and I’m sure a lot of iPhone users don’t even know about it.  However, it is a wonderful solution to the problem of imprecise fingers; it could be used in traditional desktop apps as well.

Sites with interactive javascript graphs frequently use sliders; see for instance OKCupid’s blog post on why you should date older women:

On the iPhone, it is impossible to move the slider to interact with the graph.

The problem is that there is no standard HTML way of specifying a slider; instead sites use a mishmash of Javascript to get that effect.  (Read a tutorial how).  If there were a standard HTML component, it’s likely that the iPhone could handle it more gracefully.  For instance, the iPhone changes the standard way dropdown menus are implemented

Standard desktop view of a dropdown control

iPhone view of control – a finger-friendly wheel widget pops up, occupying half of the screen.

So one solution to the problem of lack of slider support would be to have a standard HTML widget for sliders that the iPhone could then interpret and render in a more useful way (e.g. with the vertical dimension added like on the iPod slider controls).

Another potential solution would be to make a gesture that indicates a mouse drag.  There is already a way to select text, which is done in a desktop environment via the same mechanism as drag and drop (press, move mouse, release mouse button).  I could see this working in one of two ways.

1) As a multitouch gesture.  One finger is held as still as possible indicating that the background should be anchored, while the other finger proceeds to press on the draggable component and move it around the screen.  The drawback of this is that the pinch gesture currently works even when one finger is held still; thus this gesture would overload that of pinch.  I know I personally pinch by moving both fingers, but some people might not.

2) When a draggable entity is long-pressed for long enough (1, 2 seconds?) browser enter different mode where motion events do not scroll the page but instead move the draggable item.

If you have a multitouch enabled browser, you can see how dragging might work in this proof of concept page. Unfortunately the pinch gesture does not work correctly – it’s much jerkier than a standard webpage.  Their implementation suggests a third alternative, immediately entering drag mode when a draggable item is touched.  The problem with this is that, if there were multiple draggable items on the screen it would be very difficult to actually grab a portion of the screen in order to pan around to new areas.

This way of handling drag and drop is used by games on iPhone, such as Words With Friends.  Note in the left picture the scroll bars indicating that the board is being moved.  On the right the finger is moving while over the E tile, so the E is dragged along with the finger.  Moving the finger off the screen is equivalent to releasing the mouse button in a standard drag and drop action; the tile is laid down in nearest square.

Conclusion

The iPhone has introduced some great UI metaphors such as pinch and zoom as well as making the entire area of the page draggable for easy scrolling of text.  In the realm of the web browser specifically, these advances prevent two common desktop OS UI metaphors from working, that of hovering over elements and dragging and dropping elements.  The problem of hovering is not insurmountable; web developers need to be aware of this drawback and code their websites in such a way that information is not lost if you cannot view tooltips or otherwise hover over elements.  The second problem, that of an inability to select an element, drag it, and drop it, is more pressing.  There are certain tasks that are absolutely impossible to complete while using an iPhone browser, such as anything requiring you to move a slider control.

Hopefully the new version of the iPhone OS can remedy the drag and drop problem.

Categories: UI Tags: , , ,

Netflix queue manager – a case study in UI design

February 16, 2010 Leave a comment

In this blog post, I examine some good and bad aspects of user interface design present in the queue management system on Netflix.com.  By examining these issues we can see some concrete examples of more general purpose principles of UI design that are promulgated by books such as About Face by Alan Cooper

For those unfamiliar with Netflix or computer science data structures, a queue is a first-in-first-out data structure, just like a line at a grocery store.  By moving a movie to the top/front of the queue, you ensure that it is the next movie to be delivered.

Let’s examine some good and bad aspects of the UI design in the queue management system of Netflix.

Good:

The queue screen is very minimalist in its appearance, containing only relevant information without fluff.  For instance, the tabs immediately display how many movies you have both in your DVD queue as well as instant queues; you don’t have to look far.

One thing not immediately obvious to a casual observer is the consistent color coding scheme at play.  Notice in the tabs that the DVD section and any related text is red; the word Instant in the tab and all words related to the Instant queue are in blue.

This color scheme is present while searching for movies as well:

The queue screen does more than display information about the movies currently in your queue; if that were all it did, it could be a static HTML page.  Instead, this screen is where you can rearrange the order of the movies as well as delete them from the list.

Deleting a movie from the queue is entirely painless.  There is an unambiguous X link to the right of each movie.  Mercifully, clicking the X does not pop up a dialog box asking “Are you sure you want to delete the movie from the queue?” or any such nonsense.  Instead, the row becomes grayed out with an undo link replacing the previous X button.  This is absolutely the right way to do things, and I wish that more sites worked this way.  Throwing up a dialog box in front of destructive actions as a way of removing responsibility from the programmers/computer program is all too common and lazy.  It’s harder to implement the ability to undo destructive actions, but from an interaction design perspective it is far superior.  Users often click through dialogs without actually reading what they’re doing (I accidentally deleted a high-level Diablo II character this way.  I’m not bitter or anything).

Undo:

After deletion.  Note that the movie underneath the removed one has its position moved up in the queue

In order to move movies around, you have three main options.  The first is dragging and dropping the movie to its new location.  The second is typing in a value in the text box and hitting enter.  The third, and probably most useful, is hitting the Top button which pushes the movie to the front of the queue.  Each works fine, though I will have more to say about the dragging and dropping option in the next section.

Bad:

There is no mechanism to move multiple movies at once.  In general this would not be a problem, but one of the main reasons I use Netflix is to rent TV series on DVD that would otherwise be too expensive to own, and they often come in multiple discs.  If you want to move all six discs of a series up to the top of the queue, you have to move each disc individually – in reverse order.  If you start with disc 1 and move on through disc 6 pressing the TOP button each time (which is your natural inclination) you’ll end up with the series in reverse order, 6,5,4,3,2,1.

There are two solutions to this.
1) Allow multiple selection and movement.  This is executed to perfection in Animoto, an online tool for creating slideshows with music and animated transitions.

See the following screenshot:

As the image (or images) is moved around, all the other images move to make room.  The same could easily be done with rows of movies.  However, I do not feel that this is the best approach, since the most important function of the queue is to move movies to the front.  If you had a long queue and were trying to move something to the top via drag and drop, you’d have to move the items a long way.  Drag and drop is best when it occurs within the same screen so that no scrolling has to occur; it is a difficult task for some computer users to drag items to the very small sweet spot that causes the window to scroll up or down.

2) Automatically determine if the item you are attempting to move to the top of the list is part of a series.  If it is, either
  • ask if you want to move all of the DVDs up
  • automatically move all of the movies up and allow user to easily undo if this is not what he meant to do
I prefer the second option as I imagine the vast majority of time people intend to move all the DVDs in a series up to the top of a queue, and to have all those “yes” people have to answer a dialog box seems like a waste of time.

Another poor design choice for the queue screen is the lack of keyboard controls.  I like to do as much on my computer as possible without removing my hands from the keyboard; thoughtful UI design demands keyboard alternatives for the most frequently used operations.  Tab traversal works as expected through the movies, shifting the focus to the numerical position in the queue.  Unfortunately, this is the only operation available to you using only the keyboard; you cannot delete movies from the queue without using the mouse, as the tab traversal does not bring you through the fields of the individual movie.  Instead it skips from movie to movie.

Conclusion

Overall the queue management is painless, with the exception of the inability to move more than one movie at a time. I would give this aspect of the site a B+ for overall usability and design.

Categories: UI Tags: ,

R – Sorting a data frame by the contents of a column

February 12, 2010 7 comments

Let’s examine how to sort the contents of a data frame by the value of a column

> numPeople = 10
> sex=sample(c("male","female"),numPeople,replace=T)
> age = sample(14:102, numPeople, replace=T)
> income = sample(20:150, numPeople, replace=T)
> minor = age<18

This last statement might look surprising if you’re used to Java or a traditional programming language. Rather than becoming a single boolean/truth value, minor actually becomes a vector of truth values, one per row in the age column.  It’s equivalent to the much more verbose code in Java:

int[] age= ...;
for (int i = 0; i < income.length; i++) {
   minor[i] = age[i] < 18;
}

Just as expected, the value of minor is a vector:

> mode(minor)
[1] "logical"
> minor
[1] FALSE FALSE FALSE FALSE FALSE FALSE FALSE  TRUE FALSE FALSE

Next we create a data frame, which groups together our various vectors into the columns of a data structure:

> population = data.frame(sex=sex, age=age, income=income, minor=minor)
> population
 sex age income minor
1    male  68    150 FALSE
2    male  48     21 FALSE
3  female  68     58 FALSE
4  female  27    124 FALSE
5  female  84    103 FALSE
6    male  92    112 FALSE
7    male  35     65 FALSE
8  female  15    117  TRUE
9    male  89     95 FALSE
10   male  26     54 FALSE

The arguments (sex=sex, age=age, income=income, minor=minor) assign the same names to the columns as I originally named the vectors; I could just as easily call them anything.  For instance,

> data.frame(a=sex, b=age, c=income, minor=minor)
 a  b   c minor
1    male 68 150 FALSE
2    male 48  21 FALSE
3  female 68  58 FALSE
4  female 27 124 FALSE
5  female 84 103 FALSE
6    male 92 112 FALSE
7    male 35  65 FALSE
8  female 15 117  TRUE
9    male 89  95 FALSE
10   male 26  54 FALSE

But I prefer the more descriptive labels I gave previously.

> population
     sex   age income minor
1    male  68    150 FALSE
2    male  48     21 FALSE
3  female  68     58 FALSE
4  female  27    124 FALSE
5  female  84    103 FALSE
6    male  92    112 FALSE
7    male  35     65 FALSE
8  female  15    117  TRUE
9    male  89     95 FALSE
10   male  26     54 FALSE

Now let’s say we want to order by the age of the people. To do that is a one liner:

> population[order(population$age),]
 sex age income minor
8  female  15    117  TRUE
10   male  26     54 FALSE
4  female  27    124 FALSE
7    male  35     65 FALSE
2    male  48     21 FALSE
1    male  68    150 FALSE
3  female  68     58 FALSE
5  female  84    103 FALSE
9    male  89     95 FALSE
6    male  92    112 FALSE

This is not magic; you can select arbitrary rows from any data frame  with the same syntax:

> population[c(1,2,3),]
 sex age income minor
1   male  68    150 FALSE
2   male  48     21 FALSE
3 female  68     58 FALSE

The order function merely returns the indices of the rows in sorted order.

> order(population$age)
 [1]  8 10  4  7  2  1  3  5  9  6

Note the $ syntax; you select columns of a data frame by using a dollar sign and the name of the column. You can retrieve the names of the columns of a data frame with the names function.

> names(population)
[1] "sex"    "age"    "income" "minor" 

> population$income
 [1] 150  21  58 124 103 112  65 117  95  54
> income
 [1] 150  21  58 124 103 112  65 117  95  54

As you can see, they are exactly the same.

So what we’re really doing with the command

population[order(population$age),]

is

population[c(8,10,4,7,2,1,3,5,9,6),]

Note the trailing comma; what this means is to take all the columns. If we only wanted certain columns, we could specify after this comma.

> population[order(population$age),c(1,2)]
 sex age
8  female  15
10   male  26
4  female  27
7    male  35
2    male  48
1    male  68
3  female  68
5  female  84
9    male  89
6    male  92
Categories: programming, R Tags: ,

Non-edible baby? Is there any other kind?

February 11, 2010 Leave a comment

If I had a dollar for every time I read a warning like that

Categories: photo

Running totals in R

February 11, 2010 Leave a comment

Let’s say we wanted to simulate flipping a coin 50 times using the statistical language R, where a 1 is a heads and 0 is tails.

> flips=sample(0:1, 50, replace=T)
> flips [1] 0 1 0 1 0 1 1 1 1 1 1 1 1 0 0 1 1 0 1 1 0 1 0 1 1 1 1 0 0 1 0 0 0 0 0 1 0 1
[39] 1 1 1 0 1 0 0 1 1 0 1 1

Now we can plot the values to see which were heads and which were tails:

> plot(flips, main="Coin flips",ylab="0 = tails, 1 = heads")



Raw values of heads and tails

What if we want to see a running total of the number of heads over time? I was faced with just this problem for a completely different domain; I’ve written the function myself multiple times in Java and other languages but I was hoping it would be built-in to a stats language like R.  Fortunately I was right; the command you want is cumsum (cumulative sum).  There are a total of four functions like this:

Cumulative Sums, Products, and Extremes

cumsum(x)
cumprod(x)
cummax(x)
cummin(x)

They work just as you’d expect.

> cumsum(flips)
 [1]  0  1  1  2  2  3  4  5  6  7  8  9 10 10 10 11 12 12 13 14 14 15 15 16 17
[26] 18 19 19 19 20 20 20 20 20 20 21 21 22 23 24 25 25 26 26 26 27 28 28 29 30
> plot(cumsum(flips), main="Number of heads flipped over time",ylab="Number of heads")

Running total of number of heads

This is a trivial example, but it certainly simplifies my life.

Categories: programming, R

Quick hit – antialiasing in Java Graphics2D

February 9, 2010 8 comments

If you’re trying to use antialiasing in Java, chances are you’ll do some searching and come to the conclusion that you have to set the rendering hints for the Graphics object in order to eliminate your jaggies. Probably like so:

Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);

And you’ll probably wonder why your graphics are still jagged. That’s because you haven’t instructed Java what type of interpolation to use for the antialiasing; by default you’ll get Nearest Neighbor, which is fast but won’t get you far in the image department. You can instead choose to use bilinear interpolation (slower) or bicubic interpolation (slowest).

Here’s how to enable the alternate interpolation modes:

Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BILINEAR);

or

g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BICUBIC)

You make a trade off between speed and image quality; to my eyes bilinear looks fine. Compare for yourself:

Thanks to Chris Cambpell’s blog for showing me this.

Human fallibility – static analysis tools

February 9, 2010 5 comments

The theme of this post is human fallibility: no one’s perfect and we’re bound to make mistakes while coding.  The following tools can help statically analyze source or markup and find mistakes early.

Python

I am a huge fan of Python as a scripting language, as its syntax and language features allow you to code quickly and with minimal fuss. (I will definitely use it as a springboard for future blog discussion, especially with respect to its differences from Java).  I am used to statically compiled languages like the aforementioned Java, where the compiler will catch typos and uses of uninitialized variables; not having this ability in Python always makes me feel a bit hesitant to use it for more than quick scripts and small programming tasks.  (Clearly Python is well-suited to large scale production environments; this is more my hangup and lack of expertise in the language than anything else.)

Enter PyChecker, an open-source project that aims to detect some of the most common coding mistakes, including references to variables before assignment, passing wrong numbers of arguments to functions, and calling methods that don’t exist.   It won’t find all your mistakes, but if you have any long-running Python scripts, you’d much prefer to catch a typo before you start running than 90% through the computation.

JSON

JSON is an alternative to XML as a “lightweight data-interchange format”.  Unlike XML with its opening and closing angle brackets, JSON has a very clean syntax with few extraneous marks.  Here’s an example JSON file:

{
    "number": 1,
    "array": [
        5,
        6.7,
        "string",
        [
            "nested list"
        ]
    ],
    "birthdayMap": {
        "Nick": "3/24",
        "Andrew": "12/1"
    }
}

I was working on a project using JSON as its means of representing data when I ran into problems with my hand-generated JSON – I had made mistakes, omitting brackets, not closing quotation marks, or other silly mistakes.  The Java library I was using to parse the JSON read in the whole file as a String, meaning all the contextual information was lost.  When an error ocurred, I was left with a cryptic error message like “syntax error, unexpected $end, expecting ‘}’ at character 2752” with no idea where in the file the error lay.

Thanks to my coworker Dave, I found the excellent tool JSONLint which not only highlights the exact line and location of your syntax error, it also reformats your JSON to a consistent amount of indentation for each nested level.  JSONLint is indispensible if you’re fat-fingering your JSON code.

Java

PMD is a plugin for NetBeans, Eclipse, and a host of other Java IDEs and standard text editors that warns you of bad coding practices in your source code, as well as alerting you to potential errors.  There are a variety of rules specified in either XQuery notation or Java code that can be turned on and off at will; for instance if you are doing a lot of coding with interfaces to C and need to use shorts and bytes, you probably won’t want the “Don’t use shorts” warning popping up on every line you use a short in.

Some of the most useful rules I’ve found are the fall through in switch statements,
Not all of the rules are cut-and-dried, which the website acknowledges with the addition of a Controversial Rules section.  Some might be due to stylistic differences or just disagreements over whether or not certain constructs are bad practice.  For instance,

OnlyOneReturn

Since: PMD 1.0

A method should have only one exit point, and that should be the last statement in the method.

This rule is defined by the following Java class: net.sourceforge.pmd.rules.design.OnlyOneReturnRule

Example:

public class OneReturnOnly1 {
    public void foo(int x) {
        if (x > 0) {
            return "hey"; // oops, multiple exit points!
        }
        return "hi";
    }
}

Doing a search for “java multiple exit points” reveals that there is definitely a lot of discussion as to best practices in this regard, hence its inclusion in the Controversial Rules section.

Hopefully you’ve gained at least one new tool from this post; if not I’ll be posting more in days to come.

Apache Log4j

February 3, 2010 1 comment

Apache Log4J

This is the second Java library I’ve mentioned (the first was Apache Commons Primitives), and it won’t be the last.

Apache Log4J is a logging system that is designed to have the same interface as java.util.Logging’s (the faq suggests a find and replace of all the util.Logger calls with org.apache.log4j.Logger will do the trick).

Why should you use a logging framework rather than System.out.println / System.err.println calls throughout your code? Here are a few reasons

  • Ability to specify the level of detail of a message, whether it is a warning vs an error vs a debug statement. With this granularity you can filter out all but a certain level of messages so as to avoid scroll blindness when too much is being printed out to the screen.
  • Ability to configure the logger at run time without changing the binary as to whether or not certain logging is performed.
  • You get more information about when and where a logged statement came from. By default you get the time the statement was logged, as well as name of the logger that logged the message (which, if you follow the Log4j suggestion to use the class name of the file containing the logger, will tell you where the statement came from)
  • It will prepare you for developing for the Android operating system, where you cannot just call System.out.println and have it appear in a terminal. I learned about logging from Android before finding out about this log4j library; they have very similar syntax, though with slightly more succint method names
  • You can customize the way objects are rendered as text by the logger via an ObjectRenderer instance

Here’s some source code to get you started:



// Similar to the example from http://logging.apache.org/log4j/1.2/manual.html

// with additional comments

public class TestLogging {



    // Idiomatic way of getting one logger per class; will automatically

    // extract class name etc from this .class object

    private static final Logger logger = Logger.getLogger(TestLogging.class);



    public static void main(String[] args) {



        // This call is necessary somewhere to actually hook up the Logger's

        // with sensible default values.  If you don't need course grained

        // control over what is logged, this is sufficient.  Output

        // will go to standard out (terminal) after this command.

        BasicConfigurator.configure();



        logger.error("There was an error");

        logger.warn("Warning");

        logger.info("Informational message");

        logger.debug("Debug message");

        logger.trace("Really, really fine grained detail")



    }



}

I haven’t learned all the ins and outs of the library yet, but it definitely seems a bit more scalable and professional than having print statements littered throughout the code base. Furthermore, the library was designed with speed in mind so the cost of logging statements is barely more than simply dumping it to standard out.

Categories: Java, programming, regular Tags: ,

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

February 2, 2010 6 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: , , ,