[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--