INTERFACE PZPlot; (* Contour plotting routines *) (* Last edited on 2001-11-15 21:57:33 by stolfi *) IMPORT ParseParams; IMPORT PSPlot; IMPORT PZLR3Chain, PZMatch, PZCandidate, PZSegment, PZWindow; FROM PZTypes IMPORT LONG, NAT, INT, BOOL; TYPE SegmentPair = PZSegment.Pair; Point = ARRAY [0..1] OF LONG; (* X and Y coordinates *) (* CURVES, SEGMENTS, MATCHINGS *) (* The following procedures draw into an already open "PSPlot" file, *) (* with the current graphics settings. *) PROCEDURE Curve( f: PSPlot.File; READONLY c: PZLR3Chain.T; closed: BOOL := TRUE; drawEvery: NAT := 1; phase: INT := 0; ); (* Draws the given curve into file "f", as a polygonal. If "drawEvery > 1", uses only the first sample, last sample, and one sample out of every "drawEvery" samples (including "c[phase]"). *) PROCEDURE Dots( f: PSPlot.File; READONLY c: PZLR3Chain.T; size: REAL := 2.0; drawEvery: NAT := 1; phase: INT := 0; ); (* Draws dots at the points of the given curve into file "f", plotting only one sample out of every "drawEvery" samples, including "c[phase]" (modulo the array's length). The dots' diameter is "size" times the current line width. *) PROCEDURE Frame( f: PSPlot.File; color: PSPlot.Color := PSPlot.Black; lineWidth: REAL := 0.2; ); (* Draws a rectangular frame around the current plotting area. *) PROCEDURE Grid( f: PSPlot.File; gridStep: LONG; color: PSPlot.Color := PSPlot.Black; lineWidth: REAL := 0.05; ); (* Draws a coordinate grid with specified increment between lines, within the current plotting rectangle (in user coords). *) PROCEDURE Star(f: PSPlot.File; radius: REAL); (* Draws a star with given radius (in millimeters) near the upper left corner of the plotting area. *) PROCEDURE Match( f: PSPlot.File; READONLY m: PZMatch.T; READONLY vA, vB: PZLR3Chain.T; READONLY segA, segB: PZSegment.T; drawEvery: NAT; phaseA, phaseB: NAT; ); (* Draws into "f" lines connecting the points of "vA" and "vB" that are connected by the matching "m", with the current line thickness, color, and style. The matching is interpreted relative to segments "segA" and "segB". If "drawEvery > 1", plots only one every "drawEvery" pairs, including the pair "(phaseA,phaseB)" *) PROCEDURE Segment( READONLY f: PSPlot.File; s: PZSegment.T; READONLY c: PZLR3Chain.T; (* Mapped curve *) closed: BOOL; (* TRUE considers the curve closed. *) drawEvery: NAT := 1; (* Plot every "drawEvery" samples. *) phase: INT := 0; (* Phase of dot sampling. *) ); (* Plots a segment of a curve, with the current line thickness, color, and style. The segment extends from "c[s.ini]" to "c[s.fin]". The indices "s.ini" and "s.fin" are reduced modulo "NUMBER(c)"; if "s.ini > s.fin", the plotted segment is actually "c[0..s.fin]" and "c[s.ini..LAST(c)]". In this case, if "closed" is TRUE, point "c[LAST(c)]" is connected to "c[0]". If "drawEvery > 1" plots only one every "drawEvery" samples, synchronized with "c[phase]". *) PROCEDURE SegmentDots( READONLY f: PSPlot.File; s: PZSegment.T; READONLY c: PZLR3Chain.T; (* Mapped curve *) size: REAL := 2.0; drawEvery: NAT := 1; (* Plot every "drawEvery" samples. *) phase: INT := 0; (* Sampling phase *) ); (* Draws dots at every "drawEvery" samples of the segment "s", with the current line color. The dots' diameter is "size" times the current line width. If "drawEvery > 1" plots only one every "drawEvery" samples, sybchronized with "c[phase]". *) PROCEDURE SegmentLabel( READONLY f: PSPlot.File; READONLY s: PZSegment.T; READONLY c: PZLR3Chain.T; minRelDist: LONG; label: TEXT; size: REAL := 12.0; font: TEXT := "Courier"; ); (* Labels the segment "s" of curve "c". The label is placed between the segment's midpoint and the aproximate center of the whole curve, close to the segment's approximate barycenter, but at least "minRelDist" of the way from the midpoint to the curve center. *) PROCEDURE SegmentPointer( READONLY f: PSPlot.File; READONLY s: PZSegment.T; READONLY c: PZLR3Chain.T; frac: LONG; width: REAL := 4.0; length: REAL := 8.0; inside: BOOL := TRUE; ); (* Places an arrowhead pointing to a point "frac" the way from the start to the end of the segment "s" of "c", taking into account wrap-around and reversal. Thus "frac=0" means at start, "frac=1" means at end. The dimensions are relative to the current line width, The arrowhead is painted solid with the current line color. If "inside=TRUE" the arrow is placed inside the curve, otherwise it is placed outside pointing in. *) PROCEDURE SegmentBrackets( READONLY f: PSPlot.File; READONLY s: PZSegment.T; READONLY c: PZLR3Chain.T; width: REAL := 8.0; length: REAL := 4.0; ); (* Draws start and finish markers at both ends of segment "s" of "c", taking into account wrap-around and reversal. The dimensions are relative to the current line width. The brackets are drawn with the current line style and color. *) (* SCALE ICONS *) (* The following procedures draw pictorial representations of the filtering scale: the graph of the kernel, a circle with critical or characteristic radius, a scale bar, etc.. The KernelGraph and CriticalCircle procedure draw the icon within a rectangle of specified "width" and "height", with its bottom side centered on the point "(x,y)". All these dimensions are in units of the current coordinate system. The icon is drawn with the specified "lineWidth" (in mm), and the current line style and color. The line drawing is supressed in "lineWidth" is zero. If "dotStep" and "dotSize" are positive, the procedure also plots round dots along the graph, spaced "dotStep" in the horizontal direction, using the current fill color. The dots' diameter will be "dotSize" times the current line width. *) PROCEDURE GaussianKernel( f: PSPlot.File; lambda: LONG; x, y: LONG; width: LONG; height: LONG; lineWidth: REAL; dotStep: LONG := 0.0d0; dotSize: REAL := 0.0; ); (* Draws the graph of a gaussian with standard deviation "lambda", clipped to the specified "width" and scaled to fit the specified "height". The origin of the graph's coordinate system will be at "(x,y)". *) PROCEDURE CriticalCircle( f: PSPlot.File; radius: LONG; x, y: LONG; width: LONG; height: LONG; lineWidth: REAL; dotStep: LONG := 0.0d0; dotSize: REAL := 0.0; ); (* Draws a circle with specified "radius". The circle is centered in the specified rectangular window if possible; otherwise only the top part of it is shown, clipped to the rectangle. *) PROCEDURE ScaleBar( f: PSPlot.File; barStep: LONG; barCount: NAT; x, y: LONG; lineWidth: REAL; tickLength: REAL; ); (* Draws a horizontal H-bar of length "barCount*barStep", with ticks every "barStep", centered at the point "(x,y)". Uses the current line style and color. *) (* ALL-IN-ONE PROCEDURES *) (* The following procedures use fixed colors, line widths, and styles: *) PROCEDURE Candidate( f: PSPlot.File; READONLY cand: PZCandidate.T; READONLY c0, c1: PZLR3Chain.T; (* Curves, already mapped; *) whole: BOOL; closed: BOOL; colors: BOOL; thicker: BOOL; dots: BOOL; pointers: BOOL; labelSize: REAL; drawEvery: NAT := 1; drawMatchEvery: NAT := 0; ); (* Plots a candidate, consisting of two segments of the given curves "c0" and "c1" (which must have been translated and rotated by the client as desired). The two segments are drawn in thick red and blue. The segments may wrap around the end of the array, as explained in the "Segment" procedure above. If "whole" is TRUE, the unmatched parts of the contours are drawn in thin black. If "closed" is TRUE, the last point of each curve is connected to the first. If "colors" is true, uses red for the first segment, blue for the second segment, and black for the rest of both outlines. Otherwise draws the entire outlines in black. If "thicker" is true, the cancidate's segments are drawn with double-thickness lines; otherwise the same thickness is used for the entire outlines. If "dots" is TRUE then dots are drawn at the sample positions. If "pointers" is true, draws arrowheads pointing at the start and end of each segment. IF "labelSize > 0", the number of each contour (as specified by "cand") is drawn somewhere near its center.. If "drawEvery > 1", plots only one every "drawEvery" points of each contour. If "drawMatchEvery > 0" and the matching "cand.pm" is not NIL, the matched sample pairs are drawn in thin gray. If "drawMatchEvery > 1", only one out of every "drawMatchEvery" pairs is plotted. *) PROCEDURE HalfCandidate( f: PSPlot.File; s: PZSegment.T; READONLY c: PZLR3Chain.T; (* Curve, already mapped; *) whole: BOOL; (* TRUE plots the whole "c", false plots only "s". *) closed: BOOL; (* TRUE if the curve "c" (NOT the segment!) is closed. *) color: PSPlot.Color; (* Color for drawing the segment "s". *) thicker: BOOL; (* TRUE draws the segment "s" thicker. *) dots: BOOL; (* TRUE to draw a dot at each sample position. *) pointers: BOOL; (* TRUE to draw pointers at the segment's ends. *) labelSize: REAL; (* Label font size, in points. *) drawEvery: NAT := 1; (* Plots one every this many points of "c". *) ); (* Draws one half of a candidate (minus the pairing), as above: the segment "s" in the specified color, the rest of the curve "c" in black. *) TYPE ScaleIconStyle = { None, Kernel, Circle }; ScaleIconOptions = RECORD style: ScaleIconStyle; (* Icon type. *) barCount: NAT; (* Number of segments in scale bar. *) dotStep: LONG; (* Spacing of dots on icon (0 = no dots). *) tickLength: REAL; (* Length of tick marks on scale bar. *) END; (* See "ScaleIcon" below. *) PROCEDURE ScaleIcon( f: PSPlot.File; lambda: LONG; (* Basic scale length. *) o: ScaleIconOptions; ); (* Plots an iconic indication of filtering scale, at the lower left corner of the current drawing of "f". The "None" style icon is no icon at all, just a scale bar. The "Kernel" style icon is the graph of a Gaussian with deviation "lambda", clipped to "2.5*lambda" in each direction, with the scale bar as the horizontal axis. The "Circle" style icon is a circle that is critical for the filtering scale "lambda" (i.e. with radius "sqrt(e/2)*lambda"), above a scale bar. The scale bar is an horizontal line with length "o.barCount*lambda" and ticks every "lambda". In all styles, if "o.barCount = 0", the scale bar is not plotted. If "o.dotStep" is positive, the procedure also puts down dots with that spacing along the kernel graph or circle. This parameter is irrelevant if "o.style = None". The icon will be clipped or supressed if it would be too big compared to the drawing size. Ditto for the scale bar. *) CONST ScaleIconOptionsHelp = " [ -scaleBar COUNT | \\\n" & " { -scaleIcon {None|Bar|Circle|Kernel} [ -scaleDotStep NUM ] } \\\n" & " ]"; PROCEDURE ParseScaleIconOptions(pp: ParseParams.T): ScaleIconOptions RAISES {ParseParams.Error}; (* Parses scale icon parameters from the command line input, as shown in the "ScaleIconOptionsHelp" string above. "None" is nothing at all; "Bar" produces only a scale bar with one segment (same as "-scaleBar 1"); "Circle" includes "-scaleBar 1"; and "Kernel" includes "-scaleBar 2". *) (* DYNAMIC PROGRAMMING GRAPH *) (* The following procedures generate illustrations of the "dynamic programming" algorithm. All procedures draw or assume drawn the dynamic programming graph, in the form of a rectangular grid defined by segments "s[0]" and "s[1]", with cells of side "cellSize". The plot will be positioned assuming that the reference grid defined by segments "sRef[0]" and "sRef[1]" has its lower left corner at the point "(h,h)" where "h = cellSize/2". The plot will be clipped by a rectangle that contains the reference grid surrounded by a safety margin of "h". The procedures that take a match "m" interpret it relative to the segment "s", and plot the result relative to "sRef". The procedures do not take into account that the curves are periodic; the segments are interpreted literaly, without reducing them modulo "s.tot". Use "TryAllShifts" to get wrap-around plots. *) PROCEDURE SegSegGrid( READONLY f: PSPlot.File; READONLY s: SegmentPair; READONLY sRef: SegmentPair; cellSize: LONG; drawDiags: BOOL; ); (* Draws a grid of "s[0].ns" by "s[1].ns" cells, with the current line style and color. The grid is intended to be used with "MatchInGrid" below. If "drawDiags" is TRUE, the grid will include diagonal lines with the orientation specified by "s[0].rev" and "s[1].rev". *) PROCEDURE MatchBoxes( READONLY f: PSPlot.File; READONLY m: PZMatch.T; READONLY s: SegmentPair; READONLY sRef: SegmentPair; cellSize: LONG; boxSize: REAL; ); (* Draws square boxes of side "boxSize" (in mm) around the nodes of the the matching "m", with the current line style/color and fill color. *) PROCEDURE MatchPath( READONLY f: PSPlot.File; READONLY m: PZMatch.T; READONLY s: SegmentPair; READONLY sRef: SegmentPair; cellSize: LONG; ); (* Draws the steps of the matching "m", as a path in the dynamic programming grid, with current line style and color. *) PROCEDURE MatchDots( READONLY f: PSPlot.File; READONLY m: PZMatch.T; READONLY s: SegmentPair; READONLY sRef: SegmentPair; cellSize: LONG; dotSize: REAL := 0.0; ); (* Draws round dots at the nodes of the matching "m", with diameter "dotSize" (in mm), with the current line style/color and fill color. *) TYPE SegSegPlotProc = PROCEDURE (READONLY t, tRef: SegmentPair); PROCEDURE TryAllShifts( READONLY s: SegmentPair; READONLY sRef: SegmentPair; proc: SegSegPlotProc; ); (* Calls "proc(t,tRef)", for some some segment pair "tRef" that is equivalent to "sRef" modulo full cycle shift, and every segment "t" that is equivalent to "s" and intersects "tRef". *) PROCEDURE ComputeDrawEvery( f: PSPlot.File; eps: REAL; step: LONGREAL; all: BOOL; ): NAT; (* Computes the largest integer number "d" of sampling steps of length "step" that correspond to at most "eps" millimeters in either scale. As special cases, if "all = TRUE" or "step = 0", returns "d = 1". *) (* PAGINATION CONTROL FOR MULTIPLE DRAWINGS *) (* The following procedures provide automatic opening and initialization of printable Postscript or Encapsulated Postscript plot files. Programs that produce a single drawing should use them as follows: | o := ParseDrawFormatOptions(pp, single := TRUE); | ... | VAR f: PSPlot.File := NIL; | BEGIN | ... | BeginSingleDrawing(f, "foo", o); | DrawFoo(f, ...); | EndSingleDrawing(f, o) | END In this case, the output will go to a single file "foo.ps" or "foo.eps". Programs that produce multiple drawings should use instead | o := ParseDrawFormatOptions(pp, single := FALSE); | ... | VAR | nDrawn: NAT; | f: PSPlot.File := NIL; | BEGIN | ... | FOR iFoo := firstFoo TO lastFoo DO | IF worthPlotting[iFoo] THEN | BeginNewDrawing(f, "foo", iFoo, o, nDrawn); | DrawFoo(f, ...); | END | END; | NoMoreDrawings(f, o, nDrawn) | END In this case, if "o.epsFormat = FALSE", all drawings go to a single file "XXX.ps", possibly several drawings per page. If "o.epsFormat = TRUE", each drawing goes to a separate file "XXX-NNNNN.eps", where NNNNN is specified by the argument "iFoo". Note that "iFoo" (the drawing's ID number) is defined entirely by the client, while "nDrawn" (the number of drawings in the current file "f") is managed internally by the procedures. *) TYPE Range = PZWindow.Range; Window = PZWindow.T; DrawFmtOptions = RECORD epsFormat: BOOL; (* TRUE to use Encapsulated Postscript. *) (* Scale-defining options: *) window: Window; (* Plotting X and Y ranges in client units. *) actualWidth: LONG; (* Actual plot area width in mm. *) actualHeight: LONG; (* Actual plot area height in mm. *) (* Plot options for non-EPS format: *) nCols: NAT; (* Number of drawings per row. *) nRows: NAT; (* Number of drawings per column. *) END; CONST DrawFmtOptionsHelp = " [ -objectSize WIDTH HEIGHT | \\\n" & " -window XMIN XMAX YMIN YMAX \\\n" & " ] \\\n" & " [ -epsFormat [ -actualSize WIDTH HEIGHT ] | \\\n" & " -nDrawingsPerPage NUMBER NUMBER \\\n" & " ]"; PROCEDURE ParseDrawFmtOptions( pp: ParseParams.T; single: BOOL; defaultWidth: LONG; (* Default plotting area width in mm. *) defaultHeight: LONG; (* Default plotting area height in mm. *) ): DrawFmtOptions RAISES {ParseParams.Error}; (* Parses the "DrawFmtOptions" attributes from the command line, as described by "DrawFmtOptionsHelp". The "single" parameter chooses between default values appropriate for programs that produce single or multiple drawings: | single=TRUE: -nDrawingsPerPage 1 1 -actualSize 160 160. | single=FALSE: -nDrawingsPerPage 4 5 -actualSize 40 40. *) PROCEDURE BeginNewDrawing( VAR f: PSPlot.File; outName: TEXT; idDrawing: TEXT; READONLY o: DrawFmtOptions; VAR nInFile: NAT; ); (* Opens (if necessary) file "f" and prepares it for a new drawing. If "o.epsFormat" is "FALSE", the file is opened only once, with name "XXX.ps" where "XXX" is the value of "outName". If "o.epsFormat" is true, each drawing will be written to a separate file, with names "XXX-NNNNN.eps", where "NNNNN" is the value of "iDrawing". The "nInFile" parameter is managed by the procedure. *) PROCEDURE NoMoreDrawings( VAR f: PSPlot.File; READONLY o: DrawFmtOptions; VAR nInFile: NAT; ); (* To be called once after the last drawing started with "BeginNewDrawing". Will finalize the current drawing, close the file "f", and set "f = NIL". *) PROCEDURE BeginSingleDrawing( VAR f: PSPlot.File; outName: TEXT; READONLY o: DrawFmtOptions; ); (* Like "BeginNewDrawing", but appropriate if the program will produce only one drawing with the given "outName". The only difference is that the output file name is simply "XXX.ps" or "XXX.eps". *) PROCEDURE EndSingleDrawing( VAR f: PSPlot.File; READONLY o: DrawFmtOptions; ); (* To be called once after finishing the single drawing started by "BeginSingleDrawing" in "f". Will finalize the drawing, close the file, and set "f = NIL". *) END PZPlot. (* Copyright © 2001 Universidade Estadual de Campinas (UNICAMP). Authors: Helena C. G. Leitão and Jorge Stolfi. This file can be freely distributed, used, and modified, provided that this copyright and authorship notice is preserved, and that any modified versions are clearly marked as such. This software has NO WARRANTY of correctness or applicability for any purpose. Neither the authors nor their employers chall be held responsible for any losses or damages that may result from its use. *)