[reportlab-users] creating ImageMaps for charts

Dirk Datzert reportlab-users@reportlab.com
Tue, 15 Jul 2003 19:05:19 +0200


Dies ist eine mehrteilige Nachricht im MIME-Format.
--------------3F2437A37056D213847C1269
Content-Type: text/plain; charset=us-ascii
Content-Transfer-Encoding: 7bit

Hi ReportLab users,

I had the need for creating clickable charts in web-pages. As I wrote
the zope product RenderableCharts I found it very easy to do this new
issue with a special ImageMap-Renderer which takes all the chart data
and writes a html-imagemap from the bars and plots.

The patch will be included in the next release of RenderableCharts.

I decided that the best way I currently know is a new Renderer in
file renderIM.py (for renderImageMap). Until I don't want to patch all
the AttrMaps for BarCharts and LinePlots I use attribute-names beginning
with underscore:

Drawing._mapName -> <map name="{_mapName}">

Charts._mapAreaHref, Charts._mapAreaAlt
Charts._mapAreaOnMouseOver, Charts._mapAreaOnMouseOut and
Charts._mapAreaOnClick  will go to <area href="{_mapAreaHref}" 
  alt="{_mapAreaAlt}" etc.

_mapArea* can be a single String, a row of strings, or "[row][col]" of
strings

Best Regards,
Dirk
--------------3F2437A37056D213847C1269
Content-Type: text/plain; charset=us-ascii;
 name="ReportLab-1.18-ImageMap.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="ReportLab-1.18-ImageMap.patch"

diff -uNr reportlab/graphics/charts/barcharts.py reportlab.dirk/graphics/charts/barcharts.py
--- reportlab/graphics/charts/barcharts.py	Wed Jul  2 18:10:10 2003
+++ reportlab.dirk/graphics/charts/barcharts.py	Mon Jul 14 17:10:22 2003
@@ -411,6 +411,8 @@
                     r.strokeWidth = style.strokeWidth
                     r.fillColor = style.fillColor
                     r.strokeColor = style.strokeColor
+                    from reportlab.graphics.renderIM import _setMapAreaAttr
+                    _setMapAreaAttr(self, r, rowNo, colNo)
                     g.add(r)
 
                 self._addBarLabel(g,rowNo,colNo,x,y,width,height)
diff -uNr reportlab/graphics/charts/lineplots.py reportlab.dirk/graphics/charts/lineplots.py
--- reportlab/graphics/charts/lineplots.py	Wed Jul  2 18:10:10 2003
+++ reportlab.dirk/graphics/charts/lineplots.py	Mon Jul 14 22:10:09 2003
@@ -263,7 +263,10 @@
                 for colNo in range(len(row)):
                     x1, y1 = row[colNo]
                     symbol = uSymbol2Symbol(uSymbol,x1,y1,rowColor)
-                    if symbol: g.add(symbol)
+                    if symbol: 
+                        from reportlab.graphics.renderIM import _setMapAreaAttr
+                        _setMapAreaAttr(self, symbol, rowNo, colNo)
+                        g.add(symbol)
 
             # Draw item (bar) labels.
             for colNo in range(len(row)):
diff -uNr reportlab/graphics/renderIM.py reportlab.dirk/graphics/renderIM.py
--- reportlab/graphics/renderIM.py	Thu Jan  1 01:00:00 1970
+++ reportlab.dirk/graphics/renderIM.py	Tue Jul 15 18:47:25 2003
@@ -0,0 +1,228 @@
+#copyright Dirk Datzert <dirk@datzert.de> 2003
+#see license.txt for license details
+#$Header: /var/lib/cvs/n4062/RenderableCharts/ReportLab-1.18-ImageMap.patch,v 1.1 2003/07/15 16:49:40 admin Exp $
+__version__=''' $Id: ReportLab-1.18-ImageMap.patch,v 1.1 2003/07/15 16:49:40 admin Exp $ '''
+"""Usage:
+    from reportlab.graphics import renderIM
+    renderIM.drawToString(drawing)
+Other functions let you create a IM drawing as string or into a IM buffer.
+Execute the script to see some test drawings."""
+
+from reportlab.graphics.shapes import *
+from reportlab.graphics.renderbase import StateTracker, getStateDelta
+from reportlab.lib.utils import getStringIO
+
+import string, os, sys
+
+from types import TupleType, ListType
+_SeqTypes = (TupleType,ListType)
+
+# the main entry point for users...
+def draw(drawing, canvas):
+    """As it says"""
+    R = _IMRenderer()
+    R.draw(drawing, canvas)
+
+
+def _setMapAreaAttr(self, obj, row, col):
+    for attr in ['Href', 'Alt', 'OnMouseOver', 'OnMouseOut', 'OnClick']:
+        attr = '_mapArea' + attr
+
+        if hasattr(self, attr):
+            val = getattr(self, attr)
+            if type(val) in _SeqTypes:
+                val = val[row]
+                if type(val) in _SeqTypes:
+                    val = val[col]
+
+            setattr(obj, attr, val)
+
+
+from reportlab.graphics.renderbase import Renderer
+class _IMRenderer(Renderer):
+    """This draws onto a pix map image. It needs to be a class
+    rather than a function, as some image-specific state tracking is
+    needed outside of the state info in the SVG model."""
+
+    def __init__(self):
+        self._tracker = StateTracker()
+
+    def pop(self):
+        self._tracker.pop()
+        self.applyState()
+
+    def push(self,node):
+        deltas = getStateDelta(node)
+        self._tracker.push(deltas)
+        self.applyState()
+
+    def applyState(self):
+        s = self._tracker.getState()
+        self._canvas.ctm = s['ctm']
+        self._canvas.strokeWidth = s['strokeWidth']
+        self._canvas.lineCap = s['strokeLineCap']
+        self._canvas.lineJoin = s['strokeLineJoin']
+        da = s['strokeDashArray']
+        da = da and (0,da) or None
+        self._canvas.dashArray = da
+        self._canvas.setFont(s['fontName'], s['fontSize'])
+
+    def draw(self, drawing, canvas):
+        """This is the top level function, which
+        draws the drawing at the given location.
+        The recursive part is handled by drawNode."""
+        #stash references for ease of  communication
+        self._canvas = canvas
+        canvas.__dict__['_drawing'] = self._drawing = drawing
+        try:
+            self.drawNode(drawing)
+        finally:
+            #remove any circular references
+            del self._canvas, self._drawing, canvas._drawing
+
+    def drawNode(self, node):
+        """This is the recursive method called for each node
+        in the tree"""
+
+        #apply state changes
+        self.push(node)
+
+        #draw the object, or recurse
+        self.drawNodeDispatcher(node)
+
+        # restore the state
+        self.pop()
+
+    def getMapAreaAttr(self, obj):
+        _mapAreaHref = _mapAreaAlt = None
+        _mapAreaOnMouseOver = _mapAreaOnMouseOut = None
+        _mapAreaOnClick = _map = None
+
+        if hasattr(obj, '_mapAreaHref'):
+            _mapAreaHref = obj._mapAreaHref
+	    _map = _map or _mapAreaHref 
+
+        if hasattr(obj, '_mapAreaAlt'):
+            _mapAreaAlt = obj._mapAreaAlt
+            _map = _map or _mapAreaAlt
+
+        if hasattr(obj, '_mapAreaOnMouseOver'):
+            _mapAreaOnMouseOver = obj._mapAreaOnMouseOver
+            _map = _map or _mapAreaOnMouseOver
+
+        if hasattr(obj, '_mapAreaOnMouseOut'):
+            _mapAreaOnMouseOut = obj._mapAreaOnMouseOut
+            _map = _map or _mapAreaOnMouseOut
+
+        if hasattr(obj, '_mapAreaOnClick'):
+            _mapAreaOnClick = obj._mapAreaOnClick
+            _map = _map or _mapAreaOnClick
+
+        return (_map, [_mapAreaHref, _mapAreaAlt, _mapAreaOnMouseOver, _mapAreaOnMouseOut, _mapAreaOnClick])
+
+
+    def drawRect(self, rect):
+
+        c = self._canvas
+        (_map, _attr) = self.getMapAreaAttr(rect)
+
+	if _map:
+            (x1, y1, x2, y2) = (rect.x, rect.y, rect.x+rect.width, rect.y+rect.height)
+            y1 = c._drawing.height - y1
+            y2 = c._drawing.height - y2
+	    (y1, y2) = (y2, y1)
+            c.write("<area shape='rect' coords=")
+            c.write("'%s,%s,%s,%s'" % (x1,y1,x2,y2))
+            c.writeMapAreaAttr(_attr)
+            c.write(" />\n")
+
+    def drawLine(self, line): pass
+
+    def drawImage(self, image): pass
+
+    def drawCircle(self, circle):
+
+        c = self._canvas
+        (_map, _attr) = self.getMapAreaAttr(circle)
+
+	if _map:
+            (x, y, r) = (circle.cx, circle.cy, circle.r)
+            if r < 10: r = 10
+            y = c._drawing.height - y
+            c.write("<area shape='circle' coords=")
+            c.write("'%s,%s,%s'" % (x,y,r))
+            c.writeMapAreaAttr(_attr)
+            c.write(" />\n")
+
+    def drawPolyLine(self, polyline, _doClose=0): pass
+
+    def drawEllipse(self, ellipse): pass
+
+    def drawPolygon(self, polygon): pass
+
+    def drawString(self, stringObj): pass
+
+    def drawPath(self, path): pass
+
+
+class IMCanvas:
+    def __init__(self, mapname = 'imagemap'):
+        self._mapstring = ""
+        self.open(mapname)
+
+    def open(self, mapname):
+        self.write("<map name='%s'>\n" % mapname)
+        self._open = 1
+
+    def close(self):
+        if self._open:
+	    self.write("</map>\n")
+            self._open = 0
+
+    def write(self, text):
+        self._mapstring = self._mapstring + text
+
+    def writeMapAreaAttr(self, attr):
+        self.writeAttr('href', attr[0])
+        self.writeAttr('alt', attr[1])
+        self.writeAttr('OnMouseOver', attr[2])
+        self.writeAttr('OnMouseOut', attr[3])
+        self.writeAttr('OnClick', attr[4])
+
+    def writeAttr(self, attr, text):
+        if text:
+	    self.write(""" %s='%s' """ % (attr, text))
+
+    def saveToFile(self,fn): pass
+
+    def saveToString(self):
+        self.close()
+        return self._mapstring
+
+    def setFont(self,fontName,fontSize): pass
+
+    def fillstrokepath(self): pass
+
+    def saveState(self):
+        '''do nothing for compatibility'''
+        pass
+
+    restoreState = saveState
+
+def drawToIMCanvas(d, mapname = 'imagemap'):
+    mapname = getattr(d, '_mapName', mapname) 
+    c = IMCanvas(mapname=mapname)
+    draw(d, c)
+    return c
+
+def drawToFile(d,fn):
+    '''create a pixmap and draw drawing, d to it then save as a file
+    configPIL dict is passed to image save method'''
+    c = drawToIMCanvas(d)
+    c.saveToFile(fn,fmt)
+
+def drawToString(d):
+    c = drawToIMCanvas(d)
+    return c.saveToString()
+
+save = drawToString
diff -uNr reportlab/graphics/widgets/markers.py reportlab.dirk/graphics/widgets/markers.py
--- reportlab/graphics/widgets/markers.py	Tue Aug 13 14:32:51 2002
+++ reportlab.dirk/graphics/widgets/markers.py	Mon Jul 14 22:13:34 2003
@@ -181,6 +181,8 @@
                 m = m()
         else:
             m = Group()
+        from reportlab.graphics.renderIM import _setMapAreaAttr
+        _setMapAreaAttr(self, m, 0, 0)
         return m
 
 def uSymbol2Symbol(uSymbol,x,y,color):

--------------3F2437A37056D213847C1269--