Changeset 109 for trunk/src/chart.py

Show
Ignore:
Timestamp:
10/27/08 16:49:58 (4 years ago)
Author:
lgs
Message:

Some refactoring and support for negative values in line and bar charts. Inspired by Nicolas patch

Files:
1 modified

Legend:

Unmodified
Added
Removed
  • trunk/src/chart.py

    r106 r109  
    139139    def _updateXY(self): 
    140140        """Calculates all kinds of metrics for the x and y axis""" 
    141         stores = self._getDatasetsValues() 
     141        x_range_is_defined = self.options.axis.x.range is not None 
     142        y_range_is_defined = self.options.axis.y.range is not None 
     143 
     144        if not x_range_is_defined or not y_range_is_defined: 
     145            stores = self._getDatasetsValues() 
     146 
     147        # gather data for the x axis 
     148        if x_range_is_defined: 
     149            self.minxval, self.maxxval = self.options.axis.x.range 
     150        else: 
     151            xdata = [pair[0] for pair in reduce(lambda a,b: a+b, stores)] 
     152            self.minxval = float(min(xdata)) 
     153            self.maxxval = float(max(xdata)) 
     154            if self.minxval * self.maxxval > 0 and self.minxval > 0: 
     155                self.minxval = 0.0 
     156 
     157        self.xrange = self.maxxval - self.minxval 
     158        if self.xrange == 0: 
     159            self.xscale = 1.0 
     160        else: 
     161            self.xscale = 1 / self.xrange 
     162 
     163        # gather data for the y axis 
     164        if y_range_is_defined: 
     165            self.minyval, self.maxyval = self.options.axis.y.range 
     166        else: 
     167            ydata = [pair[1] for pair in reduce(lambda a,b: a+b, stores)] 
     168            self.minyval = float(min(ydata)) 
     169            self.maxyval = float(max(ydata)) 
     170            if self.minyval * self.maxyval > 0 and self.minyval > 0: 
     171                self.minyval = 0.0 
     172 
     173        self.yrange = self.maxyval - self.minyval 
     174        if self.yrange == 0: 
     175            self.yscale = 1.0 
     176        else: 
     177            self.yscale = 1 / self.yrange 
    142178 
    143179        # calculate area data 
     
    146182        height = (self.surface.get_height() 
    147183                  - self.options.padding.top - self.options.padding.bottom) 
     184 
     185        if self.minyval * self.maxyval < 0: # different signs 
     186            origin = abs(self.minyval) * self.yscale 
     187        else: 
     188            origin = 0 
     189 
    148190        self.area = Area(self.options.padding.left, 
    149191                         self.options.padding.top, 
    150                          width, height) 
    151  
    152         # gather data for the x axis 
    153         if self.options.axis.x.range: 
    154             self.minxval, self.maxxval = self.options.axis.x.range 
    155             self.xscale = self.maxxval - self.minxval 
    156         else: 
    157             xdata = [pair[0] for pair in reduce(lambda a,b: a+b, stores)] 
    158             if self.options.xOriginIsZero: 
    159                 self.minxval = 0.0 
    160             else: 
    161                 self.minxval = float(min(xdata)) 
    162             self.maxxval = float(max(xdata)) 
    163  
    164         self.xrange = self.maxxval - self.minxval 
    165         if self.xrange == 0: 
    166             self.xscale = 1.0 
    167         else: 
    168             self.xscale = 1 / self.xrange 
    169  
    170         # gather data for the y axis 
    171         if self.options.axis.y.range: 
    172             self.minyval, self.maxyval = self.options.axis.y.range 
    173             self.yscale = self.maxyval - self.minyval 
    174         else: 
    175             ydata = [pair[1] for pair in reduce(lambda a,b: a+b, stores)] 
    176             if self.options.yOriginIsZero: 
    177                 self.minyval = 0.0 
    178             else: 
    179                 self.minyval = float(min(ydata)) 
    180             self.maxyval = float(max(ydata)) 
    181  
    182         self.yrange = self.maxyval - self.minyval 
    183         if self.yrange == 0: 
    184             self.yscale = 1.0 
    185         else: 
    186             self.yscale = 1 / self.yrange 
     192                         width, height, origin) 
    187193 
    188194    def _updateChart(self): 
     
    215221            uniqx = range(len(uniqueIndices(stores)) + 1) 
    216222            roughSeparation = self.xrange / self.options.axis.x.tickCount 
    217  
    218223            i = j = 0 
    219             while i + 1 < len(uniqx) and j < self.options.axis.x.tickCount: 
    220                 if (uniqx[i + 1] - self.minxval) >= (j * roughSeparation): 
     224            while i < len(uniqx) and j < self.options.axis.x.tickCount: 
     225                if (uniqx[i] - self.minxval) >= (j * roughSeparation): 
    221226                    pos = self.xscale * (uniqx[i] - self.minxval) 
    222227                    if 0.0 <= pos <= 1.0: 
    223                         self.xticks.append((pos, uniqx[i + 1])) 
     228                        self.xticks.append((pos, uniqx[i])) 
    224229                        j += 1 
    225230                i += 1 
     
    239244                    self.yticks.append((pos, label)) 
    240245 
     246        elif self.options.axis.y.interval > 0: 
     247            interval = self.options.axis.y.interval 
     248            label = (divmod(self.minyval, interval)[0] + 1) * interval 
     249            pos = 1.0 - (self.yscale * (label - self.minyval)) 
     250            while 0.0 <= pos <= 1.0: 
     251                self.yticks.append((pos, label)) 
     252                label += interval 
     253                pos = 1.0 - (self.yscale * (label - self.minyval)) 
     254 
    241255        elif self.options.axis.y.tickCount > 0: 
    242256            prec = self.options.axis.y.tickPrecision 
     
    263277        if self.options.background.baseColor: 
    264278            cx.set_source_rgb(*hex2rgb(self.options.background.baseColor)) 
    265             x, y, w, h = 0, 0, self.area.w, self.area.h 
    266             w += self.options.padding.left + self.options.padding.right 
    267             h += self.options.padding.top + self.options.padding.bottom 
    268             cx.rectangle(x, y, w, h) 
    269             cx.fill() 
     279            cx.paint() 
    270280 
    271281        if self.options.background.chartColor: 
     
    424434        """Draws the horizontal line representing the X axis""" 
    425435        cx.new_path() 
    426         cx.move_to(self.area.x, self.area.y + self.area.h) 
    427         cx.line_to(self.area.x + self.area.w, self.area.y + self.area.h) 
     436        cx.move_to(self.area.x, 
     437                   self.area.y + self.area.h * (1.0 - self.area.origin)) 
     438        cx.line_to(self.area.x + self.area.w, 
     439                   self.area.y + self.area.h * (1.0 - self.area.origin)) 
    428440        cx.close_path() 
    429441        cx.stroke() 
     
    567579class Area(object): 
    568580    """Simple rectangle to hold an area coordinates and dimensions""" 
    569     def __init__(self, x, y, w, h): 
     581    def __init__(self, x, y, w, h, origin=0.0): 
    570582        self.x, self.y, self.w, self.h = x, y, w, h 
     583        self.origin = origin 
    571584 
    572585    def __str__(self): 
    573         return "<pycha.chart.Area@(%.2f, %.2f) %.2fx%.2f" % (self.x, self.y, 
    574                                                              self.w, self.h) 
     586        msg = "<pycha.chart.Area@(%.2f, %.2f) %.2f x %.2f Origin: %.2f>" 
     587        return  msg % (self.x, self.y, self.w, self.h, self.origin) 
    575588 
    576589class Option(dict): 
     
    594607    axis=Option( 
    595608        lineWidth=1.0, 
    596         lineColor='#000000', 
     609        lineColor='#0f0000', 
    597610        tickSize=3.0, 
    598611        labelColor='#666666', 
     
    614627            tickCount=10, 
    615628            tickPrecision=1, 
     629            interval=0, 
    616630            range=None, 
    617631            rotate=None, 
     
    635649        left=30, 
    636650        right=30, 
    637         top=15, 
    638         bottom=15, 
     651        top=30, 
     652        bottom=30, 
    639653    ), 
    640654    stroke=Option( 
     
    647661    shouldFill=True, 
    648662    barWidthFillFraction=0.75, 
    649     xOriginIsZero=True, 
    650     yOriginIsZero=True, 
    651663    pieRadius=0.4, 
    652664    colorScheme=DEFAULT_COLOR,