<> <> <> <> <<>> DIRECTORY Convert USING [RopeFromReal], FS USING [Error], Graph USING [BackgroundIndex, BWLineState, CaretIndex, CaretSpec, ColorMode, Entity, EntityColor, EntityList, FontIndex, Fonts, GraphHandle, JustifX, JustifY, LastEntityColor, LineStateRec, Mark, MaxStep, NullVec, OutputType, PaintAction, PaintInfo, PaintInfoRec, ROPE, SegmentData, SegmentDataList, StepCount, Text, Texts, UnitLineWidth, ValueList, XY], GraphCarets USING [Resume, Suspend], GraphConvert USING [CharRopeFromMark, RopeOfPlace, RopeOfSlope], GraphOps USING [LabelFont, LegendFont, Math8], GraphPrivate USING [systemColor], GraphUtil USING [Almost, BackgroundColor, BlinkMsg, TextPosToChartPos, DataBounds, FontHeight, ForegroundColor, FullName, HandleNotNil, NotANan, NotANumber, RealToScreen, RealToScreenI, RealVecToScreenVec, RopeSize], Imager USING [Box, ClipRectangle, Color, Context, DoSaveAll, Error, MaskRectangle, MaskStroke, MaskVector, PathProc, Rectangle, RotateT, ScaleT, SetColor, SetFont, SetStrokeEnd, SetStrokeJoint, SetStrokeWidth, SetXY, ShowRope, TranslateT, VEC], ImagerBackdoor USING [GetBounds], ImagerFont USING [Extents, Find, Font, RopeBoundingBox, Scale], ImagerInterpress USING [Close, Create, DoPage, Ref], Real USING [InlineFixI, InlineRoundI, Round], RealFns USING [Log, Power, SqRt], Rope USING [Cat, Concat, IsEmpty]; GraphDraw: CEDAR PROGRAM IMPORTS Convert, Imager, ImagerBackdoor, ImagerFont, ImagerInterpress, FS, GraphCarets, GraphConvert, GraphPrivate, GraphUtil, Real, RealFns, Rope EXPORTS GraphPrivate = { OPEN Graph, GraphPrivate, GraphUtil; <> TextHeightFudge: REAL = 8.0; TextWidthFudge: REAL = 8.0; shortLegendW: REAL = 200; <> bwLineState: BWLineState _ NEW[LineStateRec _ []]; <> Draw: PUBLIC PROC [handle: GraphHandle, context: Imager.Context, info: PaintInfo] = { IF context # NIL AND handle # NIL AND info # NIL THEN { OPEN handle; <> <> GraphCarets.Suspend[handle]; WITH info SELECT FROM a: REF all PaintInfoRec => DrawGraph[handle, context, a.action, a.output, a.clear, a.updateDivBds]; a: REF allCurves PaintInfoRec => DrawMajorPart[handle, context, a.action, a.output, a.clear]; r: REF rectangle PaintInfoRec => NULL; -- for now. DrawGraph[handle, context, r.action, r.output, r.clear, r.updateDivBds]; t: REF graphText PaintInfoRec => DrawText[handle, context, t.action, t.output, t.text]; e: REF graphEntity PaintInfoRec => DrawEntity[handle, context, e.action, e.output, e.entity]; l: REF legend PaintInfoRec => DrawLegend[handle, context, l.action, l.output, l.entity, l.onlyOne]; t: REF tails PaintInfoRec => DrawTails[handle, context, t.action, t.output, t.x1, t.x2, t.v1, t.v2, t.entities]; t: REF target PaintInfoRec => DrawTarget[handle, context, t.action, t.xy]; g: REF grid PaintInfoRec => DrawTicks[handle, context, g.action, g.output, g.xy, g.long, FALSE]; -- false meaning no label. ENDCASE; < DrawCrossSegments[context, handle, c];>> < DrawXhairPlaces[context, handle, x];>> <> GraphCarets.Resume[handle]; <> <> }; }; -- Draw <> DrawGraph: PROC [handle: GraphHandle, context: Imager.Context, action: PaintAction, output: OutputType, clear, updateDivBds: BOOL] = { OPEN handle; <> <<>> <> rect: Imager.Rectangle _ IF output = interpress THEN [0.0, -518.16, 670.56, 518.16] ELSE ImagerBackdoor.GetBounds[context]; <> axesRectSave: Imager.Rectangle; scaleSave: ARRAY XY OF REAL; dlSave: BOOL; IF output = interpress THEN {axesRectSave _ axesRect; scaleSave _ scale; dlSave _ normalLegend;} ELSE IF clear THEN { -- happens in screen painting only. clearProc: PROC = { context.SetColor[BackgroundColor[backgroundIndex, screen]]; context.MaskRectangle[rect]; }; context.DoSaveAll[clearProc]; <> <> <> }; normalLegend _ (output = interpress) OR rect.h >= 450; { -- setting axesRect margin1: REAL = MIN[rect.h * 0.05, 10.0]; margin2: REAL = margin1*2.0; -- MIN[rect.w * 0.05, 20.0]; xLabelH: REAL = FontHeight[imagerFonts[output][GraphOps.LabelFont]]; legendH: REAL = FontHeight[imagerFonts[output][GraphOps.LegendFont]]; -- for legend, footnote topTextsH: REAL _ MIN[rect.h * 0.17, 50.0]; -- don't calculate anything else to save time. leftTextsW: REAL = MIN[rect.h * 0.25, 70.0]; rightTextsW: REAL = MIN[rect.w * 0.35, IF normalLegend THEN leftTextsW ELSE shortLegendW]; bottomTextsH: REAL _ MIN[rect.h*0.35, xLabelH + TextHeightFudge + (IF normalLegend THEN legendH*7.0 + TextHeightFudge + TextHeightFudge ELSE 0.0)]; IF output = interpress THEN topTextsH _ topTextsH + 20; -- experimental, for now. axesRect _ [ x: rect.x + margin1 + leftTextsW, y: rect.y + margin1 + bottomTextsH, w: rect.w - margin2 - leftTextsW - rightTextsW, h: rect.h - margin2 - topTextsH - bottomTextsH ]; }; -- setting axesRect IF axesRect.w > 0.0 AND axesRect.h > 0.0 THEN { wholePicture: PROC = { ScaleAxes[handle, output, updateDivBds]; DrawAxes[handle, context, output]; -- action must be paint. DrawTicks[handle, context, action, output, x, graph.grids[x], TRUE]; DrawTicks[handle, context, action, output, y, graph.grids[y], TRUE]; DrawTexts[handle, context, output]; -- action must be paint. IF graph.entityList # NIL THEN DrawLegend[handle, context, action, output, graph.entityList.first, FALSE]; IF output = interpress THEN PrintCaretsInfo[handle, context]; -- action must be paint. IF graph.target[x].on THEN DrawTarget[handle, context, action, x]; IF graph.target[y].on THEN DrawTarget[handle, context, action, y]; DrawEntities[handle, context, output]; -- action must be paint. }; context.DoSaveAll[wholePicture]; }; IF output = interpress THEN { <> axesRect _ axesRectSave; scale _ scaleSave; normalLegend _ dlSave}; <> [chart.caretState[text].x, chart.caretState[text].y] _ TextPosToChartPos[axesRect, graph.caret[text].place]; FOR i: CaretIndex IN [primary..secondary] DO chart.caretState[i].x _ RealToScreenI[handle, graph.caret[i].place.x, x]; chart.caretState[i].y _ RealToScreenI[handle, graph.caret[i].place.y, y]; ENDLOOP; }; -- DrawGraph DrawMajorPart: PROC [handle: GraphHandle, context: Imager.Context, action: PaintAction, output: OutputType, clear: BOOL] = { OPEN handle; IF axesRect.w > 0.0 AND axesRect.h > 0.0 THEN { majorPart: PROC = { IF clear THEN { clearProc: PROC = { context.SetColor[BackgroundColor[backgroundIndex, screen]]; context.MaskRectangle[axesRect]; }; context.DoSaveAll[clearProc]; FOR i: CaretIndex IN CaretIndex DO chart.caretState[i].visible _ FALSE; ENDLOOP; }; context.ClipRectangle[axesRect]; DrawAxes[handle, context, output]; -- action must be paint. DrawTicks[handle, context, action, output, x, graph.grids[x], TRUE]; DrawTicks[handle, context, action, output, y, graph.grids[y], TRUE]; DrawTexts[handle, context, output]; -- action must be paint. <<-- no legend, no carets info, cf. DrawGraph.>> IF graph.target[x].on THEN DrawTarget[handle, context, action, x]; IF graph.target[y].on THEN DrawTarget[handle, context, action, y]; DrawEntities[handle, context, output]; -- action must be paint. }; context.DoSaveAll[majorPart]; }; }; -- DrawMajorPart DrawTarget: PROC [handle: GraphHandle, context: Imager.Context, action: PaintAction, xy: XY] = { OPEN handle; <> <> targetProc: PROC = { rect: Imager.Rectangle; tcolor: Imager.Color _ IF action = erase THEN BackgroundColor[backgroundIndex, screen] ELSE systemColor[graph.target[xy].colorIndex]; <> IF xy = x THEN { OPEN graph.target[x]; IF value < realRect.x OR value > realRect.x + realRect.w THEN RETURN; rect _ [x: RealToScreen[handle, value, x] - UnitLineWidth*width*0.5, y: axesRect.y+1.0, w: UnitLineWidth*width, h: axesRect.h-2.0]; } ELSE { OPEN graph.target[y]; IF value < realRect.y OR value > realRect.y + realRect.h THEN RETURN; rect _ [x: axesRect.x+1.0, y: RealToScreen[handle, value, y] - UnitLineWidth*width*0.5, w: axesRect.w-2.0, h: UnitLineWidth*width]; }; context.SetColor[tcolor]; context.MaskRectangle[rect]; }; context.DoSaveAll[targetProc]; }; -- DrawTarget DrawRope: PROC[context: Imager.Context, text: ROPE, x0, y0: REAL, justifX: JustifX, justifY: JustifY, color: Imager.Color, font: ImagerFont.Font, rotation: REAL] = { <> <> IF ~text.IsEmpty[] THEN { showTextProc: PROC = { context.TranslateT[[x0, y0]]; context.RotateT[rotation]; context.SetXY[[xmin, ymin]]; context.SetColor[color]; context.SetFont[font]; context.ShowRope[text]; }; -- showTextProc extents: ImagerFont.Extents; xmin, ymin, width, height: REAL; extents _ ImagerFont.RopeBoundingBox[font: font, rope: text]; width _ extents.rightExtent - extents.leftExtent; height _ extents.descent + extents.ascent; xmin_ SELECT justifX FROM right => - width - extents.leftExtent, center => - width/2.0 - extents.leftExtent, ENDCASE => - extents.leftExtent; ymin_ SELECT justifY FROM top => - height + extents.descent, center => - height/2.0 + extents.descent, ENDCASE => extents.descent; context.DoSaveAll[showTextProc]; <> }; }; -- DrawRope DrawTexts: PROC [handle: GraphHandle, context: Imager.Context, output: OutputType] = { <> <> OPEN handle, handle.axesRect; FOR tl: Texts _ graph.texts, tl.rest UNTIL tl = NIL DO t: Text _ tl.first; DrawRope[context, t.text, t.place.x * w + x, t.place.y * h + y, t.justifX, t.justifY, systemColor[t.colorIndex], imagerFonts[output][t.fontIndex], t.rotation]; ENDLOOP; }; -- DrawTexts DrawText: PROC [handle: GraphHandle, context: Imager.Context, action: PaintAction, output: OutputType, gt: Text] = { <> OPEN handle, handle.axesRect; DrawRope[context, gt.text, gt.place.x * w + x, gt.place.y * h + y, gt.justifX, gt.justifY, IF action = erase THEN BackgroundColor[backgroundIndex, output] ELSE systemColor[gt.colorIndex], imagerFonts[output][gt.fontIndex], gt.rotation]; }; -- DrawText ScaleAxes: PROC [handle: GraphHandle, output: OutputType, updateDivBds: BOOL] = { OPEN handle; <> <> useOldDivBds: BOOL _ FALSE; IF NOT updateDivBds THEN { oldOk: BOOL _ (scale[x] # 0.0 AND scale[y] # 0.0) AND (step[x] # 0.0 AND step[y] # 0.0) AND (realRect.h > 0.0 AND realRect.w > 0.0); IF oldOk THEN useOldDivBds _ TRUE; }; <<>> IF useOldDivBds THEN scale _ [axesRect.w/realRect.w, axesRect.h/realRect.h] ELSE { ScaleAxis: PROC [minDataValue, maxDataValue, axisSize: REAL, nDiv: INT] RETURNS [min, range, step, factor: REAL] = { max: REAL _ maxDataValue; min _ minDataValue; range _ max - min; step _ range/nDiv; IF graph.auto[bounds] THEN { step _ FindStepSize[range, nDiv]; min _ AlignEnd[minDataValue, step, FALSE]; max _ AlignEnd[maxDataValue, step, TRUE]; <> range _ max - min; IF NOT graph.auto[divisions] THEN step _ range/nDiv; }; factor _ axisSize/range; }; -- ScaleAxis nDivX, nDivY: INT; divBad: BOOL _ graph.division[x] < 1 OR graph.division[y] < 1; boundsBad: BOOL _ (graph.bounds.xmin >= graph.bounds.xmax) OR (graph.bounds.ymin >= graph.bounds.ymax); <<>> <> IF boundsBad OR graph.auto[bounds] THEN { graph.bounds _ DataBounds[graph.entityList, graph.bounds]; IF mergingBounds THEN { graph.bounds _ [ xmin: MIN[boundsToMerge.xmin, graph.bounds.xmin], ymin: MIN[boundsToMerge.ymin, graph.bounds.ymin], xmax: MAX[boundsToMerge.xmax, graph.bounds.xmax], ymax: MAX[boundsToMerge.ymax, graph.bounds.ymax] ]; }; IF NOT graph.auto[bounds] THEN BlinkMsg["Bounds invalid. (xmin >= xmax or ymin >= ymax.)"]; }; <> IF graph.auto[divisions] OR divBad THEN { labelFont: ImagerFont.Font _ imagerFonts[output][GraphOps.LabelFont]; longest: ROPE _ "0.4444444"; width, height: REAL; [width, height] _ RopeSize[longest, labelFont]; nDivX _ MAX[1, MIN[10, Real.Round[axesRect.w/width*0.9]]]; -- 0.9 is arbitrary nDivY _ MAX[1, MIN[10, Real.Round[axesRect.h/height*0.625]]]; -- 0.625 is arbitrary IF NOT graph.auto[divisions] THEN BlinkMsg["Number of divisions must be >= 1"]; } ELSE { nDivX _ graph.division[x]; nDivY _ graph.division[y]; }; <> [realRect.x, realRect.w, step[x], scale[x]] _ ScaleAxis[ graph.bounds.xmin, graph.bounds.xmax, axesRect.w, nDivX]; [realRect.y, realRect.h, step[y], scale[y]] _ ScaleAxis[ graph.bounds.ymin, graph.bounds.ymax, axesRect.h, nDivY]; }; <> <> }; -- ScaleAxes FindStepSize: PROC [range: REAL, nSteps: CARDINAL] RETURNS [step: REAL] = { logRange: REAL; logMext, minStep: REAL; exp: INTEGER; steps: ARRAY [0..6) OF REAL = [0.2, 0.5, 1.0, 2.0, 5.0, 10.0]; <> logRange _ RealFns.Log[10.0, range]; exp _ Real.InlineFixI[logRange]; logMext _ logRange - exp; IF logRange < 0.0 THEN { exp _ exp - 1; logMext _ logMext + 1.0}; minStep _ RealFns.Power[10.0, logMext]/nSteps; FOR i: CARDINAL IN [0..5) DO step _ steps[i]; IF step > minStep OR Almost[step, minStep] THEN EXIT ENDLOOP; IF exp >= 0 THEN THROUGH [1..exp] DO step _ step*10.0 ENDLOOP ELSE THROUGH [1..-exp] DO step _ step/10.0 ENDLOOP; step _ MAX[1.0, step]; }; -- FindStepSize AlignEnd: PROC [e, step: REAL, roundUp: BOOL] RETURNS [ae: REAL] = { absE: REAL = ABS[e]; nSteps: INTEGER; xend: REAL; IF e = 0.0 THEN RETURN[0.0]; IF e < 0.0 THEN roundUp _ ~roundUp; nSteps _ Real.Round[absE/step]; xend _ step*nSteps; IF Almost[nSteps, absE/step] THEN ae _ xend ELSE IF roundUp THEN {ae _ IF xend >= absE THEN xend ELSE step*(nSteps + 1)} ELSE {ae _ IF xend <= absE THEN xend ELSE step*(nSteps - 1)}; IF e < 0.0 THEN ae _ -ae; }; -- AlignEnd DrawAxes: PROC [handle: GraphHandle, context: Imager.Context, output: OutputType] = { <> axesProc: PROC = { OPEN handle; color: Imager.Color _ ForegroundColor[backgroundIndex, output]; <> rect: Imager.Rectangle _ axesRect; border: Imager.PathProc = { OPEN rect; xmax: REAL = x + w; ymax: REAL = y + h; moveTo[[x, y]]; lineTo[[x, ymax]]; lineTo[[xmax, ymax]]; lineTo[[xmax, y]]; }; context.SetColor[color]; context.SetStrokeEnd[square]; context.SetStrokeJoint[round]; context.SetStrokeWidth[UnitLineWidth*2.0]; context.MaskStroke[path: border, closed: TRUE]; }; context.DoSaveAll[axesProc]; }; -- DrawAxes DrawTicks: PROC[handle: GraphHandle, context: Imager.Context, action: PaintAction, output: OutputType, xyChoice: XY _ x, longTicks, drawLabel: BOOL _ FALSE] = { OPEN handle; <> <> <<>> ticksProc: PROC = { tickCount: CARDINAL; -- max tick count, starting from 0. tickLen, textPlace, tick: REAL; stdColor: Imager.Color _ GetStandardColor[handle, action, output]; labelFont: ImagerFont.Font _ imagerFonts[output][GraphOps.LabelFont]; context.SetColor[stdColor]; context.SetStrokeEnd[butt]; context.SetStrokeWidth[UnitLineWidth]; IF xyChoice = x THEN { -- x ticks (on horizontal axis) tickLen _ IF longTicks THEN axesRect.h ELSE axesRect.h/50.0; tickCount _ Real.InlineRoundI[realRect.w/step[x]]; textPlace _ axesRect.y - TextHeightFudge; FOR i: CARDINAL IN [0..tickCount] DO tick _ step[x]*scale[x]*i; IF drawLabel THEN DrawRope[context, Convert.RopeFromReal[step[x]*i + realRect.x], axesRect.x + tick, textPlace, center, top, stdColor, labelFont, 0.0]; IF i # 0 AND i # tickCount THEN { xPos: REAL = axesRect.x + tick; yStart: REAL _ axesRect.y; yEnd: REAL _ axesRect.y + tickLen; Tick: Imager.PathProc = {moveTo[[xPos, yStart]]; lineTo[[xPos, yEnd]]}; context.MaskStroke[path: Tick, closed: FALSE]; IF NOT longTicks THEN { yStart _ axesRect.y + axesRect.h; yEnd _ yStart - tickLen; context.MaskStroke[path: Tick, closed: FALSE]; }; }; ENDLOOP; } ELSE { tickLen _ IF longTicks THEN axesRect.w ELSE axesRect.w/50.0; tickCount _ Real.InlineRoundI[realRect.h/step[y]]; textPlace _ axesRect.x - TextWidthFudge; <> FOR i: CARDINAL IN [0..tickCount] DO tick _ step[y]*scale[y]*i; IF drawLabel THEN DrawRope[context, Convert.RopeFromReal[step[y]*i + realRect.y], textPlace, axesRect.y + tick, right, center, stdColor, labelFont, 0.0]; IF i # 0 AND i # tickCount THEN { yPos: REAL = axesRect.y + tick; xStart: REAL _ axesRect.x; xEnd: REAL _ axesRect.x + tickLen; Tick: Imager.PathProc = {moveTo[[xStart, yPos]]; lineTo[[xEnd, yPos]]}; context.MaskStroke[path: Tick, closed: FALSE]; IF NOT longTicks THEN { xStart _ axesRect.x + axesRect.w; xEnd _ xStart - tickLen; context.MaskStroke[path: Tick, closed: FALSE]; }; }; ENDLOOP; }; }; context.DoSaveAll[ticksProc]; }; -- DrawTicks DrawLegend: PROC [handle: GraphHandle, context: Imager.Context, action: PaintAction, output: OutputType, entity: Entity, onlyOne: BOOL] = { OPEN handle; <> <> MaxEntries: INT = 12; MaxEntriesPerColumn: INT = MaxEntries/2; notYet: BOOL _ entity # NIL; irow, icol: INT _ 0; AdvanceRC: PROC [] RETURNS [ok: BOOL _ TRUE]= { <> IF irow = MaxEntries THEN ok _ FALSE ELSE IF icol = 2 AND irow = MaxEntriesPerColumn THEN ok _ FALSE ELSE IF icol = 0 THEN {irow _ 1; icol _ IF normalLegend THEN 1 ELSE 3} ELSE { IF normalLegend AND (irow = MaxEntriesPerColumn) THEN {icol _ icol + 1; irow _ 1} ELSE irow _ irow + 1; }; }; -- AdvanceRC FOR el: EntityList _ graph.entityList, el.rest UNTIL el = NIL DO IF AdvanceRC[] THEN { IF notYet AND entity # NIL THEN IF el.first = entity THEN notYet _ FALSE; IF NOT notYet THEN { IF el.first.colorIndex = 0 THEN -- auto color selection for white curves. lastEntityColor _ el.first.colorIndex _ IF lastEntityColor = LastEntityColor THEN 1 ELSE lastEntityColor + 1; DrawLegendEntry[handle, context, action, output, el.first, icol, irow]; IF onlyOne THEN EXIT; }; } ELSE EXIT; ENDLOOP; IF AdvanceRC[] AND NOT onlyOne THEN DrawLegendEntry[handle, context, action, output, NIL, icol, irow]; -- blank }; -- DrawLegend DrawLegendEntry: PROC [handle: GraphHandle, context: Imager.Context, action: PaintAction, output: OutputType, entity: Entity, icol, irow: INT] = { OPEN handle; <> <> legendProc: PROC = { legendFont: ImagerFont.Font _ imagerFonts[output][GraphOps.LegendFont]; legendH: REAL _ FontHeight[legendFont]; labelH: REAL _ FontHeight[imagerFonts[output][GraphOps.LabelFont]]; legendW: REAL _ IF normalLegend THEN axesRect.w*0.5 ELSE shortLegendW; lx1: REAL _ SELECT icol FROM 1 => axesRect.x, 2 => axesRect.x + legendW, ENDCASE => axesRect.x + axesRect.w + 2; ty: REAL _ axesRect.y - legendH*irow + (SELECT icol FROM 1, 2 => - (labelH + TextHeightFudge + TextHeightFudge), ENDCASE => axesRect.h); IF output = screen THEN { -- clear context.SetColor[BackgroundColor[backgroundIndex, output]]; context.MaskRectangle[[lx1, ty, legendW, legendH]]; }; context.ClipRectangle[[lx1, ty, legendW, legendH]]; IF action = paint AND entity # NIL THEN { lColor: Imager.Color _ GetEntityColor[handle, action, output, entity]; lineLength: REAL _ IF entity.colorIndex IN [8..9] THEN 31.0 ELSE 34.0; lx2: REAL _ IF normalLegend THEN lx1 + 34.0 ELSE lx1; tx: REAL _ lx2 + TextWidthFudge; ly: REAL _ ty + legendH*0.5; IF normalLegend THEN { context.SetColor[lColor]; IF NOT Almost[1.0 - entity.width, 1.0] THEN { context.SetStrokeEnd[butt]; context.SetStrokeWidth[UnitLineWidth*entity.width]; IF colorMode = color THEN context.MaskVector[[lx1, ly], [lx2, ly]] ELSE { bwLineState^ _ []; MaskLineSeg[context, [lx1, ly], [lx1+lineLength, ly], entity.colorIndex, bwLineState] }; }; IF entity.mark # none THEN { lFont: ImagerFont.Font _ imagerFonts[output][ IF entity.mark > cross THEN GraphOps.LabelFont ELSE GraphOps.Math8]; DrawRope[context, GraphConvert.CharRopeFromMark[entity.mark], lx1, ly, left, center, lColor, lFont, 0.0]; }; }; DrawRope[context, FullName[entity], tx, ty, left, bottom, lColor, legendFont, 0.0]; <> }; }; -- proc context.DoSaveAll[legendProc]; }; -- DrawLegendEntry DrawEntities: PROC [handle: GraphHandle, context: Imager.Context, output: OutputType] = { OPEN handle; <> <> IF graph.entityList # NIL THEN { entitiesProc: PROC = { context.ClipRectangle[axesRect]; FOR el: EntityList _ graph.entityList, el.rest UNTIL el = NIL DO entity: Entity _ el.first; IF entity = NIL THEN LOOP; -- error ?! IF entity.colorIndex = 0 THEN -- auto color selection for white curves. lastEntityColor _ entity.colorIndex _ IF lastEntityColor = LastEntityColor THEN 1 ELSE lastEntityColor + 1; IF handle.colorMode = bw THEN DrawBWEntity[handle, context, paint, output, entity] ELSE DrawColorEntity[handle, context, paint, output, entity]; ENDLOOP; }; -- entitiesProc context.DoSaveAll[entitiesProc]; }; }; -- DrawEntities DrawBWEntity: PROC [handle: GraphHandle, context: Imager.Context, action: PaintAction, output: OutputType, entity: Entity] = { OPEN handle; <> oneCurve: PROC = { first, prevOk, currOk: BOOL _ TRUE; lastVec, newVec: Imager.VEC; -- places on screen. xseg: SegmentDataList _ entity.group.x.segments; eColor: Imager.Color _ GetEntityColor[handle, action, output, entity]; lineVisible: BOOL _ NOT Almost[1.0 - entity.width, 1.0]; IF lineVisible THEN { -- not too thin. bwLineState^ _ []; context.SetStrokeJoint[miter]; context.SetStrokeEnd[butt]; context.SetStrokeWidth[UnitLineWidth*entity.width]; }; context.SetColor[eColor]; context.TranslateT[[axesRect.x, axesRect.y]]; FOR yseg: SegmentDataList _ entity.segments, yseg.rest UNTIL yseg = NIL DO IF xseg = NIL THEN EXIT; currOk _ NotANan[xseg.first.end] AND NotANan[yseg.first.end]; IF currOk THEN newVec _ [(xseg.first.end-realRect.x)*scale[x], (yseg.first.end-realRect.y)*scale[y]]; IF first THEN first _ FALSE ELSE IF currOk AND prevOk AND lineVisible THEN MaskLineSeg[context, lastVec, newVec, entity.colorIndex, bwLineState]; IF (entity.mark # none) AND currOk THEN { eFont: ImagerFont.Font _ imagerFonts[output][ IF entity.mark > cross THEN GraphOps.LabelFont ELSE GraphOps.Math8]; DrawRope[context, GraphConvert.CharRopeFromMark[entity.mark], newVec.x, newVec.y, center, center, eColor, eFont, 0.0]; }; lastVec _ newVec; prevOk _ currOk; xseg _ xseg.rest; ENDLOOP; }; context.DoSaveAll[oneCurve]; }; -- DrawBWEntity DrawColorEntity: PROC[handle: GraphHandle, context: Imager.Context, action: PaintAction, output: OutputType, entity: Entity] = { OPEN handle; <> oneCurve: PROC = { xseg: SegmentDataList; eColor: Imager.Color _ GetEntityColor[handle, action, output, entity]; context.SetColor[eColor]; context.TranslateT[[axesRect.x, axesRect.y]]; context.SetXY[[axesRect.x, axesRect.y]]; IF NOT Almost[1.0 - entity.width, 1.0] THEN { <> curve: Imager.PathProc = { first, currOk: BOOL _ TRUE; prevOk: BOOL _ FALSE; xseg _ entity.group.x.segments; FOR yseg: SegmentDataList _ entity.segments, yseg.rest UNTIL yseg = NIL DO vec: Imager.VEC; IF xseg = NIL THEN EXIT; currOk _ NotANan[xseg.first.end] AND NotANan[yseg.first.end]; IF currOk THEN { vec _ [(xseg.first.end-realRect.x)*scale[x], (yseg.first.end-realRect.y)*scale[y]]; IF prevOk THEN lineTo[vec] ELSE moveTo[vec]; }; prevOk _ currOk; xseg _ xseg.rest; ENDLOOP; }; context.SetStrokeJoint[miter]; context.SetStrokeEnd[butt]; context.SetStrokeWidth[entity.width]; context.MaskStroke[path: curve, closed: FALSE]; }; IF entity.mark # none THEN { xseg _ entity.group.x.segments; FOR yseg: SegmentDataList _ entity.segments, yseg.rest UNTIL yseg = NIL DO vec: Imager.VEC; IF NotANan[xseg.first.end] AND NotANan[yseg.first.end] THEN { eFont: ImagerFont.Font _ imagerFonts[output][ IF entity.mark > cross THEN GraphOps.LabelFont ELSE GraphOps.Math8]; vec _ [(xseg.first.end-realRect.x)*scale[x], (yseg.first.end-realRect.y)*scale[y]]; DrawRope[context, GraphConvert.CharRopeFromMark[entity.mark], vec.x, vec.y, center, center, eColor, eFont, 0.0]; }; xseg _ xseg.rest; ENDLOOP; }; }; context.DoSaveAll[oneCurve]; }; -- DrawColorEntity LineShape: TYPE = {solid, dot, dash, longDash}; StepSize: ARRAY LineShape[dot..longDash] OF REAL = [3.0, 6.0, 9.0]; LineLimit: ARRAY LineShape[dot..longDash] OF REAL = [1.0, 4.0, 7.0]; -- beyond which is space. shape: ARRAY EntityColor OF ARRAY StepCount OF LineShape = [ [solid, solid, solid, solid, solid, solid], -- solid, useless [dot, dot, dot, dot, dot, dot], -- dot [dash, dash, dash, dash, dash, dash], -- dash [longDash, longDash, longDash, longDash, longDash, longDash], -- longDash [dot, dash, dot, dash, dot, dash], -- dot-dash [dot, longDash, dot, longDash, dot, longDash], -- dot-longDash [dot, dot, dash, dot, dot, dash], -- dot-dot-dash [dot, dot, longDash, dot, dot, longDash], -- dot-dot-longDash [dot, dash, dash, dot, dash, dash], -- dot-dash-dash [dot, longDash, longDash, dot, longDash, longDash], -- dot-longDash-longDash [dash, longDash, dash, longDash, dash, longDash], -- dash-longDash [dot, dash, longDash, dot, dash, longDash] -- dot-dash-longDash ]; MaskLineSeg: PROC[context: Imager.Context, v0, v1: Imager.VEC _ [0.0, 0.0], style: EntityColor _ 1, state: BWLineState _ NIL] = { <> IF Almost[v0.x, v1.x] AND Almost[v0.y, v1.y] THEN RETURN; IF state = NIL THEN {bwLineState^ _ []; state _ bwLineState}; IF style = 1 THEN context.MaskVector[v0, v1] ELSE { -- Note that normally state.step and state.progress will be updated. dx: REAL = v1.x - v0.x; dy: REAL = v1.y - v0.y; lengthToGo: REAL _ RealFns.SqRt[dx*dx + dy*dy]; cosine: REAL _ dx/lengthToGo; sine: REAL _ dy/lengthToGo; newVec, oldVec: Imager.VEC _ v0; stepSize, lineLimit, increment: REAL; drawIt: BOOL; UNTIL Almost[lengthToGo, 0.0] DO stepSize_ StepSize[shape[style][state.step]]; lineLimit_ LineLimit[shape[style][state.step]]; IF state.progress < lineLimit THEN { drawIt _ TRUE; increment _ MIN[lengthToGo, lineLimit - state.progress]; } ELSE { drawIt _ FALSE; increment _ MIN[lengthToGo, stepSize - state.progress]; }; lengthToGo _ lengthToGo - increment; state.progress _ state.progress + increment; newVec _ [oldVec.x + cosine*increment, oldVec.y + sine*increment]; IF drawIt THEN context.MaskVector[oldVec, newVec] ELSE IF Almost[state.progress, stepSize] THEN { state.step _ (state.step + 1) MOD MaxStep; state.progress _ 0.0; }; oldVec _ newVec; ENDLOOP; }; }; -- MaskLineSeg DrawEntity: PROC [handle: GraphHandle, context: Imager.Context, action: PaintAction, output: OutputType, entity: Entity] = { OPEN handle; IF entity # NIL THEN { entityProc: PROC = { IF entity.colorIndex = 0 THEN -- auto color selection for white curves. lastEntityColor _ entity.colorIndex _ IF lastEntityColor = LastEntityColor THEN 1 ELSE lastEntityColor + 1; <> context.ClipRectangle[axesRect]; IF colorMode = bw THEN DrawBWEntity[handle, context, action, output, entity] ELSE DrawColorEntity[handle, context, action, output, entity]; }; context.DoSaveAll[entityProc]; }; }; -- DrawEntity <> <> <> <> <> <> <<[v1, v2] _ CrossSegment[vx, vy, >> <> <<}; -- DrawCrossSegments>> DrawTails: PROC [handle: GraphHandle, context: Imager.Context, action: PaintAction, output: OutputType, x1, x2: REAL, v1, v2: ValueList, entities: EntityList] = { OPEN handle; drawTails: PROC = { entity: Entity; vec1, vec2: Imager.VEC; oneSegment: PROC = { eColor: Imager.Color _ GetEntityColor[handle, action, output, entity]; context.SetColor[eColor]; IF NOT Almost[1.0 - entity.width, 1.0] THEN { context.SetStrokeWidth[UnitLineWidth*entity.width]; IF colorMode = bw THEN { bwLineState^ _ []; MaskLineSeg[context, vec1, vec2, entity.colorIndex, bwLineState]; } ELSE context.MaskVector[vec1, vec2]; }; IF entity.mark # none THEN { eFont: ImagerFont.Font _ imagerFonts[output][ IF entity.mark > cross THEN GraphOps.LabelFont ELSE GraphOps.Math8]; DrawRope[context, GraphConvert.CharRopeFromMark[entity.mark], vec1.x, vec1.y, center, center, eColor, eFont, 0.0]; DrawRope[context, GraphConvert.CharRopeFromMark[entity.mark], vec2.x, vec2.y, center, center, eColor, eFont, 0.0]; }; }; -- oneSegment context.SetStrokeEnd[square]; context.ClipRectangle[axesRect]; context.TranslateT[[axesRect.x, axesRect.y]]; IF NotANumber[x1] THEN RETURN ELSE vec1.x _ (x1 - realRect.x)*scale[x]; IF NotANumber[x2] THEN RETURN ELSE vec2.x _ (x2 - realRect.x)*scale[x]; FOR el: EntityList _ entities, el.rest UNTIL el = NIL DO ysOk: BOOL _ TRUE; entity _ el.first; IF NotANumber[v1.first] THEN ysOk _ FALSE ELSE vec1.y _ (v1.first - realRect.y)*scale[y]; IF ysOk THEN IF NotANumber[v2.first] THEN ysOk _ FALSE ELSE vec2.y _ (v2.first - realRect.y)*scale[y]; IF entity.colorIndex = 0 THEN -- auto color selection for white curves. lastEntityColor _ entity.colorIndex _ IF lastEntityColor = LastEntityColor THEN 1 ELSE lastEntityColor + 1; IF ysOk THEN context.DoSaveAll[oneSegment]; IF (v1 _ v1.rest) = NIL OR (v2 _ v2.rest) = NIL THEN EXIT; ENDLOOP; }; -- drawTails context.DoSaveAll[drawTails]; }; -- DrawTails GetEntityColor: PROC [handle: GraphHandle, action: PaintAction, output: OutputType, entity: Entity] RETURNS [Imager.Color] = { <> OPEN handle; RETURN[ IF action = erase THEN BackgroundColor[backgroundIndex, output] ELSE IF colorMode = bw THEN ForegroundColor[backgroundIndex, output] ELSE systemColor[entity.colorIndex] ]; }; -- GetEntityColor GetStandardColor: PROC [handle: GraphHandle, action: PaintAction, output: OutputType] RETURNS [Imager.Color] = { <> OPEN handle; RETURN[ IF action = paint THEN ForegroundColor[handle.backgroundIndex, output] ELSE BackgroundColor[handle.backgroundIndex, output] ]; }; -- GetStandardColor Print: PUBLIC PROC[handle: GraphHandle, file: ROPE] RETURNS [msg: ROPE _ NIL] = { IF HandleNotNil[handle] THEN IF handle.graph # NIL THEN { OPEN handle; InterpressDrawGraph: PROC[context: Imager.Context] ~ { coordRatio: REAL = 0.25/600.0; -- 0.25 m ~ 600 pixels context.RotateT[90]; context.ScaleT[coordRatio]; Draw[handle, context, info]; }; interpress: ImagerInterpress.Ref _ NIL; info: PaintInfo _ NEW[all PaintInfoRec _ [ action: paint, output: interpress, data: all[clear: FALSE]]]; InitPFonts[handle]; interpress _ ImagerInterpress.Create[file ! FS.Error => {msg _ error.explanation; CONTINUE}]; IF msg = NIL THEN { ImagerInterpress.DoPage[interpress, InterpressDrawGraph]; ImagerInterpress.Close[interpress]; }; }; }; -- Print InitPFonts: PROC [h: GraphHandle] = { EnsurePFont: PROC [i: FontIndex] RETURNS [f: ImagerFont.Font] = { f _ h.imagerFonts[interpress][i]; IF f = NIL THEN { pFontName: ROPE _ Rope.Cat[ "Xerox/PressFonts/", h.graph.font[i].family, Rope.Concat[ IF h.graph.font[i].bold THEN "-b" ELSE "-m", IF h.graph.font[i].italic THEN "ir" ELSE "rr"] ]; f _ ImagerFont.Find[pFontName ! Imager.Error => { f _ ImagerFont.Find["Xerox/PressFonts/TimesRoman-mrr"]; CONTINUE}]; f _ ImagerFont.Scale[f, h.graph.font[i].pFontScale ! Imager.Error => {f _ ImagerFont.Scale[f, 9.0]; CONTINUE}]; }; }; -- InitPFont <<-- fonts for legend, label, and mark>> h.imagerFonts[interpress][GraphOps.LabelFont] _ EnsurePFont[GraphOps.LabelFont]; h.imagerFonts[interpress][GraphOps.LegendFont] _ EnsurePFont[GraphOps.LegendFont]; h.imagerFonts[interpress][GraphOps.Math8] _ EnsurePFont[GraphOps.Math8]; <<-- fonts for texts>> FOR ts: Texts _ h.graph.texts, ts.rest UNTIL ts = NIL DO i: FontIndex _ ts.first.fontIndex; h.imagerFonts[interpress][i] _ EnsurePFont[i]; ENDLOOP; }; -- InitPFonts PrintCaretsInfo: PROC [handle: GraphHandle, context: Imager.Context] = { OPEN handle; caretsProc: PROC = { xh1: CaretSpec _ graph.caret[primary]; xh2: CaretSpec _ graph.caret[secondary]; xh3: CaretSpec _ graph.caret[text]; cvec: Imager.VEC; cColor: Imager.Color _ ForegroundColor[backgroundIndex, interpress]; context.SetColor[cColor]; context.SetStrokeWidth[UnitLineWidth]; context.SetStrokeEnd[butt]; IF xh1.on THEN { cvec _ RealVecToScreenVec[handle, xh1.place]; context.MaskVector[[cvec.x - 8.0, cvec.y], [cvec.x + 8.0, cvec.y]]; context.MaskVector[[cvec.x, cvec.y - 8.0], [cvec.x, cvec.y + 8.0]]; }; IF xh2.on THEN { cvec _ RealVecToScreenVec[handle, xh2.place]; bwLineState^ _ []; MaskLineSeg[context, [cvec.x - 8.0, cvec.y], [cvec.x + 8.0, cvec.y], 2, bwLineState]; bwLineState^ _ []; MaskLineSeg[context, [cvec.x, cvec.y - 8.0], [cvec.x, cvec.y + 8.0], 2, bwLineState]; }; IF xh3.on THEN { cvec _ RealVecToScreenVec[handle, xh3.place]; context.MaskVector[[cvec.x - 8.0, cvec.y], [cvec.x + 8.0, cvec.y]]; context.MaskVector[[cvec.x - 5.65, cvec.y + 5.65], [cvec.x + 5.65, cvec.y - 5.65]]; context.MaskVector[[cvec.x - 5.65, cvec.y - 5.65], [cvec.x + 5.65, cvec.y + 5.65]]; }; IF xh1.on OR xh2.on THEN { bothOn: BOOL _ xh1.on AND xh2.on; rope: ROPE _ Rope.Concat["Crosshair", IF bothOn THEN "s: " ELSE ": "]; IF xh1.on THEN rope _ rope.Concat[GraphConvert.RopeOfPlace[xh1.place]]; IF bothOn THEN rope _ rope.Concat[", "]; IF xh2.on THEN rope _ rope.Concat[GraphConvert.RopeOfPlace[xh2.place]]; IF bothOn THEN rope _ rope.Cat["; slope: ", GraphConvert.RopeOfSlope[xh1.place, xh2.place]]; DrawRope[context, rope, axesRect.x + axesRect.w, axesRect.y + axesRect.h + TextHeightFudge, right, bottom, cColor, imagerFonts[interpress][GraphOps.LegendFont], 0.0]; }; }; context.DoSaveAll[caretsProc]; }; -- PrintCaretsInfo }. CHANGE LOG. SChen, created at October 9, 1985 6:13:12 pm PDT.