Gauges again, source code this time.

Apparently the mailing list doesn't do attachments. So here

    > is the source instead: - Jamil Khan

Hi Jamil -- thanks for contributing these. They look very snazzy --
nice design!

As they are they will be useful, but to make them maximally useful, I
think they should be refactored using the matplotlib API and not the
pylab interface, since gauges are typically used in GUI application
rather than one-off scripts and most application developers use the
API rather than pylab. It is a fairly easy migration -- I'll include
an example refactoring of gauge.py below. For more details on the
API, see

  http://matplotlib.sourceforge.net/faq.html#OO

Are you interested in doing this? If we get these cleaned up a bit
and documented, they would be a nice start to a new "toolkit"

  http://matplotlib.sourceforge.net/toolkits.html

Here is the modified gauge.py, which defines a Gauge as a custom
Axes. This can now be used from the pylab interface or the matplotlib
API. One other things to note: all of your setp calls could be
removed by simply passing the kwargs to the relevant function. Eg,
instead of

    p = plot(x_vect, y_vect, 'b-')
    setp(p, color='black')
    setp(p, linewidth=1.5)

you could do

    p = plot(x_vect, y_vect, 'b-')
    setp(p, color='black',linewidth=1.5)

or event better

    p = plot(x_vect, y_vect, 'b-', color='black',linewidth=1.5)

Another little trick; in several places you do things like

    temp = limits[0]
    limits[0] = limits[1]
    limits[1] = temp

With the magic of tuples, you can avoid the temporary and do this in
one line

    limits[0], limits[1] = limits[1] = limits[0]

Also, do you intend to do integer division in lines like

  graph_width/2

If not, you will want to do one of 'from __future__ import division'
or graph_width/2.

For efficiency and to save typing, you may want to replace blocks like

    x_vect = []
    x_vect.append( (graph_width/2) )
    x_vect.append( (graph_width/2) )
    x_vect.append(-(graph_width/2) )
    x_vect.append(-(graph_width/2) )
    x_vect.append( (graph_width/2) )

with
  
    x_vect = [
         graph_width/2,
         graph_width/2,
        -graph_width/2,
        -graph_width/2,
         graph_width/2,
    ]

Final comment: your code uses tabs rather than spaces for indentation,
which can cause problems and is nonstandard. Perhaps you could
configure your editor to use spaces for indentation?

Thanks for the contribution!

JDH

Example refactoring of gauge.py

#!/usr/bin/env python
"""

The Gauge widget draws a semi-circular gauge. You supply limits,
shaded regions, names and the current value, and invoke it like this:

    from pylab import figure, show

    current_value = -4.0
    limits = [-1.0,1.0,1,0.1]
    zone_colour = [[-1.0,0.0,'r'],[0.0,0.5,'y'],[0.5,1.0,'g']]
    attribute_name = "Rx MOS (24h)"
    
    graph_height = 1.6
    graph_width = 2.4
    fig_height = graph_height
    fig_width = graph_width

    fig = figure(figsize=(fig_width, fig_height ))
    
    rect = [(0.0/fig_width), (0.2/fig_height),
            (graph_width/fig_width), (graph_height/fig_height)]
    
    gauge = Gauge(fig, rect,
               xlim=( -0.1, graph_width+0.1 ),
               ylim=( -0.4, graph_height+0.1 ),
               xticks=[],
               yticks=[],
               )
    gauge.set_axis_off()
    fig.add_axes(gauge)

    show()

"""
from __future__ import division
from matplotlib.figure import Figure
from matplotlib.axes import Axes
import math
import types

from math import pi

class Gauge(Axes):
    def __init__(self, *args, **kwargs):
        Axes.__init__(self, *args, **kwargs)

        #Perform Checking
        if( limits[0] == limits[1] ):
            raise ValueError('identical_limits_exception: %s'%limits)
        if( limits[1] > limits[0] ):
            graph_positive = True
        else: #Swap the limits around
            graph_positive = False
            limits[0], limits[1] = limits[1] = limits[0]

        #There must be an integer number of minor ticks for each major tick
        if not( ((limits[2]/limits[3]) % 1.0) * limits[3] == 0 ):
            raise ValueError('bad_tick_spacing_exception')

        if( limits[2] <= 0 or
            limits[3] <= 0 or
            limits[2] < limits[3] or
            limits[3] > abs(limits[1]-limits[0]) ):
            raise ValueError('bad_limits_exception:%s' % limits)
        for zone in zone_colour:
            if( zone[0] > zone[1] ): #Swap the zones so zone[1] > zone[0]
                zone[0], zone[1] = zone[1], zone[0]
            if( zone[1] < limits[0] or zone[0] > limits[1] ):
                raise ValueError('bad_zone_exception'%zone)
            if( zone[0] < limits[0] ):
                zone[0] = limits[0]
            if( zone[1] > limits[1] ):
                zone[1] = limits[1]

        #Draw the arch
        for zone in zone_colour:
            self.draw_arch(limits, zone[0], zone[1], zone[2], False, graph_positive)
            self.draw_arch(limits, limits[0], limits[1], 'black', True, graph_positive)
            self.draw_ticks(limits, graph_positive)
            self.draw_needle(current_value, limits, graph_positive)
            self.draw_bounding_box(graph_width, graph_height)
            self.text(0.0, 0.2, attribute_name, size=10, va='bottom', ha='center')

        #The black dot.
        p = self.plot([0.0],[0.0],'.', color='#000000')

    def draw_arch(self, limits, start_angle, end_angle, colour, border, graph_positive):
        x_vect = []
        y_vect = []
        if( graph_positive ):
            start = int(180 - (start_angle - limits[0]) * (180.0/(limits[1]-limits[0])))
            end = int(180 - (end_angle - limits[0]) * (180.0/(limits[1]-limits[0])))
        else:
            start = int( (end_angle - limits[0]) * (180.0/(limits[1]-limits[0])))
            end = int( (start_angle - limits[0]) * (180.0/(limits[1]-limits[0])))

        #Draw the arch
        theta = start
        radius = 0.85
        while (theta >= end):
            x_vect.append( radius * math.cos(theta * (pi/180)) )
            y_vect.append( radius * math.sin(theta * (pi/180)) )
            theta -= 1

        theta = end
        radius = 1.0
        while (theta <= start):
            x_vect.append( radius * math.cos(theta * (pi/180)) )
            y_vect.append( radius * math.sin(theta * (pi/180)) )
            theta += 1

        if( border ):
            #Close the loop
            x_vect.append(-0.85)
            y_vect.append(0.0)

            p = self.plot(x_vect, y_vect, 'b-', color='black', linewidth=1.5)
        else:
            p = self.fill(x_vect, y_vect, colour, linewidth=0.0, alpha=0.4)

    def draw_needle(self, current_value, limits, graph_positive):
        x_vect = []
        y_vect = []

        if current_value == None:
            self.text(0.0, 0.4, "N/A", size=10, va='bottom', ha='center')
        else:
            self.text(0.0, 0.4, "%.2f" % current_value, size=10, va='bottom', ha='center')

            #Clamp the value to the limits
            if( current_value < limits[0] ):
                current_value = limits[0]
            if( current_value > limits[1] ):
                current_value = limits[1]

            theta = 0
            length = 0.95
            if( graph_positive ):
                angle = 180.0 - (current_value - limits[0]) *(180.0/abs(limits[1]-limits[0]))
            else:
                angle = (current_value - limits[0]) *(180.0/abs(limits[1]-limits[0]))

            while (theta <= 270):
                x_vect.append( length * math.cos((theta + angle) * (pi/180)) )
                y_vect.append( length * math.sin((theta + angle) * (pi/180)) )
                length = 0.05
                theta += 90

            p = self.fill(x_vect, y_vect, 'b', alpha=0.4)

    def draw_ticks(self, limits, graph_positive):
        if( graph_positive ):
            angle = 180.0
        else:
            angle = 0.0
        i = 0
        j = limits[0]

        while( i*limits[3] + limits[0] <= limits[1] ):
            x_vect = []
            y_vect = []
            if( i % (limits[2]/limits[3]) == 0 ):
                x_pos = 1.1 * math.cos( angle * (pi/180.0))
                y_pos = 1.1 * math.sin( angle * (pi/180.0))
                if( type(limits[2]) is types.FloatType ):
                    self.text( x_pos, y_pos, "%.2f" % j, size=10, va='center', ha='center', rotation=(angle - 90))
                else:
                    self.text( x_pos, y_pos, "%d" % int(j), size=10, va='center', ha='center', rotation=(angle - 90))
                tick_length = 0.15
                j += limits[2]
            else:
                tick_length = 0.05
            i += 1
            x_vect.append( 1.0 * math.cos( angle * (pi/180.0)))
            x_vect.append( (1.0 - tick_length) * math.cos( angle * (pi/180.0)))
            y_vect.append( 1.0 * math.sin( angle * (pi/180.0)))
            y_vect.append( (1.0 - tick_length) * math.sin( angle * (pi/180.0)))
            p = self.plot(x_vect, y_vect, 'b-', linewidth=1, alpha=0.4, color="black")
            if( graph_positive ):
                angle -= limits[3] * (180.0/abs(limits[1]-limits[0]))
            else:
                angle += limits[3] * (180.0/abs(limits[1]-limits[0]))
        if( i % (limits[2]/limits[3]) == 0 ):
            x_pos = 1.1 * math.cos( angle * (pi/180.0))
            y_pos = 1.1 * math.sin( angle * (pi/180.0))
            if( type(limits[2]) is types.FloatType ):
                self.text( x_pos, y_pos, "%.2f" % j, size=10, va='center', ha='center', rotation=(angle - 90))
            else:
                self.text( x_pos, y_pos, "%d" % int(j), size=10, va='center', ha='center', rotation=(angle - 90))

    def draw_bounding_box(self, graph_width, graph_height):
        x_vect = [
            graph_width/2,
            graph_width/2,
            -graph_width/2,
            -graph_width/2,
            graph_width/2,
            ]

        y_vect = [
            -0.1,
            graph_height,
            graph_height,
            -0.1,
            -0.1,
            ]

        p = self.plot(x_vect, y_vect, 'r-', linewidth=0)

if __name__=='__main__':
    from pylab import figure, show

    current_value = -4.0
    limits = [-1.0,1.0,1,0.1]
    zone_colour = [[-1.0,0.0,'r'],[0.0,0.5,'y'],[0.5,1.0,'g']]
    attribute_name = "Rx MOS (24h)"
    
    graph_height = 1.6
    graph_width = 2.4
    fig_height = graph_height
    fig_width = graph_width

    fig = figure(figsize=(fig_width, fig_height ))
    
    rect = [(0.0/fig_width), (0.2/fig_height),
            (graph_width/fig_width), (graph_height/fig_height)]
    
    gauge = Gauge(fig, rect,
               xlim=( -0.1, graph_width+0.1 ),
               ylim=( -0.4, graph_height+0.1 ),
               xticks=[],
               yticks=[],
               )
    gauge.set_axis_off()
    fig.add_axes(gauge)

    show()