Changeset 82 for trunk

Show
Ignore:
Timestamp:
03/21/08 15:05:51 (4 years ago)
Author:
lgs
Message:

Support for rendering the ticks with rotation. Also support for drawing labels in both axis. Based on patch sended by jae. View #6

Location:
trunk
Files:
4 modified

Legend:

Unmodified
Added
Removed
  • trunk/examples/barchart.py

    r81 r82  
    3535            'x': { 
    3636                'ticks': [dict(v=i, label=l[0]) for i, l in enumerate(lines)], 
     37                'label': 'Files', 
     38                'rotate': 25, 
    3739            }, 
    3840            'y': { 
    3941                'tickCount': 4, 
     42                'rotate': 25, 
     43                'label': 'Lines' 
    4044            } 
    4145        }, 
     
    5054        }, 
    5155        'padding': { 
    52             'left': 55 
     56            'left': 55, 
     57            'bottom': 55, 
    5358        }, 
    5459        'title': 'Sample Chart' 
  • trunk/src/chart.py

    r81 r82  
    1717 
    1818import copy 
     19import math 
     20 
    1921import cairo 
    2022 
     
    306308        raise NotImplementedError 
    307309 
     310    def _renderYLabel(self, cx, tick): 
     311        """Aux method for _renderAxis""" 
     312 
     313        if callable(tick): 
     314            return 
     315 
     316        x = self.area.x 
     317        y = self.area.y + tick[0] * self.area.h 
     318 
     319        cx.new_path() 
     320        cx.move_to(x, y) 
     321        cx.line_to(x - self.options.axis.tickSize, y) 
     322        cx.close_path() 
     323        cx.stroke() 
     324 
     325        label =  unicode(tick[1]) 
     326        extents = cx.text_extents(label) 
     327        labelWidth = extents[2] 
     328        labelHeight = extents[3] 
     329 
     330        if self.options.axis.y.rotate: 
     331            radians = math.radians(self.options.axis.y.rotate) 
     332            cx.move_to(x - self.options.axis.tickSize 
     333                       - (labelWidth * math.cos(radians)) 
     334                       - 4, 
     335                       y + (labelWidth * math.sin(radians)) 
     336                       + labelHeight / (2.0 / math.cos(radians))) 
     337            cx.rotate(-radians) 
     338            cx.show_text(label) 
     339            cx.rotate(radians) # this is probably faster than a save/restore 
     340        else: 
     341            cx.move_to(x - self.options.axis.tickSize - labelWidth - 4, 
     342                       y + labelHeight / 2.0) 
     343            cx.show_text(label) 
     344 
     345        return label 
     346 
     347    def _renderXLabel(self, cx, tick, fontAscent): 
     348        if callable(tick): 
     349            return 
     350 
     351        x = self.area.x + tick[0] * self.area.w 
     352        y = self.area.y + self.area.h 
     353 
     354        cx.new_path() 
     355        cx.move_to(x, y) 
     356        cx.line_to(x, y + self.options.axis.tickSize) 
     357        cx.close_path() 
     358        cx.stroke() 
     359 
     360        label = unicode(tick[1]) 
     361        extents = cx.text_extents(label) 
     362        labelWidth = extents[2] 
     363        labelHeight = extents[3] 
     364 
     365        if self.options.axis.x.rotate: 
     366            radians = math.radians(self.options.axis.x.rotate) 
     367            cx.move_to(x - (labelHeight * math.cos(radians)), 
     368                       y + self.options.axis.tickSize 
     369                       + (labelHeight * math.cos(radians)) 
     370                       + 4.0) 
     371            cx.rotate(radians) 
     372            cx.show_text(label) 
     373            cx.rotate(-radians) 
     374        else: 
     375            cx.move_to(x - labelWidth / 2.0, 
     376                       y + self.options.axis.tickSize 
     377                       + fontAscent + 4.0) 
     378            cx.show_text(label) 
     379        return label 
     380 
     381    def _getTickSize(self, cx, ticks, rotate): 
     382        tickExtents = [cx.text_extents(unicode(tick[1]))[2:4] for tick in ticks] 
     383        tickWidth = tickHeight = 0.0 
     384        if tickExtents: 
     385            tickHeight = tickHeight = self.options.axis.tickSize + 4.0 
     386            widths, heights = zip(*tickExtents) 
     387            maxWidth, maxHeight = max(widths), max(heights) 
     388            if rotate: 
     389                radians = math.radians(rotate) 
     390                sinRadians = math.sin(radians) 
     391                cosRadians = math.cos(radians) 
     392                maxHeight = maxWidth * sinRadians + maxHeight * cosRadians 
     393                maxWidth =  maxWidth * cosRadians + maxHeight * sinRadians 
     394            tickWidth += maxWidth 
     395            tickHeight += maxHeight 
     396        return tickWidth, tickHeight 
     397         
     398    def _renderAxisLabel(self, cx, tickWidth, tickHeight, label, x, y, vertical=False): 
     399        cx.new_path() 
     400        cx.select_font_face(self.options.axis.labelFont, 
     401                            cairo.FONT_SLANT_NORMAL, 
     402                            cairo.FONT_WEIGHT_BOLD) 
     403        labelWidth = cx.text_extents(label)[2] 
     404        fontAscent = cx.font_extents()[0] 
     405        if vertical: 
     406            cx.move_to(x, y + labelWidth / 2) 
     407            radians = math.radians(90) 
     408            cx.rotate(-radians) 
     409        else: 
     410            cx.move_to(x - labelWidth / 2.0, y + fontAscent) 
     411             
     412        cx.show_text(label) 
     413 
    308414    def _renderAxis(self, cx): 
    309415        """Renders axis""" 
     
    317423        if not self.options.axis.y.hide: 
    318424            if self.yticks: 
    319                 def drawYLabel(tick): 
    320                     if callable(tick): 
    321                         return 
    322  
    323                     x = self.area.x 
    324                     y = self.area.y + tick[0] * self.area.h 
    325  
    326                     cx.new_path() 
    327                     cx.move_to(x, y) 
    328                     cx.line_to(x - self.options.axis.tickSize, y) 
    329                     cx.close_path() 
    330                     cx.stroke() 
    331  
    332                     label =  unicode(tick[1]) 
    333                     extents = cx.text_extents(label) 
    334                     labelWidth = extents[2] 
    335                     labelHeight = extents[3] 
    336                     cx.move_to(x - self.options.axis.tickSize - labelWidth - 5, 
    337                                y + labelHeight / 2.0) 
    338                     cx.show_text(label) 
    339  
    340                     return label 
    341425                for tick in self.yticks: 
    342                     drawYLabel(tick) 
    343  
     426                    self._renderYLabel(cx, tick) 
     427 
     428            if self.options.axis.y.label: 
     429                cx.save() 
     430                rotate = self.options.axis.y.rotate 
     431                tickWidth, tickHeight = self._getTickSize(cx, self.yticks, rotate) 
     432                label = unicode(self.options.axis.y.label) 
     433                x = self.area.x - tickWidth - 4.0 
     434                y = self.area.y + 0.5 * self.area.h 
     435                self._renderAxisLabel(cx, tickWidth, tickHeight, label, x, y, True) 
     436                cx.restore() 
     437 
     438            # draws the vertical line representing the Y axis 
    344439            cx.new_path() 
    345440            cx.move_to(self.area.x, self.area.y) 
     
    349444 
    350445        if not self.options.axis.x.hide: 
     446            fontAscent = cx.font_extents()[0] 
    351447            if self.xticks: 
    352                 def drawXLabel(tick): 
    353                     if callable(tick): 
    354                         return 
    355  
    356                     x = self.area.x + tick[0] * self.area.w 
    357                     y = self.area.y + self.area.h 
    358  
    359                     cx.new_path() 
    360                     cx.move_to(x, y) 
    361                     cx.line_to(x, y + self.options.axis.tickSize) 
    362                     cx.close_path() 
    363                     cx.stroke() 
    364  
    365                     label = unicode(tick[1]) 
    366                     extents = cx.text_extents(label) 
    367                     labelWidth = extents[2] 
    368                     labelHeight = extents[3] 
    369                     cx.move_to(x - labelWidth / 2.0, 
    370                                y + self.options.axis.tickSize + 10) 
    371                     cx.show_text(label) 
    372                     return label 
    373448                for tick in self.xticks: 
    374                     drawXLabel(tick) 
    375  
     449                    self._renderXLabel(cx, tick, fontAscent) 
     450 
     451            if self.options.axis.x.label: 
     452                cx.save() 
     453                rotate = self.options.axis.x.rotate 
     454                tickWidth, tickHeight = self._getTickSize(cx, self.xticks, rotate) 
     455                label = unicode(self.options.axis.x.label) 
     456                x = self.area.x + self.area.w / 2.0 
     457                y = self.area.y + self.area.h + tickHeight + 4.0 
     458                self._renderAxisLabel(cx, tickWidth, tickHeight, label, x, y, False) 
     459                cx.restore() 
     460 
     461            # draws the horizontal line representing the X axis 
    376462            cx.new_path() 
    377463            cx.move_to(self.area.x, self.area.y + self.area.h) 
     
    384470    def _renderTitle(self, cx): 
    385471        if self.options.title: 
    386             # get previous font information 
    387             oldFace = cx.get_font_face() 
    388             oldMatrix = cx.get_font_matrix() 
    389              
     472            cx.save()            
    390473            cx.select_font_face(self.options.titleFont, 
    391474                                cairo.FONT_SLANT_NORMAL, 
     
    403486            cx.show_text(title) 
    404487             
    405             # restore font state 
    406             cx.set_font_face(oldFace) 
    407             cx.set_font_matrix(oldMatrix) 
     488            cx.restore() 
    408489 
    409490    def _renderLegend(self, cx): 
     
    494575            tickPrecision=1, 
    495576            range=None, 
     577            rotate=None, 
     578            label=None, 
    496579        ), 
    497580        y=Option( 
     
    501584            tickPrecision=1, 
    502585            range=None, 
     586            rotate=None, 
     587            label=None, 
    503588        ), 
    504589    ),