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, 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]; 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; IF graph.auto[bounds] OR graph.auto[divisions] THEN { step _ FindStepSize[max - min, nDiv]; min _ AlignEnd[minDataValue, step, TRUE]; max _ AlignEnd[maxDataValue, step, TRUE]; }; IF NOT graph.auto[bounds] THEN {min _ minDataValue; max _ maxDataValue}; range _ max - min; IF NOT graph.auto[divisions] THEN step _ range/nDiv; factor _ axisSize/range; }; -- ScaleAxis IF invalidBounds OR graph.auto[bounds] THEN { EnsureValidBound: PROC [min, max: REAL] RETURNS [minNew, maxNew: REAL] = { minNew _ min; maxNew _ max; IF minNew >= maxNew THEN { maxNew _ IF minNew = 0.0 AND maxNew = 0.0 THEN 1.0 ELSE minNew + MAX[ABS[minNew], ABS[maxNew]]; }; }; -- EnsureValidBounds box: Imager.Box _ DataBounds[graph.entityList]; [graph.bounds.xmin, graph.bounds.xmax] _ EnsureValidBound[box.xmin, box.xmax]; [graph.bounds.ymin, graph.bounds.ymax] _ EnsureValidBound[box.ymin, box.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]; 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 currOk _ NOT (NotANumber[xseg.first.end] OR NotANumber[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; currOk _ NOT (NotANumber[xseg.first.end] OR NotANumber[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 NOT (NotANumber[xseg.first.end] OR NotANumber[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 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. ÖGraphDraw.mesa, Copyright c 1985 by Xerox Corporation. All rights reserved. Last Edited by: Sweetsun Chen, November 15, 1985 5:54:38 pm PST constants global vars type: procs IF NOT autoRepaint THEN GraphCarets.Suspend[]; x: PaintXsegInfo => DrawCrossSegments[context, handle, c]; x: PaintPlaceInfo => DrawXhairPlaces[context, handle, x]; IF NOT autoRepaint THEN GraphCarets.Resume[]; All drawing procs below are called by Draw above, which gurantees context, handle, and handle.paintInfo not nil. IF info.output = screen, the following vars on handle are updated: axesRec, realRec, scale, step. useful values vars to save some output device dependent values before making interpress master. resume altered values after making interpress master. update carets places on graph viewer. draw x target or y target depending on info.xy. erasing action can happen only in screen painting font and color should be set before calling this proc. x0 and y0 are in screen coordinates. IF text.IsEmpty[] THEN RETURN[[x0, y0]]; RETURN[IF output = interpress THEN [x0 + width, y0 + height] ELSE ImagerBackdoor.GetCP[context]]; vars on handle referenced: graph.bounds, axesRect.w, axesRect.h, graph.division, graph.auto; updates vars on handle: realRect, scale, step. IF Almost[min, max] THEN {max _ max + 50.0; min _ min - 50.0}; -- won't happen, see below. bounds checking divisions scaling IF Almost[range, 0.0] THEN range _ 100.0; IF e < 0.0 THEN roundUp _ ~roundUp; vars on handle referenced: backgroundIndex, paintInfo.output, axesRect. must be painting instead of erasing. global const/vars refrenced: UnitLineWidth, TextHeightFudge. vars on handle referenced: graph.grids[xyChoice], backgroundIndex, paintInfo.output, imagerFont[paintInfo.output][0], axesRect, realRect, scale, step, fgColor, stdFont. vertical axis If normalLegend {icol = 1..2, irow = 1..6} otherwise {icol = 3, irow = 1..12}. DrawRope[context, entity.name, tx, ty, left, bottom, lColor, lFont]; color is the foreground color for use when colormode = black-and-white. this proc is called by DrawGraph only, when paint action = paint instead of erase. Remember to clip any rectangles before calling this proc. Remember to clip any rectangles before calling this proc. Remember to clip any rectangles, set the color and width before calling this proc. DrawLegend[context, handle, entity]; DrawCrossSegments: PROC[context: Imager.Context, handle: GraphHandle _ NIL, info: PaintXsegInfo _ NIL] = { OPEN handle; FOR el: EntityList _ graph.entityList, el.rest UNTIL el = NIL DO entity: Entity _ el.first; vx: ValueList _ entity.group.x; vy: ValueList _ entity.newValues; v1, v2: Imager.VEC; [v1, v2] _ CrossSegment[vx, vy, ENDLOOP; }; -- DrawCrossSegments caller must make sure that info.v1 and info.v2 are of the same length as graph.entityList. choose the right color for painting entity and legend. choose the right color for painting entity and legend. choose the drawing color for axes, ticks, grids, targets, and labels on axes, whether painting or erasing. ÊߘJšÏcœ Ïmœ0™Lšœ™Jšœ/™/J™—šÏk ˜ JšœŸœ˜JšœŸœâŸœRŸœ˜ÇJšœ Ÿœ˜$Jšœ@˜@Jšœ Ÿœ˜!Jšœ ŸœŸ˜®JšœŸœÔŸœ˜åJšœŸœ ˜!Jšœ Ÿœ"˜2JšœŸœ˜#JšœŸœ˜"JšœŸœ˜"J˜—šœ ŸœŸ˜JšŸœu˜|JšŸœ˜—J˜JšŸœ ˜$J˜J™ JšœŸœ˜JšœŸœ˜JšœŸœ˜J˜J™ JšœŸœ˜2J˜J™Jš ÏnœŸœŸœŸœŸœ˜QJ˜J™š  œŸœŸœŸœŸœ˜PšŸœ ŸœŸœ ŸœŸœŸœŸœŸœŸœ˜TJš œ ŸœŸœŸœŸœ˜TJšŸœŸœ Ÿœ™.Jšœ˜šŸœŸ˜!Jšœ"˜"J˜,Jšœ+˜+Jšœ'˜'Jšœ$˜$Jšœ(˜(Jšœ'˜'Jšœ8Ÿœ˜SJšœ&˜&Jšœ:™:J™9JšŸœ˜—JšŸœŸœ Ÿœ™-Jšœ˜J˜—Jšœ˜ —J˜Jšœp™pš  œŸœ˜$šœ˜J™a—J™ šœŸœŸœ˜XJšŸœ#˜'—J˜J™QJšœ˜Jš œ ŸœŸœŸœŸœ˜JšœŸœ˜ JšŸœŸœE˜ešŸœŸœ Ÿœ#˜=šœ Ÿœ˜Jšœ<˜˜>JšœŸœ+˜5JšœŸœ+˜5JšœŸœ˜"Jšœ,˜,Jšœ&˜&JšœŸœ˜JšœŸœŸœ,˜>JšœŸœŸœ-˜?JšŸœŸœŸœ$˜EJ˜—šŸœ˜Jšœ˜Jšœ˜J˜—J˜J™šœ8˜8Jšœ9˜9—šœ8˜8Jšœ9˜9—Jšœ ˜J˜š   œŸœ Ÿœ ŸœŸœŸœ˜KJšœ Ÿœ˜JšœŸœ˜JšœŸœ˜JšœŸœŸœŸœ#˜?J˜JšŸœŸœ™)J˜$J˜J˜šŸœŸœ˜Jšœ˜J˜—J˜.šŸœŸœŸœŸ˜Jšœ˜JšŸœŸœŸœŸ˜4JšŸœ˜—š Ÿœ ŸœŸœ ŸœŸ˜=JšŸœŸœ ŸœŸœ˜3—JšœŸœ ˜Jšœ˜J˜—š  œŸœ Ÿœ ŸœŸœŸœ˜GJšœŸœŸœ˜JšœŸœ˜JšœŸœ˜ J˜JšŸœ ŸœŸœ˜JšŸœ Ÿœ™#J˜!J˜JšŸœŸœ ˜+šŸœŸœ˜JšŸœŸœŸœŸœ˜:JšŸœŸœŸœŸœ˜;—JšŸœ Ÿœ ˜šœ ˜J˜———š œ˜JšœG™Gšœ ŸœŸœ˜šœI˜IJ™$—Jšœ"˜"šœŸœ˜&JšœŸœ ˜JšœŸœ ˜Jšœ˜Jšœ˜J˜Jšœ˜J˜—J˜Jšœ˜J˜Jšœ*˜*Jšœ)Ÿœ˜/J˜—J˜Jšœ ˜J˜—š   œŸœ9ŸœŸœŸœŸœ˜yJšœ<™ ˜IJšœ# ˜.Jšœ/˜>Jšœ"˜1Jšœ*˜=Jšœ$˜4Jšœ4˜LJšœ2˜BJšœ+˜?J˜J˜—š  œŸœ˜*JšœŸœ<Ÿœ˜VJ™RJšŸœŸœŸœŸœ˜9JšŸœ ŸœŸœ*˜=JšŸœ Ÿœ˜,šŸœD˜KJšœŸœ˜JšœŸœ˜Jšœ Ÿœ˜/JšœŸœ˜JšœŸœ˜JšœŸœ˜ Jšœ Ÿœ˜%JšœŸœ˜ šŸœŸ˜ Jšœ-˜-Jšœ/˜/šŸœŸœ˜$Jšœ Ÿœ˜Jšœ Ÿœ)˜8J˜—šŸœ˜Jšœ Ÿœ˜Jšœ Ÿœ(˜7J˜—Jšœ$˜$Jšœ,˜,JšœB˜BJšŸœŸœ#˜1šŸœŸœ"Ÿœ˜/JšœŸœ ˜*J˜J˜—Jšœ˜JšŸœ˜—Jšœ˜—Jšœ˜—J˜—š  œ˜JšŸœ˜ Jšœ)˜)šŸœ ŸœŸœ˜šœ Ÿœ˜šŸœŸœ)˜Gšœ&Ÿœ#Ÿœ˜QJšŸœ˜——Jšœ$™$Jšœ ˜ JšŸœŸœ&˜J˜—Jšœ˜J˜—š œŸœ˜*šœ Ÿœ˜Jšœ&˜&Jšœ(˜(Jšœ#˜#Jšœ Ÿœ˜JšœD˜DJšœ˜J˜&J˜šŸœŸœ˜Jšœ-˜-JšœE˜EJšœE˜EJ˜—šŸœŸœ˜Jšœ-˜-J˜JšœW˜WJ˜JšœW˜WJ˜—šŸœŸœ˜Jšœ-˜-JšœE˜EJšœS˜SJšœS˜SJ˜—šŸœŸœŸœ˜JšœŸœ Ÿœ˜!Jš œŸœŸœŸœŸœ˜FJšŸœŸœ9˜GJšŸœŸœ˜(JšŸœŸœ9˜GšŸœŸœ˜+Jšœ0˜0—šœJ˜JJšœ/˜/—Jšœ˜—J˜—Jšœ˜Jšœ˜—J˜J˜šŸœŸœ˜ Jšœ1˜1——…—iV“