From 5878680b3a6dcd6b57f2a01eb69b7e7b8c096d8d Mon Sep 17 00:00:00 2001 From: =?utf8?q?Andr=C3=A9=20Wobst?= Date: Wed, 24 Aug 2005 18:26:10 +0000 Subject: [PATCH] reorganize do methods in graph; support for mutual linking of axes between graphs git-svn-id: https://pyx.svn.sourceforge.net/svnroot/pyx/trunk/pyx@2350 069f4177-920e-0410-937b-c2a4a81bcd90 --- CHANGES | 9 +- examples/bargraphs/compare.py | 4 +- examples/bargraphs/multisubaxis.py | 4 +- pyx/graph/axis/axis.py | 74 +++++--- pyx/graph/graph.py | 345 +++++++++++++++++++------------------ 5 files changed, 238 insertions(+), 198 deletions(-) diff --git a/CHANGES b/CHANGES index a9095708..2d298549 100644 --- a/CHANGES +++ b/CHANGES @@ -84,10 +84,11 @@ TODO: in expressions now; subnames argument removed since it became pointless; adujstaxis became independend from selectstyle for all styles now) - remove multiple painting of frompath in histogram and barpos styles - - style&graph module: - - TODO: separate axis range calculation from dolayout and allow for - several graphs participating on axis range calculation (support for - mutual linking of axis between graphs) + - graph, axis and style module: + - support for mutual linking of axes between graphs + (TODO: documentation, basically the setlinkedaxis method) + - new domethods dependency handling + - separate axis range calculation from dolayout - color module: - transparency support (PDF only) (TODO: documentation) - text module: diff --git a/examples/bargraphs/compare.py b/examples/bargraphs/compare.py index 95de24ad..4b5703f4 100644 --- a/examples/bargraphs/compare.py +++ b/examples/bargraphs/compare.py @@ -11,8 +11,8 @@ a = graph.axis.nestedbar(painter=bap(nameattrs=[trafo.rotate(45), innerticklength=0.2)) g = graph.graphxy(width=8, x=a) -g.plot([graph.data.file("bar.dat", xname=1, y=2), - graph.data.file("bar.dat", xname=1, y=3)], +g.plot([graph.data.file("bar.dat", xname="$1, 0", y=2), + graph.data.file("bar.dat", xname="$1, 1", y=3)], [graph.style.bar()]) g.writeEPSfile("compare") g.writePDFfile("compare") diff --git a/examples/bargraphs/multisubaxis.py b/examples/bargraphs/multisubaxis.py index cf342d18..c3020fcf 100644 --- a/examples/bargraphs/multisubaxis.py +++ b/examples/bargraphs/multisubaxis.py @@ -8,8 +8,8 @@ from pyx import * mynestedbaraxis = graph.axis.bar(defaultsubaxis=graph.axis.bar(dist=0)) g = graph.graphxy(width=8, x=mynestedbaraxis) -g.plot([graph.data.list([["A", 5], ["B", 6]], xname=1, y=2), - graph.data.list([["A", 7], ["B", 8], ["C", 9]], xname=1, y=2)], +g.plot([graph.data.list([[("A", "x"), 5], [("B", "x"), 6]], xname=1, y=2), + graph.data.list([[("A", "y"), 7], [("B", "y"), 8], [("C", "y"), 9]], xname=1, y=2)], [graph.style.barpos(fromvalue=0), graph.style.bar()]) g.writeEPSfile("multisubaxis") g.writePDFfile("multisubaxis") diff --git a/pyx/graph/axis/axis.py b/pyx/graph/axis/axis.py index 434ec196..112b6df1 100644 --- a/pyx/graph/axis/axis.py +++ b/pyx/graph/axis/axis.py @@ -319,6 +319,20 @@ class bar(_axis): def adjustaxis(self, data, columndata, errorname): for value in columndata: + + # some checks and error messages + try: + len(value) + except: + raise ValueError("tuple expected by bar axis '%s'" % errorname) + try: + value + "" + except: + pass + else: + raise ValueError("tuple expected by bar axis '%s'" % errorname) + assert len(value) == 2, "tuple of size two expected by bar axis '%s'" % errorname + name = value[0] if name is not None and name not in data.names: if self.subaxes: @@ -338,10 +352,9 @@ class bar(_axis): data.size += subaxis.data.size def convert(self, data, value): - try: - axis = data.subaxes[value[0]] - except KeyError: + if value[0] is None: return None + axis = data.subaxes[value[0]] vmin = axis.vmin vmax = axis.vmax return axis.vmin + axis.convert(value[1]) * (axis.vmax - axis.vmin) @@ -453,7 +466,7 @@ autosizedlin = autosizedlinear class anchoredaxis: - def __init__(self, axis, errorname): + def __init__(self, axis=None, errorname="unknown"): assert not isinstance(axis, anchoredaxis), errorname self.axis = axis self.errorname = errorname @@ -461,18 +474,30 @@ class anchoredaxis: self.canvas = None self.positioner = None - def convert(self, x): - assert self.canvas is not None, self.errorname - return self.axis.convert(self.data, x) + def setcreatecall(self, function, *args, **kwargs): + self._createfunction = function + self._createargs = args + self._createkwargs = kwargs - def adjustaxis(self, columndata): - self.axis.adjustaxis(self.data, columndata, self.errorname) + def createforlinked(self): + if not self.canvas: + self._createfunction(*self._createargs, **self._createkwargs) def setpositioner(self, positioner): assert positioner is not None, self.errorname assert self.positioner is None, self.errorname self.positioner = positioner + def convert(self, x): + assert self.canvas is not None, self.errorname + return self.axis.convert(self.data, x) + + def adjustaxis(self, columndata): + if self.canvas is None: + self.axis.adjustaxis(self.data, columndata, self.errorname) + else: + warnings.warn("ignore axis range adjustment of already finished axis '%s'" % self.errorname) + def vbasepath(self, v1=None, v2=None): return self.positioner.vbasepath(v1=v1, v2=v2) @@ -525,26 +550,29 @@ class _nopainter: pass class linkedaxis(anchoredaxis): - def __init__(self, axis, errorname="manual-linked", painter=_nopainter): - assert isinstance(axis, anchoredaxis), errorname - if painter is _nopainter: - self.painter = axis.axis.linkpainter - else: - self.painter = painter - self.linkedto = axis - self.axis = axis.axis - self.errorname = "%s (linked to %s)" % (errorname, axis.errorname) - self.data = axis.data + def __init__(self, linkedaxis=None, errorname="manual-linked", painter=_nopainter): + self.painter = painter + self.linkedto = None + self.errorname = errorname self.canvas = None self.positioner = None - - def adjustaxis(self, columndata): - print "not adjusting" + if linkedaxis: + self.setlinkedaxis(linkedaxis) + + def setlinkedaxis(self, linkedaxis): + assert isinstance(linkedaxis, anchoredaxis), errorname + self.linkedto = linkedaxis + self.axis = linkedaxis.axis + self.errorname = "%s (linked to %s)" % (self.errorname, linkedaxis.errorname) + self.data = linkedaxis.data + if self.painter is _nopainter: + self.painter = linkedaxis.axis.linkpainter def create(self, graphtexrunner): + assert self.linkedto is not None, self.errorname assert self.positioner is not None, self.errorname if self.canvas is None: - self.linkedto.create(graphtexrunner) + self.linkedto.createforlinked() self.canvas = self.axis.createlinked(self.data, self.positioner, graphtexrunner, self.errorname, self.painter) return self.canvas diff --git a/pyx/graph/graph.py b/pyx/graph/graph.py index 04cdcd9d..ba461da4 100644 --- a/pyx/graph/graph.py +++ b/pyx/graph/graph.py @@ -96,7 +96,7 @@ class plotitem: for columnname in self.usedcolumnnames: try: useitems.append((columnname, self.data.columns[columnname])) - except: + except KeyError: useitems.append((columnname, self.dynamiccolumns[columnname])) if not useitems: raise ValueError("cannot draw empty data") @@ -124,12 +124,50 @@ class plotitem: raise AttributeError("access to styledata attribute '%s' failed" % attr) +class graph(canvas.canvas): + + def __init__(self): + canvas.canvas.__init__(self) + self.axes = {} + self.plotitems = [] + self._calls = {} + self.didranges = 0 + self.diddata = 0 + + def did(self, method, *args, **kwargs): + if not self._calls.has_key(method): + self._calls[method] = [] + for callargs in self._calls[method]: + if callargs == (args, kwargs): + return 1 + self._calls[method].append((args, kwargs)) + return 0 + + def bbox(self): + self.finish() + return canvas.canvas.bbox(self) -class graphxy(canvas.canvas): + def registerPS(self, registry): + self.finish() + canvas.canvas.registerPS(self, registry) - def plot(self, data, styles=None): - if self.haslayout: - raise RuntimeError("layout setup was already performed") + def registerPDF(self, registry): + self.finish() + canvas.canvas.registerPDF(self, registry) + + def outputPS(self, file, writer, context): + self.finish() + canvas.canvas.outputPS(self, file, writer, context) + + def outputPDF(self, file, writer, context): + self.finish() + canvas.canvas.outputPDF(self, file, writer, context) + + def plot(self, data, styles=None, rangewarning=1): + if self.didranges and rangewarnings: + raise warnings.warn("axes ranges have already been analysed; no further adjustments will be performed") + if self.diddata: + raise RuntimeError("can't add further data while data has already been processed") singledata = 0 try: for d in data: @@ -154,6 +192,49 @@ class graphxy(canvas.canvas): else: return plotitems + def doranges(self): + if self.did(self.doranges): + return + for plotitem in self.plotitems: + plotitem.adjustaxesstatic(self) + for plotitem in self.plotitems: + plotitem.makedynamicdata(self) + for plotitem in self.plotitems: + plotitem.adjustaxesdynamic(self) + self.didranges = 1 + + def dodata(self): + if self.did(self.dodata): + return + self.dolayout() + self.dobackground() + + # count the usage of styles and perform selects + styletotal = {} + def stylesid(styles): + return ":".join([str(id(style)) for style in styles]) + for plotitem in self.plotitems: + try: + styletotal[stylesid(plotitem.styles)] += 1 + except: + styletotal[stylesid(plotitem.styles)] = 1 + styleindex = {} + for plotitem in self.plotitems: + try: + styleindex[stylesid(plotitem.styles)] += 1 + except: + styleindex[stylesid(plotitem.styles)] = 0 + plotitem.selectstyles(self, styleindex[stylesid(plotitem.styles)], + styletotal[stylesid(plotitem.styles)]) + + for plotitem in self.plotitems: + plotitem.draw(self) + + self.diddata = 1 + + +class graphxy(graph): + def pos_pt(self, x, y, xaxis=None, yaxis=None): if xaxis is None: xaxis = self.axes["x"] @@ -210,23 +291,6 @@ class graphxy(canvas.canvas): return path.line_pt(self.xpos_pt, self.ypos_pt + vy*self.height_pt, self.xpos_pt + self.width_pt, self.ypos_pt + vy*self.height_pt) - def keynum(self, key): - try: - while key[0] in string.letters: - key = key[1:] - return int(key) - except IndexError: - return 1 - - def removedomethod(self, method): - hadmethod = 0 - while 1: - try: - self.domethods.remove(method) - hadmethod = 1 - except ValueError: - return hadmethod - def axistrafo(self, axis, t): c = canvas.canvas([t]) c.insert(axis.canvas) @@ -240,90 +304,80 @@ class graphxy(canvas.canvas): # it is an x-axis self.axistrafo(axis, trafo.translate_pt(0, self.ypos_pt + v*self.height_pt - axis.positioner.y1_pt)) - def dolayout(self): - if not self.removedomethod(self.dolayout): return - - # adjust the axes ranges - for plotitem in self.plotitems: - plotitem.adjustaxesstatic(self) - for plotitem in self.plotitems: - plotitem.makedynamicdata(self) - for plotitem in self.plotitems: - plotitem.adjustaxesdynamic(self) - - # finish all axes - keys = list(self.axes.keys()) - keys.sort() #TODO: alphabetical sorting breaks for axis numbers bigger than 9 - for key in keys: - self.axes[key].create(self.texrunner) - if key[1:]: - num = int(key[1:]) + def doaxispositioner(self, axisname): + if self.did(self.doaxispositioner, axisname): + return + self.doranges() + if axisname == "x": + self.axes["x"].setpositioner(positioner.lineaxispos_pt(self.xpos_pt, self.ypos_pt, + self.xpos_pt + self.width_pt, self.ypos_pt, + (0, 1), self.xvgridpath)) + elif axisname == "x2": + self.axes["x2"].setpositioner(positioner.lineaxispos_pt(self.xpos_pt, self.ypos_pt + self.height_pt, + self.xpos_pt + self.width_pt, self.ypos_pt + self.height_pt, + (0, -1), self.xvgridpath)) + elif axisname == "y": + self.axes["y"].setpositioner(positioner.lineaxispos_pt(self.xpos_pt, self.ypos_pt, + self.xpos_pt, self.ypos_pt + self.height_pt, + (1, 0), self.yvgridpath)) + elif axisname == "y2": + self.axes["y2"].setpositioner(positioner.lineaxispos_pt(self.xpos_pt + self.width_pt, self.ypos_pt, + self.xpos_pt + self.width_pt, self.ypos_pt + self.height_pt, + (-1, 0), self.yvgridpath)) + else: + if axisname[1:] == "3": + dependsonaxisname = axisname[0] + else: + dependsonaxisname = "%s%d" % (axisname[0], int(axisname[1:]) - 2) + self.doaxiscreate(dependsonaxisname) + sign = 2*(int(axisname[1:]) % 2) - 1 + if axisname[0] == "x": + y_pt = self.axes[dependsonaxisname].positioner.y1_pt - sign * (self.axes[dependsonaxisname].canvas.extent_pt + self.axesdist_pt) + self.axes[axisname].setpositioner(positioner.lineaxispos_pt(self.xpos_pt, y_pt, + self.xpos_pt + self.width_pt, y_pt, + (0, sign), self.xvgridpath)) else: - num = 1 - if num: - nextkey = "%s%i" % (key[0], (num+2)) - if self.axes.has_key(nextkey): - sign = 2*(num % 2) - 1 - if key[0] == "x": - y_pt = self.axes[key].positioner.y1_pt - sign * (self.axes[key].canvas.extent_pt + self.axesdist_pt) - apositioner = positioner.lineaxispos_pt(self.xpos_pt, y_pt, - self.xpos_pt + self.width_pt, y_pt, - (0, sign), self.xvgridpath) - else: - x_pt = self.axes[key].positioner.x1_pt - sign * (self.axes[key].canvas.extent_pt + self.axesdist_pt) - apositioner = positioner.lineaxispos_pt(x_pt, self.ypos_pt, - x_pt, self.ypos_pt + self.height_pt, - (sign, 0), self.yvgridpath) - self.axes[nextkey].setpositioner(apositioner) + x_pt = self.axes[dependsonaxisname].positioner.x1_pt - sign * (self.axes[dependsonaxisname].canvas.extent_pt + self.axesdist_pt) + self.axes[axisname].setpositioner(positioner.lineaxispos_pt(x_pt, self.ypos_pt, + x_pt, self.ypos_pt + self.height_pt, + (sign, 0), self.yvgridpath)) + + def doaxiscreate(self, axisname): + if self.did(self.doaxiscreate, axisname): + return + self.doaxispositioner(axisname) + self.axes[axisname].create(self.texrunner) + def dolayout(self): + if self.did(self.dolayout): + return + for axisname in self.axes.keys(): + self.doaxiscreate(axisname) if self.xaxisat is not None: self.axisatv(self.axes["x"], self.axes["y"].convert(self.xaxisat)) if self.yaxisat is not None: self.axisatv(self.axes["y"], self.axes["x"].convert(self.yaxisat)) - self.haslayout = 1 - def dobackground(self): - self.dolayout() - if not self.removedomethod(self.dobackground): return + if self.did(self.dobackground): + return if self.backgroundattrs is not None: self.draw(path.rect_pt(self.xpos_pt, self.ypos_pt, self.width_pt, self.height_pt), self.backgroundattrs) def doaxes(self): + if self.did(self.doaxes): + return self.dolayout() - if not self.removedomethod(self.doaxes): return + self.dobackground() for axis in self.axes.values(): self.insert(axis.canvas) - def dodata(self): - self.dolayout() - - # count the usage of styles and perform selects - styletotal = {} - def stylesid(styles): - return ":".join([str(id(style)) for style in styles]) - for plotitem in self.plotitems: - try: - styletotal[stylesid(plotitem.styles)] += 1 - except: - styletotal[stylesid(plotitem.styles)] = 1 - styleindex = {} - for plotitem in self.plotitems: - try: - styleindex[stylesid(plotitem.styles)] += 1 - except: - styleindex[stylesid(plotitem.styles)] = 0 - plotitem.selectstyles(self, styleindex[stylesid(plotitem.styles)], - styletotal[stylesid(plotitem.styles)]) - - if not self.removedomethod(self.dodata): return - for plotitem in self.plotitems: - plotitem.draw(self) - def dokey(self): - self.dolayout() - if not self.removedomethod(self.dokey): return + if self.did(self.dokey): + return + self.dodata() + self.dobackground() if self.key is not None: c = self.key.paint(self.plotitems) bbox = c.bbox() @@ -340,10 +394,25 @@ class graphxy(canvas.canvas): self.insert(c, [trafo.translate_pt(x, y)]) def finish(self): - while len(self.domethods): - self.domethods[0]() + self.doaxes() + self.dodata() + self.dokey() + + def __init__(self, xpos=0, ypos=0, width=None, height=None, ratio=goldenmean, + key=None, backgroundattrs=None, axesdist=0.8*unit.v_cm, + xaxisat=None, yaxisat=None, **axes): + graph.__init__(self) + + self.xpos = xpos + self.ypos = ypos + self.xpos_pt = unit.topt(self.xpos) + self.ypos_pt = unit.topt(self.ypos) + self.xaxisat = xaxisat + self.yaxisat = yaxisat + self.key = key + self.backgroundattrs = backgroundattrs + self.axesdist_pt = unit.topt(axesdist) - def initwidthheight(self, width, height, ratio): self.width = width self.height = height if width is None: @@ -356,29 +425,24 @@ class graphxy(canvas.canvas): self.width_pt = unit.topt(self.width) self.height_pt = unit.topt(self.height) - def initaxes(self, axes): - self.axes = {} - for key, aaxis in axes.items(): + for axisname, aaxis in axes.items(): if aaxis is not None: if not isinstance(aaxis, axis.linkedaxis): - self.axes[key] = axis.anchoredaxis(aaxis, key) + self.axes[axisname] = axis.anchoredaxis(aaxis, axisname) else: - self.axes[key] = aaxis - for key, axisat in [("x", self.xaxisat), ("y", self.yaxisat)]: - okey = key + "2" - if not axes.has_key(key): + self.axes[axisname] = aaxis + for axisname, axisat in [("x", xaxisat), ("y", yaxisat)]: + okey = axisname + "2" + if not axes.has_key(axisname): if not axes.has_key(okey): - self.axes[key] = axis.anchoredaxis(axis.linear(), key) - self.axes[okey] = axis.linkedaxis(self.axes[key], okey) + self.axes[axisname] = axis.anchoredaxis(axis.linear(), axisname) + self.axes[okey] = axis.linkedaxis(self.axes[axisname], okey) else: - self.axes[key] = axis.linkedaxis(self.axes[okey], key) + self.axes[axisname] = axis.linkedaxis(self.axes[okey], axisname) elif not axes.has_key(okey) and axisat is None: - self.axes[okey] = axis.linkedaxis(self.axes[key], okey) + self.axes[okey] = axis.linkedaxis(self.axes[axisname], okey) if self.axes.has_key("x"): - self.axes["x"].setpositioner(positioner.lineaxispos_pt(self.xpos_pt, self.ypos_pt, - self.xpos_pt + self.width_pt, self.ypos_pt, - (0, 1), self.xvgridpath)) self.xbasepath = self.axes["x"].basepath self.xvbasepath = self.axes["x"].vbasepath self.xgridpath = self.axes["x"].gridpath @@ -389,14 +453,7 @@ class graphxy(canvas.canvas): self.xtickdirection = self.axes["x"].tickdirection self.xvtickdirection = self.axes["x"].vtickdirection - if self.axes.has_key("x2"): - self.axes["x2"].setpositioner(positioner.lineaxispos_pt(self.xpos_pt, self.ypos_pt + self.height_pt, - self.xpos_pt + self.width_pt, self.ypos_pt + self.height_pt, - (0, -1), self.xvgridpath)) if self.axes.has_key("y"): - self.axes["y"].setpositioner(positioner.lineaxispos_pt(self.xpos_pt, self.ypos_pt, - self.xpos_pt, self.ypos_pt + self.height_pt, - (1, 0), self.yvgridpath)) self.ybasepath = self.axes["y"].basepath self.yvbasepath = self.axes["y"].vbasepath self.ygridpath = self.axes["y"].gridpath @@ -407,62 +464,16 @@ class graphxy(canvas.canvas): self.ytickdirection = self.axes["y"].tickdirection self.yvtickdirection = self.axes["y"].vtickdirection - if self.axes.has_key("y2"): - self.axes["y2"].setpositioner(positioner.lineaxispos_pt(self.xpos_pt + self.width_pt, self.ypos_pt, - self.xpos_pt + self.width_pt, self.ypos_pt + self.height_pt, - (-1, 0), self.yvgridpath)) - self.axesnames = ([], []) - for key in self.axes.keys(): - if len(key) != 1 and (not key[1:].isdigit() or key[1:] == "1"): - raise ValueError("invalid axis count") - if key[0] == "x": - self.axesnames[0].append(key) - elif key[0] == "y": - self.axesnames[1].append(key) - else: + for axisname, aaxis in self.axes.items(): + if axisname[0] not in "xy" or (len(axisname) != 1 and (not axisname[1:].isdigit() or + axisname[1:] == "1")): raise ValueError("invalid axis name") - - def __init__(self, xpos=0, ypos=0, width=None, height=None, ratio=goldenmean, - key=None, backgroundattrs=None, axesdist=0.8*unit.v_cm, - xaxisat=None, yaxisat=None, **axes): - canvas.canvas.__init__(self) - self.xpos = xpos - self.ypos = ypos - self.xpos_pt = unit.topt(self.xpos) - self.ypos_pt = unit.topt(self.ypos) - self.xaxisat = xaxisat - self.yaxisat = yaxisat - self.initwidthheight(width, height, ratio) - self.initaxes(axes) - self.key = key - self.backgroundattrs = backgroundattrs - self.axesdist = axesdist - self.axesdist_pt = unit.topt(axesdist) - self.plotitems = [] - self.domethods = [self.dolayout, self.dobackground, self.doaxes, self.dodata, self.dokey] - self.haslayout = 0 - - def bbox(self): - self.finish() - return canvas.canvas.bbox(self) - - def registerPS(self, registry): - self.finish() - canvas.canvas.registerPS(self, registry) - - def registerPDF(self, registry): - self.finish() - canvas.canvas.registerPDF(self, registry) - - def outputPS(self, file, writer, context): - self.finish() - canvas.canvas.outputPS(self, file, writer, context) - - def outputPDF(self, file, writer, context): - self.finish() - canvas.canvas.outputPDF(self, file, writer, context) - + if axisname[0] == "x": + self.axesnames[0].append(axisname) + else: + self.axesnames[1].append(axisname) + aaxis.setcreatecall(self.doaxiscreate, axisname) # some thoughts, but deferred right now -- 2.11.4.GIT