def draw(self, state, plotCoordinates, plotDefinitions, performanceTable): """Draw the plot element. This stage consists of creating an SVG image of the pre-computed data. @type state: ad-hoc Python object @param state: State information that persists long enough to use quantities computed in C{prepare} in the C{draw} stage. This is a work-around of lxml's refusal to let its Python instances maintain C{self} and it is unrelated to DataTableState. @type plotCoordinates: PlotCoordinates @param plotCoordinates: The coordinate system in which this plot element will be placed. @type plotDefinitions: PlotDefinitions @type plotDefinitions: The dictionary of key-value pairs that forms the <defs> section of the SVG document. @type performanceTable: PerformanceTable @param performanceTable: Measures and records performance (time and memory consumption) of the drawing process. @rtype: SvgBinding @return: An SVG fragment representing the fully drawn plot element. """ svg = SvgBinding.elementMaker performanceTable.begin("PlotHistogram draw") cumulative = self.get("cumulative", defaultFromXsd=True, convertType=True) vertical = self.get("vertical", defaultFromXsd=True, convertType=True) visualization = self.get("visualization", defaultFromXsd=True) output = svg.g() if len(state.count) > 0: if state.fieldType is not self.fieldTypeNumeric: if vertical: strings = plotCoordinates.xstrings else: strings = plotCoordinates.ystrings newCount = [] for string in strings: try: index = state.edges.index(string) except ValueError: newCount.append(0.0) else: newCount.append(state.count[index]) state.count = newCount state.edges = [(i - 0.5, i + 0.5) for i in xrange(len(strings))] if vertical: Ax = NP("array", [ low if low is not None else float("-inf") for low, high in state.edges ], dtype=NP.dtype(float)) Bx = NP(Ax.copy()) Cx = NP("array", [ high if high is not None else float("inf") for low, high in state.edges ], dtype=NP.dtype(float)) Dx = NP(Cx.copy()) Ay = NP("zeros", len(state.count), dtype=NP.dtype(float)) if cumulative: Cy = NP("cumsum", NP("array", state.count, dtype=NP.dtype(float))) By = NP("roll", Cy, 1) By[0] = 0.0 else: By = NP("array", state.count, dtype=NP.dtype(float)) Cy = NP(By.copy()) Dy = NP(Ay.copy()) else: if cumulative: Cx = NP("cumsum", NP("array", state.count, dtype=NP.dtype(float))) Bx = NP("roll", Cx, 1) Bx[0] = 0.0 else: Bx = NP("array", state.count, dtype=NP.dtype(float)) Cx = NP(Bx.copy()) Ax = NP("zeros", len(state.count), dtype=NP.dtype(float)) Dx = NP(Ax.copy()) Ay = NP("array", [ low if low is not None else float("-inf") for low, high in state.edges ], dtype=NP.dtype(float)) By = NP(Ay.copy()) Cy = NP("array", [ high if high is not None else float("inf") for low, high in state.edges ], dtype=NP.dtype(float)) Dy = NP(Cy.copy()) AX, AY = plotCoordinates(Ax, Ay) BX, BY = plotCoordinates(Bx, By) CX, CY = plotCoordinates(Cx, Cy) DX, DY = plotCoordinates(Dx, Dy) if visualization == "skyline": gap = self.get("gap", defaultFromXsd=True, convertType=True) if vertical: if gap > 0.0 and NP( NP(DX - gap / 2.0) - NP(AX + gap / 2.0)).min() > 0.0: AX += gap / 2.0 BX += gap / 2.0 CX -= gap / 2.0 DX -= gap / 2.0 else: if gap > 0.0 and NP( NP(AY + gap / 2.0) - NP(DY - gap / 2.0)).min() > 0.0: AY -= gap / 2.0 BY -= gap / 2.0 CY += gap / 2.0 DY += gap / 2.0 pathdata = [] nextIsMoveto = True for i in xrange(len(state.count)): iprev = i - 1 inext = i + 1 if vertical and By[i] == 0.0 and Cy[i] == 0.0: if i > 0 and not nextIsMoveto: pathdata.append("L %r %r" % (DX[iprev], DY[iprev])) nextIsMoveto = True elif not vertical and Bx[i] == 0.0 and Cx[i] == 0.0: if i > 0 and not nextIsMoveto: pathdata.append("L %r %r" % (DX[iprev], DY[iprev])) nextIsMoveto = True else: if nextIsMoveto or gap > 0.0 or ( vertical and DX[iprev] != AX[i]) or ( not vertical and DY[iprev] != AY[i]): pathdata.append("M %r %r" % (AX[i], AY[i])) nextIsMoveto = False pathdata.append("L %r %r" % (BX[i], BY[i])) pathdata.append("L %r %r" % (CX[i], CY[i])) if i == len(state.count) - 1 or gap > 0.0 or ( vertical and DX[i] != AX[inext]) or ( not vertical and DY[i] != AY[inext]): pathdata.append("L %r %r" % (DX[i], DY[i])) style = self.getStyleState() del style["marker-size"] del style["marker-outline"] output.append( svg.path(d=" ".join(pathdata), style=PlotStyle.toString(style))) elif visualization == "polyline": pathdata = [] for i in xrange(len(state.count)): if i == 0: pathdata.append("M %r %r" % (AX[i], AY[i])) pathdata.append("L %r %r" % ((BX[i] + CX[i]) / 2.0, (BY[i] + CY[i]) / 2.0)) if i == len(state.count) - 1: pathdata.append("L %r %r" % (DX[i], DY[i])) style = self.getStyleState() del style["marker-size"] del style["marker-outline"] output.append( svg.path(d=" ".join(pathdata), style=PlotStyle.toString(style))) elif visualization == "smooth": smoothingSamples = math.ceil(len(state.count) / 2.0) BCX = NP(NP(BX + CX) / 2.0) BCY = NP(NP(BY + CY) / 2.0) xarray = NP("array", [AX[0]] + list(BCX) + [DX[-1]], dtype=NP.dtype(float)) yarray = NP("array", [AY[0]] + list(BCY) + [DY[-1]], dtype=NP.dtype(float)) samples = NP("linspace", AX[0], DX[-1], int(smoothingSamples), endpoint=True) smoothingScale = abs(DX[-1] - AX[0]) / smoothingSamples xlist, ylist, dxlist, dylist = PlotCurve.pointsToSmoothCurve( xarray, yarray, samples, smoothingScale, False) pathdata = PlotCurve.formatPathdata(xlist, ylist, dxlist, dylist, PlotCoordinates(), False, True) style = self.getStyleState() fillStyle = dict( (x, style[x]) for x in style if x.startswith("fill")) fillStyle["stroke"] = "none" strokeStyle = dict( (x, style[x]) for x in style if x.startswith("stroke")) if style["fill"] != "none" and len(pathdata) > 0: if vertical: firstPoint = plotCoordinates(Ax[0], 0.0) lastPoint = plotCoordinates(Dx[-1], 0.0) else: firstPoint = plotCoordinates(0.0, Ay[0]) lastPoint = plotCoordinates(0.0, Dy[-1]) pathdata2 = [ "M %r %r" % firstPoint, pathdata[0].replace("M", "L") ] pathdata2.extend(pathdata[1:]) pathdata2.append(pathdata[-1]) pathdata2.append("L %r %r" % lastPoint) output.append( svg.path(d=" ".join(pathdata2), style=PlotStyle.toString(fillStyle))) output.append( svg.path(d=" ".join(pathdata), style=PlotStyle.toString(strokeStyle))) elif visualization == "points": currentStyle = PlotStyle.toDict(self.get("style") or {}) style = self.getStyleState() if "fill" not in currentStyle: style["fill"] = "black" BCX = NP(NP(BX + CX) / 2.0) BCY = NP(NP(BY + CY) / 2.0) svgId = self.get("svgId") if svgId is None: svgIdMarker = plotDefinitions.uniqueName() else: svgIdMarker = svgId + ".marker" marker = PlotScatter.makeMarker( svgIdMarker, self.get("marker", defaultFromXsd=True), style, self.childOfTag("PlotSvgMarker")) plotDefinitions[marker.get("id")] = marker markerReference = "#" + marker.get("id") output.extend( svg.use( **{ "x": repr(x), "y": repr(y), defs.XLINK_HREF: markerReference }) for x, y in itertools.izip(BCX, BCY)) else: raise NotImplementedError("TODO: add 'errorbars'") svgId = self.get("svgId") if svgId is not None: output["id"] = svgId performanceTable.end("PlotHistogram draw") return output
def draw(self, state, plotCoordinates, plotDefinitions, performanceTable): """Draw the plot element. This stage consists of creating an SVG image of the pre-computed data. @type state: ad-hoc Python object @param state: State information that persists long enough to use quantities computed in C{prepare} in the C{draw} stage. This is a work-around of lxml's refusal to let its Python instances maintain C{self} and it is unrelated to DataTableState. @type plotCoordinates: PlotCoordinates @param plotCoordinates: The coordinate system in which this plot element will be placed. @type plotDefinitions: PlotDefinitions @type plotDefinitions: The dictionary of key-value pairs that forms the <defs> section of the SVG document. @type performanceTable: PerformanceTable @param performanceTable: Measures and records performance (time and memory consumption) of the drawing process. @rtype: SvgBinding @return: An SVG fragment representing the fully drawn plot element. """ svg = SvgBinding.elementMaker performanceTable.begin("PlotHistogram draw") cumulative = self.get("cumulative", defaultFromXsd=True, convertType=True) vertical = self.get("vertical", defaultFromXsd=True, convertType=True) visualization = self.get("visualization", defaultFromXsd=True) output = svg.g() if len(state.count) > 0: if state.fieldType is not self.fieldTypeNumeric: if vertical: strings = plotCoordinates.xstrings else: strings = plotCoordinates.ystrings newCount = [] for string in strings: try: index = state.edges.index(string) except ValueError: newCount.append(0.0) else: newCount.append(state.count[index]) state.count = newCount state.edges = [(i - 0.5, i + 0.5) for i in xrange(len(strings))] if vertical: Ax = NP("array", [low if low is not None else float("-inf") for low, high in state.edges], dtype=NP.dtype(float)) Bx = NP(Ax.copy()) Cx = NP("array", [high if high is not None else float("inf") for low, high in state.edges], dtype=NP.dtype(float)) Dx = NP(Cx.copy()) Ay = NP("zeros", len(state.count), dtype=NP.dtype(float)) if cumulative: Cy = NP("cumsum", NP("array", state.count, dtype=NP.dtype(float))) By = NP("roll", Cy, 1) By[0] = 0.0 else: By = NP("array", state.count, dtype=NP.dtype(float)) Cy = NP(By.copy()) Dy = NP(Ay.copy()) else: if cumulative: Cx = NP("cumsum", NP("array", state.count, dtype=NP.dtype(float))) Bx = NP("roll", Cx, 1) Bx[0] = 0.0 else: Bx = NP("array", state.count, dtype=NP.dtype(float)) Cx = NP(Bx.copy()) Ax = NP("zeros", len(state.count), dtype=NP.dtype(float)) Dx = NP(Ax.copy()) Ay = NP("array", [low if low is not None else float("-inf") for low, high in state.edges], dtype=NP.dtype(float)) By = NP(Ay.copy()) Cy = NP("array", [high if high is not None else float("inf") for low, high in state.edges], dtype=NP.dtype(float)) Dy = NP(Cy.copy()) AX, AY = plotCoordinates(Ax, Ay) BX, BY = plotCoordinates(Bx, By) CX, CY = plotCoordinates(Cx, Cy) DX, DY = plotCoordinates(Dx, Dy) if visualization == "skyline": gap = self.get("gap", defaultFromXsd=True, convertType=True) if vertical: if gap > 0.0 and NP(NP(DX - gap/2.0) - NP(AX + gap/2.0)).min() > 0.0: AX += gap/2.0 BX += gap/2.0 CX -= gap/2.0 DX -= gap/2.0 else: if gap > 0.0 and NP(NP(AY + gap/2.0) - NP(DY - gap/2.0)).min() > 0.0: AY -= gap/2.0 BY -= gap/2.0 CY += gap/2.0 DY += gap/2.0 pathdata = [] nextIsMoveto = True for i in xrange(len(state.count)): iprev = i - 1 inext = i + 1 if vertical and By[i] == 0.0 and Cy[i] == 0.0: if i > 0 and not nextIsMoveto: pathdata.append("L %r %r" % (DX[iprev], DY[iprev])) nextIsMoveto = True elif not vertical and Bx[i] == 0.0 and Cx[i] == 0.0: if i > 0 and not nextIsMoveto: pathdata.append("L %r %r" % (DX[iprev], DY[iprev])) nextIsMoveto = True else: if nextIsMoveto or gap > 0.0 or (vertical and DX[iprev] != AX[i]) or (not vertical and DY[iprev] != AY[i]): pathdata.append("M %r %r" % (AX[i], AY[i])) nextIsMoveto = False pathdata.append("L %r %r" % (BX[i], BY[i])) pathdata.append("L %r %r" % (CX[i], CY[i])) if i == len(state.count) - 1 or gap > 0.0 or (vertical and DX[i] != AX[inext]) or (not vertical and DY[i] != AY[inext]): pathdata.append("L %r %r" % (DX[i], DY[i])) style = self.getStyleState() del style["marker-size"] del style["marker-outline"] output.append(svg.path(d=" ".join(pathdata), style=PlotStyle.toString(style))) elif visualization == "polyline": pathdata = [] for i in xrange(len(state.count)): if i == 0: pathdata.append("M %r %r" % (AX[i], AY[i])) pathdata.append("L %r %r" % ((BX[i] + CX[i])/2.0, (BY[i] + CY[i])/2.0)) if i == len(state.count) - 1: pathdata.append("L %r %r" % (DX[i], DY[i])) style = self.getStyleState() del style["marker-size"] del style["marker-outline"] output.append(svg.path(d=" ".join(pathdata), style=PlotStyle.toString(style))) elif visualization == "smooth": smoothingSamples = math.ceil(len(state.count) / 2.0) BCX = NP(NP(BX + CX) / 2.0) BCY = NP(NP(BY + CY) / 2.0) xarray = NP("array", [AX[0]] + list(BCX) + [DX[-1]], dtype=NP.dtype(float)) yarray = NP("array", [AY[0]] + list(BCY) + [DY[-1]], dtype=NP.dtype(float)) samples = NP("linspace", AX[0], DX[-1], int(smoothingSamples), endpoint=True) smoothingScale = abs(DX[-1] - AX[0]) / smoothingSamples xlist, ylist, dxlist, dylist = PlotCurve.pointsToSmoothCurve(xarray, yarray, samples, smoothingScale, False) pathdata = PlotCurve.formatPathdata(xlist, ylist, dxlist, dylist, PlotCoordinates(), False, True) style = self.getStyleState() fillStyle = dict((x, style[x]) for x in style if x.startswith("fill")) fillStyle["stroke"] = "none" strokeStyle = dict((x, style[x]) for x in style if x.startswith("stroke")) if style["fill"] != "none" and len(pathdata) > 0: if vertical: firstPoint = plotCoordinates(Ax[0], 0.0) lastPoint = plotCoordinates(Dx[-1], 0.0) else: firstPoint = plotCoordinates(0.0, Ay[0]) lastPoint = plotCoordinates(0.0, Dy[-1]) pathdata2 = ["M %r %r" % firstPoint, pathdata[0].replace("M", "L")] pathdata2.extend(pathdata[1:]) pathdata2.append(pathdata[-1]) pathdata2.append("L %r %r" % lastPoint) output.append(svg.path(d=" ".join(pathdata2), style=PlotStyle.toString(fillStyle))) output.append(svg.path(d=" ".join(pathdata), style=PlotStyle.toString(strokeStyle))) elif visualization == "points": currentStyle = PlotStyle.toDict(self.get("style") or {}) style = self.getStyleState() if "fill" not in currentStyle: style["fill"] = "black" BCX = NP(NP(BX + CX) / 2.0) BCY = NP(NP(BY + CY) / 2.0) svgId = self.get("svgId") if svgId is None: svgIdMarker = plotDefinitions.uniqueName() else: svgIdMarker = svgId + ".marker" marker = PlotScatter.makeMarker(svgIdMarker, self.get("marker", defaultFromXsd=True), style, self.childOfTag("PlotSvgMarker")) plotDefinitions[marker.get("id")] = marker markerReference = "#" + marker.get("id") output.extend(svg.use(**{"x": repr(x), "y": repr(y), defs.XLINK_HREF: markerReference}) for x, y in itertools.izip(BCX, BCY)) else: raise NotImplementedError("TODO: add 'errorbars'") svgId = self.get("svgId") if svgId is not None: output["id"] = svgId performanceTable.end("PlotHistogram draw") return output