diff -cr thuban-cvs/Thuban/Model/classgen.py thuban/Thuban/Model/classgen.py
*** thuban-cvs/Thuban/Model/classgen.py	2003-09-26 13:36:01.000000000 -0500
--- thuban/Thuban/Model/classgen.py	2004-01-30 17:25:08.000000000 -0500
***************
*** 1,6 ****
--- 1,7 ----
  # Copyright (c) 2003 by Intevation GmbH
  # Authors:
  # Jonathan Coles <jonathan@intevation.de>
+ # Daniel Calvelo Aros <dca@users.sourceforge.net>
  #
  # This program is free software under the GPL (>=v2)
  # Read the file COPYING coming with Thuban for details.
***************
*** 14,21 ****
  # $Id: classgen.py,v 1.18 2003/09/26 18:36:01 bh Exp $
  
  import operator
  
! from color import Color, Transparent
  from range import Range
  from classification import Classification, ClassGroupSingleton, \
      ClassGroupRange, ClassGroupProperties
--- 15,24 ----
  # $Id: classgen.py,v 1.18 2003/09/26 18:36:01 bh Exp $
  
  import operator
+ from math import sqrt
+ import random
  
! from color import Color, Transparent, rgb2hsv, hsv2rgb
  from range import Range
  from classification import Classification, ClassGroupSingleton, \
      ClassGroupRange, ClassGroupProperties
***************
*** 334,343 ****
  
          newProps = ClassGroupProperties()
  
!         self.__SetProperty(self.prop1.GetLineColor(),
                             self.prop2.GetLineColor(),
                             index, newProps.SetLineColor)
!         self.__SetProperty(self.prop1.GetFill(), self.prop2.GetFill(),
                             index, newProps.SetFill)
  
          w = (self.prop2.GetLineWidth() - self.prop1.GetLineWidth()) \
--- 337,346 ----
  
          newProps = ClassGroupProperties()
  
!         self._SetProperty(self.prop1.GetLineColor(),
                             self.prop2.GetLineColor(),
                             index, newProps.SetLineColor)
!         self._SetProperty(self.prop1.GetFill(), self.prop2.GetFill(),
                             index, newProps.SetFill)
  
          w = (self.prop2.GetLineWidth() - self.prop1.GetLineWidth()) \
***************
*** 347,353 ****
  
          return newProps
  
!     def __SetProperty(self, color1, color2, index, setf):
          """Use setf to set the appropriate property for the point
          index percent between color1 and color2. setf is a function
          to call that accepts a Color object or Transparent.
--- 350,356 ----
  
          return newProps
  
!     def _SetProperty(self, color1, color2, index, setf):
          """Use setf to set the appropriate property for the point
          index percent between color1 and color2. setf is a function
          to call that accepts a Color object or Transparent.
***************
*** 371,376 ****
--- 374,380 ----
                  (color2.green - color1.green) * index + color1.green,
                  (color2.blue  - color1.blue)  * index + color1.blue))
  
+ 
  class MonochromaticRamp(CustomRamp):
      """Helper class to make ramps between two colors."""
  
***************
*** 391,401 ****
  
          CustomRamp.__init__(self, sp, ep)
  
  grey_ramp         = MonochromaticRamp(Color(1, 1, 1),  Color(0, 0, 0))
  red_ramp          = MonochromaticRamp(Color(1, 1, 1),  Color(.8, 0, 0))
  green_ramp        = MonochromaticRamp(Color(1, 1, 1),  Color(0, .8, 0))
  blue_ramp         = MonochromaticRamp(Color(1, 1, 1),  Color(0, 0, .8))
! green_to_red_ramp = MonochromaticRamp(Color(0, .8, 0), Color(1, 0, 0))
  
  class HotToColdRamp:
      """A ramp that generates properties with colors ranging from
--- 395,479 ----
  
          CustomRamp.__init__(self, sp, ep)
  
+ class HSVCustomRamp(CustomRamp):
+     """Ramp between two property groups interpolated in HSV colorpsace"""
+     # Had to de-privatize __SetProperty, otherwise it can't be overriden by siblings
+ 
+     def _SetProperty(self, color1, color2, index, setf):
+         #
+         # FIXME: This first part is copied from CustomRamp.
+         # It's not correct anyway, as transparency is faked either
+         # by not drawing or (here) considering a white background
+         #
+         if color1 is Transparent and color2 is Transparent:
+             setf(Transparent)
+         elif color1 is Transparent:
+             setf(Color(
+                  color2.red   * index,
+                  color2.green * index,
+                  color2.blue  * index))
+         elif color2 is Transparent:
+             setf(Color(
+                  color1.red   * index,
+                  color1.green * index,
+                  color1.blue  * index))
+         else:
+             #
+             # This is the interesting part
+             #
+             h1,s1,v1 = rgb2hsv( (color1.red, color1.green, color1.blue) )
+             h2,s2,v2 = rgb2hsv( (color2.red, color2.green, color2.blue) )
+             #
+             # Special case 1: grays are given red hue by default, although they do not have hue proper.
+             #                 this can trick the interpolation: going from white to blue would
+             #                 give pale purple shades
+             #
+             saturation_threshold = 1e-5 # Let's have some flexibility here...
+             if s1<=saturation_threshold and s2>saturation_threshold: # Color 2 really has a hue
+                 h1 = h2
+             if s2<=saturation_threshold and s1>saturation_threshold:
+                 h2 = h1
+             # All other cases are Ok, including s1=s2=0
+ 
+             #
+             # Special case 2: hue is a ring, you don't go from yellow to violet through green,
+             #                 you move directly from yellow through orange and red to violet.
+             #                 Or at least it seems more intuitive that way,
+             #
+             if abs(h2-h1)>.5: # we go "the other way around"
+                 if h1>h2:
+                     h2 = h2+1.0
+                 else:
+                     h1 = h1+1.0
+                 hh = h1 + index*(h2-h1)
+                 hh = hh - int(hh) # equivalent to hh mod 1
+             else:
+                 hh = h1 + index*(h2-h1)
+ 
+             #
+             # Special case 3: black is unsaturated by default, so going from black to green
+             #                 goes through greenish dark grays instead of dark greens
+             #
+             value_threshold = 1e-5 # Let's have some flexibility here...
+             if v1<=value_threshold and v2>value_threshold: # Color 1 is black
+                 s1 = s2
+             if v2<=value_threshold and v1>value_threshold:
+                 s2 = s1
+             
+             r,g,b = hsv2rgb( ( hh ,
+                                s1 + index*(s2-s1) ,
+                                v1 + index*(v2-v1) ) )
+             setf( Color(r,g,b) )
+ 
+ class HSVRamp(HSVCustomRamp,MonochromaticRamp):
+     def __init__(self, prop1, prop2):
+         MonochromaticRamp.__init__(self, prop1, prop2)
+ 
  grey_ramp         = MonochromaticRamp(Color(1, 1, 1),  Color(0, 0, 0))
  red_ramp          = MonochromaticRamp(Color(1, 1, 1),  Color(.8, 0, 0))
  green_ramp        = MonochromaticRamp(Color(1, 1, 1),  Color(0, .8, 0))
  blue_ramp         = MonochromaticRamp(Color(1, 1, 1),  Color(0, 0, .8))
! green_to_red_ramp = HSVRamp(Color(0, .8, 0), Color(1, 0, 0))
  
  class HotToColdRamp:
      """A ramp that generates properties with colors ranging from
diff -cr thuban-cvs/Thuban/Model/color.py thuban/Thuban/Model/color.py
*** thuban-cvs/Thuban/Model/color.py	2003-08-05 07:38:52.000000000 -0500
--- thuban/Thuban/Model/color.py	2004-01-30 17:25:08.000000000 -0500
***************
*** 58,60 ****
--- 58,95 ----
  Transparent = _Transparent()
  Black = Color(0, 0, 0)
  
+ 
+ def rgb2hsv(rgb):
+     """ pre: rgb is a (R,G,B) tuple with each in [0;1] """
+     s = float(min( rgb ))
+     v = float(max( rgb ))
+     if s==v: #gray
+         if v>0:
+             return (0.0, 1.0-float(s)/v, v)
+         else: #black
+             return (0.0,0.0,0.0)
+     r,g,b=[float(i) for i in rgb]
+     if b==v: #blue shades
+         h = 2.0/3 + 1.0/6*(r-g)/(v-s)
+     elif g==v: #green shades
+         h = 1.0/3 + 1.0/6*(b-r)/(v-s)
+     else: #red shades
+         h = 1.0/6* (g-b)/(v-s)
+     if h<0: #negative red
+         h += 1
+     s = 1.0-s/v
+     return (h,s,v)
+ 
+ def hsv2rgb(hsv):
+     h,s,v=[float(i) for i in hsv]
+     i = int( h*6.0 )
+     f = h*6.0-i
+     p = (1.0-s)*v
+     q = (1.0-s*f)*v
+     t = (1.0-(1.0-f)*s)*v
+     return ( (v,t,p),
+              (q,v,p),
+              (p,v,t),
+              (p,q,v),
+              (t,p,v),
+              (v,p,q) )[ i ]
diff -cr thuban-cvs/Thuban/UI/classgen.py thuban/Thuban/UI/classgen.py
*** thuban-cvs/Thuban/UI/classgen.py	2003-08-08 08:01:30.000000000 -0500
--- thuban/Thuban/UI/classgen.py	2004-01-30 17:25:08.000000000 -0500
***************
*** 32,38 ****
  from Thuban.Model.classgen import \
      generate_uniform_distribution, generate_singletons, generate_quantiles, \
      CustomRamp, grey_ramp, red_ramp, green_ramp, blue_ramp, green_to_red_ramp, \
!     HotToColdRamp, FixedRamp
  
  
  USEALL_BMP  = "group_use_all"
--- 32,38 ----
  from Thuban.Model.classgen import \
      generate_uniform_distribution, generate_singletons, generate_quantiles, \
      CustomRamp, grey_ramp, red_ramp, green_ramp, blue_ramp, green_to_red_ramp, \
!     HotToColdRamp, FixedRamp, HSVCustomRamp
  
  
  USEALL_BMP  = "group_use_all"
***************
*** 912,917 ****
--- 912,918 ----
  ID_CUSTOMRAMP_EDITEND = 4004
  ID_CUSTOMRAMP_SPROP = 4005
  ID_CUSTOMRAMP_EPROP = 4006
+ ID_CUSTOMRAMP_MODECHOICE = 4007
  
  class CustomRampPanel(wxPanel):
  
***************
*** 919,925 ****
          wxPanel.__init__(self, parent, -1)
  
          topSizer = wxStaticBoxSizer(wxStaticBox(self, -1, ""), wxHORIZONTAL)
! 
          bsizer = wxBoxSizer(wxVERTICAL)
          bsizer.Add(wxStaticText(self, -1, _("Start:")), 0, wxALL | wxCENTER, 4)
          self.startPropCtrl = classifier.ClassGroupPropertiesCtrl(
--- 920,932 ----
          wxPanel.__init__(self, parent, -1)
  
          topSizer = wxStaticBoxSizer(wxStaticBox(self, -1, ""), wxHORIZONTAL)
! 	
! 	self.modeChoice = wxRadioBox( self, ID_CUSTOMRAMP_MODECHOICE, 
! 	                         _("Mode"),wxDefaultPosition,wxDefaultSize, 
! 				 [_("HSV Ramp"),_("RGB Ramp")],
! 				 1, wxRA_SPECIFY_COLS)
!         topSizer.Add( self.modeChoice, 0, wxLEFT|wxALIGN_CENTER_VERTICAL )
!       
          bsizer = wxBoxSizer(wxVERTICAL)
          bsizer.Add(wxStaticText(self, -1, _("Start:")), 0, wxALL | wxCENTER, 4)
          self.startPropCtrl = classifier.ClassGroupPropertiesCtrl(
***************
*** 968,974 ****
                        | wxALIGN_CENTER_HORIZONTAL \
                        | wxALIGN_CENTER_VERTICAL, 
                     4)
- 
          EVT_BUTTON(self, ID_CUSTOMRAMP_COPYSTART, self._OnCopyStart)
          EVT_BUTTON(self, ID_CUSTOMRAMP_COPYEND, self._OnCopyEnd)
          EVT_BUTTON(self, ID_CUSTOMRAMP_EDITSTART, self._OnEditStart)
--- 975,980 ----
***************
*** 979,985 ****
          topSizer.SetSizeHints(self)
  
      def GetRamp(self):
!         return CustomRamp(self.startPropCtrl.GetProperties(),
                            self.endPropCtrl.GetProperties())
  
      def _OnCopyStart(self, event):
--- 985,995 ----
          topSizer.SetSizeHints(self)
  
      def GetRamp(self):
!     	if self.modeChoice.GetStringSelection()==_("HSV Ramp"):
! 	    rampType = HSVCustomRamp
! 	else:
! 	    rampType = CustomRamp
!         return rampType(self.startPropCtrl.GetProperties(),
                            self.endPropCtrl.GetProperties())
  
      def _OnCopyStart(self, event):
