DIRECTORY Convert USING [RopeFromReal], Graph USING [CaretIndex, CaretSpec, Entity, EntityColor, EntityList, FontIndex, JustifX, JustifY, LastEntityColor, Mark, NullVec, ROPE, SegmentData, SegmentDataList, Text, Texts, UnitLineWidth, ValueList, XY], GraphCarets USING [Resume, Suspend], GraphConvert USING [CharRopeFromMark, RopeOfPlace, RopeOfSlope], GraphPrivate USING [BackgroundIndex, BWLineState, ColorMode, Fonts, GraphHandle, LineStateRec, MaxStep, OutputType, PaintInfo, StepCount, systemColor], GraphUtil USING [Almost, BackgroundColor, BlinkMsg, DataBounds, FontHeight, ForegroundColor, FullName, RealToScreen, RealToScreenI, RealVecToScreenVec], Imager USING [Box, ClipRectangle, Color, Context, DoSaveAll, MaskRectangle, MaskStroke, MaskVector, PathProc, Rectangle, 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; LabelWidth: REAL = 60.0; LabelHeight: REAL = 16; 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]; 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. 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; wtlSave: BOOL; IF info.output = interpress THEN {axesRectSave _ axesRect; scaleSave _ scale; wtlSave _ windowTooLow;} ELSE IF info.clear THEN { -- happens in screen painting only. clearProc: PROC = { context.SetColor[BackgroundColor[backgroundIndex, screen]]; context.MaskRectangle[rect]; }; context.DoSaveAll[clearProc]; }; axesRect _ [rect.x + 50.0, rect.y + rect.h*0.05, rect.w - 100.0, rect.h*0.9]; windowTooLow _ (info.output = screen) AND rect.h < 440; IF NOT windowTooLow THEN { -- leave space for title/legend. legendEtc: REAL _ FontHeight[imagerFonts[paintInfo.output][0]]*7 + TextHeightFudge; axesRect.y _ axesRect.y + legendEtc; axesRect.h _ axesRect.h - rect.h*0.1 - legendEtc; }; 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 NOT windowTooLow THEN DrawLegends[context, handle]; IF info.output = interpress THEN PrintCaretsInfo[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[wholePicture]; }; IF info.output = interpress THEN {axesRect _ axesRectSave; scale _ scaleSave; windowTooLow _ wtlSave}; 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 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] = { IF ~text.IsEmpty[] THEN { showTextProc: PROC = { context.TranslateT[[x0, y0]]; 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]]; 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]]; }; -- 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; step _ IF graph.auto[divisions] THEN FindStepSize[maxDataValue - minDataValue, nDiv] ELSE (maxDataValue - minDataValue)/nDiv; IF graph.auto[bounds] THEN { min _ AlignEnd[minDataValue, step, FALSE]; max _ AlignEnd[maxDataValue, step, TRUE]; IF Almost[min, max] THEN {max _ max + 50.0; min _ min - 50.0}; } ELSE { min _ minDataValue; max _ maxDataValue; }; range _ max - min; factor _ axisSize/range; }; -- ScaleAxis IF invalidBounds THEN { graph.bounds _ DataBounds[graph.entityList]; 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 nDivX _ MAX[1, MIN[10, Real.RoundLI[axesRect.w/LabelWidth*0.625]]]; nDivY _ MAX[1, MIN[10, Real.RoundLI[axesRect.h/LabelHeight*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]; IF Almost[range, 0.0] THEN range _ 100.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 - 0.5]; 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]; 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]; 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 DrawLegends: DrawProc = { OPEN handle; FOR el: EntityList _ graph.entityList, el.rest UNTIL el = NIL DO entity: Entity _ el.first; IF entity.colorIndex = 0 THEN -- auto color selection for white curves. lastEntityColor _ entity.colorIndex _ ((lastEntityColor + 1) MOD LastEntityColor) + 1; DrawLegend[context, handle, entity]; ENDLOOP; }; -- DrawLegends DrawLegend: PROC [context: Imager.Context, handle: GraphHandle, entity: Entity] = { OPEN handle; legendProc: PROC = { lColor: Imager.Color _ GetEntityColor[handle, entity]; lFont: ImagerFont.Font _ imagerFonts[paintInfo.output][0]; fontHeight: REAL _ FontHeight[lFont]; MaxEntriesPerColumn: CARDINAL = 6; lineLength: REAL _ IF entity.colorIndex IN [8..9] THEN 31.0 ELSE 34.0; lx1: REAL _ IF entity.colorIndex <= MaxEntriesPerColumn THEN axesRect.x ELSE axesRect.x + axesRect.w*0.5; lx2: REAL _ lx1 + 34.0; tx: REAL _ lx2 + TextWidthFudge; row: CARDINAL _ entity.colorIndex MOD MaxEntriesPerColumn; ty: REAL _ axesRect.y - (TextHeightFudge + TextHeightFudge) - fontHeight*(IF row = 0 THEN 7 ELSE (row + 1)); ly: REAL _ ty + fontHeight*0.5; context.SetColor[BackgroundColor[backgroundIndex, paintInfo.output]]; context.MaskRectangle[[lx1, ty, axesRect.w*0.5, fontHeight]]; context.ClipRectangle[[lx1, ty, axesRect.w*0.5, fontHeight]]; IF paintInfo.action = paint 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], [lx2, ly], entity.colorIndex, bwLineState] }; DrawRope[context, FullName[entity], tx, ty, left, bottom, lColor, lFont]; }; }; -- proc IF NOT windowTooLow THEN context.DoSaveAll[legendProc]; }; -- DrawLegend 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 _ ((lastEntityColor + 1) MOD 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: 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 newVec _ [(xseg.first.end-realRect.x)*scale[x], (yseg.first.end-realRect.y)*scale[y]]; IF first THEN first _ FALSE ELSE MaskLineSeg[context, lastVec, newVec, entity.colorIndex, bwLineState]; IF entity.mark # none 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]; }; lastVec _ newVec; 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: BOOL _ TRUE; xseg _ entity.group.x.segments; FOR yseg: SegmentDataList _ entity.segments, yseg.rest UNTIL yseg = NIL DO vec: Imager.VEC _ [(xseg.first.end-realRect.x)*scale[x], (yseg.first.end-realRect.y)*scale[y]]; IF first THEN {moveTo[vec]; first _ FALSE} ELSE lineTo[vec]; 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 _ [(xseg.first.end-realRect.x)*scale[x], (yseg.first.end-realRect.y)*scale[y]]; eFont: ImagerFont.Font _ imagerFonts[paintInfo.output][IF entity.mark > cross THEN 0 ELSE LAST[FontIndex]]; DrawRope[context, GraphConvert.CharRopeFromMark[entity.mark], vec.x, vec.y, center, center, eColor, eFont]; 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 _ ((lastEntityColor + 1) MOD LastEntityColor) + 1; DrawLegend[context, handle, entity]; 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]]; vec1.x _ (info.x1 - realRect.x)*scale[x]; vec2.x _ (info.x2 - realRect.x)*scale[x]; FOR el: EntityList _ graph.entityList, el.rest UNTIL el = NIL DO entity _ el.first; vec1.y _ (v1.first - realRect.y)*scale[y]; vec2.y _ (v2.first - realRect.y)*scale[y]; IF entity.colorIndex = 0 THEN -- auto color selection for white curves. lastEntityColor _ entity.colorIndex _ ((lastEntityColor + 1) MOD LastEntityColor) + 1; 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]]; }; }; context.DoSaveAll[caretsProc]; }; -- PrintCaretsInfo }. CHANGE LOG. SChen, created at October 9, 1985 6:13:12 pm PDT. tGraphDraw.mesa, Copyright c 1985 by Xerox Corporation. All rights reserved. Last Edited by: Sweetsun Chen, October 21, 1985 10:23:53 pm PDT 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. bounds checking divisions scaling 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 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. 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šœŸœwŸœGŸœ˜ÑJšœ Ÿœ˜$Jšœ@˜@Jšœ Ÿœ…˜—Jšœ Ÿœ‰˜˜JšœŸœËŸœ˜ÜJšœŸœ ˜!Jšœ Ÿœ"˜2JšœŸœ˜#JšœŸœ˜"JšœŸœ˜"J˜—šœ ŸœŸ˜JšŸœu˜|JšŸœ˜—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šœ8Ÿœ˜SJšœ:™:J™9JšŸœ˜—JšŸœŸœ Ÿœ™-Jšœ˜J˜—Jšœ˜ —J˜Jšœp™pš  œŸœ˜$šœ˜J™a—J™ šœŸœŸœ˜XJšŸœ#˜'—J˜J™QJšœ˜Jš œ ŸœŸœŸœŸœ˜Jšœ Ÿœ˜JšŸœŸœF˜fšŸœŸœ Ÿœ#˜=šœ Ÿœ˜Jšœ<˜J˜—šŸœ˜Jšœ˜Jšœ˜J˜—J˜J˜Jšœ ˜—J™J™šŸœŸœ˜Jšœ,˜,JšŸœŸœŸœ=˜[Jšœ˜J˜—Jšœ ™ šŸœŸœ Ÿœ˜˜>—šŸœŸœŸœ˜!JšœŸœ˜JšœŸœ˜JšœŸœ˜"JšœG˜GJšœ'Ÿœ˜.šŸœŸœ Ÿœ˜Jšœ;˜;Jšœ'Ÿœ˜.J˜—J˜—JšŸœ˜—J˜—šŸœ˜Jšœ Ÿœ Ÿœ Ÿœ˜:Jšœ,˜,Jšœ(˜(J˜J™ šŸœŸœŸœŸ˜$Jšœ˜šŸœ Ÿœ@˜QJšœ@˜@—šŸœŸœŸœ˜!JšœŸœ˜JšœŸœ˜JšœŸœ˜"JšœG˜GJšœ'Ÿœ˜.šŸœŸœ Ÿœ˜Jšœ;˜;Jšœ'Ÿœ˜.J˜—J˜—JšŸœ˜—J˜—J˜—J˜Jšœ ˜J˜—š  œŸœ˜&šŸœ,ŸœŸœŸ˜@J˜šŸœŸœ)˜GJšœ=Ÿœ˜V—Jšœ$˜$JšŸœ˜—šœ˜J˜—š  œŸœC˜SJšŸœ˜ šœ Ÿœ˜J˜6J˜:Jšœ Ÿœ˜%JšœŸœ˜"Jš œ ŸœŸœŸœŸœŸœ˜FšœŸœŸœ*Ÿœ ˜GJšŸœ˜!—JšœŸœ˜JšœŸœ˜ JšœŸœŸœ˜;Jš œŸœBŸœ ŸœŸœ ˜lJšœŸœ˜J˜J˜EJšœ=˜=Jšœ=˜=šŸœŸœ˜"J˜J˜3J˜JšŸœŸœ)˜BšŸœ˜Jšœ˜JšœI˜IJ˜—JšœI˜IJ˜—Jšœ˜ —JšŸœŸœŸœ˜7Jšœ ˜—J˜—š  œŸœ˜'JšœG™GJ™RšŸœŸœŸœ˜ šœŸœ˜J˜ šŸœ,ŸœŸœŸ˜@J˜Jš Ÿœ ŸœŸœŸœ ˜&šŸœŸœ)˜GJšœ=Ÿœ˜V—JšŸœŸœ&˜CJšŸœ*˜.JšŸœ˜—Jšœ˜—Jšœ ˜ J˜—Jšœ˜J˜š  œŸœDŸœ˜bJ™9šœ Ÿœ˜JšœŸœŸœ˜JšœŸœ˜1Jšœ0˜0Jšœ6˜6Jšœ˜Jšœ˜Jšœ-˜-Jšœ ˜ Jšœ˜Jšœ3˜3šŸœ4ŸœŸœŸ˜JJšœV˜VJšŸœŸœ Ÿ˜JšŸœG˜KšŸœŸœ˜Jš œ7ŸœŸœŸœŸœ ˜kšœ=˜=Jšœ3˜3—J˜—Jšœ˜Jšœ˜JšŸœ˜—J˜—Jšœ˜Jšœ˜—J˜š œŸœCŸœ˜dJ™9šœ Ÿœ˜Jšœ˜šœ˜JšœŸœŸœ˜Jšœ˜šŸœ4ŸœŸœŸ˜JJšœ ŸœP˜_JšŸœŸœŸœŸœ ˜ ˜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šœ)˜)šŸœ ŸœŸœ˜šœ Ÿœ˜šŸœŸœ)˜GJšœ=Ÿœ˜V—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——…—ZV}x