def _writeAlternateText(self, theSvg, thePoint, theId, theText, theAltS, yOffs=Coord.Dim(0, 'pt')): """Composes and writes the (pop-up) alternate text. thePoint is the physical point to locate both texts.""" # Write a grouping element and give it the alternate ID with SVGWriter.SVGGroup(theSvg, {'id' : 't%s%s' % (theId, self.ALT_ID_SUFFIX), 'opacity' : '0.0'}): altFontSize = self.ALT_FONT_PROPERTIES[self.ALT_FONT_FAMILY]['size'] altFontLenFactor = self.ALT_FONT_PROPERTIES[self.ALT_FONT_FAMILY]['lenFactor'] altFontHeightFactor = self.ALT_FONT_PROPERTIES[self.ALT_FONT_FAMILY]['heightFactor'] # Compute masking box for alternate maxChars = max([len(s) for s in theAltS]) # Take around 80% of character length boxWidth = Coord.Dim(altFontSize * maxChars * altFontLenFactor, 'pt') if len(theAltS) < 2: boxHeight = Coord.Dim(altFontSize * 2, 'pt') else: boxHeight = Coord.Dim(altFontSize * len(theAltS) * altFontHeightFactor, 'pt') boxAttrs = { 'fill' : self.ALT_RECT_FILL } with SVGWriter.SVGRect( theSvg, # Edge the plot point up and left by a bit Coord.newPt( thePoint, incX=Coord.Dim(-1 * altFontSize * (1 + len(theText) * altFontLenFactor / 2.0), 'pt'), incY=Coord.Dim(-1*altFontHeightFactor * altFontSize, 'pt') + yOffs, ), Coord.Box(boxWidth, boxHeight), boxAttrs, ): pass # As the main text is centered and the alt text is left # justified we need to move the text plot point left by a bit. myAltTextPt = Coord.newPt( thePoint, incX=Coord.Dim(-1 * altFontSize * len(theText) * altFontLenFactor / 2.0, 'pt'), incY=yOffs, ) with SVGWriter.SVGText(theSvg, myAltTextPt, 'Courier', altFontSize, { 'font-weight' : "normal", } ): self._writeStringListToTspan(theSvg, myAltTextPt, theAltS)
def test_pcTo(self): """TestPlotNodeBboxBoxyChildren.test_pcTo() - test pcTo().""" self.assertEqual(self._pnbcObj.pcTo(self._logicalDatum, 0), Coord.Pt( Coord.Dim(23, 'mm'), Coord.Dim(195, 'mm'), )) self.assertEqual(self._pnbcObj.pcTo(self._logicalDatum, 1), Coord.Pt( Coord.Dim(27, 'mm'), Coord.Dim(195, 'mm'), )) self.assertEqual(self._pnbcObj.pcTo(self._logicalDatum, 2), Coord.Pt( Coord.Dim(31, 'mm'), Coord.Dim(195, 'mm'), ))
def testBbChildren(self): """Tests PlotNode() set/get bbChildren.""" myObj = PlotNode.PlotNodeBbox() myObj.bbChildren = Coord.Box( Coord.Dim(1, 'px'), Coord.Dim(2, 'px'), ) self.assertEqual(myObj.bbChildren, Coord.Box( Coord.Dim(1, 'px'), Coord.Dim(2, 'px'), )) self.assertEqual( myObj.bbChildrenWidth, Coord.Dim(1, 'px'), ) self.assertEqual( myObj.bbChildrenDepth, Coord.Dim(2, 'px'), )
def test_pcRoll(self): """TestPlotNodeBboxBoxyChildren.test_pcRoll() - test pcRoll().""" #parentChildTakeoffPoint #print #print 'myObj.parentChildTakeoffPoint[0]:', myObj.parentChildTakeoffPoint(myD, 0) self.assertEqual(self._pnbcObj.pcRoll(self._logicalDatum, 0), Coord.Pt( Coord.Dim(23, 'mm'), Coord.Dim(188, 'mm'), )) #print #print 'myObj.parentChildTakeoffPoint[1]:', myObj.parentChildTakeoffPoint(myD, 1) self.assertEqual(self._pnbcObj.pcRoll(self._logicalDatum, 1), Coord.Pt( Coord.Dim(27, 'mm'), Coord.Dim(188, 'mm'), )) #print #print 'myObj.parentChildTakeoffPoint[2]:', myObj.parentChildTakeoffPoint(myD, 2) self.assertEqual(self._pnbcObj.pcRoll(self._logicalDatum, 2), Coord.Pt( Coord.Dim(31, 'mm'), Coord.Dim(188, 'mm'), ))
def testScale(self): """Dim() scale().""" myObj = Coord.Dim(12, 'px') myObj = myObj.scale(2.0) self.assertEqual(myObj.value, 24) self.assertEqual(myObj.units, 'px')
def test_01(self): """TestSVGlWriter.test_01(): <desc> and four rectangles. From second example in http://www.w3.org/TR/2003/REC-SVG11-20030114/struct.html#NewDocumentOverview""" myF = io.StringIO() myViewPort = Coord.Box( Coord.Dim(5, 'cm'), Coord.Dim(4, 'cm'), ) with SVGWriter.SVGWriter(myF, myViewPort) as xS: with XmlWrite.Element(xS, 'desc'): xS.characters('Four separate rectangles') myPt = Coord.Pt(Coord.Dim(0.5, 'cm'), Coord.Dim(0.5, 'cm')) myBx = Coord.Box(Coord.Dim(2.0, 'cm'), Coord.Dim(1.0, 'cm')) with SVGWriter.SVGRect(xS, myPt, myBx): pass myPt = Coord.Pt(Coord.Dim(0.5, 'cm'), Coord.Dim(2.0, 'cm')) myBx = Coord.Box(Coord.Dim(1.0, 'cm'), Coord.Dim(1.5, 'cm')) with SVGWriter.SVGRect(xS, myPt, myBx): pass myPt = Coord.Pt(Coord.Dim(3.0, 'cm'), Coord.Dim(0.5, 'cm')) myBx = Coord.Box(Coord.Dim(1.5, 'cm'), Coord.Dim(2.0, 'cm')) with SVGWriter.SVGRect(xS, myPt, myBx): pass myPt = Coord.Pt(Coord.Dim(3.5, 'cm'), Coord.Dim(3.0, 'cm')) myBx = Coord.Box(Coord.Dim(1.0, 'cm'), Coord.Dim(0.5, 'cm')) with SVGWriter.SVGRect(xS, myPt, myBx): pass myPt = Coord.Pt(Coord.Dim(0.01, 'cm'), Coord.Dim(0.01, 'cm')) myBx = Coord.Box(Coord.Dim(4.98, 'cm'), Coord.Dim(3.98, 'cm')) with SVGWriter.SVGRect(xS, myPt, myBx, attrs={ 'fill': "none", 'stroke': "blue", 'stroke-width': ".02cm", }): pass #print #print myF.getvalue() self.assertEqual( myF.getvalue(), """<?xml version='1.0' encoding="utf-8"?> <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> <svg height="4.00cm" version="1.1" width="5.00cm" xmlns="http://www.w3.org/2000/svg"> <desc>Four separate rectangles</desc> <rect height="1.00cm" width="2.00cm" x="0.50cm" y="0.50cm" /> <rect height="1.50cm" width="1.00cm" x="0.50cm" y="2.00cm" /> <rect height="2.00cm" width="1.50cm" x="3.00cm" y="0.50cm" /> <rect height="0.50cm" width="1.00cm" x="3.50cm" y="3.00cm" /> <rect fill="none" height="3.98cm" stroke="blue" stroke-width=".02cm" width="4.98cm" x="0.01cm" y="0.01cm" /> </svg> """)
def test_04(self): """TestSVGlWriter.test_04(): a line. Based on http://www.w3.org/TR/2003/REC-SVG11-20030114/shapes.html#LineElement""" myF = io.StringIO() myViewPort = Coord.Box( Coord.Dim(12, 'cm'), Coord.Dim(4, 'cm'), ) with SVGWriter.SVGWriter(myF, myViewPort) as xS: with XmlWrite.Element(xS, 'desc'): xS.characters( 'Example line01 - lines expressed in user coordinates') #xS.comment(" Show outline of canvas using 'rect' element ") myPt = Coord.Pt(Coord.baseUnitsDim(1), Coord.baseUnitsDim(1)) myBx = Coord.Box(Coord.baseUnitsDim(1198), Coord.baseUnitsDim(398)) with SVGWriter.SVGRect(xS, myPt, myBx, { 'fill': "none", 'stroke': "blue", 'stroke-width': "2" }): pass # Make a group with SVGWriter.SVGGroup(xS, {'stroke': 'green'}): with SVGWriter.SVGLine( xS, Coord.Pt(Coord.baseUnitsDim(100), Coord.baseUnitsDim(300)), Coord.Pt(Coord.baseUnitsDim(300), Coord.baseUnitsDim(100)), {'stroke-width': "5"}): pass with SVGWriter.SVGLine( xS, Coord.Pt(Coord.baseUnitsDim(300), Coord.baseUnitsDim(300)), Coord.Pt(Coord.baseUnitsDim(500), Coord.baseUnitsDim(100)), {'stroke-width': "10"}): pass with SVGWriter.SVGLine( xS, Coord.Pt(Coord.baseUnitsDim(500), Coord.baseUnitsDim(300)), Coord.Pt(Coord.baseUnitsDim(700), Coord.baseUnitsDim(100)), {'stroke-width': "15"}): pass with SVGWriter.SVGLine( xS, Coord.Pt(Coord.baseUnitsDim(700), Coord.baseUnitsDim(300)), Coord.Pt(Coord.baseUnitsDim(900), Coord.baseUnitsDim(100)), {'stroke-width': "20"}): pass with SVGWriter.SVGLine( xS, Coord.Pt(Coord.baseUnitsDim(900), Coord.baseUnitsDim(300)), Coord.Pt(Coord.baseUnitsDim(1100), Coord.baseUnitsDim(100)), {'stroke-width': "25"}): pass #print #print myF.getvalue() self.assertEqual( myF.getvalue(), """<?xml version='1.0' encoding="utf-8"?> <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> <svg height="4.00cm" version="1.1" width="12.00cm" xmlns="http://www.w3.org/2000/svg"> <desc>Example line01 - lines expressed in user coordinates</desc> <rect fill="none" height="398px" stroke="blue" stroke-width="2" width="1198px" x="1px" y="1px" /> <g stroke="green"> <line stroke-width="5" x1="100px" x2="300px" y1="300px" y2="100px" /> <line stroke-width="10" x1="300px" x2="500px" y1="300px" y2="100px" /> <line stroke-width="15" x1="500px" x2="700px" y1="300px" y2="100px" /> <line stroke-width="20" x1="700px" x2="900px" y1="300px" y2="100px" /> <line stroke-width="25" x1="900px" x2="1100px" y1="300px" y2="100px" /> </g> </svg> """)
class SVGTreeNodeMain(IncGraphSVGBase.SVGTreeNodeBase): """This does most of the heavy lifting of plotting the include graph SVG. Challenges are plotting things in the 'right' order and with the 'right' JavaScript so that the DHTML does not look too hideous. Basic principle here is that `plotInitialise()` writes static data. In our case just the pretty histogram pop-up (Ed. is this right???). Then `plotToSVGStream()` is called - this is implemented in the base class. FInally `plotFinalise()` is called - this overlays the DHTML text. This is a little tricky as our way of DHTML is to switch opacity on underlying objects the switching boundary being the overlying object (e.g. '?'). So _all_ the underlying objects need to be written first so that the overlying objects are always 'visible' to trigger onmouseover/onmouseout on the underlying object.""" COMMON_UNITS = 'mm' WIDTH_PER_TOKEN = Coord.Dim(1.0 / 1000.0, COMMON_UNITS) WIDTH_MINIMUM = Coord.Dim(5, COMMON_UNITS) FILE_DEPTH = Coord.Dim(32.0, COMMON_UNITS) SPACE_PARENT_CHILD = Coord.Dim(16.0, COMMON_UNITS) FILE_PADDING = Coord.Pad( Coord.Dim(4.0, COMMON_UNITS), # prev Coord.Dim(2.0, COMMON_UNITS), # next Coord.Dim(16.0, COMMON_UNITS), # parent Coord.Dim(16.0, COMMON_UNITS), # child ) # Lines joining root level children ATTRS_LINE_ROOT_CHILDREN_JOIN = { 'stroke-width': "8", 'stroke': "lightgrey", } # Node attributes (e.e. for rectangles) # Normal nodes ATTRS_NODE_NORMAL = { 'fill': "mistyrose", 'stroke': "black", 'stroke-width': "1", } # Nodes that are empty ATTRS_NODE_MT = { 'fill': "aqua", 'stroke': "black", 'stroke-width': "1", } # Conditionally compiled stuff ATTRS_NODE_CONDITIONAL = { 'fill': "salmon", #"orangered", 'stroke': "black", #'stroke-dasharray' : "4,4", 'stroke-width': "1", } ATTRS_LINE_NORMAL_TO = { 'stroke-width': "2", 'stroke': "black", } ATTRS_LINE_NORMAL_FROM = { 'stroke-width': "0.5", 'stroke': "black", } ATTRS_LINE_MT_TO = { 'stroke-width': "2", 'stroke': "aqua", 'stroke-dasharray': "8,8", } ATTRS_LINE_MT_FROM = { 'stroke-width': "0.5", 'stroke': "aqua", 'stroke-dasharray': "8,8", } ATTRS_LINE_CONDITIONAL_TO = { 'stroke-width': "0.5", 'stroke': "black", 'stroke-dasharray': "8,2,2,2", } ATTRS_LINE_CONDITIONAL_FROM = { 'stroke-width': "0.25", 'stroke': "black", 'stroke-dasharray': "8,2,2,2", } # CSS entries STYLE_VERDANA_12 = 'text.V12' CLASS_VERDANA_12 = 'V12' ATTRS_VERDANA_12 = { 'dominant-baseline': 'middle', 'font-family': 'Verdana', 'font-size': '12', 'text-anchor': 'middle', 'text-decoration': 'underline', } # Used for histogram STYLE_VERDANA_9 = 'text.V9' CLASS_VERDANA_9 = 'V9' ATTRS_VERDANA_9 = { 'dominant-baseline': 'middle', 'font-family': 'Verdana', 'font-size': '9', # 'opacity' : '1.0', # 'text-anchor' : 'middle', # 'text-decoration' : 'underline', } STYLE_TEXT_SCALE = 'text.scale' CLASS_TEXT_SCALE = 'scale' ATTRS_TEXT_SCALE = { 'dominant-baseline': 'middle', 'font-family': 'Verdana', 'font-size': '12', 'text-anchor': 'left', } # font-family="Courier" font-size="10" font-weight="normal" STYLE_COURIER_10 = 'text.C10' CLASS_COURIER_10 = 'C10' ATTRS_COURIER_10 = { 'font-family': 'Courier', 'font-size': '10', 'font-weight': '10', } # Invisible rectangle STYLE_RECT_INVIS = 'rect.invis' CLASS_RECT_INVIS = 'invis' ATTRS_RECT_INVIS = { 'fill': 'red', 'opacity': '0', 'stroke': 'black', 'stroke-width': '1', } # # Chevron attributes CHEVRON_COLOUR_FILL = "palegreen" #"cornflowerblue" CHEVRON_COLOUR_STROKE = "black" CHEVRON_STROKE_WIDTH = ".5" # Histogram plotting HIST_DEPTH = Coord.Dim(4.0, COMMON_UNITS) # This controls plot order as well as colour # Note: Unusually they are in sweep='-' i.e logical left-to-right order HIST_PP_TOKEN_TYPES_COLOURS = ( ( 'header-name', 'orange', ), # Not used as 'derived' token ( 'identifier', 'blue', ), # Top 3! ( 'string-literal', 'cyan', ), ( 'pp-number', 'green', ), # Next most popular after top 3 ( 'character-literal', 'magenta', ), ( 'preprocessing-op-or-punc', 'red', ), # Top 3! ( 'non-whitespace', 'black', ), ( 'concat', 'yellow', ), ( 'whitespace', 'white', ), # Top 3! ) HIST_RECT_COLOUR_STROKE = "black" HIST_RECT_STROKE_WIDTH = ".5" HIST_LEGEND_ID = "HistogramLegend" # The placeholder text for JavaScript rollover POPUP_TEXT = ' ? ' def __init__(self, theFig, theLineNum): super(SVGTreeNodeMain, self).__init__(theFig, theLineNum) # PpTokenCount object for my children only, set on finalise self._tokenCounterChildren = PpTokenCount.PpTokenCount() ## PpTokenCount object for me and my my children , set on finalise #self._tokenCounterTotal = PpTokenCount.PpTokenCount() # Total number of significant tokens in all children self._numChildSigTokens = 0 # Mandatory override of the bounding box object self._bb = PlotNode.PlotNodeBboxBoxy() if theFig is None: # Root node, children only self._dataMap = None else: self._dataMap = {} # Take a copy of the include graph data self._dataMap['numToks'] = theFig.numTokens self._dataMap['condComp'] = theFig.condComp # A PpTokenCount.PpTokenCount() object for this node only. self._dataMap['tokenCntr'] = theFig.tokenCounter self._dataMap['findLogic'] = theFig.findLogic # A list of tuples of (Coord.Pt, Cooord.Box, attributes) that are to be # written last as <rect class="invis" ... self._triggerS = [] # We have two passes self._numPassesToPlotSelf = 2 #============================================ # Section: Accessor methods used by ancestors #============================================ @property def tokenCounter(self): """This is the PpTokenCount.PpTokenCount() for me only.""" if self.isRoot: return PpTokenCount.PpTokenCount() return self._dataMap['tokenCntr'] @property def tokenCounterChildren(self): """This is the computed PpTokenCount.PpTokenCount() for all my descendents.""" ##"""This is the PpTokenCount.PpTokenCount() for my children.""" return self._tokenCounterChildren @property def tokenCounterTotal(self): """This is the computed PpTokenCount.PpTokenCount() me plus my descendents.""" retVal = PpTokenCount.PpTokenCount() retVal += self.tokenCounter retVal += self.tokenCounterChildren return retVal @property def condComp(self): """A string of conditional tests.""" assert (not self.isRoot) return self._dataMap['condComp'] @property def findLogic(self): """The find logic as a string.""" assert (not self.isRoot) return self._dataMap['findLogic'] #======================================== # End: Accessor methods used by ancestors #======================================== #=================================== # Section: Finalisation and plotting #=================================== def finalise(self): """Finalisation this sets up all the bounding boxes of me and my children.""" for aChild in self._children: aChild.finalise() # Now accumulate my children's bounding boxes and token counts self._tokenCounterChildren = PpTokenCount.PpTokenCount() #self._tokenCounterTotal = PpTokenCount.PpTokenCount() #if not self.isRoot: # self._tokenCounterTotal += self.tokenCounter self._numChildSigTokens = 0 for aChild in self._children: self._bb.extendChildBbox(aChild.bb.bbSigma) self._tokenCounterChildren += aChild.tokenCounterTotal #self._tokenCounterTotal += aChild.tokenCounter self._numChildSigTokens += aChild.tokenCounterTotal.tokenCountNonWs( isAll=False) # Set up my bounding box only if non-root node if not self.isRoot: #self._bb.width = max(self.WIDTH_MINIMUM, self.WIDTH_PER_TOKEN.scale(self._tokenCount)) self._bb.width = self.WIDTH_MINIMUM \ + self.WIDTH_PER_TOKEN.scale(self.tokenCounterTotal.tokenCountNonWs(isAll=False)) self._bb.depth = self.FILE_DEPTH self._bb.bbSelfPadding = self.FILE_PADDING if len(self._children) > 0: self._bb.bbSpaceChildren = self.SPACE_PARENT_CHILD # Bounding boxes now set up def writePreamble(self, theS): """Write any preamble such as CSS or JavaScript. To be implemented by child classes.""" cssDict = { 'tspan': { 'white-space': 'pre' }, self.STYLE_VERDANA_12: self.ATTRS_VERDANA_12, self.STYLE_VERDANA_9: self.ATTRS_VERDANA_9, self.STYLE_COURIER_10: self.ATTRS_COURIER_10, self.STYLE_TEXT_SCALE: self.ATTRS_TEXT_SCALE, self.STYLE_RECT_INVIS: self.ATTRS_RECT_INVIS, } with XmlWrite.Element(theS, 'defs', {}): theS.writeCSS(cssDict) self._writeECMAScript(theS) self._writeScaleControls(theS) def _writeScaleControls(self, theSvg): """Write the text elements that control re-scaling.""" myAttrs = { 'class': self.CLASS_TEXT_SCALE, } myPointP = Coord.Pt( Coord.Dim(8.0, self.COMMON_UNITS), Coord.Dim(4.0, self.COMMON_UNITS), ) with SVGWriter.SVGGroup(theSvg, {'id': 'scaleGroup'}): with SVGWriter.SVGText(theSvg, myPointP, None, None, myAttrs): theSvg.characters('Select scale (bold selected):') myAttrs['text-decoration'] = "underline" myPointP = Coord.newPt(myPointP, incX=Coord.Dim(64, 'mm'), incY=None) for scale in self.SCALE_FACTORS: myAttrs['onclick'] = "scaleGraphic(%s, '%s')" % (scale, scale) myAttrs['id'] = str(scale) if scale == self._scale: myAttrs['font-weight'] = 'bold' else: myAttrs['font-weight'] = 'normal' text = '%d%%' % int(scale * 100) with SVGWriter.SVGText(theSvg, myPointP, None, None, myAttrs): theSvg.characters(text) myPointP = Coord.newPt(myPointP, incX=Coord.Dim(5 * len(text), 'mm'), incY=None) def plotInitialise(self, theSvg, theDatumL, theTpt): """Plot the histogram legend once only.""" self.commentFunctionBegin(theSvg) # self._plotHistogramLegend(theSvg, theTpt) self.commentFunctionEnd(theSvg) def plotFinalise(self, theSvg, theDatumL, theTpt): """Finish the plot. In this case we write the text overlays.""" # Plot all text elements so that they are on top self.commentFunctionBegin(theSvg, File=self._fileName) self._writeTriggers(theSvg) self.commentFunctionEnd(theSvg, File=self._fileName) def _writeTriggers(self, theSvg): """Write the rectangles that trigger pop-up text last so that they are on top.""" for aChild in self._children: aChild._writeTriggers(theSvg) for _pt, _box, _attrs in self._triggerS: with SVGWriter.SVGRect(theSvg, _pt, _box, _attrs): pass def _plotSelf(self, theSvg, theDatumL, theTpt, thePassNum, idStack): """Plot me to a stream at the logical datum point""" assert (not self.isRoot) if thePassNum == 0: self.commentFunctionBegin(theSvg, File=self._fileName, Node=self.nodeName, Pass=thePassNum) # Plot self if self.condCompState: if self.numTokens > 0: myAttrs = self.ATTRS_NODE_NORMAL else: myAttrs = self.ATTRS_NODE_MT else: myAttrs = self.ATTRS_NODE_CONDITIONAL if self._bb.hasSetArea: # Plot my box at: myBoxDatumP = theTpt.boxDatumP( self._bb.plotPointSelf(theDatumL), self._bb.box) with SVGWriter.SVGRect( theSvg, myBoxDatumP, theTpt.boxP(self._bb.box), myAttrs, ): pass self._plotSelfInternals(theSvg, theDatumL, theTpt) elif thePassNum == 1: self._plotTextOverlay(theSvg, theDatumL, theTpt, idStack) self.commentFunctionEnd(theSvg, File=self._fileName, Node=self.nodeName, Pass=thePassNum) def plotRoot(self, theSvg, theDatumL, theTpt, passNum): if passNum == 1: self._plotHistogramLegend(theSvg, theTpt) def _plotSelfToChildren(self, theSvg, theDatumL, theTpt): """Plot links from me to my children to a stream at the (self) logical datum point.""" assert (len(self._children) > 0) assert (not self.isRoot) #print 'TRACE: plotSelfToChildren()', theDatumL myDatumL = self._bb.plotPointSelf(theDatumL) #nameP = self.nodeName for i, datumChildL in self._enumerateChildren(theDatumL, theTpt): if self._children[i].condCompState: if self._children[i].numTokens > 0: myAttrsTo = self.ATTRS_LINE_NORMAL_TO myAttrsFrom = self.ATTRS_LINE_NORMAL_FROM else: myAttrsTo = self.ATTRS_LINE_MT_TO myAttrsFrom = self.ATTRS_LINE_MT_FROM else: myAttrsTo = self.ATTRS_LINE_CONDITIONAL_TO myAttrsFrom = self.ATTRS_LINE_CONDITIONAL_FROM #nameC = self._children[i]._dataMap['name'] if theTpt.positiveSweepDir: childOrd = len(self._children) - i - 1 else: childOrd = i # Parent to child linePtsFirst = [ theTpt.pt(l) for l in ( self._bb.pcRoll(myDatumL, childOrd), self._bb.pcTo(myDatumL, childOrd), self._children[i].bb.pcLand(datumChildL), self._children[i].bb.pcStop(datumChildL), ) ] # Now child to parent linePtsSecond = [ theTpt.pt(l) for l in ( self._children[i].bb.cpRoll(datumChildL), self._children[i].bb.cpTo(datumChildL), self._bb.cpLand(myDatumL, childOrd), self._bb.cpStop(myDatumL, childOrd), ) ] if theTpt.positiveSweepDir: linePtsSecond, linePtsFirst = linePtsFirst, linePtsSecond j = 1 #theSvg.comment(' %s to %s ' % (nameP, nameC)) while j < len(linePtsFirst): with SVGWriter.SVGLine( theSvg, linePtsFirst[j - 1], linePtsFirst[j], myAttrsTo, ): pass j += 1 j = 1 #theSvg.comment(' %s to %s ' % (nameC, nameP)) while j < len(linePtsSecond): with SVGWriter.SVGLine( theSvg, linePtsSecond[j - 1], linePtsSecond[j], myAttrsFrom, ): pass j += 1 def _plotRootChildToChild(self, theSvg, theDatumL, theTpt): """Join up children of root node with vertical lines.""" assert (len(self._children) > 0) assert (self.isRoot) self.commentFunctionBegin(theSvg) ptNextL = None for i, datumChildL in self._enumerateChildren(theDatumL, theTpt): if i > 0: ptPrevL = theTpt.prevdcL( self._children[i].bb.plotPointSelf(datumChildL), self._children[i].bb.box, ) with SVGWriter.SVGLine( theSvg, theTpt.pt(ptNextL), theTpt.pt(ptPrevL), self.ATTRS_LINE_ROOT_CHILDREN_JOIN, ): pass ptNextL = theTpt.nextdcL( self._children[i].bb.plotPointSelf(datumChildL), self._children[i].bb.box, ) self.commentFunctionEnd(theSvg) def _plotSelfInternals(self, theSvg, theDl, theTpt): """Plot structures inside the box and the static text that is the file name.""" # Histograms of token types if self.__mustPlotSelfHistogram(): # First the histogram of just me myHistDl = self._bb.plotPointSelf(theDl) self._plotHistogram(theSvg, myHistDl, theTpt, self.tokenCounter) # Now the histogram of my children if self.__mustPlotChildHistogram(): # Shuffle down a bit myHistDl = Coord.newPt(myHistDl, None, self.HIST_DEPTH) self._plotHistogram(theSvg, myHistDl, theTpt, self._tokenCounterChildren) # Now the Chevron self._plotChevron(theSvg, theDl, theTpt) # The filename as display text (no animation) if not self.isRoot: self._plotFileName(theSvg, theDl, theTpt) def _plotTextOverlay(self, theSvg, theDatumL, theTpt, idStack): """Plots all the text associated with the parent and child. We write the hidden objects first then the visible objects. This is because the hidden objects are controlled onmouseover/onmouseout on the visible objects and they have to be later in the SVG file for this to work.""" self.commentFunctionBegin(theSvg, File=self._fileName, Node=self.nodeName) # TODO: Re-think this as it is non-intuitive. Essentially it should not # matter whether the child or the parent node is plotted first but it is # essential that all the 'hidden' DHTML text for any node is written # first, _then_ the 'visible' text that controls that 'hidden' text. # # Child information first if len(self._children) > 0 and not self.isRoot: self._plotTextOverlayChildren(theSvg, theDatumL, theTpt) # Histogram if self.__mustPlotSelfHistogram(): # Write a single '?' in the middle myHistDl = self._bb.plotPointSelf(theDatumL) self._plotTextOverlayHistogram(theSvg, myHistDl, theTpt) if self.__mustPlotChildHistogram(): # Shuffle down a bit myHistDl = Coord.newPt(myHistDl, None, self.HIST_DEPTH) self._plotTextOverlayHistogram(theSvg, myHistDl, theTpt) if not self.isRoot: self._plotTextOverlayTokenCountTable(theSvg, theDatumL, theTpt) self._plotFileNameStackPopup(theSvg, theDatumL, theTpt, idStack) self.commentFunctionEnd(theSvg, File=self._fileName, Node=self.nodeName) def _plotTextOverlayChildren(self, theSvg, theDatumL, theTpt): """Plot text associated with my children to a stream at the (self) logical datum point.""" assert (len(self._children) > 0) assert (not self.isRoot) self.commentFunctionBegin(theSvg, File=self._fileName) for i, datumChildL in self._enumerateChildren(theDatumL, theTpt): self._plotWhereWhyHow(theSvg, i, datumChildL, theTpt) self.commentFunctionEnd(theSvg, File=self._fileName) def _plotWhereWhyHow(self, theSvg, iChild, theDatumL, theTpt): """Plot description of Where/Why/How inclusion of a single child to a stream at the (self) logical datum point.""" assert (not self.isRoot) assert (len(self._children) > 0) assert (iChild >= 0 and iChild < len(self._children)) self.commentFunctionBegin(theSvg, File=self._fileName) #myDatumL = theDatumL #myDatumL = Coord.newPt(theDatumL, incX=self.FILE_PADDING.prev, incY=None)#self.FILE_PADDING.parent.scale(-1.0)) myDatumL = self._children[iChild].bb.plotPointSelf(theDatumL) # Move logical datum logically 'up' and 'right' by half # myDatumL = Coord.newPt( # myDatumL, # incX=self._children[iChild].bb.width.scale(0.5), # incY=self.FILE_PADDING.parent.scale(-0.5), # ) myAltTxtPointP = theTpt.pt( Coord.newPt( myDatumL, incX=self._children[iChild].bb.width.scale(0.5), incY=self.FILE_PADDING.parent.scale(-0.5), )) altTextS = [] altTextS.append('Where: %s#%d ' \ % (self.nodeName, self._children[iChild].lineNum)) if len(self._children[iChild].condComp) > 0: # altTextS.append( # ' Why: %s since: %s ' \ # % ( # str(self._children[iChild].condCompState), # self._children[iChild].condComp # ) # ) assert self._children[iChild].condCompState altTextS.append( ' Why: %s ' \ % ( self._children[iChild].condComp ) ) else: altTextS.append( ' Why: %s ' \ % (str(self._children[iChild].condCompState) ) ) altTextS.append(' How: #include %s' % ', '.join(self._children[iChild].findLogic)) # self.writeAltTextAndMouseOverText( # theSvg, myPointP, theSvg.id, # self.POPUP_TEXT, altTextS, Coord.Dim(20, 'pt')) triggerBoxP = theTpt.boxP( Coord.Box(self._children[iChild].bb.width, self.FILE_PADDING.parent)) # triggerPointP = theTpt.pt(myDatumL) triggerPointP = theTpt.pt( Coord.newPt( myDatumL, incX=self._children[iChild].bb.width, incY=self.FILE_PADDING.parent.scale(-1.0), )) self.writeAltTextAndMouseOverRect( theSvg, theSvg.id, myAltTxtPointP, altTextS, triggerPointP, triggerBoxP, ) self.commentFunctionEnd(theSvg, File=self._fileName) def _plotTextOverlayHistogram(self, theSvg, theHistDl, theTpt): """Plot the text associated with a histogram.""" myCentreL = Coord.newPt(theHistDl, self._bb.width.scale(0.5), self.HIST_DEPTH.scale(0.5)) myPointP = theTpt.pt(myCentreL) # TODO: The myPointP.x.value + 2, myPointP.y.value - 2 # looks wrong. It is not using theTpt. myAttrs = { 'class' : self.CLASS_RECT_INVIS, 'onmouseover' : "showHistogram(%s, %s)" \ % (myPointP.x.value + 3, myPointP.y.value + 2), 'onmouseout' : "hideHistogram()", } myWidth = self._bb.width myBox = Coord.Box(myWidth, self.HIST_DEPTH) with SVGWriter.SVGRect(theSvg, theTpt.boxDatumP(theHistDl, myBox), theTpt.boxP(myBox), myAttrs): pass def _fileNamePoint(self, theDatumL, theTpt): """Returns the point to plot the file name or None.""" if self._bb.hasSetArea: myDatumL = self._bb.plotPointSelf(theDatumL) textPointL = theTpt.tdcL(myDatumL, self._bb.box) # Logical increment to previous child is 'logical left' i.e. -x textPointL = Coord.newPt(textPointL, incX=self.FILE_PADDING.prev.scale(0.5), incY=None) return theTpt.pt(textPointL) def _fileIdStackToListOfStr(self, theIdStack): """Given a list of alternating file names and line numbers such as: ['root', 3, foo.h, 7, bar.h] this returns a list of strings thus: ['root#3, 'foo.h#7, 'bar.h']""" # Create file stack myAltS = [] i = 0 while i < len(theIdStack): if i + 1 < len(theIdStack): myAltS.append('%s#%d' % (theIdStack[i], theIdStack[i + 1])) else: myAltS.append(theIdStack[i]) i += 2 return myAltS def _plotFileName(self, theSvg, theDatumL, theTpt): """Writes out the file name adjacent to the file box as static text.""" self.commentFunctionBegin(theSvg, File=self._fileName) if self._bb.hasSetArea: textPointP = self._fileNamePoint(theDatumL, theTpt) assert textPointP is not None myAttrs = { 'class': self.CLASS_VERDANA_12, 'opacity': '1.0', } with SVGWriter.SVGText(theSvg, textPointP, None, None, myAttrs): theSvg.characters(os.path.basename(self.nodeName)) self.commentFunctionEnd(theSvg, File=self._fileName) def _plotFileNameStackPopup(self, theSvg, theDatumL, theTpt, idStack): """Writes out the file name at the top with a pop-up with the absolute path.""" self.commentFunctionBegin(theSvg, File=self._fileName) if self._bb.hasSetArea: textPointP = self._fileNamePoint(theDatumL, theTpt) assert textPointP is not None # Make the trigger box 14 points high to cover the 12 pt text and # 12pts per char in the triggerBoxP = Coord.Box( Coord.baseUnitsDim(12 * len(os.path.basename(self.nodeName))), Coord.baseUnitsDim(14)) triggerPointP = Coord.newPt( textPointP, triggerBoxP.width.scale(-0.5), triggerBoxP.depth.scale(-0.5), ) self.writeAltTextAndMouseOverRect( theSvg, theSvg.id, textPointP, self._fileIdStackToListOfStr(idStack), triggerPointP, triggerBoxP, ) self.commentFunctionEnd(theSvg, File=self._fileName) def _plotTextOverlayTokenCountTable(self, theSvg, theDatumL, theTpt): """Plots the token count table as text+alternate text.""" assert (not self.isRoot) self.commentFunctionBegin(theSvg, File=self._fileName) myDatumL = self._bb.plotPointSelf(theDatumL) # Move to center myDatumL = Coord.newPt( myDatumL, incX=self._bb.width, #.scale(0.5), incY=None) # incY=self._bb.depth.scale(0.5)) triggerBoxL = self._bb.box if self.__mustPlotSelfHistogram(): myDatumL = Coord.newPt(myDatumL, None, self.HIST_DEPTH) triggerBoxL = Coord.Box(triggerBoxL.width, triggerBoxL.depth - self.HIST_DEPTH) if self.__mustPlotChildHistogram(): myDatumL = Coord.newPt(myDatumL, None, self.HIST_DEPTH) triggerBoxL = Coord.Box(triggerBoxL.width, triggerBoxL.depth - self.HIST_DEPTH) myDatumP = theTpt.pt(myDatumL) altTextS = self._altTextsForTokenCount() self.writeAltTextAndMouseOverRect( theSvg, theSvg.id, myDatumP, altTextS, myDatumP, theTpt.boxP(triggerBoxL), ) self.commentFunctionEnd(theSvg, File=self._fileName) def _altTextsForTokenCount(self): """Returns a list of strings that are the alternate text for token counts.""" assert (not self.isRoot) if len(self._children) > 0: myCounterTotal = PpTokenCount.PpTokenCount() myCounterTotal += self.tokenCounter myCounterTotal += self.tokenCounterChildren FIELD_WIDTH = 7 myTokTypeS = [t[0] for t in self.HIST_PP_TOKEN_TYPES_COLOURS] typeLen = max([len(t) for t in myTokTypeS]) altTextS = [] if len(self._children) > 0: altTextS.append('%*s %*s [%*s] %*s [%*s] %*s [%*s]' \ % (typeLen, 'Type', FIELD_WIDTH, 'Me', FIELD_WIDTH, 'Me', FIELD_WIDTH, 'Child', FIELD_WIDTH, 'Child', FIELD_WIDTH, 'All', FIELD_WIDTH, 'All', ) ) else: altTextS.append('%*s %*s [%*s]' \ % (typeLen, 'Type', FIELD_WIDTH, 'Me', FIELD_WIDTH, 'Me', ) ) # cntrAll = cntrSig = 0 cntrTotalS = [ 0, ] * 6 for t in myTokTypeS: cntrTotalS[0] += self.tokenCounter.tokenCount(t, isAll=True) cntrTotalS[1] += self.tokenCounter.tokenCount(t, isAll=False) line = '%*s %*d [%*d]' \ % (typeLen, t, FIELD_WIDTH, self.tokenCounter.tokenCount(t, isAll=True), FIELD_WIDTH, self.tokenCounter.tokenCount(t, isAll=False), ) if len(self._children) > 0: line += ' %*d [%*d] %*d [%*d]' \ % (FIELD_WIDTH, self.tokenCounterChildren.tokenCount(t, isAll=True), FIELD_WIDTH, self.tokenCounterChildren.tokenCount(t, isAll=False), FIELD_WIDTH, myCounterTotal.tokenCount(t, isAll=True), FIELD_WIDTH, myCounterTotal.tokenCount(t, isAll=False), ) cntrTotalS[2] += self.tokenCounterChildren.tokenCount( t, isAll=True) cntrTotalS[3] += self.tokenCounterChildren.tokenCount( t, isAll=False) cntrTotalS[4] += myCounterTotal.tokenCount(t, isAll=True) cntrTotalS[5] += myCounterTotal.tokenCount(t, isAll=False) altTextS.append(line) line = '%*s %*d [%*d]' \ % (typeLen, 'Total', FIELD_WIDTH, cntrTotalS[0], FIELD_WIDTH, cntrTotalS[1], ) if len(self._children) > 0: line += ' %*d [%*d] %*d [%*d]' \ % (FIELD_WIDTH, cntrTotalS[2], FIELD_WIDTH, cntrTotalS[3], FIELD_WIDTH, cntrTotalS[4], FIELD_WIDTH, cntrTotalS[5], ) altTextS.append(line) return altTextS def __mustPlotSelfHistogram(self): return self.tokenCounter.tokenCountNonWs(isAll=False) > 0 def __mustPlotChildHistogram(self): return self.__mustPlotSelfHistogram() \ and len(self._children) > 0 \ and self._numChildSigTokens > 0 def _plotHistogram(self, theSvg, theHistDl, theTpt, theTokCounter): myTokCountTotal = theTokCounter.totalAllUnconditional # Avoid divide by zero errors assert (theTokCounter.tokenCountNonWs(isAll=False) > 0) assert (myTokCountTotal > 0) #myPos = Coord.Dim(0, self.COMMON_UNITS) myHistDl = theHistDl #self._bb.plotPointSelf(theDl) for k, myFill in self.HIST_PP_TOKEN_TYPES_COLOURS: tCount = theTokCounter.tokenCount(k, isAll=False) if tCount > 0: myWidth = self._bb.width.scale(tCount / (1.0 * myTokCountTotal)) myBox = Coord.Box(myWidth, self.HIST_DEPTH) # Convert to physical and plot with SVGWriter.SVGRect( theSvg, theTpt.boxDatumP(myHistDl, myBox), theTpt.boxP(myBox), { 'fill': myFill, 'stroke': self.HIST_RECT_COLOUR_STROKE, 'stroke-width': self.HIST_RECT_STROKE_WIDTH, }, ): pass # Increment the datum myHistDl = Coord.newPt(myHistDl, incX=myWidth, incY=None) def _plotHistogramLegend(self, theSvg, theTpt): """Plot a standardised legend. This is plotted as a group within a defs.""" myDatumP = Coord.Pt( Coord.Dim(0.0, self.COMMON_UNITS), Coord.Dim(0.0, self.COMMON_UNITS), ) with SVGWriter.SVGGroup(theSvg, { 'id': self.HIST_LEGEND_ID, 'opacity': '0.0' }): idVal = 0 # Outline rectangle with SVGWriter.SVGRect( theSvg, myDatumP, Coord.Box( Coord.Dim(48.0, self.COMMON_UNITS), Coord.Dim(40.0, self.COMMON_UNITS), ), { 'fill': self.ALT_RECT_FILL, 'id': '%d' % idVal, }, ): idVal += 2 myDatumP = Coord.newPt( myDatumP, incX=Coord.Dim(2.0, self.COMMON_UNITS), incY=Coord.Dim(2.0, self.COMMON_UNITS), ) myTokIdxS = list(range(len(self.HIST_PP_TOKEN_TYPES_COLOURS))) if theTpt.positiveSweepDir: myTokIdxS.reverse() for iC in myTokIdxS: myBox = Coord.Box(self.HIST_DEPTH, self.HIST_DEPTH) # Convert to physical and plot with SVGWriter.SVGRect( theSvg, myDatumP, myBox, { 'fill': self.HIST_PP_TOKEN_TYPES_COLOURS[iC][1], 'stroke': self.HIST_RECT_COLOUR_STROKE, 'stroke-width': self.HIST_RECT_STROKE_WIDTH, 'id': '%d' % idVal }, ): idVal += 2 myTextDatumP = Coord.newPt( myDatumP, incX=self.HIST_DEPTH + Coord.Dim(2.0, self.COMMON_UNITS), incY=self.HIST_DEPTH.scale(0.5), ) with SVGWriter.SVGText( theSvg, myTextDatumP, None, None, { 'font-family': 'Verdana', 'font-size': '10', 'dominant-baseline': 'middle', 'id': '%d' % idVal, }): theSvg.characters(self.HIST_PP_TOKEN_TYPES_COLOURS[iC][0]) idVal += 2 # Increment the datum myDatumP = Coord.newPt(myDatumP, incX=None, incY=self.HIST_DEPTH) def _plotChevron(self, theSvg, theDl, theTpt): """Plots a wedge to represent the relative number of tokens in me and my children. D------------------.------------------| | | |------------------.------------------| | | A-----------B------.------D-----------| | \ . / | | \ . / | | \ . / | | \ . / | | \ . / | ------------------\C/------------------ We plot in the order D moveto A moveto B lineto C lineto D lineto B """ mySelfTokCount = self.tokenCounter.tokenCountNonWs(isAll=False) if mySelfTokCount == 0 and self._numChildSigTokens == 0: return # Find D myDl = self._bb.plotPointSelf(theDl) # Point C, all use this myPtC = Coord.newPt(myDl, self._bb.width.scale(0.5), self._bb.depth) # Increment by one or two histogram depths to point A if self.__mustPlotSelfHistogram(): myDl = Coord.newPt(myDl, None, self.HIST_DEPTH) if self.__mustPlotChildHistogram(): myDl = Coord.newPt(myDl, None, self.HIST_DEPTH) # Figure out move to B if self._numChildSigTokens == 0: # Chevron takes full width polyLogicalPtS = [ myDl, myPtC, Coord.newPt(myDl, self._bb.width, None), ] else: ratioChevron = 1.0 * mySelfTokCount / (self._numChildSigTokens + mySelfTokCount) myChevronOffset = self._bb.width.scale(0.5 * ratioChevron) #theSvg.comment(' Chevron offset: %s ' % str(myChevronOffset)) # Offset to B myDl = Coord.newPt(myDl, self._bb.width.scale(0.5) - myChevronOffset, None) polyLogicalPtS = [ myDl, myPtC, Coord.newPt(myDl, myChevronOffset.scale(2.0), None), ] polyPhysicalPtS = [theTpt.pt(p) for p in polyLogicalPtS] #theSvg.comment(' \npolyPhysicalPtS: %s \n' % str([str(p) for p in polyPhysicalPtS])) j = 1 while j < len(polyPhysicalPtS): with SVGWriter.SVGLine( theSvg, polyPhysicalPtS[j - 1], polyPhysicalPtS[j], { 'stroke-width': "2", 'stroke': "black", }, ): pass j += 1 # def writeAltTextAndMouseOverText( # self, theSvg, thePoint, theId, theText, theAltS, yOffs): # """Writes out the alternate text to the SVG stream immediately and returns # a tuple of (thePoint, 'Verdana', 12, myAttrs, theText) to write out later # (so it is on top).""" # self._writeAlternateText(theSvg, thePoint, theId, theText, theAltS, yOffs) # # <text id="original" font-family="Verdana" font-size="12" text-anchor="middle" x="250" y="250">Original text.</text> # myAttrs = { # 'id' : 't%s' % theId, # 'class' : self.CLASS_VERDANA_12, # 'opacity' : '1.0', # # #'font-weight' : "bold", # # 'text-decoration' : "underline", # # 'text-anchor' : "middle", # # 'dominant-baseline' : "middle", # # #'dominant-baseline' : "text-after-edge", # 'onmouseover' : "swapOpacity('t%s', 't%s')" \ # % (theId, theId+self.ALT_ID_SUFFIX), # 'onmouseout' : "swapOpacity('t%s', 't%s')" \ # % (theId, theId+self.ALT_ID_SUFFIX), # } # # self._addECMAScriptMouseOverAttrs(myAttrs, theId, theId+self.ALT_ID_SUFFIX) # self._triggerText.append((thePoint, None, None, myAttrs, theText)) def writeAltTextAndMouseOverRect(self, theSvg, theId, theAltPt, theAltS, theTrigPt, theTrigRect): """Composes and writes the (pop-up) alternate text. Also writes a trigger rectangle.""" # Write a grouping element and give it the alternate ID with SVGWriter.SVGGroup(theSvg, { 'id': 't%s%s' % (theId, self.ALT_ID_SUFFIX), 'opacity': '0.0' }): altFontSize = self.ALT_FONT_PROPERTIES[ self.ALT_FONT_FAMILY]['size'] altFontLenFactor = self.ALT_FONT_PROPERTIES[ self.ALT_FONT_FAMILY]['lenFactor'] altFontHeightFactor = self.ALT_FONT_PROPERTIES[ self.ALT_FONT_FAMILY]['heightFactor'] # Compute masking box for alternate maxChars = max([len(s) for s in theAltS]) # Take around 80% of character length boxWidth = Coord.Dim(altFontSize * maxChars * altFontLenFactor, 'pt') if len(theAltS) < 2: boxHeight = Coord.Dim(altFontSize * 2, 'pt') else: boxHeight = Coord.Dim( altFontSize * len(theAltS) * altFontHeightFactor, 'pt') boxAttrs = {'fill': self.ALT_RECT_FILL} with SVGWriter.SVGRect( theSvg, theAltPt, Coord.Box(boxWidth, boxHeight), boxAttrs, ): pass # As the main text is centered and the alt text is left # justified we need to move the text plot point left by a bit. myAltTextPt = Coord.newPt( theAltPt, incX=Coord.Dim(1 * altFontSize * 3 * altFontLenFactor / 2.0, 'pt'), incY=Coord.Dim(12, 'pt'), ) with SVGWriter.SVGText(theSvg, myAltTextPt, 'Courier', altFontSize, { 'font-weight': "normal", }): self._writeStringListToTspan(theSvg, myAltTextPt, theAltS) # Add the trigger rectangle for writing on finalise boxAttrs = { 'class' : self.CLASS_RECT_INVIS, 'id' : 't%s' % theId, 'onmouseover' : "swapOpacity('t%s', 't%s')" \ % (theId, theId+self.ALT_ID_SUFFIX), 'onmouseout' : "swapOpacity('t%s', 't%s')" \ % (theId, theId+self.ALT_ID_SUFFIX), } self._triggerS.append((theTrigPt, theTrigRect, boxAttrs))
def plotToFileObj(self, theFileObj, theTpt): """Root level call to plot to a file object. The SVG stream is created here.""" if self._numPassesToPlotSelf < 1: raise ValueError('No self._numPassesToPlotSelf set!') # Make viewBox user coordinates * self.VIEWBOX_SCALE myRootAttrs = { # 'viewBox' : '0 0 %d %d' \ # % ( # theTpt.canvasP().width.value * self.VIEWBOX_SCALE, # theTpt.canvasP().depth.value * self.VIEWBOX_SCALE, # ), 'xmlns:xlink' : self.NAMESPACE_XLINK, } # Bit of a hacky way to add enough margin for the pop-ups or rather # drop downs. This adds space for the bottom most boxes. canvasY = theTpt.canvasP().depth + Coord.Dim(60, 'mm') + Coord.Dim(8, 'mm') myCanvas = Coord.Box( theTpt.canvasP().width + Coord.Dim(60, 'mm'), canvasY, ) # Shrink canvas if it is a large plot yOffsetForScalingText = Coord.Dim(10, 'mm') scaleIdx = self.SCALE_FACTORS.index(1) assert scaleIdx >= 0 while scaleIdx > 0 and canvasY > self.SCALE_MAX_Y: canvasY = canvasY.scale(0.5) scaleIdx -= 1 self._scale = self.SCALE_FACTORS[scaleIdx] with SVGWriter.SVGWriter(theFileObj, myCanvas, myRootAttrs, mustIndent=cpip.INDENT_ML) as myS: # yOffsetForScalingText is applied wrong, should respect theTpt myDatum = Coord.Pt( CANVAS_PADDING.prev - yOffsetForScalingText, CANVAS_PADDING.parent, ) self.writePreamble(myS) myS.comment(' Root position: %s, Sweep direction: %s canvas=%s datum=%s' \ % (theTpt.rootPos, theTpt.sweepDir, theTpt.canvasP(), myDatum), newLine=True) # Shift the drawing down a bit to make way for the scale buttons. with SVGWriter.SVGGroup(myS, {'transform' : "translate(0, 24)"}): with SVGWriter.SVGGroup(myS, { 'id' : 'graphic', 'transform' : "scale(%s)" % self.SCALE_FACTORS[scaleIdx] }): # Apply a group element for scaling the plot # More hackery: yOffsetForScalingText is applied wrong, should respect theTpt with SVGWriter.SVGRect( myS, Coord.newPt( Coord.zeroBaseUnitsPt(), incX=None, incY=yOffsetForScalingText), theTpt.canvasP(), { 'fill' : 'none', 'stroke' : 'grey', 'stroke-width' : '2', }, ): pass # Start the plot self.plotInitialise(myS, myDatum, theTpt) # Now plot all contents for p in range(self._numPassesToPlotSelf): self.plotToSVGStream(myS, myDatum, theTpt, p, []) # Finish the plot self.plotFinalise(myS, myDatum, theTpt)
def testIsub_00(self): """Dim() -=.""" myObj_0 = Coord.Dim(1, 'in') myObj_0 -= Coord.Dim(18, 'px') self.assertEqual(myObj_0.value, 0.75) self.assertEqual(myObj_0.units, 'in')
def testIadd_01(self): """Dim() += when initial units are None.""" myObj_0 = Coord.Dim(12, None) myObj_0 += Coord.Dim(18, 'px') self.assertEqual(myObj_0.value, 30) self.assertEqual(myObj_0.units, 'px')
def testStr(self): """Dim() __str__().""" myObj = Coord.Dim(12, 'px') self.assertEqual(str(myObj), 'Dim(12px)')
def testConstructor(self): """Dim() constructor.""" myObj = Coord.Dim(12, 'px') #print myObj self.assertEqual(myObj.value, 12) self.assertEqual(myObj.units, 'px')
def testMin_00(self): """Dim() min(...) [00].""" myObj_0 = Coord.Dim(71, 'px') myObj_1 = Coord.Dim(72, 'px') self.assertEqual(myObj_0, min(myObj_0, myObj_1)) self.assertNotEqual(myObj_1, min(myObj_0, myObj_1))
def testCmp_01(self): """Dim() cmp() [01].""" myObj_0 = Coord.Dim(1, 'in') myObj_1 = Coord.Dim(1, 'in') self.assertEqual(myObj_0, myObj_1) self.assertTrue(myObj_0 == myObj_1)
import sys import inspect import pprint import cpip from cpip.core import FileIncludeGraph #from cpip.core import PpTokenCount from cpip.util import XmlWrite from cpip.plot import Coord #from cpip.plot import PlotNode from cpip.plot import SVGWriter from cpip.plot import TreePlotTransform CANVAS_PADDING = Coord.Pad( Coord.Dim(4.0, 'mm'), # prev i.e. left Coord.Dim(4.0, 'mm'), # next i.e. right Coord.Dim(4.0, 'mm'), # parent i.e. top Coord.Dim(4.0, 'mm'), # child i.e. bottom ) def processIncGraphToSvg(theLex, theFilePath, theClass, tptPos, tptSweep): """Convert a Include graph from a PpLexer to SVG in theFilePath.""" myVis = FileIncludeGraph.FigVisitorTree(theClass) theLex.fileIncludeGraphRoot.acceptVisitor(myVis) # Tree is now a graph of: theClass myIgs = myVis.tree() # Pad the canvass myWidth = CANVAS_PADDING.prev \ + myIgs.plotCanvas.width \ + CANVAS_PADDING.next
class SVGTreeNodeBase(FileIncludeGraph.FigVisitorTreeNodeBase): COMMON_UNITS = 'mm' # User units for viewBox and ploygon UNNAMED_UNITS = 'px' VIEWBOX_SCALE = 8.0 #=============================================================================== # CANVAS_PADDING = Coord.Pad( # Coord.Dim(4.0, COMMON_UNITS), # prev i.e. left # Coord.Dim(4.0, COMMON_UNITS), # next i.e. right # Coord.Dim(4.0, COMMON_UNITS), # parent i.e. top # Coord.Dim(4.0, COMMON_UNITS), # child i.e. bottom # ) #=============================================================================== # Attributes for alternate text ALT_RECT_FILL = 'khaki'#'yellow' ALT_ID_SUFFIX = '.alt' ALT_FONT_FAMILY = 'monospace' ALT_FONT_PROPERTIES = { 'Courier' : { 'size' : 10, 'lenFactor' : 0.5, 'heightFactor' : 1.2, }, 'monospace' : { 'size' : 10, 'lenFactor' : 0.5, 'heightFactor' : 1.2, }, } NAMESPACE_XLINK = 'http://www.w3.org/1999/xlink' # Used to rescale SVG rather than zooming in the browser as the latter is # slow with Chrome and Safari (both WebKit) and pretty much everything else. # Initial, presentational, scale is chose depending on the size of the diagram. SCALE_FACTORS = (0.05, 0.1, 0.25, 0.5, 1.0, 1.5, 2.0,) # Used to decide initial scale SCALE_MAX_Y = Coord.Dim(1000, 'mm') # POPUP_COORD_Y_OFFSET = Coord.Dim(20, 'pt') def __init__(self, theFig, theLineNum): super(SVGTreeNodeBase, self).__init__(theLineNum) self._isRoot = theFig is None if theFig is None: # Root node, children only self._fileName = None # This does not include children self._numTokens = -1 self._condCompState = None else: self._fileName = theFig.fileName # This is significant tokens only and does not include children self._numTokens = theFig.numTokensSig self._condCompState = theFig.condCompState # The bounding box, to be (re)set by derived classes self._bb = None self.TRACE = cpip.SVG_COMMENT_FUNCTIONS # Number of passes to call plotSelf() self._numPassesToPlotSelf = 0 #============================================ # Section: Trace/Debug #============================================ def dumpToStream(self, theS=sys.stdout, p=''): """Debug/trace.""" theS.write('%sFile: %s\n' % (p, self.nodeName)) for aLine in str(self.bb).splitlines(): theS.write('%s%s\n' % (p, aLine)) for aChild in self._children: aChild.dumpToStream(theS, p+' ') def commentFunctionBegin(self, theSvg, **kwargs): """Injects a comment into the SVG with the start of the executing function name.""" if self.TRACE: theSvg.comment(' %s(): %s %s' % (inspect.stack()[1][3], 'BEGIN', pprint.pformat(kwargs)), newLine=True) def commentFunctionEnd(self, theSvg, **kwargs): """Injects a comment into the SVG with the completion of the executing function name.""" if self.TRACE: theSvg.comment(' %s(): %s %s' % (inspect.stack()[1][3], 'END', pprint.pformat(kwargs)), newLine=True) #============================================ # End: Trace/Debug #============================================ #============================================ # Section: Accessor methods used by ancestors #============================================ @property def isRoot(self): return self._isRoot @property def numTokens(self): """The number of significant tokens for me only (not including children).""" return self._numTokens @property def nodeName(self): """This is the file name or 'Root'.""" if self.isRoot: return 'Root' return self._fileName @property def condCompState(self): """True/False if conditionally compiled node.""" assert(not self.isRoot) return self._condCompState @property def bb(self): """Returns a PlotNode.PlotNodeBboxBoxy() object for this node.""" assert(self._bb is not None) return self._bb @property def plotCanvas(self): """The logical size of the plot canvas as a Coord.Box().""" return self.bb.bbSigma #======================================== # End: Accessor methods used by ancestors #======================================== #=================================== # Section: Finalisation and plotting #=================================== def finalise(self): """This will be called on finalisation. For depth first finalisation the child class should call finalise on each child first.""" raise NotImplementedError('finalise() not implemented') def plotToFilePath(self, theFileName, theTpt): """Root level call to plot to a SVG file, theTpt is an TreePlotTransform object and is used to transform the internal logical coordinates to physical plot positions.""" self.plotToFileObj(open(theFileName, 'w'), theTpt) def plotToFileObj(self, theFileObj, theTpt): """Root level call to plot to a file object. The SVG stream is created here.""" if self._numPassesToPlotSelf < 1: raise ValueError('No self._numPassesToPlotSelf set!') # Make viewBox user coordinates * self.VIEWBOX_SCALE myRootAttrs = { # 'viewBox' : '0 0 %d %d' \ # % ( # theTpt.canvasP().width.value * self.VIEWBOX_SCALE, # theTpt.canvasP().depth.value * self.VIEWBOX_SCALE, # ), 'xmlns:xlink' : self.NAMESPACE_XLINK, } # Bit of a hacky way to add enough margin for the pop-ups or rather # drop downs. This adds space for the bottom most boxes. canvasY = theTpt.canvasP().depth + Coord.Dim(60, 'mm') + Coord.Dim(8, 'mm') myCanvas = Coord.Box( theTpt.canvasP().width + Coord.Dim(60, 'mm'), canvasY, ) # Shrink canvas if it is a large plot yOffsetForScalingText = Coord.Dim(10, 'mm') scaleIdx = self.SCALE_FACTORS.index(1) assert scaleIdx >= 0 while scaleIdx > 0 and canvasY > self.SCALE_MAX_Y: canvasY = canvasY.scale(0.5) scaleIdx -= 1 self._scale = self.SCALE_FACTORS[scaleIdx] with SVGWriter.SVGWriter(theFileObj, myCanvas, myRootAttrs, mustIndent=cpip.INDENT_ML) as myS: # yOffsetForScalingText is applied wrong, should respect theTpt myDatum = Coord.Pt( CANVAS_PADDING.prev - yOffsetForScalingText, CANVAS_PADDING.parent, ) self.writePreamble(myS) myS.comment(' Root position: %s, Sweep direction: %s canvas=%s datum=%s' \ % (theTpt.rootPos, theTpt.sweepDir, theTpt.canvasP(), myDatum), newLine=True) # Shift the drawing down a bit to make way for the scale buttons. with SVGWriter.SVGGroup(myS, {'transform' : "translate(0, 24)"}): with SVGWriter.SVGGroup(myS, { 'id' : 'graphic', 'transform' : "scale(%s)" % self.SCALE_FACTORS[scaleIdx] }): # Apply a group element for scaling the plot # More hackery: yOffsetForScalingText is applied wrong, should respect theTpt with SVGWriter.SVGRect( myS, Coord.newPt( Coord.zeroBaseUnitsPt(), incX=None, incY=yOffsetForScalingText), theTpt.canvasP(), { 'fill' : 'none', 'stroke' : 'grey', 'stroke-width' : '2', }, ): pass # Start the plot self.plotInitialise(myS, myDatum, theTpt) # Now plot all contents for p in range(self._numPassesToPlotSelf): self.plotToSVGStream(myS, myDatum, theTpt, p, []) # Finish the plot self.plotFinalise(myS, myDatum, theTpt) def writePreamble(self, theS): """Write any preamble such as CSS or JavaScript. To be implemented by child classes.""" raise NotImplementedError def plotInitialise(self, theSvg, theDatumL, theTpt): """Called once immediately before the recursive plotToSVGStream(). Can be overridden in child classes for specific use.""" pass def plotFinalise(self, theSvg, theDatumL, theTpt): """Called once immediately before the plot is closed. Can be overridden in child classes for specific use.""" pass def plotToSVGStream(self, theSvg, theDatumL, theTpt, passNum, idStack): """Plot me to a stream and my children at the logical datum point, this is a recursive call.""" self.commentFunctionBegin(theSvg, File=self._fileName, Pass=passNum) if not self.isRoot: if self.lineNum != -1: idStack.append(self.lineNum) idStack.append(self.nodeName) if len(self._children) > 0: if passNum == 0: if self.isRoot: self._plotRootChildToChild(theSvg, theDatumL, theTpt) else: self._plotSelfToChildren(theSvg, theDatumL, theTpt) # Recursive call # TODO: Consider reversing this so that drop-downs appear over # children. # for i, datumChildL in self._enumerateChildren(theDatumL, theTpt): for i, datumChildL in reversed(list(self._enumerateChildren(theDatumL, theTpt))): self._children[i].plotToSVGStream(theSvg, datumChildL, theTpt, passNum, idStack) # Plot me last so I sit over any me-to-child lines if not self.isRoot: self._plotSelf(theSvg, theDatumL, theTpt, passNum, idStack) else: self.plotRoot(theSvg, theDatumL, theTpt, passNum) if not self.isRoot: if self.lineNum != -1: idStack.pop() idStack.pop() self.commentFunctionEnd(theSvg, File=self._fileName, Pass=passNum) def plotRoot(self, theSvg, theDatumL, theTpt, passNum): """Call to plot any root node, for example our child class uses this to plot the histogram legend before starting on the text.""" pass def _plotSelf(self, theSvg, theDatumL, theTpt, idStack): """Plot me to a stream at the logical datum point. Must be provided by child classes.""" raise NotImplementedError('_plotSelf() not implemented') def _plotRootChildToChild(self, theSvg, theDatumL, theTpt): """In the case of me being root this plots child to child.""" assert(self.isRoot) raise NotImplementedError('_plotRootChildToChild() not implemented') def _plotSelfToChildren(self, theSvg, theDatumL, theTpt): """In the case of me being not root this plots me to my children.""" assert(self.isRoot) raise NotImplementedError('_plotSelfToChildren() not implemented') def _enumerateChildren(self, theDatumL, theTpt): """Generates a tuple of (index, logical_datum_point) for my children.""" assert(len(self._children) > 0) datumChildL = theTpt.startChildrenLogicalPos( self._bb.childBboxDatum(theDatumL), self._bb.bbChildren, ) for i, aChild in enumerate(self._children): datumChildL = theTpt.preIncChildLogicalPos(datumChildL, aChild.bb.bbSigma) yield i, datumChildL datumChildL = theTpt.postIncChildLogicalPos(datumChildL, aChild.bb.bbSigma) #============================================= # Section: Writing SVG code to do pop-up text. #============================================= def _writeECMAScript(self, theSvg): """Writes the ECMA script for pop-up text switching.""" myScriptS = [] myScriptS.append(""" function swapOpacity(idFrom, idTo) { var svgFrom = document.getElementById(idFrom); var svgTo = document.getElementById(idTo); var attrFrom = svgFrom.getAttribute("opacity"); var attrTo = svgTo.getAttribute("opacity"); svgTo.setAttributeNS(null, "opacity", attrFrom); svgFrom.setAttributeNS(null, "opacity", attrTo); } function setOpacity(id, value) { var svgElem = document.getElementById(id); svgElem.setAttributeNS(null, "opacity", value); } """) # Pop-up for histogram that uses different technique myScriptS.append(""" function showHistogram(x, y) { var histElem = document.getElementById("HistogramLegend"); // Use the ID to compute the y offset. The x offset is 8.0mm for text, // 2.0mm or 0mm for rect for (var i = 0; i < 38; i += 2) { var elem = histElem.children[i / 2] if (i == 0) { var xOffset = 0; } else if (elem.nodeName == "text") { var xOffset = 8; } else { var xOffset = 2; } elem.setAttributeNS(null, "x", x + xOffset + "mm"); elem.setAttributeNS(null, "y", y + i + "mm"); } histElem.setAttributeNS(null, "opacity", 1.0); } function hideHistogram() { setOpacity("HistogramLegend", 0.0) } """) # Scaling controls myScriptS.append(""" function scaleGraphic(scale, theId) { var graphicGroup = document.getElementById("graphic"); graphicGroup.setAttributeNS(null, "transform", "scale(" + scale + ")"); // Un-bold all then bold the txtId. var scaleGroup = document.getElementById("scaleGroup"); for (var i = 0; i < scaleGroup.children.length; ++i) { var elem = scaleGroup.children[i]; if (elem.id == theId) { elem.setAttributeNS(null, "font-weight", "bold"); } else { elem.setAttributeNS(null, "font-weight", "normal"); } } } """) theSvg.writeECMAScript(u''.join(myScriptS)) def _writeAlternateText(self, theSvg, thePoint, theId, theText, theAltS, yOffs=Coord.Dim(0, 'pt')): """Composes and writes the (pop-up) alternate text. thePoint is the physical point to locate both texts.""" # Write a grouping element and give it the alternate ID with SVGWriter.SVGGroup(theSvg, {'id' : 't%s%s' % (theId, self.ALT_ID_SUFFIX), 'opacity' : '0.0'}): altFontSize = self.ALT_FONT_PROPERTIES[self.ALT_FONT_FAMILY]['size'] altFontLenFactor = self.ALT_FONT_PROPERTIES[self.ALT_FONT_FAMILY]['lenFactor'] altFontHeightFactor = self.ALT_FONT_PROPERTIES[self.ALT_FONT_FAMILY]['heightFactor'] # Compute masking box for alternate maxChars = max([len(s) for s in theAltS]) # Take around 80% of character length boxWidth = Coord.Dim(altFontSize * maxChars * altFontLenFactor, 'pt') if len(theAltS) < 2: boxHeight = Coord.Dim(altFontSize * 2, 'pt') else: boxHeight = Coord.Dim(altFontSize * len(theAltS) * altFontHeightFactor, 'pt') boxAttrs = { 'fill' : self.ALT_RECT_FILL } with SVGWriter.SVGRect( theSvg, # Edge the plot point up and left by a bit Coord.newPt( thePoint, incX=Coord.Dim(-1 * altFontSize * (1 + len(theText) * altFontLenFactor / 2.0), 'pt'), incY=Coord.Dim(-1*altFontHeightFactor * altFontSize, 'pt') + yOffs, ), Coord.Box(boxWidth, boxHeight), boxAttrs, ): pass # As the main text is centered and the alt text is left # justified we need to move the text plot point left by a bit. myAltTextPt = Coord.newPt( thePoint, incX=Coord.Dim(-1 * altFontSize * len(theText) * altFontLenFactor / 2.0, 'pt'), incY=yOffs, ) with SVGWriter.SVGText(theSvg, myAltTextPt, 'Courier', altFontSize, { 'font-weight' : "normal", } ): self._writeStringListToTspan(theSvg, myAltTextPt, theAltS) def _writeStringListToTspan(self, theSvg, thePointX, theList): """Converts a multi-line string to tspan elements in monospaced format. theSvg is the SVG stream. theAttrs is the attributes of the enclosing <text> element. theStr is the string to write. This writes the tspan elements within an existing text element, thus: <text id="original.alt" font-family="Courier" font-size="12" text-anchor="middle" x="250" y="250"> <tspan xml:space="preserve"> One</tspan> <tspan x="250" dy="1em" xml:space="preserve"> Two</tspan> <tspan x="250" dy="1em" xml:space="preserve">Three</tspan> </text> """ #theSvg.xmlSpacePreserve() for i, aLine in enumerate(theList): elemAttrs = {}#'xml:space' : "preserve"} if i > 0: elemAttrs['x'] = SVGWriter.dimToTxt(thePointX.x) elemAttrs['dy'] = "1.5em" with XmlWrite.Element(theSvg, 'tspan', elemAttrs): theSvg.characters(aLine) theSvg.characters(' ')
def testSigma_03(self): """Tests PlotNode() get sigma width and depth (parent is null node).""" myObj = PlotNode.PlotNodeBbox() myObj.width = None self.assertEqual(myObj.width, None) myObj.depth = None self.assertEqual(myObj.depth, None) myPad = Coord.Pad( Coord.Dim(0.5, 'in'), # prev Coord.Dim(0.5, 'in'), # next Coord.Dim(0.5, 'in'), # parent Coord.Dim(0.5, 'in'), # child ) myObj.bbSelfPadding = myPad myObj.bbSpaceChildren = Coord.Dim(0.5, 'in') self.assertEqual(myObj.bbSpaceChildren, Coord.Dim(0.5, 'in')) myObj.bbChildren = Coord.Box( Coord.Dim(4, 'in'), Coord.Dim(2.5, 'in'), ) self.assertEqual( myObj.bbChildrenWidth, Coord.Dim(4, 'in'), ) self.assertEqual( myObj.bbChildrenDepth, Coord.Dim(2.5, 'in'), ) self.assertEqual(myObj.bbChildren, Coord.Box( Coord.Dim(4, 'in'), Coord.Dim(2.5, 'in'), )) self.assertEqual( myObj.bbSigmaWidth, Coord.Dim(4.0, 'in'), ) self.assertEqual( myObj.bbSigmaDepth, Coord.Dim(2.5, 'in'), ) self.assertEqual( myObj.bbSigma, Coord.Box( Coord.Dim(4.0, 'in'), Coord.Dim(2.5, 'in'), ))
def testCentre_02(self): """Tests PlotNode() get the centre point.""" myObj = PlotNode.PlotNodeBbox() myObj.width = Coord.Dim(3, 'in') self.assertEqual(myObj.width, Coord.Dim(3, 'in')) myObj.depth = Coord.Dim(1, 'in') self.assertEqual(myObj.depth, Coord.Dim(1, 'in')) myPad = Coord.Pad( Coord.Dim(0.1, 'in'), # prev Coord.Dim(0.3, 'in'), # next Coord.Dim(0.5, 'in'), # parent Coord.Dim(0.7, 'in'), # child ) myObj.bbSelfPadding = myPad myObj.bbSpaceChildren = Coord.Dim(0.9, 'in') self.assertEqual( myObj.plotPointCentre( Coord.Pt( Coord.Dim(0.0, 'in'), Coord.Dim(0.0, 'in'), ), ), Coord.Pt( Coord.Dim(1.6, 'in'), Coord.Dim(1.0, 'in'), ), )
def testBbChildren_00(self): """TestPlotNodeBboxWithChildren.testBbChildren_00() - three children.""" myObj = PlotNode.PlotNodeBbox() myObj.width = Coord.Dim(12, 'mm') myObj.depth = Coord.Dim(8, 'mm') #print '\nTRACE myObj', myObj myObj.bbSelfPadding = Coord.Pad( Coord.Dim(1, 'mm'), # prev Coord.Dim(3, 'mm'), # next Coord.Dim(5, 'mm'), # parent Coord.Dim(7, 'mm'), # child ) myObj.bbSpaceChildren = Coord.Dim(16, 'mm') self.assertEqual(True, myObj.bbChildrenWidth is None) self.assertEqual(True, myObj.bbChildrenDepth is None) self.assertEqual(0, myObj.numChildren) myObj.extendChildBbox( Coord.Box( Coord.Dim(15, 'mm'), Coord.Dim(7, 'mm'), )) # Child box now w:15, d:7 self.assertEqual(1, myObj.numChildren) self.assertEqual(myObj.bbChildrenWidth, Coord.Dim(15, 'mm')) self.assertEqual(myObj.bbChildrenDepth, Coord.Dim(7, 'mm')) myObj.extendChildBbox( Coord.Box( Coord.Dim(31, 'mm'), Coord.Dim(29, 'mm'), )) # Child box now w:15+31=46, d:max(7,29)=29 self.assertEqual(2, myObj.numChildren) self.assertEqual(myObj.bbChildrenWidth, Coord.Dim(46, 'mm')) self.assertEqual(myObj.bbChildrenDepth, Coord.Dim(29, 'mm')) myObj.extendChildBbox( Coord.Box( Coord.Dim(11, 'mm'), Coord.Dim(9, 'mm'), )) # Child box now w:46+11=57, d:max(29,9)=29 self.assertEqual(3, myObj.numChildren) self.assertEqual(myObj.bbChildrenWidth, Coord.Dim(57, 'mm')) self.assertEqual(myObj.bbChildrenDepth, Coord.Dim(29, 'mm')) # bbSigma: # Width is 57mm as children are wider than me # Depth is 5 + 8 + 7 + 16 + 29 = 65mm #print #print 'myObj.bbSigma:', myObj.bbSigma self.assertEqual(myObj.bbSigma, Coord.Box( Coord.Dim(57, 'mm'), Coord.Dim(65, 'mm'), )) # Set my datum up myD = Coord.Pt( Coord.Dim(135, 'mm'), Coord.Dim(19, 'mm'), ) # x should be 135 + 0.5 * (57-(1+12+3) + 1) = 135 + 0.5 * 41 + 1 = 156.5 #print #print 'myObj.plotPointSelf:', myObj.plotPointSelf(myD) self.assertEqual( myObj.plotPointSelf(myD), Coord.Pt( Coord.Dim(156.5, 'mm'), Coord.Dim(24, 'mm'), )) #childBboxDatum #print #print 'myObj.childBboxDatum:', myObj.childBboxDatum(myD) self.assertEqual(myObj.childBboxDatum(myD), Coord.Pt( Coord.Dim(135, 'mm'), Coord.Dim(55, 'mm'), ))
def _plotHistogramLegend(self, theSvg, theTpt): """Plot a standardised legend. This is plotted as a group within a defs.""" myDatumP = Coord.Pt( Coord.Dim(0.0, self.COMMON_UNITS), Coord.Dim(0.0, self.COMMON_UNITS), ) with SVGWriter.SVGGroup(theSvg, { 'id': self.HIST_LEGEND_ID, 'opacity': '0.0' }): idVal = 0 # Outline rectangle with SVGWriter.SVGRect( theSvg, myDatumP, Coord.Box( Coord.Dim(48.0, self.COMMON_UNITS), Coord.Dim(40.0, self.COMMON_UNITS), ), { 'fill': self.ALT_RECT_FILL, 'id': '%d' % idVal, }, ): idVal += 2 myDatumP = Coord.newPt( myDatumP, incX=Coord.Dim(2.0, self.COMMON_UNITS), incY=Coord.Dim(2.0, self.COMMON_UNITS), ) myTokIdxS = list(range(len(self.HIST_PP_TOKEN_TYPES_COLOURS))) if theTpt.positiveSweepDir: myTokIdxS.reverse() for iC in myTokIdxS: myBox = Coord.Box(self.HIST_DEPTH, self.HIST_DEPTH) # Convert to physical and plot with SVGWriter.SVGRect( theSvg, myDatumP, myBox, { 'fill': self.HIST_PP_TOKEN_TYPES_COLOURS[iC][1], 'stroke': self.HIST_RECT_COLOUR_STROKE, 'stroke-width': self.HIST_RECT_STROKE_WIDTH, 'id': '%d' % idVal }, ): idVal += 2 myTextDatumP = Coord.newPt( myDatumP, incX=self.HIST_DEPTH + Coord.Dim(2.0, self.COMMON_UNITS), incY=self.HIST_DEPTH.scale(0.5), ) with SVGWriter.SVGText( theSvg, myTextDatumP, None, None, { 'font-family': 'Verdana', 'font-size': '10', 'dominant-baseline': 'middle', 'id': '%d' % idVal, }): theSvg.characters(self.HIST_PP_TOKEN_TYPES_COLOURS[iC][0]) idVal += 2 # Increment the datum myDatumP = Coord.newPt(myDatumP, incX=None, incY=self.HIST_DEPTH)
def setUp(self): self._pnbcObj = PlotNode.PlotNodeBboxBoxy() self._pnbcObj.width = Coord.Dim(12, 'mm') self._pnbcObj.depth = Coord.Dim(8, 'mm') #print '\nTRACE self._pnbcObj', self._pnbcObj self._pnbcObj.bbSelfPadding = Coord.Pad( Coord.Dim(1, 'mm'), # prev Coord.Dim(3, 'mm'), # next Coord.Dim(5, 'mm'), # parent Coord.Dim(7, 'mm'), # child ) self._pnbcObj.bbSpaceChildren = Coord.Dim(16, 'mm') self.assertEqual(True, self._pnbcObj.bbChildrenWidth is None) self.assertEqual(True, self._pnbcObj.bbChildrenDepth is None) self.assertEqual(0, self._pnbcObj.numChildren) self._pnbcObj.extendChildBbox( Coord.Box( Coord.Dim(15, 'mm'), Coord.Dim(7, 'mm'), )) # Child box now w:15, d:7 self.assertEqual(1, self._pnbcObj.numChildren) self.assertEqual(self._pnbcObj.bbChildrenWidth, Coord.Dim(15, 'mm')) self.assertEqual(self._pnbcObj.bbChildrenDepth, Coord.Dim(7, 'mm')) self._pnbcObj.extendChildBbox( Coord.Box( Coord.Dim(31, 'mm'), Coord.Dim(29, 'mm'), )) # Child box now w:15+31=46, d:max(7,29)=29 self.assertEqual(2, self._pnbcObj.numChildren) self.assertEqual(self._pnbcObj.bbChildrenWidth, Coord.Dim(46, 'mm')) self.assertEqual(self._pnbcObj.bbChildrenDepth, Coord.Dim(29, 'mm')) self._pnbcObj.extendChildBbox( Coord.Box( Coord.Dim(11, 'mm'), Coord.Dim(9, 'mm'), )) # Child box now w:46+11=57, d:max(29,9)=29 self.assertEqual(3, self._pnbcObj.numChildren) self.assertEqual(self._pnbcObj.bbChildrenWidth, Coord.Dim(57, 'mm')) self.assertEqual(self._pnbcObj.bbChildrenDepth, Coord.Dim(29, 'mm')) # Logical datum self._logicalDatum = Coord.Pt( Coord.Dim(21, 'mm'), Coord.Dim(180, 'mm'), )
def test_05(self): """TestSVGlWriter.test_05(): a polyline. Based on http://www.w3.org/TR/2003/REC-SVG11-20030114/shapes.html#PolylineElement""" myF = io.StringIO() myViewPort = Coord.Box( Coord.Dim(12, 'cm'), Coord.Dim(4, 'cm'), ) with SVGWriter.SVGWriter(myF, myViewPort, {'viewBox': "0 0 1200 400"}) as xS: with XmlWrite.Element(xS, 'desc'): xS.characters( 'Example line01 - lines expressed in user coordinates') #xS.comment(" Show outline of canvas using 'rect' element ") myPt = Coord.Pt(Coord.baseUnitsDim(1), Coord.baseUnitsDim(1)) myBx = Coord.Box(Coord.baseUnitsDim(1198), Coord.baseUnitsDim(398)) with SVGWriter.SVGRect(xS, myPt, myBx, { 'fill': "none", 'stroke': "blue", 'stroke-width': "2" }): pass # Make a group with SVGWriter.SVGPolyline(xS, [ Coord.Pt(Coord.baseUnitsDim(50), Coord.baseUnitsDim(375)), Coord.Pt(Coord.baseUnitsDim(150), Coord.baseUnitsDim(375)), Coord.Pt(Coord.baseUnitsDim(150), Coord.baseUnitsDim(325)), Coord.Pt(Coord.baseUnitsDim(250), Coord.baseUnitsDim(325)), Coord.Pt(Coord.baseUnitsDim(250), Coord.baseUnitsDim(375)), Coord.Pt(Coord.baseUnitsDim(350), Coord.baseUnitsDim(375)), Coord.Pt(Coord.baseUnitsDim(350), Coord.baseUnitsDim(250)), Coord.Pt(Coord.baseUnitsDim(450), Coord.baseUnitsDim(250)), Coord.Pt(Coord.baseUnitsDim(450), Coord.baseUnitsDim(375)), Coord.Pt(Coord.baseUnitsDim(550), Coord.baseUnitsDim(375)), Coord.Pt(Coord.baseUnitsDim(550), Coord.baseUnitsDim(175)), Coord.Pt(Coord.baseUnitsDim(650), Coord.baseUnitsDim(175)), Coord.Pt(Coord.baseUnitsDim(650), Coord.baseUnitsDim(375)), Coord.Pt(Coord.baseUnitsDim(750), Coord.baseUnitsDim(375)), Coord.Pt(Coord.baseUnitsDim(750), Coord.baseUnitsDim(100)), Coord.Pt(Coord.baseUnitsDim(850), Coord.baseUnitsDim(100)), Coord.Pt(Coord.baseUnitsDim(850), Coord.baseUnitsDim(375)), Coord.Pt(Coord.baseUnitsDim(950), Coord.baseUnitsDim(375)), Coord.Pt(Coord.baseUnitsDim(950), Coord.baseUnitsDim(25)), Coord.Pt(Coord.baseUnitsDim(1050), Coord.baseUnitsDim(25)), Coord.Pt(Coord.baseUnitsDim(1050), Coord.baseUnitsDim(375)), Coord.Pt(Coord.baseUnitsDim(1150), Coord.baseUnitsDim(375)), ], { 'fill': 'none', 'stroke': 'blue', 'stroke-width': "5" }): pass #print #print myF.getvalue() self.assertEqual( myF.getvalue(), """<?xml version='1.0' encoding="utf-8"?> <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> <svg height="4.00cm" version="1.1" viewBox="0 0 1200 400" width="12.00cm" xmlns="http://www.w3.org/2000/svg"> <desc>Example line01 - lines expressed in user coordinates</desc> <rect fill="none" height="398px" stroke="blue" stroke-width="2" width="1198px" x="1px" y="1px" /> <polyline fill="none" points="50,375 150,375 150,325 250,325 250,375 350,375 350,250 450,250 450,375 550,375 550,175 650,175 650,375 750,375 750,100 850,100 850,375 950,375 950,25 1050,25 1050,375 1150,375" stroke="blue" stroke-width="5" /> </svg> """)
def testBbSpacePadding(self): """Tests PlotNode() set/get bbSpacePadding.""" myObj = PlotNode.PlotNodeBbox() myObj.width = Coord.Dim(12, 'px') myObj.depth = Coord.Dim(6, 'px') #print '\nTRACE myObj', myObj myPad = Coord.Pad( Coord.Dim(1, 'px'), # prev Coord.Dim(2, 'px'), # next Coord.Dim(3, 'px'), # parent Coord.Dim(4, 'px'), # child ) #print '\nTRACE myPad setting', myPad myObj.bbSelfPadding = myPad #print '\nTRACE myObj setting done', myObj a = myObj.bbSelfPadding self.assertEqual( myObj.bbSelfPadding, Coord.Pad( Coord.Dim(1, 'px'), Coord.Dim(2, 'px'), Coord.Dim(3, 'px'), Coord.Dim(4, 'px'), )) #print '\nTRACE myObj', myObj self.assertEqual( myObj.bbSelfWidth, # 1 + 12 + 2 = 15 Coord.Dim(15, 'px'), ) self.assertEqual( myObj.bbSelfDepth, # 3 + 6 + 4 Coord.Dim(13, 'px'), ) #print #print str(myObj) self.assertEqual( str(myObj), """|.......PlotNode: w=Dim(12px), d=Dim(6px) |bbSpaceChildren: None |..bbSelfPadding: Pad(prev=Dim(1px), next=Dim(2px), parent=Dim(3px), child=Dim(4px)) |.....bbChildren: None |........bbSigma: Box(width=Dim(15px), depth=Dim(13px))""", )
def setUp(self): self._boxDefault = Coord.Box( Coord.Dim(300, None), Coord.Dim(500, None), )
def testIsub_01(self): """Dim() -= when initial units are None.""" myObj_0 = Coord.Dim(36, None) myObj_0 -= Coord.Dim(12, 'px') self.assertEqual(myObj_0.value, 24) self.assertEqual(myObj_0.units, 'px')