<> <> <> <<>> DIRECTORY Convert USING [RopeFromReal], Graph USING [BackgroundIndex, BWLineState, CaretIndex, CaretSpec, ColorMode, Entity, EntityColor, EntityList, FontIndex, Fonts, GraphHandle, JustifX, JustifY, LastEntityColor, LineStateRec, Mark, MaxStep, NullVec, OutputType, PaintInfo, ROPE, SegmentData, SegmentDataList, StepCount, Text, Texts, UnitLineWidth, ValueList, XY], GraphCarets USING [Resume, Suspend], GraphConvert USING [CharRopeFromMark, RopeOfPlace, RopeOfSlope], GraphPrivate USING [systemColor], GraphUtil USING [Almost, BackgroundColor, BlinkMsg, DataBounds, FontHeight, ForegroundColor, FullName, NotANan, NotANumber, RealToScreen, RealToScreenI, RealVecToScreenVec, RopeSize], Imager USING [Box, ClipRectangle, Color, Context, DoSaveAll, MaskRectangle, MaskStroke, MaskVector, PathProc, Rectangle, RotateT, SetColor, SetFont, SetStrokeEnd, SetStrokeJoint, SetStrokeWidth, SetXY, ShowRope, TranslateT, VEC], ImagerBackdoor USING [GetBounds], ImagerFont USING [Extents, Font, RopeBoundingBox], Real USING [FixI, RoundI, RoundLI], RealFns USING [Log, Power, SqRt], Rope USING [Cat, Concat, IsEmpty]; GraphDraw: CEDAR PROGRAM IMPORTS Convert, Imager, ImagerBackdoor, ImagerFont, 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 _ []]; <> DrawProc: TYPE = PROC [context: Imager.Context _ NIL, handle: GraphHandle _ NIL]; <> Draw: PUBLIC PROC [context: Imager.Context _ NIL, handle: GraphHandle _ NIL] = { IF context # NIL AND handle # NIL THEN IF handle.paintInfo # NIL THEN { OPEN handle; autoRepaint: BOOL _ paintInfo.item = all AND (NOT paintInfo.clear) AND chart.dirty; <> GraphCarets.Suspend[]; SELECT handle.paintInfo.item FROM all => DrawGraph[context, handle]; allCurves => DrawMajorPart[context, handle]; graphEntity => DrawEntity[context, handle]; graphText => DrawText[context, handle]; tails => DrawTails[context, handle]; rectangle => DrawGraph[context, handle]; target => DrawTargets[context, handle]; grid => DrawTicks[context, handle, handle.paintInfo.xy, FALSE]; -- false: no label. legend => DrawLegend[context, handle]; < DrawCrossSegments[context, handle, c];>> < DrawXhairPlaces[context, handle, x];>> ENDCASE; <> GraphCarets.Resume[]; }; }; -- Draw <> DrawGraph: DrawProc = { OPEN handle; info: PaintInfo _ paintInfo; <> <> rect: Imager.Rectangle _ IF info.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 info.output = interpress THEN {axesRectSave _ axesRect; scaleSave _ scale; dlSave _ normalLegend;} ELSE IF info.clear THEN { -- happens in screen painting only. clearProc: PROC = { context.SetColor[BackgroundColor[backgroundIndex, screen]]; context.MaskRectangle[rect]; }; context.DoSaveAll[clearProc]; FOR i: CaretIndex IN CaretIndex DO chart.caretState[i].visible _ FALSE; ENDLOOP; }; normalLegend _ (info.output = interpress) OR rect.h >= 450; { -- setting axesRect margin1: REAL = MIN[rect.h * 0.05, 10.0]; margin2: REAL = MIN[rect.w * 0.05, 20.0]; xLabelH: REAL = FontHeight[imagerFonts[paintInfo.output][0]]; legendH: REAL = FontHeight[imagerFonts[paintInfo.output][1]]; -- 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)]; 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]; DrawAxes[context, handle]; DrawTicks[context, handle, x, TRUE]; DrawTicks[context, handle, y, TRUE]; DrawTexts[context, handle]; IF graph.entityList # NIL THEN { info.entity _ graph.entityList.first; info.onlyOne _ FALSE; DrawLegend[context, handle]; }; IF info.output = interpress THEN PrintCaretsInfo[context, handle]; IF graph.target[x].on THEN {info.xy _ x; DrawTargets[context, handle]}; IF graph.target[y].on THEN {info.xy _ y; DrawTargets[context, handle]}; DrawEntities[context, handle]; }; context.DoSaveAll[wholePicture]; }; <> IF info.output = interpress THEN {axesRect _ axesRectSave; scale _ scaleSave; normalLegend _ dlSave}; <> FOR i: CaretIndex IN CaretIndex 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: DrawProc = { OPEN handle; IF axesRect.w > 0.0 AND axesRect.h > 0.0 THEN { majorPart: PROC = { IF paintInfo.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[context, handle]; DrawTicks[context, handle, x, TRUE]; DrawTicks[context, handle, y, TRUE]; DrawTexts[context, handle]; IF graph.target[x].on THEN {paintInfo.xy _ x; DrawTargets[context, handle]}; IF graph.target[y].on THEN {paintInfo.xy _ y; DrawTargets[context, handle]}; DrawEntities[context, handle]; }; context.DoSaveAll[majorPart]; }; }; -- DrawMajorPart DrawTargets: DrawProc = { OPEN handle; <> targetProc: PROC = { info: PaintInfo _ handle.paintInfo; rect: Imager.Rectangle; tcolor: Imager.Color _ IF info.action = erase THEN BackgroundColor[backgroundIndex, screen] ELSE systemColor[graph.target[info.xy].colorIndex]; <> IF info.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]; }; -- DrawTargets 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: DrawProc = { 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[paintInfo.output][t.fontIndex], t.rotation]; ENDLOOP; }; -- DrawTexts DrawText: DrawProc = { OPEN handle, handle.axesRect; t: Text _ paintInfo.text; DrawRope[context, t.text, t.place.x * w + x, t.place.y * h + y, t.justifX, t.justifY, GetTextColor[handle, t], imagerFonts[paintInfo.output][t.fontIndex], t.rotation]; }; -- DrawText ScaleAxes: PROC[handle: GraphHandle] = { OPEN handle; <> <> nDivX, nDivY: INT; invalidDiv: BOOL _ graph.division[x] < 1 OR graph.division[y] < 1; invalidBounds: BOOL _ (graph.bounds.xmin >= graph.bounds.xmax) OR (graph.bounds.ymin >= graph.bounds.ymax); 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 <<>> <> IF invalidBounds 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 invalidDiv THEN { -- 5/8 = 0.625 labelFont: ImagerFont.Font _ imagerFonts[paintInfo.output][0]; maxX: ROPE _ Convert.RopeFromReal[graph.bounds.xmax]; minX: ROPE _ Convert.RopeFromReal[graph.bounds.xmin]; wMaxX, wMinX, width, height: REAL; [wMaxX, height] _ RopeSize[maxX, labelFont]; [wMinX, ] _ RopeSize[minX, labelFont]; width _ MAX[wMaxX, wMinX]; nDivX _ MAX[1, MIN[10, Real.RoundLI[axesRect.w/width*0.625]]]; nDivY _ MAX[1, MIN[10, Real.RoundLI[axesRect.h/height*0.625]]]; IF NOT graph.auto[divisions] THEN BlinkMsg["Divisions must be >= 2"]; } 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.FixI[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.RoundLI[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: DrawProc = { <> axesProc: PROC = { OPEN handle; color: Imager.Color _ ForegroundColor[backgroundIndex, paintInfo.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[context: Imager.Context, handle: GraphHandle, xyChoice: XY _ x, drawLabel: BOOL _ FALSE] = { OPEN handle; <> <> <<>> ticksProc: PROC = { tickCount: CARDINAL; -- max tick count, starting from 0. tickLen, textPlace, tick: REAL; gridsOn: BOOL _ graph.grids[xyChoice]; stdColor: Imager.Color _ GetStandardColor[handle]; stdFont: ImagerFont.Font _ imagerFonts[paintInfo.output][0]; context.SetColor[stdColor]; context.SetStrokeEnd[butt]; context.SetStrokeWidth[UnitLineWidth]; IF xyChoice = x THEN { -- x ticks (on horizontal axis) tickLen _ IF gridsOn THEN axesRect.h ELSE axesRect.h/50.0; tickCount _ Real.RoundI[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, stdFont, 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 gridsOn THEN { yStart _ axesRect.y + axesRect.h; yEnd _ yStart - tickLen; context.MaskStroke[path: Tick, closed: FALSE]; }; }; ENDLOOP; } ELSE { tickLen _ IF gridsOn THEN axesRect.w ELSE axesRect.w/50.0; tickCount _ Real.RoundI[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, stdFont, 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 gridsOn THEN { xStart _ axesRect.x + axesRect.w; xEnd _ xStart - tickLen; context.MaskStroke[path: Tick, closed: FALSE]; }; }; ENDLOOP; }; }; context.DoSaveAll[ticksProc]; }; -- DrawTicks DrawLegend: DrawProc = { OPEN handle; MaxEntries: INT = 12; MaxEntriesPerColumn: INT = MaxEntries/2; notYet: BOOL _ paintInfo.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 paintInfo.entity # NIL THEN IF el.first = paintInfo.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[context, handle, el.first, icol, irow]; IF paintInfo.onlyOne THEN EXIT; }; } ELSE EXIT; ENDLOOP; IF AdvanceRC[] AND NOT paintInfo.onlyOne THEN DrawLegendEntry[context, handle, NIL, icol, irow]; -- blank }; -- DrawLegend DrawLegendEntry: PROC [context: Imager.Context, handle: GraphHandle, entity: Entity, icol, irow: INT] = { OPEN handle; legendProc: PROC = { lFont: ImagerFont.Font _ imagerFonts[paintInfo.output][1]; legendH: REAL _ FontHeight[lFont]; labelH: REAL _ FontHeight[imagerFonts[paintInfo.output][0]]; 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 paintInfo.output = screen THEN { -- clear context.SetColor[BackgroundColor[backgroundIndex, paintInfo.output]]; context.MaskRectangle[[lx1, ty, legendW, legendH]]; }; context.ClipRectangle[[lx1, ty, legendW, legendH]]; IF paintInfo.action = paint AND entity # NIL THEN { lColor: Imager.Color _ GetEntityColor[handle, 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.SetStrokeEnd[butt]; context.SetStrokeWidth[UnitLineWidth*entity.width]; context.SetColor[lColor]; IF colorMode = color THEN context.MaskVector[[lx1, ly], [lx2, ly]] ELSE { bwLineState^ _ []; MaskLineSeg[context, [tx, ly], [lx1+lineLength, ly], entity.colorIndex, bwLineState] }; }; DrawRope[context, FullName[entity], tx, ty, left, bottom, lColor, lFont, 0.0]; <> }; }; -- proc context.DoSaveAll[legendProc]; }; -- DrawLegendEntry DrawEntities: DrawProc = { 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[context, handle, entity] ELSE DrawColorEntity[context, handle, entity]; ENDLOOP; }; -- entitiesProc context.DoSaveAll[entitiesProc]; }; }; -- DrawEntities DrawBWEntity: PROC [context: Imager.Context, handle: GraphHandle, 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, entity]; bwLineState^ _ []; context.SetColor[eColor]; context.TranslateT[[axesRect.x, axesRect.y]]; context.SetStrokeJoint[mitered]; context.SetStrokeEnd[butt]; context.SetStrokeWidth[UnitLineWidth*entity.width]; 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 THEN MaskLineSeg[context, lastVec, newVec, entity.colorIndex, bwLineState]; IF entity.mark # none AND currOk THEN { eFont: ImagerFont.Font _ imagerFonts[paintInfo.output][IF entity.mark > cross THEN 0 ELSE LAST[FontIndex]]; 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[context: Imager.Context, handle: GraphHandle, entity: Entity] = { OPEN handle; <> oneCurve: PROC = { xseg: SegmentDataList; 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; }; eColor: Imager.Color _ GetEntityColor[handle, entity]; context.SetColor[eColor]; context.SetStrokeJoint[mitered]; context.SetStrokeEnd[butt]; context.SetStrokeWidth[entity.width]; context.TranslateT[[axesRect.x, axesRect.y]]; context.SetXY[[axesRect.x, axesRect.y]]; 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[paintInfo.output][IF entity.mark > cross THEN 0 ELSE LAST[FontIndex]]; 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: DrawProc = { OPEN handle; entity: Entity _ handle.paintInfo.entity; 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[context, handle, entity] ELSE DrawColorEntity[context, handle, entity]; }; context.DoSaveAll[entityProc]; }; }; -- DrawEntity <> <> <> <> <> <> <<[v1, v2] _ CrossSegment[vx, vy, >> <> <<}; -- DrawCrossSegments>> DrawTails: DrawProc = { OPEN handle; info: PaintInfo _ handle.paintInfo; <> drawTails: PROC = { v1: ValueList _ info.v1; v2: ValueList _ info.v2; entity: Entity; vec1, vec2: Imager.VEC; oneSegment: PROC = { context.SetStrokeWidth[UnitLineWidth*entity.width]; context.SetColor[GetEntityColor[handle, entity]]; IF colorMode = bw THEN { bwLineState^ _ []; MaskLineSeg[context, vec1, vec2, entity.colorIndex, bwLineState]; } ELSE context.MaskVector[vec1, vec2]; }; -- oneSegment context.SetStrokeEnd[square]; context.ClipRectangle[axesRect]; context.TranslateT[[axesRect.x, axesRect.y]]; IF NotANumber[info.x1] THEN RETURN ELSE vec1.x _ (info.x1 - realRect.x)*scale[x]; IF NotANumber[info.x2] THEN RETURN ELSE vec2.x _ (info.x2 - realRect.x)*scale[x]; FOR el: EntityList _ graph.entityList, 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, entity: Entity] RETURNS [Imager.Color] = { <> OPEN handle; RETURN[ IF paintInfo.action = erase THEN BackgroundColor[backgroundIndex, paintInfo.output] ELSE IF colorMode = bw THEN ForegroundColor[backgroundIndex, paintInfo.output] ELSE systemColor[entity.colorIndex] ]; }; -- GetEntityColor GetTextColor: PROC [handle: GraphHandle, text: Text] RETURNS [Imager.Color] = { <> OPEN handle; RETURN[ IF paintInfo.action = erase THEN BackgroundColor[backgroundIndex, paintInfo.output] ELSE systemColor[text.colorIndex] ]; }; -- GetTextColor GetStandardColor: PROC [handle: GraphHandle] RETURNS [Imager.Color] = { <> OPEN handle; RETURN[ IF paintInfo.action = paint THEN ForegroundColor[handle.backgroundIndex, paintInfo.output] ELSE BackgroundColor[handle.backgroundIndex, paintInfo.output] ]; }; -- GetStandardColor PrintCaretsInfo: DrawProc = { 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 - 17.0, cvec.y], [cvec.x + 17.0, cvec.y]]; context.MaskVector[[cvec.x, cvec.y - 17.0], [cvec.x, cvec.y + 17.0]]; }; IF xh2.on THEN { cvec _ RealVecToScreenVec[handle, xh2.place]; bwLineState^ _ []; MaskLineSeg[context, [cvec.x - 17.0, cvec.y], [cvec.x + 17.0, cvec.y], 2, bwLineState]; bwLineState^ _ []; MaskLineSeg[context, [cvec.x, cvec.y - 17.0], [cvec.x, cvec.y + 17.0], 2, bwLineState]; }; IF xh3.on THEN { cvec _ RealVecToScreenVec[handle, xh3.place]; context.MaskVector[[cvec.x - 17.0, cvec.y], [cvec.x + 17.0, cvec.y]]; context.MaskVector[[cvec.x - 12.0, cvec.y + 12.0], [cvec.x + 12.0, cvec.y - 12.0]]; context.MaskVector[[cvec.x - 12.0, cvec.y - 12.0], [cvec.x + 12.0, cvec.y + 12.0]]; }; 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.y + axesRect.h, left, bottom, cColor, imagerFonts[paintInfo.output][0], 0.0]; }; }; context.DoSaveAll[caretsProc]; }; -- PrintCaretsInfo }. CHANGE LOG. SChen, created at October 9, 1985 6:13:12 pm PDT.