Index: /anuga_core/source/anuga/visualiser/offline.py
===================================================================
--- /anuga_core/source/anuga/visualiser/offline.py	(revision 3537)
+++ /anuga_core/source/anuga/visualiser/offline.py	(revision 3538)
@@ -7,4 +7,9 @@
 class OfflineVisualiser(Visualiser):
     """A VTK-powered offline visualiser which runs in its own thread.
+    In addition to the functions provided by the standard visualiser,
+    the following additional functions are provided:
+
+    precache_height_quantities() - Precache all the vtkpoints
+    structures for any dynamic height based quantities to render.
     """
     def __init__(self, source):
@@ -56,5 +61,5 @@
         y = ravel(array(fin.variables['y'], Float))
         if dynamic is True:
-            q = array(fin.variables[quantityName][self.frameNumber], Float)
+            q = array(fin.variables[quantityName][frameNumber], Float)
         else:
             q = ravel(array(fin.variables[quantityName], Float))
@@ -67,4 +72,26 @@
         fin.close()
         return points
+
+    def precache_height_quantities(self):
+        """Precache any height-based quantities. Call before rendering
+        beigns."""
+        for q in self.height_quantities:
+            if self.height_dynamic[q] is True:
+                print 'Precaching %s' % q
+                for i in range(self.maxFrameNumber + 1): # maxFrameNumber is zero-indexed
+                    print ' - Frame %d of %d' % (i, self.maxFrameNumber)
+                    self.vtk_heightQuantityCache[i][q]\
+                        = self.read_height_quantity(q, True, i)
+
+    def build_quantity_dict(self):
+        quantities = {}
+        fin = NetCDFFile(self.source, 'r')
+        for q in filter(lambda n:n != 'x' and n != 'y' and n != 'z' and n != 'time' and n != 'volumes', fin.variables.keys()):
+            if len(fin.variables[q].shape) == 1: # Not a time-varying quantity
+                quantities[q] = ravel(array(fin.variables[q], Float))
+            else: # Time-varying, get the current timestep data
+                quantities[q] = array(fin.variables[q][self.frameNumber], Float)
+        fin.close()
+        return quantities
 
     def setup_gui(self):
Index: /anuga_core/source/anuga/visualiser/run_offline.py
===================================================================
--- /anuga_core/source/anuga/visualiser/run_offline.py	(revision 3537)
+++ /anuga_core/source/anuga/visualiser/run_offline.py	(revision 3538)
@@ -2,5 +2,9 @@
 
 o = OfflineVisualiser("../../../../swollen_viewer/tests/cylinders.sww")
+#o = OfflineVisualiser("../../../../swollen_viewer/tests/karratha_100m.sww")
 o.render_quantity_height("elevation", dynamic=False)
 o.render_quantity_height("stage", dynamic=True)
+#o.colour_height_quantity('stage', (0.0, 0.0, 0.8))
+o.colour_height_quantity('stage', (lambda q:q['stage'], 0, 10))
+#o.precache_height_quantities()
 o.run()
Index: /anuga_core/source/anuga/visualiser/visualiser.py
===================================================================
--- /anuga_core/source/anuga/visualiser/visualiser.py	(revision 3537)
+++ /anuga_core/source/anuga/visualiser/visualiser.py	(revision 3538)
@@ -1,5 +1,6 @@
 from threading import Event, Thread
 from Tkinter import Tk, Button, N, E, S, W
-from vtk import vtkActor, vtkPolyDataMapper, vtkRenderer
+from types import FunctionType, TupleType
+from vtk import vtkActor, vtkFloatArray, vtkPolyDataMapper, vtkRenderer
 from vtk.tk.vtkTkRenderWidget import vtkTkRenderWidget
 
@@ -17,4 +18,7 @@
         self.height_dynamic = {}
         self.height_offset = {}
+
+        # Structures for colouring quantities
+        self.colours_height = {}
 
         # Structures used for VTK
@@ -84,9 +88,48 @@
             actor = self.vtk_actors[quantityName] = vtkActor()
             actor.SetMapper(mapper)
+            self.vtk_renderer.AddActor(actor)
+
+        if self.colours_height.has_key(quantityName):
+            colour = self.colours_height[quantityName]
+            if type(colour) == TupleType:
+                if type(colour[0]) == FunctionType:
+                    # It's a function, so take colour[1] as the
+                    # lower bound on the scalar range and
+                    # colour[2] as the upper bound on the scalar
+                    # range.
+                    scalars = vtkFloatArray()
+                    map(scalars.InsertNextValue, colour[0](self.build_quantity_dict()))
+                    self.vtk_polyData[quantityName].GetPointData().SetScalars(scalars)
+                    mapper.SetScalarRange(colour[1:])
+                    mapper.Update()
+                else:
+                    # It's a 3-tuple representing an RGB value.
+                    actor.GetProperty().SetColor(colour)
+            else:
+                actor.GetProperty().SetColor(0.5, 0.5, 0.5)
+        else:
             actor.GetProperty().SetColor(0.5, 0.5, 0.5)
-            self.vtk_renderer.AddActor(actor)
 
     # --- Colour Coding --- #
 
+    def build_quantity_dict(self):
+        """Build a dictionary mapping quantity name->list of vertex
+        values for that quantity. Subclasses are expected to override
+        this function."""
+        pass
+
+    def colour_height_quantity(self, quantityName, colour=(0.5, 0.5, 0.5)):
+        """Add colouring to a height based quantity.
+
+        The colour parameter can be one of the following:
+        - a 3-tuple of values in [0,1] to specify R, G, B values
+        - a 3-tuple of values:
+          - a function that takes a dictionary mapping quantity name->Numeric array of vertex values.
+            This function returns a list of vertex values to be used in the colour coding.
+          - a float for the lower bound on the colouring
+          - a float for the upper bound on the colouring
+        """
+        self.colours_height[quantityName] = colour
+            
     # --- Vector Fields --- #
 
