This essay will provide you with some crude, and in some ways flawed, subroutines... but I hope the corners I've cut make them more accessible to Lazarus beginners.
This essay, by the way, was created after my problems with "fencepost errors" in respect of drawing with Lazarus and Delphi. (They were explained in PixelProblem.htm.) I may have RE-made those old mistakes, in the (relatively trivial) graphing elements of this... but THINK (and hope!) not.
You can download (free) the full sourcecode for this application. It is full of rems and demos to help you get to grips with using the code.
Very sorry but...When first published, there were two flaws in the code. And in the CODE shown here, and in the FIRST of the two downloads on offer, they may persist. (But there's good news in the next paragraph!) They were subtle. The code "mostly works"; the overall design was fine. Just a few boo-boos in the fine print in at least one subroutine (sigh). Even the calling conventions were fine!... but if the graph height is 400, then if you use the old code of iDGToolYScale to calculate a Y coordinate for DGToolDoDotWWr, you get bad results sometimes. (It might even be that it was just a subroutine (extRemap) used by iDGToolYScale. I forget the details... I HAVE WORKING CODE NOW. I just need to tidy this page.) (Specific examples of things that the old code got wrong are given in similar text near the bottom of the page.) If you keep the graph height (as set by the constant iDGToolImgHeight) at 150, I think you get good results for any values... please let me know if you discover exceptions... or find the cause! (Please say, "In your page lt2n-drawtools-pt2.htm....")
I think I've found the cure. But at the moment don't have to time to fix the text below, or the OLD .zip that you can still download. You'll have to keep the following in mind, and make edits to the code in the .zip.... OR find your way around the second zip, where everything works (I hope! Again.) but you have to wade through some scraps of MAKING it work!
A link to download the old .zip is in the similar text below. The one that has the fixed subroutines, but some "other stuff" cluttering it can be downloaded in this zip, a version of the tools demo software I created while working on the bug. It still has the bug, but at least there's an easy, interactive way to try using the subroutines.
"Just" replace the bodies (the stuff between the subroutines' "begin" and "end") of the two subroutines indicated by the rems in the following. Everything else... even the "headers" of the affected subroutines... can stay the same. Apologies for the nuisance. The un-patched code DID run... for the test data I gave it. Obviously I need to work on my app-testing skills.
// -------- iDGToolYScale ----------------- begin if iRawMin>iRawMax then begin iTmp:=iRawMin; iRawMin:=iRawMax; iRawMax:=iTmp; end; extRaw:=extRemap(extRaw,iRawMin,iRawMax, iUseMin,iUseMax); extFactor:=(iDGToolImgHeight-1)/100; extRaw:=extRaw*extFactor; iTmp:=Round(extRaw); result:=iDGToolImgHeight-1-iTmp; end;//of iDGToolYScale // ------------------------------------ // -------- extRemap ------------------ begin iShiftWantedsToZero:=iOutMin;//**1 iShiftHavesToZero:=iSourceMin;//**2 extScaleFactor:=(iSourceMax-iShiftHavesToZero)/ (iOutMax-iShiftWantedsToZero);//**3 extTmp:=extInput-iSourceMin; extTmp:=extTmp/extScaleFactor; result:=iOutMin+extTmp; end;//extRemap
And, to come in new version when it comes out, a new function...
// ------------------------------------ // -------- iDGToolConstrain ---------- function TLT2N_DrawTools_bF1.iDGToolConstrain (iToConstrain,iMin,iMax:integer):integer; //Needs work... should the constraining be to range of // iMin-iMax INCLUSIVE? EXCLUSIVE? begin if iToConstrain<iMin then iToConstrain:=iMin; if iToConstrain>iMax then iToConstrain:=iMax; result:=iToConstrain; end;
The plan is that you will use that along to way to plotting a point for a datum, in cases where you want "too big" or "too small" values to be converted to "biggest I can plot"/ "smallest I can plot". To be explained more fully in revised tutorial, in due course... but don't look for anything "deep" here. You probably grasped the entire purpose at a glance, and using iDGToolConstrain isn't much harder than seeing its purpose.
And now for the tutorial!...
You can download a zip with a version of the tools demo software I created while working on the bug. It still has the bug. (If you use copy/paste to patch in the two revisions given above, it SHOULD be okay!) But it does, at least, give you an easy, interactive way to try using...
DGToolDoDotWWr(iX, iDGToolYScale(iRawValue,100,150,80,90), clbBlue);
... with different values where you see "100,150,80,90" in the line above.
Back to happier matters...
Many of the subroutines are meant to be of general use. You should be able to "patch" any of the routines into other programs. Any such subroutine will have "DGTool" (from "DrawGraphTool") at or near the start of their names.
So, for example, you can export DGToolScrollLeft to your own code quite easily.
Two of the subroutines meant for incorporating in your code are more generally useful, and not tied to the "Drawing Tools" aspects of what is presented here. They are the ones called "Delay" and "extRemap". (The "trivial" iDGToolConstrain, when it becomes part of the tool set is also "generally useful", but it would be as easy to write your own as to copy/paste that for use elsewhere. It was included because it will often be wanted when using the other tools.)
If you are not an "old hand" at using subroutines, you may benefit from a tutorial on subroutines which I wrote for Delphi, Lazarus's inspiration. (For our wants here, there are hardly any differences.) Good use of subroutines- i.e. procedures and functions- makes programming a lot easier!
But. There's always a but!...
To insert these subroutines into your own code, you must also export a few global variables... something we try to avoid. However, they too have DGTool in their names, to help you. I will try to do a concise "How to export" guide later.
And finally, subroutines for drawing implies a place to draw on, doesn't it? You'll need an Image object on your form. Again, guidance will be given.
This essay carries on from the first "Draw Graph Tools", lt2n-drawtools.htm.. but should be useable on its own. If you want to be taken gently by the hand through the early parts of what's assumed knowledge here, try the first essay first.
"Drawtools_b" needs what that needed...
procedure DGToolDrawLine procedure DGToolDoDotWWr
and adds...
procedure DGToolClearDrawingArea procedure DGToolScrollLeft function iDGToolYScale procedure Delay function extRemap
This draws a line. It takes 5 parameters:
An X and a Y for where the line starts
Another X and Y for where the line goes to
And a TColor parameter which specifies the color for the line.
The X and Y coordinates should be held in integer type variables.
I believe if you request...
DGToolDrawLine(0,0,100,100,clBlue)
... (assuming the drawing area is large enough, you will get the first dot in the upper right hand corner. (THAT is pixel 0,0, and I know this subroutine will put a dot at the pixel specified for the start of the line.
the "units" for those parameters is pixels.
I am less sure about exactly where the line will end! It will, I think, include a colored pixel at 99,99. But it might also do one at 100,100 if I am wrong.
The color parameter can be a system defined color constant, e.g. clTeal. Or it can be a number. The link given will help you with ways to get the right number for the color you want.
This procedure needs three parameters: Two integers to specify an X and a Y, and another color code. (See previous, for comments on color codes. They are "just numbers", or simple system constants, like clGreen.)
Note: These coordinates (the X and the Y) are in the system which is normal in programming. "0,0" is at the upper left of the graph. (Unlike what mathematicians usually use: 0,0 at the lower left.
DGToolDoDotWWr has a nice feature. (It would have been called "DGToolDoDot" without the feature.) If you specify a Bad Number (too big, or too small (i.e. negative)) for the X or Y coordinate, it will "wrap". The "WWr" in the procedure's name is for "With Wrap".
The "Line of Dots" button in the app associated with this tutorial uses DGToolDoDotWWr to draw a diagonal line of dots.
The program that supplies you with the subroutines has some demos in it. The following is there to demonstrate DGToolDoDotWWr.
procedure TLT2N_DrawTools_bF1.buLineOfDotsClick(Sender: TObject); var xTmp, yTmp:integer; begin xTmp:=10; for yTmp:=10 to 200 do begin DGToolDoDotWWr(xTmp,yTmp,clRed); xTmp:=xTmp+1; delay(20); end;// of for... end;// of buLineOfDotsClick
Want to make it snow (in green!) in the drawing area?...
procedure TLT2N_DrawTools_bF1. Button1Click(Sender: TObject); var c1:integer; begin for c1:=0 to 500 do DGToolDoDotWWr(random(iDGToolImgWidth), random(iDGToolImgHeight),clGreen); end;
Nota Bene:
By using DGToolYScale (see below) "on" the values passed to DGToolDoDotWWr, you can say "put the dot between 10 and 20 percent "up" the graph. And if the value being plotted is, say 280, from a sensor expected to return values between 200 and 300, put the dot 15% of the way from the 10% level to the 20% level.
Re-read that. DGToolDoDotWWr with DGToolYScale give you very simple plotting of multiple graphs on one drawing surface. See also the source-code, which contains demos. Full sourcecode supplied.
Note that the sourcecode was replaced with a more polished version on 23 Dec 2018, about 8:10pm New York time. (The version identified internally, and in the title bar of the window, when you run the .exe, as "23Dec2018, 19:30".) If you downloaded the .zip before that time, fetch the updated version.
DGToolClearDrawingArea; ...ummm... clears the drawing area! I.e. erases everything on it. Changes everything to whatever color you have stored in the global variable "clDGToolGraphBackGround" (type TColor). You can use the Lazarus-provided color constants, e.g. clRed, or a raw number. See http://wiki.freepascal.org/Colors. That variable also now determines the "background color" when the graphing area is first displayed, by the way. (In lt2n_drawtools_a, it always had a white background.)
Why not add a parameter to specify the color to make the drawing area? Having the background color in a variable has other uses... most notably in erasing lines. If you draw a line in, say, red (on a white background), and later draw another line, in the same place, but this time using the background color, you will "erase" the first line. (XORing with the color you used to create the line is a little bit better... but a story for a Google search, or another day.)
DGToolScrollLeft scrolls everything on the graph to the left one pixel's worth. The right hand column is filled with whatever color clDGToolGraphBackGround specifies at the moment. The column of pixels that was at the left edge of the graph "falls off", and is lost.
This might seem an arcane procedure. Who would want it? But once you have this, you can do Good Stuff. It is fundamental to the Scroll Demo, and to what is billed as "the Main Event", according to the button texts. (In that, a function creates a fake "reading" from a "sensor" which might be recording the temperature of something with a changing temperture.) Here's the code for the Scroll Demo:
procedure TLT2N_DrawTools_bF1.DemoBuildFrmRt; //(a demo routine) var iY, iCount:integer; begin for iCount:=0 to 70 do begin DGToolScrollLeft; iY:=10+(iCount mod 30);//Goes 10,11,12...38,39,10, 11... DGToolDoDotWWr(iDGToolImgWidth-1,iY,clRed); Delay(40); end;//for end;//BuildFrmRt; (a demo of a trick)
Generally useful. A simple little thing, but to save you going off to the internet land to fetch your own. Gives a non-blocking delay of at least... but probably not exactly... x milliseconds.
Generally useful. A function to "remap" a number. For the graph drawing this essay is about, you won't need to use it directly... but it is an ingredient of iDGToolYScale, which I will present in a moment. And it is available to you to use directly, when you need it.
Needs 5 numbers... If you passed 280,200,300,10,20, it would return 18: Start with the 280. That is the number being re-mapped. the 200,300 tells you the range that it came FROM, i.e. the minimum and maximum numbers that the 280 could have been. (Note that 280 is 80% "along" the number line from 200 to 300.)
The last two parameters declare the range you want the number mapped TO. 10 to 20, in this case. 80% along THAT line is 18.
If that doesn't yet make sense, re-read the above?
This routine is really helpful if you are, say, doing a graph of some numbers. Let's say you're doing graphs of some weather and home heating control temperatures. Let's say your data is in degrees C. And in your area, the outside tture can swing from -10 degrees C to +205 degrees. But, on your graph, you want those numbers to plot from 10% up the Y axis to, say, 90 %.
You could "pre-process" the temperature sensor number with extRemap, and pass those results to DGToolDoDotWWr, as the Y value.
But, in this suite of graph drawing routines, as inimated above, there's an even better function: iDGToolYScale. I only mentioned (just now) what you could do to help you see if my explanation was good enough.
It is simpler to explain what extRemap does, than it was to write the subroutine, I promise you! (Try it, if you don't believe me!)
(The function does not break down if you pass, say, 310 to it... but the result will be more than 20).
Just before we start with DGToolYScale, I have to tell you that this TUTORIAL unravelled a bit towards the end. but, you can fetch the full sourcecode of DrawTools_B, for free. THAT has been completed... carefully... and is full of demos and rems. If I were an informed reader of this tutorial, I would glance the stuff about DGToolYScale that comes next, and when you've had enough, go to the sourcecode!
Note that the sourcecode was replaced with a more polished version on 23 Dec 2018, about 8:10pm New York time. (The version identified internally, and in the title bar of the window, when you run the .exe, as "23Dec2018, 19:30".) If you downloaded the .zip before that time, fetch the updated version.
DGToolYScale is a "helper" function, for use with DGToolDoDotWWr, which was available as part of lt2n_drawtools_a.
DGToolDoDotWWr puts a dot on the graph in the color of your choice, and takes three parameters.... iX, iY and iColor. iX and iY say WHERE the dot should go, and iColor determines the color it will be.
iX determines the left-and-right-ness of the dot. Use 0 to put it in the left-most column, and iDGToolImgWidth-1 to put it in the right hand column. (Note the "-1")
So far, so simple, so useable.
iY determines the up-down-ness of the dot. 0 puts it at the top of the graph, iDGToolImgHeight-1 (don't miss the "-1") puts it at the bottom of the graph. Not particularly user-friendly.
So I created DGToolYScale
Hang on to you hat.
It has 5 parameters... we'll come to them in a moment.
Suppose you have a sensor that, in the conditions which interest you, returns values from 28 to 232. And suppose that your graphing area is 150 pixels high. We'll assume that the sensor reading is in iSensor.
A VERY crude answer would be...
procedure TLT2N_DrawTools_bF1.DemoCrudeScaling; //demo of a crude plotting of a faked sensor // reading //The HEART of this is the call to DGToolDoDotWWr. Most of // the rest is there to make it happen 60 times, and to // provide faked sensor readings var bCount:byte;//(0-255) shiDrift:shortint;//(-128 to 127) boDirectionUp:boolean;//Which way should readings drift? iSensor:integer;//(positive AND negative numbers in the range allowed.) function iFakedSensor:integer; //uses several shared variables. Bad practice! begin iSensor:=iSensor+shiDrift; result:=iSensor; end;//iFakedSensor begin //From here... iSensor:=100;//Initial value from sensor. shiDrift:=-2; boDirectionUp:=true; //... to here: Just setting up the sensor reading faker. //Now read sensor, plot at right edge, scroll... 60 times. for bCount:=0 to 60 do begin DGToolDoDotWWr(iDGToolImgWidth-1,iFakedSensor,clGreen); // max sensible second term: iDGToolImgHeight-1 DGToolScrollLeft; Delay(20); end;//for... end;//DemoCrudeScaling
That calls DGToolDoDotWWr repeatedly, with the X coordinate set so that the new dots appear at the right hand side of the 150 pixel high graph. (You can't tell it is 150 pixels from the code fragment... but it is, in the context I want!) After each is plotted, the graph scrolls one pixel to the left. What's REALLY crude is that the sensor readings are 98, 96, 94, 92....
.. and they eventually become negative. Which would be BAD, if we were just using Lazarus's pixel plotting routine, but because we are using our DGToolDoDotWWr (even though it has Lazarus's routine at it's heart), at least our line of dots "wraps".
But! And for me it is a biggish "but"... the line goes UP when our numbers go DOWN.
We'll deal with that in a moment. First let's create a "good" "fake" sensor. We wanted one to give values from 28 to 232, remember.
Don't be overwhelmed by the following... it took a lot of code to create a sensible fake sensor... if you skim down to the bottom, where it says "begin //main block of DemoCrudeScaling", you'll see that as long as we have a source of sensor readings, doing the graph is still quite simple... mostly what we had before!
procedure TLT2N_DrawTools_bF1.DemoCrudeScaling; //demo of a crude plotting of a faked sensor // reading //The HEART of this is the call to DGToolDoDotWWr. Most of // the rest is there to make it happen 60 times, and to // provide faked sensor readings var bCount, bDrift:byte;//(0-255) boDirectionUp:boolean;//Which way should readings drift? iSensor:integer;//(positive AND negative numbers in the range allowed.) function iFakedSensor:integer; //Uses several shared variables. Bad practice! begin //From time to time, change direction of // drift if (random(10)=0) then boDirectionUp:= not boDirectionUp; //From time to time, change size of drift if (random(8)=0) then begin bDrift:=random(3); end;//change size of drift. //Apply drift to previous sensor reading if boDirectionUp then iSensor:=iSensor+bDrift//no ; here else iSensor:=iSensor-bDrift; //Clip, if value has gone beyond // the (arbitrary) bonds required of // this routine's design parameters if (iSensor>232) then begin iSensor:=232; boDirectionUp:=false; end; if (iSensor<28) then begin iSensor:=28; boDirectionUp:=true; end; result:=iSensor; end;//iFakedSensor begin //main block of DemoCrudeScaling //From here... iSensor:=100;//Initial value from sensor. bDrift:=2; boDirectionUp:=true; //... to here: Just setting up the sensor reading faker. //Now read sensor, plot at right edge, scroll... many times. for bCount:=0 to 160 do begin DGToolDoDotWWr(iDGToolImgWidth-1,iFakedSensor,clGreen); // max sensible second term: iDGToolImgHeight-1 DGToolScrollLeft; Delay(20); end;//for... end;//DemoCrudeScaling
And that "works"... fairly well. But remember that our graph is 150 pixels high, and that our (arbitrarily chosen, for discussion) sensor returns nothing less than 28, but sometimes readings as high as 232. If the sensor returns values above 150, the line will wrap. Untidy. And the part of the graph from y=0 to y=26 isn't used at all (except for wrap-arounds.)
And it is still all upside down.
A simple, crude "fix" to the sometimes the numbers are too big" problem would be to use the following for the "plot pixel". Note the "div 2". That divides the number in iFakedSensor, and sends the result to DGToolDoDotWWr. Now the number passed for plotting... previously 28 to 232 is 14 to 116. (inclusive).
DGToolDoDotWWr(iDGToolImgWidth-1,iFakedSensor div 2,clGreen);
That would "work". But is terribly "ad hoc". But we'll use it as a starting point for the goal we are working towards: A function to "fix" ANY range of numbers to fit where we want them on our graph. It will be called DGToolYScale... as in "this scales a Y value to make it "right" for the graphing area available."
It will also fix the problem of the numbers being "upside down".
Note that I said that DGToolYScale will let us put the dots from the readings where we want them. Sometimes they will be using the whole height of the graphing area. Sometimes we will have two graphs on the graphing area... one on the top half, the other on the bottom half.
First we'll take what we had a moment ago, and rearrange things somewhat, so that we have started our DGToolYScale. For the moment, it will just do the "div 2" that was our crude fix. (I'll explain the "ext" and the "round" in a moment.
Here's the main part of our "demo" code, and all of the code, so far....
for bCount:=0 to 160 do begin DGToolDoDotWWr(iDGToolImgWidth-1,iDGToolYScale(iFakedSensor),clGreen); // max sensible second term: iDGToolImgHeight-1 DGToolScrollLeft; Delay(5); end;//for... end;//DemoCrudeScaling function TLT2N_DrawTools_bF1.iDGToolYScale(extRaw:extended):integer; begin result:=round(extRaw/2); end;//iDGToolYScale(extRaw:extended):integer;
The "extended" data type allows fractions. In our discussion, our sensor only returns integers. But we're building iDGToolYScale to be able to handle many possibilities. (Some will say "but that will be slow.". Yes. It will. The whole APPROACH is flawed, if you want, say, to display audio waveforms in real time. But the principles you'll learn here will still be relevant when you re-work the code to make it do the same thing, but faster.)
When I pass an integer-type value into an extended-type variable, there is no problem. The fact that extRaw can hold 4.5 doesn't stop it holding "plain" 4. (Though it might call it "4.0"!)
So the function can take 4 or 4.5 as the value that is passed TO it.
When you are dealing with "real" type numbers, of which "extended" is one class, you don't use "div 2". (That's for when you are using integer-type data, and it truncates, by the way. 7 div 2 is 3.) With real-type variable, you use "/" to divide. And the answer can include a fractional part.
So far, so good? We're going through iDGToolYScale, and have now looked at "extRaw/2".
The "round(extRaw/2)" says "round off the result of extRaw divided by two". THAT will be an integer, and thus it can be the result for iDGToolYScale
So, good, we have a structure. Now lets make iDGToolYScale a whole lot fancier.
Besides the number to be scaled, returned in integer form, we will add 4 numbers to what we send to the function.
The first two will specify the lowest and highest values we expect the sensor to return. (If values outside that range arise, they will only lead to dots higher or lower than we wanted, with wrapping happening when necessary.)
The second two should be in the range 0-99, inclusive.
If they are 0 and 9, the dots resulting should appear in the bottom 10% of the graphing area.
To make the dots all be in the upper half, you would use 50 and 99.
And, oh by the way, the enhanced function also turns the graph "the right way up". That happens by the magic of it's last line.
Umm. Things got a bit messy. Sorry. "The Story" will not be fully present here. I got too engrossed in Making It Work, and failed to record, document all that went into getting from "Hello World" to "The Finished Product".
For fellow obsessives out there, I saved what I could. I will try to zip and upload that, later. You can dig in it to your heart's content.
But most of us are mostly interested in the finished product. So... without all the details of how I got there, we have, ta da!...
Thank you for trying to read the above, imperfect text. I hope your efforts will be rewarded when you download (free) the full sourcecode for this application. It is full of rems and demos to help you get to grips with using this code... which is, I sincerely believe, MUCH better than this documentation!
Note that the sourcecode was replaced with a more polished version on 23 Dec 2018, about 8:10pm New York time. (The version identified internally, and in the title bar of the window, when you run the .exe, as "23Dec2018, 19:30".) If you downloaded the .zip before that time, fetch the updated version.
Very sorry but...At the moment there is a flaw, somewhere in the code. It "mostly works" (sigh)... but if the graph height is 400, then if you use iDGToolYScale to calculate a Y coordinate for DGToolDoDotWWr, you get bad results sometimes.
If you repeatedly plot 100 to 150 in iRawValue with...
for iX:=0 to 200 do begin DGToolDoDotWWr(iX, iDGToolYScale(iRawValue,100,150,80,90), clbBlue); end;
... you will find nice diagonal lines,almost like the...
... you would expect...
But! It will be in bottom of the graph pane, in the 10-20 % (up from bottom) band, instead of the 80-90% band, where it should be. If you ask for 100-150 to be plotted in the 60-70%, you get even worse results... if, as I say, the graph height is set (with the constant iDGToolImgHeight) at 400.
If you keep the graph height at 150, I think you get good results for any values... please let me know if you discover exceptions... or find the cause! (Please say, "In your page lt2n-drawtools-pt2.htm...."
You can also download a zip with a version of the tools demo software I created while working on the bug. It still has the bug, but at least there's an easy, interactive way to try using...
DGToolDoDotWWr(iX, iDGToolYScale(iRawValue,100,150,80,90), clbBlue);
... with different values where you see "100,150,80,90" in the line above.
In a sense, these tools are the 2018 manifestation of something I've been writing new versions of since 1983. It should be "coming on" by now!
Your thoughts, as ever, very welcome.
Here is the code for the core subroutines, as they stood in the '23 Dec 2018, 20:50' version of 'LT2N_DrawTools_B'. Remember that each place where TLT2N_DrawTools_bF1. appears, you will have to change that to match the context in your app. And that they will need forward declarations in the interface section of the code.
//=================================== procedure TLT2N_DrawTools_bF1.DGToolClearDrawingArea; //Sets all pixels in drawing area to //whatever color is specified by the value in //the global clDGToolGraphBackGround;(TColor) //Note that there is a slight differnce between //how Delphi and Lazarus initialize things... //The TImage object? The Bitmap? The picture? //canvas? Something like that! THIS works with //Lazarus, and would with Delphi, too, I think. //Delphi people can get away with not //initializing something. Note I said "Get away //with"... always a bad thing to lean on //in programming. begin //Pen, brush: One is outline, other is fill... //clDGToolGraphBackGround:=clRed; imgDGTool.canvas.brush.color:=clDGToolGraphBackGround; imgDGTool.canvas.pen.color:=clDGToolGraphBackGround; imgDGTool.picture.bitmap. canvas.rectangle(0,0,iDGToolImgWidth,iDGToolImgHeight); //bmDGTool.canvas.pen.color:=clDGToolGraphBackGround; //DGToolClearDrawingArea; //bmDGTool.canvas.rectangle(0,0,iDGToolImgWidth,iDGToolImgHeight); //Last two terms tested for fencepost errors... to re- // test, set brush to black, pen to red end;//DGToolClearDrawingArea; //=================================== procedure TLT2N_DrawTools_bF1. DGToolDrawLine(iX1,iY1, iX2,iY2:integer;clInk:TColor); begin imgDGTool.canvas.pen.color:=clInk; imgDGTool.picture.bitmap.canvas.moveto(iX1,iY1); imgDGTool.picture.bitmap.canvas.lineto(iX2,iY2); end; //DGToolDrawLine //=================================== procedure TLT2N_DrawTools_bF1. DGToolDoDotWWr(iX1,iY1 :integer;clInk:TColor); //vers 23 Dec 2018 //TOOLS_B version... improved.. twice... //over "original original" in // Tools_a. This wraps if values // of iY1 become negative. (Tools_a may // have been upgraded to this by the time // you are reading these comments... or may // still exist in the early, flawed, // "mostly works" form it was originally // published in) begin //Next two: New since 1Dec18 iX1:=iX1 mod iDGToolImgWidth; while (iX1<0) do iX1:=iX1+iDGToolImgWidth; iY1:=iY1 mod iDGToolImgHeight;//REVISED while (iY1<0) do iY1:=iY1+iDGToolImgHeight; imgDGTool.picture.bitmap. canvas.pixels[iX1,iY1]:=clInk;//REVISED end; //DGToolDrawLineWWr //=================================== procedure TLT2N_DrawTools_bF1. DGToolScrollLeft; var rectSource,rectDest:TRect; begin //First: Copy all but first column one place to the left. rectSource:=Rect(1,0,iDGToolImgWidth,iDGToolImgHeight); rectDest:=Rect(0,0,iDGToolImgWidth-1,iDGToolImgHeight); imgDGTool.picture.bitmap. canvas.CopyRect(rectDest,imgDGTool.picture.bitmap. canvas,rectSource); //Then: Erase right hand column to backgorund color imgDGTool.canvas.pen.color:=clDGToolGraphBackGround; imgDGTool.picture.bitmap. canvas.moveto(iDGToolImgWidth-1,0); imgDGTool.picture.bitmap. canvas.lineto(iDGToolImgWidth-1,iDGToolImgHeight); //In prev: Definitely NOT "-1" on iDGToolImgHeight // (Because it is lineTO??) end;//DGToolScrollLeft; //=================================== //An important "helper" function... function TLT2N_DrawTools_bF1. iDGToolYScale(extRaw:extended; iRawMin,iRawMax:integer; iUseMin,iUseMax:byte):integer; //Makes reference to some global constants: // (list) //This is primarily a service routine for // DGToolDoDotWWr. //Takes iRaw, and converts it to a value suitable // for use as a Y value for plotting a pixel on // graph. //"Suitable" includes inverting things, so big // numbers are high on the cartesian plane, // low numbers are towards bottom. //iRawMin, iRawMax should hold the minimum and // maximum values expected for the raw data. // (These should be INCLUSIVE of range expected... ARE THEY? //(Are negatives allowed?? Inversion (i.e. iRawMin > iRawMax) // If the actual data exceeds any expectations, // the resulting dots may be above or below // the band of the graph specified by iUseMin, // iUseMax. The worst thing that can happens // is that data will "wrap", vertically. You // will be saved by the "out of range" // provisions in DGToolDoDotWWr. //iUseMin, iUseMax specify where the data should // appear, vertically, on the graph. If 0 and 99 // respectively, iRawMin should plot at bottom // of graph, iRawMax should plot at top. // In other words, they are expressed in percents. // Due to rounding errors, etc, these may not // be where you will always find them, exactly. // ... or they may not be exactly where planned due // ... to problems with the details of the programming // ... Tests of "boundary cases" are still needed. In // ... particular, check that no datum will go unseen, // ... i.e. be "off edge of page". var iTmp:integer; extFactor:extended; begin //Supply iUseMin and iUseMax with 60 and 69 // if iRawMin should plot 60% up the graphing // area, and iRawMax should plot 69% UP the graphing // area. The routine returns the coordinate, // as needed for the standard Lazarus "Pixel", so // before we do anything else, we will convert the // "percents" to coordinate values. //Example... if 50, 60 were passed when iDGToolImgHeight // were 200, then they should be changed to 100 and 120. // (There may be one or more "-1"s needed somewhere // here. Sigh. //First check if user has inverted the raw range spec, and // fix, if so. if iRawMin>iRawMax then begin iTmp:=iRawMin; iRawMin:=iRawMax; iRawMax:=iTmp; end; extFactor:=(iDGToolImgHeight-1)/100; iUseMin:=trunc(iUseMin*extFactor);//trunc, not round, on purpose! iUseMax:=trunc(iUseMax*extFactor); extRaw:=extRemap(extRaw,iRawMin,iRawMax, iUseMin,iUseMax); iTmp:=Round(extRaw); //"iRaw" is no lnoger the "raw" number we had when //we entered this routine. It has been converted //to more useable value. //and finally "invert" the value... // Up 'til now we've been working as it the // graph origen (0,0) is at TOP of page, as // indeed it is in the Lazarus (and Delphi) // world. We want low numbers to print LOW // in the graph, not high... and this achieves // that. result:=iDGToolImgHeight-1-iTmp; end;//iDGToolYScale //=================================== //An important "helper" function, and useful in // many contexts, not just these Drawing Tools function TLT2N_DrawTools_bF1. extRemap(extInput:extended; iSourceMin,iSourceMax, iOutMin,iOutMax:integer):extended; //This is used inside iDGToolYScale // //I have not yet investigated what happens // if you have negative numbers anywhere. //Nor what happens if you spec, say... // 123, 50, 150, 500, 400 // (123: to be shifted and scaled, // 50 to 150: Range that 123 will be in // 500 to 400, the range you want it // remapped to. If the function can be // made tolerant of such inputs, then as // the input (123) rises, the output would // fall. And vice versa. Sho8uld be possible. // But I haven't tested for that yet. //While some of the numbers you will want to // shift will have fractional parts, you must // specify the range of what you start from // with integers. So, if the input could range, // say, from 3.5 to 7.8, declare the source // range as from 2 to 8. (The numbers spec'd // are treated as part of the range.) //Likewise, you must specify the range they // will map to with integers. You want the // results to run from 76.3 to 95.2? Specify // 75 to 96. //In the following names, "have" refers to the // numbers you "have" before you do the shift // and scale. //"Wanted" refers to the numbers you want after // tbe shifting and scaling. var iShiftWantedsToZero,iShiftHavesToZero:integer; extScaleFactor:extended; begin iShiftWantedsToZero:=iOutMin;//**1 iShiftHavesToZero:=iSourceMin;//**2 extScaleFactor:=(iSourceMax-iShiftHavesToZero)/ (iOutMax-iShiftWantedsToZero);//**3 extInput:=(extInput-iShiftHavesToZero)/extScaleFactor;//**4 result:=extInput+iShiftWantedsToZero;//**5 end;//extRemap //=================================== //A useful little function, and useful in // many contexts, not just these Drawing Tools procedure TLT2N_DrawTools_bF1.Delay(qwMilliSeconds: QWord); //Copied from third reply in https://forum.lazarus. // freepascal.org/index.php?topic=40760.0 //Creates non-blocking delay of about AMilliseconds // //Cannot be moved to sau because it needs access to // object "application". var qwTmp: QWord; begin qwTmp := GetTickCount64 + qwMilliSeconds;//Note: //if overflow occurs, it won't matter, I think //The problem CAN be solved... Have I done it here? //The problem would arise if this were called with //qwMilliseconds = to, say, 1000, when GetTickCount //was less than 1000 "ticks" from the maximum //number that function can return. The solution //lies in the fact that //the value returned by GetTickCount ALSO rolls //over. (There may be issues with RangeChecking //throwing up a warning... this bridge can be //crossed if we get to it. Sigh. while (GetTickCount64 < qwTmp) and (not Application.Terminated) do Application.ProcessMessages; end;//Delay
Search across all my sites with the Google search...
|
Page tested for compliance with INDUSTRY (not MS-only) standards, using the free, publicly accessible validator at validator.w3.org. Mostly passes. There were two "unknown attributes" in Google+ button code. Sigh.
....... P a g e . . . E n d s .....