Using Mathematica 6 Graphics Effectively

Christopher Carlson

Wolfram Research

Preliminaries

Front End ↔ MathLink ↔ Kernel

Most of these examples are 2D, but the same comments apply to 3D.

You're seeing the current development, debug version.  Your speed will probably be better.

Thanks Yu-Sung Chang, Lou D'Andria, and Frank Brand, who suggested some examples.

How to Build A Point Cloud Magnifier

Suppose I have this cloud of points, which is some places is too dense to see the individual points.  I' d like to build a magnifier attached to the mouse that lets me move over the image and magnify parts of it.  The magnifier would work like this.  I find the direction and distance of a given point from the position of the mouse cursor, and displace the point away from the cursor by an amount proportional to the distance from the cursor.  So points near the cursor spread out a lot—they magnify—and points far from the cursor don' t move.

Graphics[Table[Point[RandomReal[{-1,1},2]],{300}]]

I want the magnification at the cursor to be constant, and I want the magnification to fall off smoothly to unit magnification as I move away.  So the slope at the origin has to be zero, something like this.

Graphics[{},PlotRange->3,Axes->True]

There are no doubt lots of possible useful functions, but the one that immediately came to mind to me was a Gaussian, which is essentially (e^(-(x^2))), with 1 added so that it falls off to 1 instead of 0.

Plot[1 + (E^(-(x^2))),{x,-3,3},PlotRange->3]

So here's a prototype of that functionality.  I start with a list of random coordinates.  These are displayed inside a Graphics expression that contains a Dynamic quantity that depends on the mouse position.  Given the mouse position, a list of Points is formed after modifying the coordinate positions by the magnification function.  Whenever the mouse moves, the value of MousePosition changes, and the Dynamic quantity is updated.  So here's the result.

DynamicModule[{pts = Table[RandomReal[{-1,1},2],{300}]},
Graphics[{
Dynamic[With[{t=MousePosition[{"Graphics",Graphics},10{1,1}]},Point[# + (#-t) (E^(-(3 Norm[#-t])^2))]]&  /@ pts]
},PlotRange->1]]

Whoa!  It's dog-slow.

How to Build A Point Cloud Magnifier (cont'd)

It's the code that's slow, not Mathematica.  I've made a couple of intentional mistakes that slow things down.  I'll give you a few seconds to see if you can figure out what they are.  Hint: they're a misplaced bracket and expression head in the highlighted code.

DynamicModule[{pts = Table[RandomReal[{-1,1},2],{300}]},
Graphics[{
Dynamic[With[{t=MousePosition[{"Graphics",Graphics},10{1,1}]},Point[# + (#-t)(E^(-(3 Norm[#-t])^2))]]&  /@ pts]
},PlotRange->1]]

The first mistake is that MousePosition occurs within the function that is mapped onto the list of coordinates.  So for each of the 300 points, there is a Mathlink transaction: the kernel asks the front end where the mouse is and the front end replies with the mouse coordinates.  All those Mathlink transactions really bog things down.  Fixing that required moving just one closing bracket so that MousePosition is evaluated once.

DynamicModule[{pts = Table[RandomReal[{-1,1},2],{300}]},
Graphics[{
Dynamic[With[{t=MousePosition[{"Graphics",Graphics},10{1,1}]},Point[# + (#-t)(E^(-(3 Norm[#-t])^2))]&  /@ pts]]
},PlotRange->1]]

That speeds things up quite a bit.

Lesson 1: Minimize kernel traffic.

How to Build A Point Cloud Magnifier (cont'd)

But it's still not as fast as one would expect.  There's one more big improvement having to do with the highlighted code.

DynamicModule[{pts = Table[RandomReal[{-1,1},2],{300}]},
Graphics[{
Dynamic[With[{t=MousePosition[{"Graphics",Graphics},10{1,1}]},Point[# + (#-t)(E^(-(3 Norm[#-t])^2))]&  /@ pts]]
},PlotRange->1]]

Here you can take advantage of a new feature in 6.0: multi-objects.  Instead of one point object for each coordinate, you can wrap an entire list of coordinates in a single Point object.  It's much, much faster that way.

DynamicModule[{pts = Table[RandomReal[{-1,1},2],{3000}]},
Graphics[{
Point[Dynamic[With[{t=MousePosition[{"Graphics",Graphics},10{1,1}]},( # + (#-t)(E^(-(3 Norm[#-t])^2)))&  /@ pts]]]
},PlotRange->1]]

Now we're talking.  It's nice and smooth with 300 points.  How about an order of magnitude more?  Still fine.  An order of magnitude more and it's usable, but no longer interactive.

Multi-objects have a couple of benefits: 

(1) Only a single Point object is created in the front end.  Overheads associated with formatting and drawing are performed once rather than once for each Point individually.

(2) Point data can be sent to the front end as a packed array, which is hugely more efficient than non-packed arrays.  

This is an unpacked array:

TreeForm[{{1.,2.},{3.,4.}}]

This is a packed array:

Grid[{{"2x2 Lists"},{1.},{2.},{3.},{4.}},Background->LightYellow,Dividers->All]

Lesson 2: Always prefer multi-objects over individual objects.

More Fun With Point Clouds

This is too much fun not to digress to.  But it also illustrates a useful graphics technique.

DynamicModule[{pts = Table[RandomReal[{-1,1},2],{300}]},
Graphics[{
Point[Dynamic[With[{t=MousePosition[{"Graphics",Graphics},10{1,1}]},( # + (#-t)(E^(-(3 Norm[#-t])^2)))&  /@ pts]]]
},PlotRange->1]]

Bear with me while I make a couple of changes to make this more effective.  I'll use a regular grid of points instead of a random cloud, and increase the plot range so I have a little white space in the margins to work with.  That give me this nice mouse-under-the-carpet effect.

DynamicModule[{pts = Flatten[Table[{x,y},{x,-1,1,.05},{y,-1,1,.05}],1]},
Graphics[{
Point[Dynamic[With[{t=MousePosition[{"Graphics",Graphics},10{1,1}]},( # + (#-t)(E^(-(3 Norm[#-t])^2)))&  /@ pts]]]
},PlotRange->2]]

Finally, instead of parking the mouse out in the netherlands of the graphic—which is a bit of a hack—I'll be explicit about not transforming points if the mouse is not inside the graphic

DynamicModule[{pts = Flatten[Table[{x,y},{x,-1,1,.05},{y,-1,1,.05}],1]},
Graphics[{
Point[Dynamic[With[{t=MousePosition[{"Graphics",Graphics}]},If[t===None,pts,( # + (#-t)(E^(-(3 Norm[#-t])^2)))&  /@ pts]]]]
},PlotRange->2]]

Now, what happens if instead of applying the magnification function to the original, untransformed points each time the mouse moves, we save the transformed points and apply the magnification transform to the transformed points each time around?

DynamicModule[{pts = Flatten[Table[{x,y},{x,-1,1,.05},{y,-1,1,.05}],1]},
Graphics[{
Point[Dynamic[With[{t=MousePosition[{"Graphics",Graphics}]},If[t===None,pts,pts=( # + .2(#-t)(E^(-(3 Norm[#-t])^2)))&  /@ pts]]]]
},PlotRange->2]]

So, why do the points keep moving when the mouse is in the graphic?  It's because of the way Dynamic works.  When a quantity is inside Dynamic, the front end wakes up and reevaluates it if any of the quantities that it depends on has changed.  The value within this Dynamic depends on 'pts'.  If the mouse is within the graphic 'pts' is reassigned by evaluating the Dynamic.  So the Dynamic wakes up and evaluates again...  As long as the cursor is within the graphic, the points will keep moving, unless they reach a quiescent state where their value doesn't change.

More Fun With Point Clouds: Gravity

This is a very useful technique, whose principle is summarized by this pseudocode.

DynamicModule[{state = initialState},
	Graphics[{
	  ...,
	  Dynamic[state = f[state]; render[state]],
	  ...
	  }]
	]

So let's look at what it takes to give the cursor a gravitational field.  For the state, we need to keep track of not just the position, but also the velocity of each point.  The state is initialized to {x,y} with velocity {0,0}.  The state function maps each position to the position plus the current velocity, and the velocity is increased by an amount proportional to the square of the inverse of the distance of the point from the source of the gravity.

DynamicModule[{state =Flatten[ Table[{{x,y}, {0,0}},{x,-1,1,.05},{y,-1,1,.05}],1]},
Graphics[{
Point[Dynamic[With[{p=MousePosition[{"Graphics",Graphics},10{1,1}]},
state = {#[[1]] + #[[2]], With[{v=p-#[[1]]},(#[[2]] + .0002Normalize[v]/Max[Norm[v],.1]^2)]}&  /@ state; First /@ state
]]]
},PlotRange->2]]

This is an absurdly small amount of code for this kind of a demo.  What's more, you actually have a chance of understanding how the code works by reading it, unlike some other compact languages, APL, for example.

Now, of course, the graphic stays in motion even when the mouse leaves the graphic, because once a particle has acquired velocity, it stays in motion even after the source of the gravity has disappeared.

Lesson 3: Keep things in motion with self-triggering Dynamics.

I'll return to this technique later, but first I want to return to the magnifier example.

A Point Cloud Magnifier on a Background

Suppose a large part of your graphic is static background, as in this example with a background of colored disks.  Perhaps surprisingly, the magnifier works nearly as efficiently as it did without the background.  That's because the front end takes great pains to update only what is necessary, namely, only the dynamic part.

DynamicModule[{pts = Table[RandomReal[{-1,1},2],{3000}]},
Graphics[{
SeedRandom[111];Table[{Hue[RandomReal[],.5,1],Disk[RandomReal[{-1,1},2],.2]},{400}],
Point[Dynamic[With[{t=MousePosition[{"Graphics",Graphics},10{1,1}]},( # + (#-t)(E^(-(3 Norm[#-t])^2)))&  /@ pts]]]
},PlotRange->1]]

If the Dynamic is pulled out to around the entire Graphics expression, the response is much slower.

DynamicModule[{pts = Table[RandomReal[{-1,1},2],{3000}]},
Dynamic[Graphics[{
SeedRandom[111];Table[{Hue[RandomReal[],.5,1],Disk[RandomReal[{-1,1},2],.2]},{400}],
Point[With[{t=MousePosition[{"Graphics",Graphics},10{1,1}]},( # + (#-t)(E^(-(3 Norm[#-t])^2)))&  /@ pts]]
},PlotRange->1]]]

But if you put an inner Dynamic around the dynamic part of the graphic, and if nothing changes within the outer Dynamic, only the inner part will be updated.

DynamicModule[{pts = Table[RandomReal[{-1,1},2],{3000}]},
Dynamic[Graphics[{
SeedRandom[111];Table[{Hue[RandomReal[],.5,1],Disk[RandomReal[{-1,1},2],.2]},{400}],
Point[Dynamic[With[{t=MousePosition[{"Graphics",Graphics},10{1,1}]},( # + (#-t)(E^(-(3 Norm[#-t])^2)))&  /@ pts]]]
},PlotRange->1]]]

Well that's obvious, you may be saying.  But what some people don't realize is that the same comments apply to Manipulate.  The contents of a Manipulate are effectively wrapped with Dynamic.  The entire expression is reevaluated each time a control moves.

DynamicModule[{pts = Table[RandomReal[{-1,1},2],{3000}]},
Manipulate[Graphics[{
SeedRandom[111];Table[{Hue[RandomReal[],.5,1],Disk[RandomReal[{-1,1},2],.2]},{400}],
Point[( # + (#-t)(E^(-(3 Norm[#-t])^2)))&  /@ pts]
},PlotRange->1],
{{t,{0,0}},{-1,-1},{1,1},Locator}]]

But you can improve response by explicitly wrapping Dynamic around those parts that actually need to update.  Then only those parts will be updated and transmitted to the front end.  The improvement in response can be dramatic.

DynamicModule[{pts = Table[RandomReal[{-1,1},2],{3000}]},
Manipulate[Graphics[{
SeedRandom[111];Table[{Hue[RandomReal[],.5,1],Disk[RandomReal[{-1,1},2],.2]},{400}],
Point[Dynamic[( # + (#-t)(E^(-(3 Norm[#-t])^2)))&  /@ pts]]
},PlotRange->1],
{{t,{0,0}},{-1,-1},{1,1},Locator}]]

Lesson 4: Use targetted Dynamics to minimize updating and improve response.

Caveat: But aggegrate Dynamics at a higher level if there are lots of individual Dynamics close together.

Richer Graphics Objects: GraphicsComplex

Suppose there are more than just points in the graphic.  For example, suppose we want to magnify a graphic that contains a mesh of points and lines.  This is one way to do it.

With[{d=40},
With[{pts = Flatten[Table[{x,y},{x, -1, 1,2./(d-1)},{y,-1, 1,2./(d-1)}],1]},
Framed[Graphics[{
Dynamic[With[{t=MousePosition[{"Graphics",Graphics},10{1,1}]},
Module[{pts1 =( # + (#-t)(E^(-(3 Norm[#-t])^2)))&  /@ pts},{
Line[Flatten[Table[Table[{pts1[[d j+i]],pts1[[d j+ i+1]]},{i,1,d-1}],{j,0,d-1}],1]],
Line[Flatten[Table[Table[{pts1[[d j+i]],pts1[[d j+ i+d]]},{i,1,d}],{j,0,d-2}],1]],
PointSize[.02],Red,Point[pts1]
}]]]
},PlotRange->1,PlotRangePadding->.1]]]]

It's not very fast.  One reason is that each vertex is replicated three times, once for each line that passes through it and once for the point.  So each vertex is being sent across MathLink three times.  GraphicsComplex allows you to factor out the vertices, listing each one only once.

With[{d=40},
With[{pts = Flatten[Table[{x,y},{x, -1, 1,2./(d-1)},{y,-1, 1,2./(d-1)}],1]},
Framed[Graphics[{
Dynamic[With[{t=MousePosition[{"Graphics",Graphics},10{1,1}]},
Module[{pts1 =( # + (#-t)(E^(-(3 Norm[#-t])^2)))&  /@ pts},
GraphicsComplex[pts1,{
Line[Flatten[Table[Table[{d j+i,d j+ i+1},{i,1,d-1}],{j,0,d-1}],1]],
Line[Flatten[Table[Table[{d j+i,d j+ i+d},{i,1,d}],{j,0,d-2}],1]],
PointSize[.02],Red,Point[Range[d^2]]
}]
]]]
},PlotRange->1,PlotRangePadding->.1]]]]

That's better, but still not as responsive as it can be.  But using GraphicsComplex lets us move the Dynamic from around the entire GraphicsComplex to just the points.  So the graphics primitives are sent once to the front end, and only the coordinates change as you move the mouse.  Now the resonse is fluid.

With[{d=40},
DynamicModule[{pts = Flatten[Table[{x,y},{x, -1, 1,2./(d-1)},{y,-1, 1,2./(d-1)}],1]},
Framed[Graphics[GraphicsComplex[Dynamic[With[{t=MousePosition[{"Graphics",Graphics},10{1,1}]},( # + (#-t)(E^(-(3 Norm[#-t])^2)))&  /@ pts]],{
Line[Flatten[Table[Table[{d j+i,d j+ i+1},{i,1,d-1}],{j,0,d-1}],1]],
Line[Flatten[Table[Table[{d j+i,d j+ i+d},{i,1,d}],{j,0,d-2}],1]],
PointSize[.02],Red,Point[Range[d^2]]
}],PlotRange->1,PlotRangePadding->.1]]]]

Lesson 5: Use GraphicsComplex to eliminate coordinate redundancy and target Dynamic at the coordinates.

Images as GraphicsComplexes

I wondered how far I could push this magnification technique.  Could I use it to magnify bitmap images?

The answer is no, if the images are Raster objects.  Rasters are static, stiff objects that can be rotated and skewed, but not warped.  But if we turn a Raster into an equivalent GraphicsComplex first, it can be warped.

To make a GraphicsComplex that corresponds to a Raster image, we take advantage of the VertexColors option.  If vertex colors are specified in a GraphicsComplex, polygons in the complex use those colors for their vertices, shading the interior of the polygon so that the color interpolates smoothly between vertices.  To convert a Raster to a GraphicsComplex, we create one vertex corresponding to the middle of each pixel and assign it the color of the pixel.  When the GraphicsComplex is filled with square polygons as shown below, the result is an image that is virtually indistinguishable from the original Raster.

SeedRandom[111];
colors = Table[RGBColor[RandomReal[{0,1},3]],{i,1,2},{j,1,2}];
Graphics[{
Gray,
Line[Table[{{x,-1.5},{x,1.5}},{x, -1,1,1}]],
Line[Table[{{-1.5,y},{1.5,y}},{y, -1,1,1}]],
Table[{colors[[i,j]], Rectangle[{i,j}-{2,2}]},{i,1,2},{j,1,2}],
Polygon[.5{{-1,-1},{-1,1},{1,1},{1,-1}},VertexColors->{colors[[1,1]],colors[[1,2]],colors[[2,2]],colors[[2,1]]}],
Black,
PointSize[.015],Point[Flatten[Table[{x,y},{x,-1.5,1.5,1},{y,-1.5,1.5,1}],1]],
Line[Table[{{x,-1.5},{x,1.5}},{x, -.5,.5,1}]],
Line[Table[{{-1.5,y},{1.5,y}},{y, -.5,.5,1}]]
},PlotRangePadding->-.2]

Here's an image from ExampleData.

girlRaster = ExampleData[{"TestImage","Girl"}][[1]];

Graphics[girlRaster]

This is the function that converts a Raster to the equivalent GraphicsComplex.

ToRasterComplex[Raster[data_, {{xMin_, yMin_}, {xMax_, yMax_}},{vMin_, vMax_}, opts___]] :=
Module[{ni, nj},
{ni, nj} = Take[Dimensions[data],2];
GraphicsComplex[Developer`ToPackedArray[N[Flatten[Table[{j,i},{i, 1, ni},{j, 1, nj}],1]]],
Polygon[Developer`ToPackedArray[Flatten[Table[With[{k=(i-1)(nj) + j},{k, k+1, k + nj+1,k + nj}],{i, 1, ni-1},{j, 1, nj-1}],1]]],
VertexColors->Developer`ToPackedArray[N[Flatten[Table[RGBColor[data[[i,j]]/255.],{i, 1, ni},{j, 1, nj}],1]]]
]
];

girlRasterComplex = ToRasterComplex[girlRaster];

Here's the resulting GraphicsComplex image.  It's a little slower to render than the Raster, but now the image can be warped.

Graphics[girlRasterComplex]

Here is the magnifying function for GraphicsComplex.

MagnifyRasterComplex[x_GraphicsComplex]:=
DynamicModule[{pts=x[[1]], m=2, n=2,r=.03},
Graphics[{
GraphicsComplex[Dynamic[With[{t=MousePosition[{"Graphics",Graphics}]},Developer`ToPackedArray[N[If[t===None,pts,(t + ((m-1)(E^(-(r Norm[#-t])^n)) + 1)(#-t))&  /@ pts]]]]],Sequence @@ Rest[x]]
}]];

And here is the image, interactively magnified.  The response is not fluid, but usable:

MagnifyRasterComplex[girlRasterComplex]

It is very important to ensure that the coordinate data in the GraphicsComplex is a packed array.  For the sake of comparison, here is the same magnification function with unpacked data.  It's not as fast.

MagnifyRasterComplexUnpacked[x_GraphicsComplex]:=
DynamicModule[{pts=x[[1]], m=2, n=2,r=.03},
Graphics[{
GraphicsComplex[Dynamic[With[{t=MousePosition[{"Graphics",Graphics}]},Developer`FromPackedArray[N[If[t===None,pts,(t + ((m-1)(E^(-(r Norm[#-t])^n)) + 1)(#-t))&  /@ pts]]]]],Sequence @@ Rest[x]]
}]];

MagnifyRasterComplexUnpacked[girlRasterComplex]

Lesson 6: Make sure arrays are packed before sending them via MathLink.

Magnifying Graph Plots, Density Plots, ...

Lots of Mathematica outputs use GraphicsComplex for efficiency.  By packaging the magnifying behavior as a function, it can be applied to arbitrary outputs.  The function just picks out the GraphicsComplexes, and replaces their point lists with dynamically magnified point lists.

MagnifyGraphicsComplex[g_Graphics, r_:3] :=
g/.GraphicsComplex[pts_, rest___] :> GraphicsComplex[Dynamic[With[{t=MousePosition[{"Graphics",Graphics}]},If[t===None,pts,( # + (#-t)(E^(-(r Norm[#-t])^2)))&  /@ pts]]], rest]

GraphPlot output:

MagnifyGraphicsComplex[GraphPlot[Table[j->FromDigits[RotateRight[IntegerDigits[j,2]],2],{j,0,2^9-1}]], .3]

DensityPlot output:

MagnifyGraphicsComplex[DensityPlot[Sin[x y],{x,0,2π},{y,0,2π},Mesh->All],1]

Changing the View with a Dynamic PlotRange

Many times you can achieve fast interactive response by sending all your data at once to the front end, then picking out just the data you want to see by changing an option or turning on or off the visiblity of some of the data.

DynamicModule[{worldGraphic = CountryData["World","Shape"], xMin, xMax,yMin,yMax, xSize, ySize},
{{xMin, xMax},{yMin,yMax}} = FullOptions[worldGraphic,PlotRange];
xSize = xMax - xMin;
ySize = yMax - yMin;
Manipulate[
Deploy[Show[CountryData["World","Shape"], PlotRange->Dynamic[{{center[[1]] - (xSize / zoom)/2,center[[1]] + (xSize / zoom)/2}, {center[[2]] - (ySize / zoom)/2,center[[2]] + (ySize / zoom)/2}}],ImageSize->600]],
{{center,{0, 0}},{xMin, yMin},{xMax, yMax}},
{{zoom,1},1,10}
]]

Here's another example of the same principle.  You can do animations with self-triggering Dynamics.  The trigger variable, i, is the frame index of the animation.  When the index changes, a new frame is generated.

DynamicModule[{i=0},
Dynamic[i = Mod[i+1,10];Plot3D[Sin[(1 + .1 i) x y/3],{x,0,2π},{y,0,2π}]]
]

If the frames are expensive to compute, your response may be poor.  By precomputing the frames, loading them into the front end, and then indexing them with the frame index, the animation is much smoother.  I do the indexing with a FlipView, which stores a list of frames that I can index with the dynamic variable.  Of course, the up-front cost is higher.

DynamicModule[{i=0},
FlipView[Table[Plot3D[Sin[f x y/3],{x,0,2π},{y,0,2π}],{f, 1, 2, .1}],Dynamic[1 + (i = Mod[i+1,10])]]
]

Lesson 7: Leave as much data as possible in the front end.  Change it with minimal dynamic updates.

Improving Efficiency with Rasterize

Here is a graph whose nodes have graphics as labels.  The interactive response when dragging a node (triple-click to select one) is not very good.

SeedRandom[111111];DynamicModule[{molecules=Show[#,ImageSize->75,Background->White]& /@ Select[ChemicalData[#,"MoleculePlot"]& /@ RandomSample[ChemicalData[],50],MatchQ[#,_Graphics3D]&]},
GraphPlot[Map[molecules[[#]]&,GraphData[{"QuarticTransitive",39},"EdgeRules"],{-1}],VertexLabeling->True,ImageSize->700]
]

The interactive response can be improved by rasterizing the node graphics.  Rasterize converts a graphic to an equivalent Graphics[Raster[...]], which is usually more efficient to render than the original graphic, particularly if the original contains a lot of primitives.  The drag response in this graph is better.

SeedRandom[111111];DynamicModule[{molecules=Rasterize@Show[#,ImageSize->75,Background->White]& /@ Select[ChemicalData[#,"MoleculePlot"]& /@ RandomSample[ChemicalData[],50],MatchQ[#,_Graphics3D]&]},
GraphPlot[Map[molecules[[#]]&,GraphData[{"QuarticTransitive",39},"EdgeRules"],{-1}],VertexLabeling->True,ImageSize->700]
]

Lesson 8: Rasterize static graphics for faster rendering.

Hiding Information in Graphics

There are lots of ways to hide information in graphics.  Hiding information is a good thing: it's there if you want it, but it doesn't clutter the display.  Some function results, like those of WorldPlot have built-in data-hiding.  WorldPlot wraps each country polygon with a Tooltip that gives the country's name, as in

Tooltip[polygon, name]

<<WorldPlot`

africa = WorldPlot[{Africa,RandomColors}]

There are lots of other constructs you can use to hide data in graphics.  Some are drop-in replacements for Tooltip.  For example, StatusArea...

africa/.Tooltip->StatusArea

... and PopupWindow.

africa/.Tooltip->PopupWindow

Others have a slightly different syntax, but with a little pattern matching, you can still use replacement rules to sneak them in to WorldPlot output.  With Annotation, you can tag parts of a graphic with information that can be displayed remotely from the graphic.

africa /.Tooltip[polygon_, name_]:>Annotation[polygon,name,"Mouse"]

Style[Dynamic[MouseAnnotation[]],"Title"]

With Mouseover, you can change the appearance of an object when the mouse hovers over it.

africa /.Tooltip[polygon_, _]:>Mouseover[polygon,{With[{p=Cases[polygon,_Polygon,∞]},{Black,p}]}]

Any graphics primitive can be made into a button or hyperlink.  With a simple replacement, your map becomes an almanac.

africa/. Tooltip[polygon_, name_]:>Tooltip[Hyperlink[polygon,"http://en.wikipedia.org/wiki/"<>name],name]

Notice that this example combines a tooltip with a hyperlink.  You can attach an arbitrary number of functionalities to a graphics object.

africa/. Tooltip[polygon_, name_]:>Tooltip[Hyperlink[Mouseover[polygon,{With[{p=Cases[polygon,_Polygon,∞]},{Black,p}]}],"http://en.wikipedia.org/wiki/"<>name],name]

Finally, polygons or any other parts of graphics can be turned into buttons that trigger arbitrary functions.

africa /.Tooltip[polygon_, name_]:>Button[polygon,MathLink`CallFrontEnd[SpeakTextPacket[name]]]

Lesson 9: Add information to graphics without adding clutter via Tooltip, Mouseover, Hyperlink, ...

Sound in Graphics

There was a time when sound in Mathematica was limited to playing waveforms, and it was cumbersome and slow to use sounds.  Version 6 gives you access to MIDI sounds, which makes it much, much easier to use sounds in your graphics and demonstrations.  This is the basic formula for playing a sound.

EmitSound[Sound[SoundNote["C"]]]

There are also percussion sounds.

EmitSound[Sound[SoundNote[1, .1, "Percussion"]]]

I wanted to know what sounds are out there, so I evaluated this.  This has got to be one of the all-time best Mathematica one-liners I have ever evaluated.

EmitSound[Sound[SoundNote[#,.1,"Percussion"]&/@Range[-32,35]]]

This is a demo of bouncing balls from the Dynamic documentation.

Framed@DynamicModule[{
contents={}
},EventHandler[Graphics[{PointSize[0.1],Point[Dynamic[
contents=Map[If[#[[1,2]]≥0,{#[[1]]-#[[2]],#[[2]]+{0,0.001}},{{#[[1,1]],0},{1,-0.8}#[[2]]}]&,contents];
Map[First,contents]]
]},PlotRange->{{0,1},{0,1}}],"MouseDown":>(AppendTo[contents,{MousePosition["Graphics"],{0,0}}])]]

There's something missing: you can't hear the balls bouncing.  It's trivial to do something about that.

EmitSound[Sound[SoundNote[1,.1,23]]]

All you have to do is emit a sound at the point where it is detected that the ball has hit the floor.

Framed@DynamicModule[{
contents={}
},EventHandler[Graphics[{PointSize[0.1],Point[Dynamic[
contents=Map[If[#[[1,2]]≥0,
{#[[1]]-#[[2]],#[[2]]+{0,0.001}},
EmitSound[Sound[SoundNote[1,.1,23]]];{{#[[1,1]],0},{1,-0.8}#[[2]]}]&,contents];
Map[First,contents]]
]},PlotRange->{{0,1},{0,1}}],"MouseDown":>(AppendTo[contents,{MousePosition["Graphics"],{0,0}}])]]

That sound is not a very convincing bounce sound, so I put together a Manipulate to let me explore what's out there and pick a better bounce sound.

Manipulate[Framed@DynamicModule[{
contents={}
},EventHandler[Graphics[{PointSize[0.1],Point[Dynamic[
contents=Map[If[#[[1,2]]≥0,{#[[1]]-#[[2]],#[[2]]+{0,0.001}},EmitSound[Sound[SoundNote[note,.1,"Percussion"]]];{{#[[1,1]],0},{1,-0.8}#[[2]]}]&,contents];
Map[First,contents]]
]},PlotRange->{{0,1},{0,1}}],"MouseDown":>(AppendTo[contents,{MousePosition["Graphics"],{0,0}}])]],
{note, -32, 35,1,Appearance->"Labeled"}]

-17 seems like a reasonable, basketball-ish kind of sound

Framed@DynamicModule[{
contents={}
},EventHandler[Graphics[{PointSize[0.1],Point[Dynamic[
contents=Map[If[#[[1,2]]≥0,{#[[1]]-#[[2]],#[[2]]+{0,0.001}},EmitSound[Sound[SoundNote[-17,.1,"Percussion"]]];{{#[[1,1]],0},{1,-0.8}#[[2]]}]&,contents];
Map[First,contents]]
]},PlotRange->{{0,1},{0,1}}],"MouseDown":>(AppendTo[contents,{MousePosition["Graphics"],{0,0}}])]]

You have control over every aspect of the sound, including its volume.  So let's add some realism by making the volume of the sound proportional to the velocity of the ball when it bounces.

Dynamic[v]

Framed@DynamicModule[{
contents={}
},EventHandler[Graphics[{PointSize[0.1],Point[Dynamic[
contents=Map[If[#[[1,2]]≥0,{#[[1]]-#[[2]],#[[2]]+{0,0.001}},EmitSound[Sound[SoundNote[-17,.1,"Percussion"],SoundVolume->(v=20 #[[2,2]])]];{{#[[1,1]],0},{1,-0.8}#[[2]]}]&,contents];
Map[First,contents]]
]},PlotRange->{{0,1},{0,1}}],"MouseDown":>(AppendTo[contents,{MousePosition["Graphics"],{0,0}}])]]

Once we've figured out the necessary constant, we can dispense with the debugging code.

Framed@DynamicModule[{
contents={}
},EventHandler[Graphics[{PointSize[0.1],Point[Dynamic[
contents=Map[If[#[[1,2]]≥0,{#[[1]]-#[[2]],#[[2]]+{0,0.001}},EmitSound[Sound[SoundNote[-17,.1,"Percussion"],SoundVolume->20 #[[2,2]]]];{{#[[1,1]],0},{1,-0.8}#[[2]]}]&,contents];
Map[First,contents]]
]},PlotRange->{{0,1},{0,1}}],"MouseDown":>(AppendTo[contents,{MousePosition["Graphics"],{0,0}}])]]

It's a rhythym machine!  Let's take it one step further: introducing... the Bounsonium!

Framed@DynamicModule[{
contents={}
},EventHandler[Graphics[{PointSize[0.1],Point[Dynamic[
contents=Map[If[#[[1,2]]≥0,{#[[1]]-#[[2]],#[[2]]+{0,0.001}},EmitSound[Sound[SoundNote[IntegerPart[32 #[[1]]],.1,79],SoundVolume->20#[[2,2]]]];{{#[[1,1]],0},{1,-0.9}#[[2]]}]&,contents];
Map[First,contents]]
]},PlotRange->{{0,1},{0,1}}],"MouseDown":>(AppendTo[contents,{MousePosition["Graphics"],{0,0}}])]]

Lesson 10: It's easy to use sound to improve the interactive experience.

Lessons Learned

Minimize kernel traffic.

Always prefer multi-objects over individual objects.

Keep things in motion with self-triggering Dynamics.

Use targetted Dynamics to minimize updating and improve response.

Use GraphicsComplex to eliminate coordinate redundancy and target Dynamic at the coordinates.

Make sure arrays are packed before sending them via MathLink.

Leave as much data as possible in the front end.  Change it with minimal dynamic updates.

Rasterize static graphics for faster rendering.

Add information to graphics without adding clutter via Tooltip, Mouseover, Hyperlink, ...

It's easy to use sound to improve the interactive experience.