""" ChartPane.py Scaling routines to facillitate drawing graphs and charts in *.pdf format using ReportLab library """ #****************************************************************************** # globals #****************************************************************************** # Imports import types from reportlab.lib import colors from reportlab.graphics.shapes import * from reportlab.graphics.charts.textlabels import Label from reportlab.graphics import renderPDF from math import * #****************************************************************************** # Global variables and initialization code #****************************************************************************** # Class definitions class Dim: WIDTH,\ HEIGHT\ = range(2) class Coord: X,\ Y\ = range(2) class Bound: MIN,\ MAX\ = range(2) class TickLen: LONG,\ MED,\ SHORT\ = range(3) #-------------------------------------- class ChartPane: def __init__(self, theOffset, thePrintable, xAxisCoordList, xAxisFunction, yAxisCoordList, yAxisFunction, xMirror = False, yMirror = False): """ input: theOffset[originX, originY]: location of the pane's origin (x,y zero point) on the page, in points thePrintable[width, height]: panes width and height in points xAxisCoordList: list of numbers for the x axis tick marks of the graph that will fit inside this pane xAxisFunction: scaling function for the x axis tick marks, e.g., to make the axis into a logarithmic scale or something like that yAxisCoordList yAxisFunction functions: ChartPane.X(xCoord): returns xCoord offset and scaled to the appropriate location inside the pane ChartPane.Y(yCoord) """ self.origin = [0.0, 0.0] self.scaleFactor = [0.0, 0.0] self.zero = [0.0, 0.0] self.min = [0.0, 0.0] self.max = [0.0, 0.0] self.xLimit= [0.0, 0.0] self.yLimit= [0.0, 0.0] self.tick= [0.0, 0.0, 0.0] self.origin[Coord.X] = theOffset[Coord.X] xAxisCoordList.sort() xDelta = xAxisFunction(xAxisCoordList[-1])-xAxisFunction(xAxisCoordList[0]) if xDelta == 0.0: xDelta = 0.0001 self.scaleFactor[Coord.X] = thePrintable[Dim.WIDTH] / xDelta self.zero[Coord.X] = xAxisFunction(xAxisCoordList[0]) if xMirror: self.xMirror = xAxisFunction(xAxisCoordList[-1]) - self.zero[Coord.X] else: self.xMirror = 0 self.min[Coord.X] = xAxisCoordList[0] self.max[Coord.X] = xAxisCoordList[-1] if xMirror: self.min[Coord.X] = xAxisCoordList[-1] - self.min[Coord.X] if self.min[Coord.X] == 0: self.min[Coord.X] = 0.001 self.max[Coord.X] = xAxisCoordList[-1] - self.max[Coord.X] if self.max[Coord.X] == 0: self.max[Coord.X] = 0.001 self.xLimit[Bound.MIN] = xAxisFunction(self.min[Coord.X])-self.zero[Coord.X] self.xLimit[Bound.MIN] = self.xLimit[Bound.MIN] * self.scaleFactor[Coord.X] + self.origin[Coord.X] self.xLimit[Bound.MAX] = xAxisFunction(self.max[Coord.X])-self.zero[Coord.X] self.xLimit[Bound.MAX] = self.xLimit[Bound.MAX] * self.scaleFactor[Coord.X] + self.origin[Coord.X] self.origin[Coord.Y] = theOffset[Coord.Y] yAxisCoordList.sort() self.scaleFactor[Coord.Y] = \ thePrintable[Dim.HEIGHT] / (yAxisFunction(yAxisCoordList[-1])-yAxisFunction(yAxisCoordList[0])) self.zero[Coord.Y] = yAxisFunction(yAxisCoordList[0]) if yMirror: self.yMirror = yAxisFunction(yAxisCoordList[-1]) - self.zero[Coord.Y] else: self.yMirror = 0 self.min[Coord.Y] = yAxisCoordList[0] self.max[Coord.Y] = yAxisCoordList[-1] if yMirror: self.min[Coord.Y] = yAxisCoordList[-1] - self.min[Coord.Y] if self.min[Coord.Y] == 0: self.min[Coord.Y] = 0.001 self.max[Coord.Y] = yAxisCoordList[-1] - self.max[Coord.Y] if self.max[Coord.Y] == 0: self.max[Coord.Y] = 0.001 self.xLimit[Bound.MIN] = xAxisFunction(self.min[Coord.X])-self.zero[Coord.X] self.yLimit[Bound.MIN] = yAxisFunction(self.min[Coord.Y])-self.zero[Coord.Y] self.yLimit[Bound.MIN] = self.yLimit[Bound.MIN] * self.scaleFactor[Coord.Y] + self.origin[Coord.Y] self.yLimit[Bound.MAX] = yAxisFunction(self.max[Coord.Y])-self.zero[Coord.Y] self.yLimit[Bound.MAX] = self.yLimit[Bound.MAX] * self.scaleFactor[Coord.Y] + self.origin[Coord.Y] def X(self, xCoord): if self.xMirror != 0: xCoord = self.xMirror - xCoord return xCoord * self.scaleFactor[Coord.X] + self.origin[Coord.X] else: return xCoord * self.scaleFactor[Coord.X] + self.origin[Coord.X] def Y(self, yCoord): if self.yMirror != 0: yCoord = self.yMirror - yCoord return yCoord * self.scaleFactor[Coord.Y] + self.origin[Coord.Y] else: return yCoord * self.scaleFactor[Coord.Y] + self.origin[Coord.Y] #-------------------------------------- class Page: """Converts page dimensions from inches to points input: theDimension[widthInches, heightInches] theMargin[leftMarginWidthInches, topMarginWidthInches] available: Page.ptsPerInche = 72 Page.sheet[totalWidthPoints, totalHeightPoints] Page.margin[leftMarginWidthPoints, topMarginWidthPoints] Page.printable[printableWidthPoints, printableHeightPoints]""" def __init__(self, theDimension, theMargin): self.ptsPerInch = 72.0 self.sheet = [0.0, 0.0] self.sheet[Dim.WIDTH] = theDimension[Dim.WIDTH] * self.ptsPerInch self.sheet[Dim.HEIGHT] = theDimension[Dim.HEIGHT] * self.ptsPerInch self.margin = [0.0, 0.0] self.margin[Dim.WIDTH] = theMargin[Dim.WIDTH] * self.ptsPerInch self.margin[Dim.HEIGHT] = theMargin[Dim.HEIGHT] * self.ptsPerInch self.printable = [0.0, 0.0] self.printable[Dim.WIDTH] = self.sheet[Dim.WIDTH] - (self.margin[Dim.WIDTH]*2.0) self.printable[Dim.HEIGHT] = self.sheet[Dim.HEIGHT] - (self.margin[Dim.HEIGHT]*2.0) self.tick= [0.0, 0.0, 0.0] self.tick[TickLen.LONG] = 0.2 * self.ptsPerInch self.tick[TickLen.MED] = self.tick[TickLen.LONG] * 0.75 self.tick[TickLen.SHORT] = self.tick[TickLen.LONG] * 0.5 #****************************************************************************** # Function definitions #-------------------------------------- def AddCurvedLines(drawing, chartPane, xCoordList, yCoordList, xyFunction, theStrokeWidth=0.5, theStrokeColor=colors.black, theStrokeDashArray=None, theLabelAnchor='w', theLabelAngle=0, theLabelFormat="%d", theLabelColor=colors.black, theLabelSize=7, theLabelLocationPercent=0.5): xCoordList.sort() yCoordList.sort() printIndexTrigger = int(len(xCoordList)*theLabelLocationPercent) for yCoord in yCoordList: xyCurve = [] printIndex = -1 for xCoord in xCoordList: curveCoord = [0.0, 0.0] curveCoord[Coord.X], curveCoord[Coord.Y] = xyFunction(xCoord, yCoord) curveCoord[Coord.X] = curveCoord[Coord.X] - chartPane.zero[Coord.X] curveCoord[Coord.Y] = curveCoord[Coord.Y] - chartPane.zero[Coord.Y] xyCurve.append((chartPane.X(curveCoord[Coord.X]), chartPane.Y(curveCoord[Coord.Y]))) printIndex = printIndex + 1 if printIndex == printIndexTrigger: cLabel = Label() cLabel.setOrigin(chartPane.X(curveCoord[Coord.X]), chartPane.Y(curveCoord[Coord.Y])) cLabel.boxAnchor = theLabelAnchor cLabel.angle = theLabelAngle cLabel.setText(theLabelFormat % yCoord) cLabel.fillColor = theLabelColor cLabel.fontSize = theLabelSize drawing.add(cLabel) if len(xyCurve) > 1: drawing.add(PolyLine(xyCurve, strokeColor=theStrokeColor, strokeWidth=theStrokeWidth, strokeDashArray=theStrokeDashArray)) #-------------------------------------- def AddCurvedTickLines(drawing, chartPane, page, xCoordList, yCoordList, xyFunction, tickEnds='', tickMetric = False, tickLen = None, labelTickEnds='b', theStrokeWidth=0.5, theStrokeColor=colors.black, theStrokeDashArray=None, theLabelFormat="%d", theLabelColor=colors.black, theLabelSize=7, labelEnds=''): xCoordList.sort() yCoordList.sort() tickCoordStart = [0.0, 0.0] tickCoordEnd= [0.0, 0.0] curveCoord = [0.0, 0.0] startCoord = [0.0, 0.0] endCoord = [0.0, 0.0] if tickLen == None: tickLen = page.tick[TickLen.SHORT] for yCoord in yCoordList: xyCurve = [] lastCoord = [0.0, 0.0] lastCoord[Coord.X] = xCoordList[0] - (xCoordList[1]-xCoordList[0]) lastCoord[Coord.Y] = yCoord lastCoord[Coord.X], lastCoord[Coord.Y] = xyFunction(lastCoord[Coord.X], lastCoord[Coord.Y]) lastCoord[Coord.X] = lastCoord[Coord.X] - chartPane.zero[Coord.X] lastCoord[Coord.Y] = lastCoord[Coord.Y] - chartPane.zero[Coord.Y] for xCoord in xCoordList: # append to curve curveCoord[Coord.X], curveCoord[Coord.Y] = xyFunction(xCoord, yCoord) curveCoord[Coord.X] = curveCoord[Coord.X] - chartPane.zero[Coord.X] curveCoord[Coord.Y] = curveCoord[Coord.Y] - chartPane.zero[Coord.Y] xyCurve.append((chartPane.X(curveCoord[Coord.X]), chartPane.Y(curveCoord[Coord.Y]))) # draw tick mark if len(tickEnds) > 0: xDelta = chartPane.X(curveCoord[Coord.X])-chartPane.X(lastCoord[Coord.X]) if xDelta == 0.0: xDelta = 0.0001 if tickMetric: tickLen = page.tick[TickLen.SHORT] if xCoord % 10.0 == 0: tickLen = page.tick[TickLen.LONG] elif xCoord % 5.0 == 0: tickLen = page.tick[TickLen.MED] lineAngle = atan2(chartPane.Y(curveCoord[Coord.Y])-chartPane.Y(lastCoord[Coord.Y]), xDelta) cclockLineAngle = lineAngle - radians(90) cosCClockLineAngle = cos(cclockLineAngle) sinCClockLineAngle = sin(cclockLineAngle) clockLineAngle = lineAngle + radians(90) cosClockLineAngle = cos(clockLineAngle) sinClockLineAngle = sin(clockLineAngle) tickCoordEnd[Coord.X] = tickCoordStart[Coord.X] = chartPane.X(curveCoord[Coord.X]) tickCoordEnd[Coord.Y] = tickCoordStart[Coord.Y] = chartPane.Y(curveCoord[Coord.Y]) if tickEnds.count('a') > 0: tickCoordStart[Coord.X] = tickCoordStart[Coord.X] + cosCClockLineAngle * tickLen tickCoordStart[Coord.Y] = tickCoordStart[Coord.Y] + sinCClockLineAngle * tickLen if tickEnds.count('b') > 0: tickCoordEnd[Coord.X] = tickCoordEnd[Coord.X] + cosClockLineAngle * tickLen tickCoordEnd[Coord.Y] = tickCoordEnd[Coord.Y] + sinClockLineAngle * tickLen drawing.add(Line(tickCoordStart[Coord.X], tickCoordStart[Coord.Y], tickCoordEnd[Coord.X], tickCoordEnd[Coord.Y], strokeColor=theStrokeColor, strokeWidth=theStrokeWidth, strokeDashArray=theStrokeDashArray)) if not tickMetric or xCoord % 10.0 == 0: if labelTickEnds.count('a') > 0: hLabel = Label() hLabel.setOrigin(tickCoordStart[Coord.X], tickCoordStart[Coord.Y]) hLabel.boxAnchor = 'w' hLabel.angle = degrees(cclockLineAngle) hLabel.setText(theLabelFormat % (xCoord)) hLabel.fillColor = theLabelColor hLabel.fontSize = theLabelSize drawing.add(hLabel) if labelTickEnds.count('b') > 0: hLabel = Label() hLabel.setOrigin(tickCoordEnd[Coord.X], tickCoordEnd[Coord.Y]) hLabel.boxAnchor = 'e' hLabel.angle = degrees(cclockLineAngle) hLabel.setText(theLabelFormat % (xCoord)) hLabel.fillColor = theLabelColor hLabel.fontSize = theLabelSize drawing.add(hLabel) lastCoord[Coord.X] = curveCoord[Coord.X] lastCoord[Coord.Y] = curveCoord[Coord.Y] if len(xyCurve) > 1: drawing.add(PolyLine(xyCurve, strokeColor=theStrokeColor, strokeWidth=theStrokeWidth, strokeDashArray=theStrokeDashArray)) # draw line labels startCoord = xyCurve[0] endCoord =xyCurve[-1] xDelta = endCoord[Coord.X]-startCoord[Coord.X] if xDelta == 0.0: xDelta = 0.0001 theLabelAngle = atan2((endCoord[Coord.Y]-startCoord[Coord.Y]), xDelta) theLabelAngle = degrees(theLabelAngle) if labelEnds.count('a') > 0: curveCoord[Coord.X], curveCoord[Coord.Y] = xyFunction(xCoordList[0], yCoord) curveCoord[Coord.X] = curveCoord[Coord.X] - chartPane.zero[Coord.X] curveCoord[Coord.Y] = curveCoord[Coord.Y] - chartPane.zero[Coord.Y] hLabel = Label() hLabel.setOrigin(chartPane.X(curveCoord[Coord.X]) + (theStrokeWidth*2), chartPane.Y(curveCoord[Coord.Y])) hLabel.boxAnchor= 'e' hLabel.angle = theLabelAngle hLabel.setText(theLabelFormat % (yCoord)) hLabel.fillColor = theLabelColor hLabel.fontSize = theLabelSize drawing.add(hLabel) if labelEnds.count('b') > 0: curveCoord[Coord.X], curveCoord[Coord.Y] = xyFunction(xCoordList[-1], yCoord) curveCoord[Coord.X] = curveCoord[Coord.X] - chartPane.zero[Coord.X] curveCoord[Coord.Y] = curveCoord[Coord.Y] - chartPane.zero[Coord.Y] hLabel = Label() hLabel.setOrigin(chartPane.X(curveCoord[Coord.X]) - (theStrokeWidth*2), chartPane.Y(curveCoord[Coord.Y])) hLabel.boxAnchor = 'w' hLabel.angle = theLabelAngle hLabel.setText(theLabelFormat % (yCoord)) hLabel.fillColor = theLabelColor hLabel.fontSize = theLabelSize drawing.add(hLabel) #-------------------------------------- def AddCurvedTickPoints(drawing, chartPane, page, xCoordList, yCoordList, xyFunction, tickMetric = False, labelTickEnds='b', theStrokeWidth=0.5, theStrokeColor=colors.black, theStrokeDashArray=None, theLabelFormat="%d", theLabelColor=colors.black, theLabelSize=7): xCoordList.sort() yCoordList.sort() tickCoordStart = [0.0, 0.0] tickCoordEnd= [0.0, 0.0] for yCoord in yCoordList: lastCoord = [0.0, 0.0] lastCoord[Coord.X] = xCoordList[0] - (xCoordList[1]-xCoordList[0]) lastCoord[Coord.Y] = yCoord lastCoord[Coord.X], lastCoord[Coord.Y] = xyFunction(lastCoord[Coord.X], lastCoord[Coord.Y]) lastCoord[Coord.X] = lastCoord[Coord.X] - chartPane.zero[Coord.X] lastCoord[Coord.Y] = lastCoord[Coord.Y] - chartPane.zero[Coord.Y] for xCoord in xCoordList: curveCoord = [0.0, 0.0] curveCoord[Coord.X], curveCoord[Coord.Y] = xyFunction(xCoord, yCoord) curveCoord[Coord.X] = curveCoord[Coord.X] - chartPane.zero[Coord.X] curveCoord[Coord.Y] = curveCoord[Coord.Y] - chartPane.zero[Coord.Y] # draw tick mark tickLen = page.tick[TickLen.SHORT] xDelta = chartPane.X(curveCoord[Coord.X])-chartPane.X(lastCoord[Coord.X]) if xDelta == 0.0: xDelta = 0.0001 lineAngle = atan2(chartPane.Y(curveCoord[Coord.Y])-chartPane.Y(lastCoord[Coord.Y]), xDelta) antiLineAngle = radians(180) - lineAngle cclockLineAngle = lineAngle - radians(90) clockLineAngle = lineAngle + radians(90) tickCoordEnd[Coord.X] = tickCoordStart[Coord.X] = chartPane.X(curveCoord[Coord.X]) tickCoordEnd[Coord.Y] = tickCoordStart[Coord.Y] = chartPane.Y(curveCoord[Coord.Y]) tickCoordStart[Coord.X] = tickCoordStart[Coord.X] + cos(lineAngle) * tickLen tickCoordStart[Coord.Y] = tickCoordStart[Coord.Y] + sin(lineAngle) * tickLen tickCoordEnd[Coord.X] = tickCoordEnd[Coord.X] - cos(lineAngle) * tickLen tickCoordEnd[Coord.Y] = tickCoordEnd[Coord.Y] - sin(lineAngle) * tickLen drawing.add(Line(tickCoordStart[Coord.X], tickCoordStart[Coord.Y], tickCoordEnd[Coord.X], tickCoordEnd[Coord.Y], strokeColor=theStrokeColor, strokeWidth=theStrokeWidth, strokeDashArray=theStrokeDashArray)) tickCoordEnd[Coord.X] = tickCoordStart[Coord.X] = chartPane.X(curveCoord[Coord.X]) tickCoordEnd[Coord.Y] = tickCoordStart[Coord.Y] = chartPane.Y(curveCoord[Coord.Y]) tickCoordStart[Coord.X] = tickCoordStart[Coord.X] + cos(cclockLineAngle) * tickLen tickCoordStart[Coord.Y] = tickCoordStart[Coord.Y] + sin(cclockLineAngle) * tickLen tickCoordEnd[Coord.X] = tickCoordEnd[Coord.X] + cos(clockLineAngle) * tickLen tickCoordEnd[Coord.Y] = tickCoordEnd[Coord.Y] + sin(clockLineAngle) * tickLen drawing.add(Line(tickCoordStart[Coord.X], tickCoordStart[Coord.Y], tickCoordEnd[Coord.X], tickCoordEnd[Coord.Y], strokeColor=theStrokeColor, strokeWidth=theStrokeWidth, strokeDashArray=theStrokeDashArray)) if not tickMetric or xCoord % 10.0 == 0: if labelTickEnds.count('a') > 0: hLabel = Label() hLabel.setOrigin(tickCoordStart[Coord.X], tickCoordStart[Coord.Y]) hLabel.boxAnchor = 'w' hLabel.angle = degrees(cclockLineAngle) hLabel.setText(theLabelFormat % (xCoord)) hLabel.fillColor = theLabelColor hLabel.fontSize = theLabelSize drawing.add(hLabel) if labelTickEnds.count('b') > 0: hLabel = Label() hLabel.setOrigin(tickCoordEnd[Coord.X], tickCoordEnd[Coord.Y]) hLabel.boxAnchor = 'e' hLabel.angle = degrees(cclockLineAngle) hLabel.setText(theLabelFormat % (xCoord)) hLabel.fillColor = theLabelColor hLabel.fontSize = theLabelSize drawing.add(hLabel) #-------------------------------------- def AddHLimitedLines(drawing, chartPane, yCoordList, yFunction, xMin, xMax, xyFunction, theStrokeWidth=0.5, theStrokeColor=colors.black, theStrokeDashArray=None, theLabelAnchor='w', theLabelAngle=0, theLabelFormat="%d", theLabelColor=colors.black, theLabelSize=7): for yNode in yCoordList: if type(yNode) != TupleType: yCoord = yNode yLabel = theLabelFormat % (yCoord) else: yCoord = yNode[0] yLabel = yNode[1] xCoord1, junk = xyFunction(xMin, yCoord) xCoord1 = xCoord1 - chartPane.zero[Coord.X] xCoord2, junk = xyFunction(xMax, yCoord) xCoord2 = xCoord2 - chartPane.zero[Coord.X] yCoord1 = yFunction(yCoord)-chartPane.zero[Coord.Y] yCoord2 = yCoord1 drawing.add(Line(chartPane.X(xCoord1), chartPane.Y(yCoord1), chartPane.X(xCoord2), chartPane.Y(yCoord2), strokeColor=theStrokeColor, strokeWidth=theStrokeWidth, strokeDashArray=theStrokeDashArray)) hLabel = Label() if theLabelAnchor == 'w': hLabel.setOrigin(chartPane.X(xCoord2), chartPane.Y(yCoord1)) else: hLabel.setOrigin(chartPane.X(xCoord1), chartPane.Y(yCoord1)) hLabel.boxAnchor = theLabelAnchor hLabel.angle = theLabelAngle hLabel.setText(yLabel) hLabel.fillColor = theLabelColor hLabel.fontSize = theLabelSize drawing.add(hLabel) #-------------------------------------- def AddHLines(drawing, chartPane, yCoordList, yFunction, theStrokeWidth=0.5, theStrokeColor=colors.black, theStrokeDashArray=None, theLabelAnchor='w', theLabelAngle=0, theLabelFormat="%d", theLabelColor=colors.black, theLabelSize=7, labelEnds='ab'): xCoord1 = chartPane.xLimit[Bound.MIN] xCoord2 = chartPane.xLimit[Bound.MAX] for yNode in yCoordList: if type(yNode) != TupleType: yCoord = yNode yLabel = theLabelFormat % (yCoord) else: yCoord = yNode[0] yLabel = yNode[1] yCoord1 = yFunction(yCoord)-chartPane.zero[Coord.Y] yCoord2 = yCoord1 drawing.add(Line(xCoord1, chartPane.Y(yCoord1), xCoord2, chartPane.Y(yCoord2), strokeColor=theStrokeColor, strokeWidth=theStrokeWidth, strokeDashArray=theStrokeDashArray)) if labelEnds.count('a') > 0: hLabel = Label() hLabel.setOrigin(xCoord1 + (theStrokeWidth*2), chartPane.Y(yCoord1)) hLabel.boxAnchor= 'w' hLabel.angle = theLabelAngle hLabel.setText(yLabel) hLabel.fillColor = theLabelColor hLabel.fontSize = theLabelSize drawing.add(hLabel) if labelEnds.count('b') > 0: hLabel = Label() hLabel.setOrigin(xCoord2 - (theStrokeWidth*2), chartPane.Y(yCoord1)) hLabel.boxAnchor = 'e' hLabel.angle = theLabelAngle hLabel.setText(yLabel) hLabel.fillColor = theLabelColor hLabel.fontSize = theLabelSize drawing.add(hLabel) #-------------------------------------- def AddHDecimalScale(drawing, chartPane, xCoordList, xFunction, yCoord, yFunction, tickEnds='b', theStrokeWidth=0.5, theStrokeColor=colors.black, theStrokeDashArray=None, theLabelColor=colors.black, theLabelSize=7, labelEnds='b'): xCoord1 = chartPane.xLimit[Bound.MIN] xCoord2 = chartPane.xLimit[Bound.MAX] yCoord1 = yFunction(yCoord)-chartPane.zero[Coord.Y] drawing.add(Line(xCoord1, chartPane.Y(yCoord1), xCoord2, chartPane.Y(yCoord1), strokeColor=theStrokeColor, strokeWidth=theStrokeWidth, strokeDashArray=theStrokeDashArray)) tickDelta = xCoordList[-1] - xCoordList[0] for xNode in range(tickDelta): xCoord = xNode + xCoordList[0] xCoord1 = xFunction(xCoord)-chartPane.zero[Coord.X] if xCoord % 10 == 0: tickLength = 10 elif xCoord % 5 == 0: tickLength = 5 else: tickLength = 0 if tickLength != 0: yTickCoord1 = chartPane.Y(yCoord1) yTickCoord2 = chartPane.Y(yCoord1) if tickEnds.count('a') > 0: yTickCoord2 = yTickCoord2 + tickLength if tickEnds.count('b') > 0: yTickCoord1 = yTickCoord1 - tickLength drawing.add(Line(chartPane.X(xCoord1), yTickCoord1, chartPane.X(xCoord1), yTickCoord2, strokeColor=theStrokeColor, strokeWidth=theStrokeWidth, strokeDashArray=theStrokeDashArray)) if xCoord % 100 == 0: if labelEnds.count('a') > 0: hLabel = Label() hLabel.setOrigin(chartPane.X(xCoord1), yTickCoord2) hLabel.boxAnchor = 'w' hLabel.angle = 90 hLabel.setText("%d" % (xCoord)) hLabel.fillColor = theLabelColor hLabel.fontSize = theLabelSize drawing.add(hLabel) if labelEnds.count('b') > 0: hLabel = Label() hLabel.setOrigin(chartPane.X(xCoord1), yTickCoord1) hLabel.boxAnchor = 'e' hLabel.angle = 90 hLabel.setText("%d" % (xCoord)) hLabel.fillColor = theLabelColor hLabel.fontSize = theLabelSize drawing.add(hLabel) #-------------------------------------- def AddVLimitedLines(drawing, chartPane, xCoordList, xFunction, yMin, yMax, xyFunction, theStrokeWidth=0.5, theStrokeColor=colors.black, theStrokeDashArray=None, theLabelAnchor='w', theLabelAngle=0, theLabelFormat="%d", theLabelColor=colors.black, theLabelSize=7): for xNode in xCoordList: if type(xNode) != TupleType: xCoord = xNode xLabel = theLabelFormat % (xCoord) else: xCoord = xNode[0] xLabel = xNode[1] xCoord1 = xFunction(xCoord)-chartPane.zero[Coord.X] xCoord2 = xCoord1 junk, yCoord1 = xyFunction(xCoord, yMin) yCoord1 = yCoord1 - chartPane.zero[Coord.Y] junk, yCoord2 = xyFunction(xCoord, yMax) yCoord2 = yCoord2 - chartPane.zero[Coord.Y] drawing.add(Line(chartPane.X(xCoord1), chartPane.Y(yCoord1), chartPane.X(xCoord2), chartPane.Y(yCoord2), strokeColor=theStrokeColor, strokeWidth=theStrokeWidth, strokeDashArray=theStrokeDashArray)) hLabel = Label() if theLabelAnchor == 'w': hLabel.setOrigin(chartPane.X(xCoord1), chartPane.Y(yCoord2)) else: hLabel.setOrigin(chartPane.X(xCoord1), chartPane.Y(yCoord1)) hLabel.boxAnchor = theLabelAnchor hLabel.angle = theLabelAngle hLabel.setText(xLabel) hLabel.fillColor = theLabelColor hLabel.fontSize = theLabelSize drawing.add(hLabel) #-------------------------------------- def AddVLines(drawing, chartPane, xCoordList, xFunction, theStrokeWidth=0.5, theStrokeColor=colors.black, theStrokeDashArray=None, theLabelAnchor='w', theLabelAngle=0, theLabelFormat="%d", theLabelColor=colors.black, theLabelSize=7, labelEnds='ab'): yCoord1 = chartPane.yLimit[Bound.MIN] yCoord2 = chartPane.yLimit[Bound.MAX] for xNode in xCoordList: if type(xNode) != TupleType: xCoord = xNode xLabel = theLabelFormat % (xCoord) else: xCoord = xNode[0] xLabel = xNode[1] xCoord1 = xFunction(xCoord)-chartPane.zero[Coord.X] xCoord2 = xCoord1 drawing.add(Line(chartPane.X(xCoord1), yCoord1, chartPane.X(xCoord2), yCoord2, strokeColor=theStrokeColor, strokeWidth=theStrokeWidth, strokeDashArray=theStrokeDashArray)) if labelEnds.count('a') > 0: hLabel = Label() hLabel.setOrigin(chartPane.X(xCoord1), yCoord1 + (theStrokeWidth*2)) hLabel.boxAnchor = 'w' hLabel.angle = theLabelAngle hLabel.setText(xLabel) hLabel.fillColor = theLabelColor hLabel.fontSize = theLabelSize drawing.add(hLabel) if labelEnds.count('b') > 0: hLabel = Label() hLabel.setOrigin(chartPane.X(xCoord1), yCoord2 - (theStrokeWidth*2)) hLabel.boxAnchor = 'e' hLabel.angle = theLabelAngle hLabel.setText(xLabel) hLabel.fillColor = theLabelColor hLabel.fontSize = theLabelSize drawing.add(hLabel) #-------------------------------------- def AddLabel(drawing, chartPane, theLabel, coord, xFunction, yFunction, theLabelAnchor='w', theLabelAngle=0, theLabelColor=colors.black, theLabelSize=7): hLabel = Label() labelCoord = [0.0, 0.0] labelCoord[Coord.X] = xFunction(coord[Coord.X])-chartPane.zero[Coord.X] labelCoord[Coord.Y] = yFunction(coord[Coord.Y])-chartPane.zero[Coord.Y] hLabel.setOrigin(chartPane.X(labelCoord[Coord.X]), chartPane.Y(labelCoord[Coord.Y])) hLabel.boxAnchor = theLabelAnchor hLabel.angle = theLabelAngle hLabel.setText(theLabel) hLabel.fillColor = theLabelColor hLabel.fontSize = theLabelSize drawing.add(hLabel) #****************************************************************************** # Start of Demo #****************************************************************************** #-------------------------------------- def yFunction(y): return log10(y) #-------------------------------------- def xFunction(x): return log10(x) #-------------------------------------- def mrFunction(x, a): # x = ve # a = mr # dv = ve * ln(mr) # returns (ve, dv), scaled by log10 return (xFunction(x), yFunction(x * 9.81 * log(a))) #-------------------------------------- def mrInverseFunction(x, y): # x = rm # y = dv # ve = dv / ln(mr) # returns (ve, dv), scaled by log10 return (xFunction((y/10) / log(x)), yFunction(y)) #-------------------------------------- def missionLowFunction(x, y): return (xFunction(x), y) #-------------------------------------- def demo(): fileName = "RocketChart01.pdf" page = Page((11, 8.5), (0.125, 0.125)) # chart data veTicks = [2649, 1e4, 1e5, 1e6, 1e7, 1e8] dvTicks = [250, 1000, 1e4, 1e5, 1e6, 1e7,14153000] massRatioLines = [1.01, 1.05, 1.1, 1.25, 1.5, 1.75, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20] # thrust-to-weight ratio less than one engineTW0List = [ (29000,'VASIMR low'), (147000,'VASIMR med'), (294000,'VASIMR high'), (9000,'Solar Moth'), (74000,'J x B Electric'), (132300,'ACMF'), (157000,'Ion'), (314000,'Magnetiplasmadynamic'), (598000,'AIM'), (7840000,'He3D Fusion, Antimatter Plasma Hydrogen'), (100000000, 'Antimatter Beam Core') ] # thrust-to-weight ratio greater than one engineTW1List = [ (9221, 'LANTR (Nerva)'), (6347, 'LANTR (LOX)'), (9810, 'NTR Particle-bed, Basic MITEE'), (22000, 'ArcJet, D-T Fusion'), (4500, 'Chemical Max'), (12750,'Monatomic-H MITEE'), (17660,'NTR Gas Core coaxial, Hybrid Electro-Thermal MITEE'), (10791, 'Antimatter Solid Core'), (24500,'Antimatter Gas Core'), (2649,'NTR Solid-Carbon Monoxide, Nitrogen'), (3306,'NTR Solid-Carbon Dioxide'), (4042,'NTR Solid-Water'), (5101,'NTR Solid-Ammonia'), (6318,'NTR Solid-Methane'), (8093,'NTR Solid-Hydrogen'), (40000,'Laser Thermal'), (1000000,'AV:T Fusion (cruise)'), (980000,'H-B Fusion, Antimatter Plasma Water'), (19620,'NTR-LIQUID/LARS'), (210000,'Mini-Mag Orion'), (98000,'NTR Gas Core max'), (8000000,'Magnetic Confinement Fusion'), (66000,'NSWR 20%'), (4700000,'NSWR 80%'), (10000000,'Inertial Confinement Fusion'), (30000,'Mass Driver'), (43000,'Metahelium H*, Colloid Ion, ORION fission'), (21600,'Metahelium He IV-A'), (20405,'NTR Gas Core closed cycle'), (73000,'ORION fusion'), (9800000,'ORION max'), (35000,'NTR Gas Core open cycle'), (50000,'NTR gas open 2nd gen'), (30000000,'H->He Fusion max'), (50000000,'H->Fe Fusion max'), (125000,'AV:T Fusion combat') ] missionLowList = [ (8640,'Earth Liftoff'), (1800,'Luna, Callisto Liftoff'), (3180,'Mercury Liftoff'), (3555,'Mars Liftoff'), (1520,'Europa Liftoff'), (1980,'Io, Ganymede Liftoff'), (2390,'Titan Liftoff'), (4667,'Earth-Luna Hohman'), (17141,'Earth-Mercury Hohman'), (5201,'Earth-Venus Hohman'), (5591,'Earth-Mars Hohman'), (11171,'Earth-Ceres Hohman'), (14434,'Earth-Jupiter Hohman'), (15734,'Earth-Saturn Hohman'), (38960,'Earth-Uranus Hohman'), (40242,'Earth-Neptune Hohman'), (24249,'Earth-Pluto Hohman'), (75210,'Earth-Mercury I-2'), (63330,'Earth-Venus I-2'), (52930,'Earth-Mars I-2'), (44730,'Earth-Ceres I-2'), (72690,'Earth-Jupiter I-2'), (55770,'Earth-Saturn I-2'), (106230,'Earth-Mercury I-3'), (98620,'Earth-Venus I-3'), (94110,'Earth-Mars I-3'), (92160,'Earth-Ceres I-3'), (118010,'Earth-Jupiter I-3'), (108680,'Earth-Saturn I-3') ] missionHiList = [ (397000,'Earth-Mercury 0.01g'), (281000,'Earth-Venus 0.01g'), (370000,'Earth-Mars 0.01g'), (655000,'Earth-Ceres 0.01g'), (1000000,'Earth-Jupiter 0.01g'), (1420000,'Earth-Saturn 0.01g'), (1205000,'Earth-Mercury 0.1g'), (815000,'Earth-Venus 0.1g'), (1115000,'Earth-Mars 0.1g'), (2040000,'Earth-Ceres 0.1g'), (3142000,'Earth-Jupiter 0.1g'), (4477000,'Earth-Saturn 0.1g'), (617600,'Solar Escape Velocity'), (14153000,'Earth-Saturn 1g'), (9930000,'Earth-Jupiter 1g'), (6441000,'Earth-Ceres 1g'), (3508000,'Earth-Mars 1g'), (260000,'Earth-Luna 1g'), (2552000,'Earth-Venus 1g'), (3794000,'Earth-Mercury 1g') ] chartPane = ChartPane(page.margin, page.printable, veTicks, xFunction, dvTicks, yFunction) drawing = Drawing(page.sheet[Dim.WIDTH], page.sheet[Dim.HEIGHT]) # add x axis AddVLines(drawing, chartPane, veTicks, xFunction, theStrokeWidth=0.5, theStrokeColor=colors.gray, theLabelFormat="%.1e", theLabelAngle=90) # add y axis AddHLines(drawing, chartPane, dvTicks, yFunction, theStrokeWidth=0.5, theStrokeColor=colors.gray, theLabelFormat="%.1e") # add mass ratio lines AddCurvedLines(drawing, chartPane, veTicks, massRatioLines, mrFunction, theLabelFormat="%.2f", theLabelAngle=36.5) # add engine lines massRatioLines.sort() AddVLimitedLines(drawing, chartPane, engineTW0List, xFunction, massRatioLines[0]-0.001, massRatioLines[-1], mrFunction, theStrokeDashArray=[2,2], theLabelAngle=90, theLabelAnchor='e') AddVLimitedLines(drawing, chartPane, engineTW1List, xFunction, massRatioLines[0]-0.001, massRatioLines[-1], mrFunction, theLabelAngle=90, theLabelAnchor='e') # add mission lines massRatioLines.sort() AddHLimitedLines(drawing, chartPane, missionLowList, yFunction, 2649, 1.5e6, missionLowFunction, theLabelAnchor='w') AddHLimitedLines(drawing, chartPane, missionHiList, yFunction, massRatioLines[-1]+10, massRatioLines[0], mrInverseFunction, theLabelAnchor='e') # add labels AddLabel(drawing, chartPane, "Rocket Performance Graph", (2.5e6, 8.4e2), xFunction, yFunction, theLabelSize=24) AddLabel(drawing, chartPane, "D = E · ln(R)", (1.1e7, 5.9e2), xFunction, yFunction, theLabelSize=21) AddLabel(drawing, chartPane, "Winchell Chung\nNyrath the nearly wise\nhttp://www.projectrho.com/rocket/index.html", (1.1e7, 3.5e2), xFunction, yFunction, theLabelSize=7) AddLabel(drawing, chartPane, "Delta-V (meters/sec)", (6e7, 5.1e4), xFunction, yFunction, theLabelSize=18, theLabelAngle=90) AddLabel(drawing, chartPane, "Delta-V (meters/sec)", (3e3, 3.3e5), xFunction, yFunction, theLabelSize=18, theLabelAngle=90) AddLabel(drawing, chartPane, "Exhaust Velocity (meters/sec)", (7.5e4, 6e2), xFunction, yFunction, theLabelSize=18) AddLabel(drawing, chartPane, "Mass Ratio", (1.5e5, 2.5e5), xFunction, yFunction, theLabelSize=18, theLabelAngle=36.5) AddLabel(drawing, chartPane, """Hohman are minimum-delta-V, maximum-duration impulse trajectories. I-3 are impulse trajectories near the transition between delta-V levels for high impulse trajectories and low brachistochrone trajectories (it is a hyperbolic solar escape orbit plus 30 km/s). I-2 is an impulse trajectory in-between Hohman and I-3 (it is equivalent to an elliptical orbit from Mercury to Pluto) Note that propulsion systems with thrust-to-weight ratios below one can only perform a Hohman or other impulse trajectory, they are incapable of performing brachistochrones or lift-offs. Such propulsion systems have dotted vertical lines on the table. """, (4e6,8000), xFunction, yFunction, theLabelSize=10) AddLabel(drawing, chartPane, """These are constant acceleration brachistochrone type trajectories, where the spacecraft accelerates to the midpoint, does a skew flip, then decelerates to destination """, (7000, 3500000), xFunction, yFunction, theLabelSize=10) # print file renderPDF.drawToFile(drawing, fileName, '') print "all done" #****************************************************************************** # module/program switch if __name__ == "__main__": demo()