Re: Mike: it's Working, more explanation Please.
- From: Randy Gardner <RandyGardner@xxxxxxxxxxxxxxxxxxxxxxxxx>
- Date: Mon, 10 Jul 2006 09:25:02 -0700
Mike:
Wow! Great help, I really apreciate it!!!!!
I got it working and I tested it for my application by tweeking your example.
I set my pbsize (Twips) and drew a line from (0,0)-(30,75) using pConvertX/Y.
I could just use your code, BUT, I wouldn't have learn anything, so
would you please explain what is happening
in the functions: pConvertX/Y and pConvertSizeX/Y.
I don't understand what VB is doing when it scales a control. Also what made
(0,0) lower left corner.
Can (0,0) be any corner? What if I want to display positive
and negative numbers (Y) with a different scale for + and -?
Thanks again.
--
Randy
"Mike Williams" wrote:
"Randy Gardner" <RandyGardner@xxxxxxxxxxxxxxxxxxxxxxxxx> wrote in message.
news:2BEEE33D-B958-4BCA-BD57-B14AD2AA1075@xxxxxxxxxxxxxxxx
I am graphing two dimennsioal data. I want to graph: Line (x1,y1)-(x2,y2)
where the x and y values are my actual data. Example: The peak value of
my Y axis data might be 75 and the peak value of my X axis data might be
30 and I want to fill the picture box.
Here's something that does what you want. It sets up a picture box in the
way that you require (including setting the y coordinates so the positive
values move upwards). You can then draw your data into the picture box using
your own values as you have requested. The picture box itself is actually
hidden at runtime, because it is not required for display purposes. Instead
the code draws the graph from the picture box directly to your Form, or to
another picture box if you wish, or to the printer. In this way you can draw
the finished graph at any physical location and size you require, opaquely
or transparently (as shown in the code), and you can draw it to the printer
in a way that takes full advantage of the printer's high resolution. I've
put lots of comments in the code so you can see exactly what is going on,
and I've written much of the code in my usual "long winded" way, and so some
of the code uses more lines of code than are actually required. I've done
this in an attempt to make it easier to follow so that you can more easily
amend it yourself to do exactly as you require. Paste the code into a VB
Form containing one picture box and three command buttons.
Mike
Option Explicit
Private Declare Function GetDeviceCaps Lib "gdi32" _
(ByVal hdc As Long, ByVal nindex As Long) As Long
Private Const PHYSICALOFFSETX As Long = 112
Private Const PHYSICALOFFSETY As Long = 113
Private pOffsetX As Single, pOffsetY As Single
Private currentObject As Object
Private Sub Form_Load()
Me.WindowState = vbMaximized
Me.AutoRedraw = True
' The Picture Box is only really required so that we can
' set its logical size to the size of graph we require and
' also so that we can use the very useful VB ScaleWidth,
' ScaleHeight, ScaleLeft and ScaleTop and other properties
' to enable us to easily wite our graph drawing code. We
' do not actually need to draw anything into it because we
' instead draw directly to the desired output device
' (usually the Form or a Printer or perhaps another
' PictureBox). We can therefore make the Picture1
' PictureBox invisible, which we do in the following
' line . . .
Picture1.Visible = False
Command1.Caption = "2 graphs to Form"
Command2.Caption = "3 graphs to Printer"
Command3.Caption = "lots of graphs to Form"
Me.Show ' to allow the resize to take place
End Sub
Private Function pConvertX(x As Single) As Single
pConvertX = Picture1.ScaleX(x, Picture1.ScaleMode, _
currentObject.ScaleMode) - Picture1.ScaleX(Picture1.ScaleLeft, _
Picture1.ScaleMode, currentObject.ScaleMode) + pOffsetX
End Function
Private Function pConvertY(y As Single) As Single
pConvertY = Picture1.ScaleY(y, Picture1.ScaleMode, _
currentObject.ScaleMode) - Picture1.ScaleY(Picture1.ScaleTop, _
Picture1.ScaleMode, currentObject.ScaleMode) + pOffsetY
End Function
Private Function pConvertSizeX(size As Single) As Single
pConvertSizeX = Picture1.ScaleX(size, _
Picture1.ScaleMode, currentObject.ScaleMode)
End Function
Private Function pConvertSizeY(size As Single) As Single
pConvertSizeY = Picture1.ScaleY(size, _
Picture1.ScaleMode, currentObject.ScaleMode)
End Function
Private Sub DrawGraph1(Dest As Object, _
xPos As Single, yPos As Single, _
wide As Single, high As Single, _
backClr As Long, borderClr As Long)
If TypeOf Dest Is PictureBox Or _
TypeOf Dest Is Form Or _
TypeOf Dest Is Printer Then
' That's okay. We can carry on.
Else
Exit Sub
' we only want this routine to accept a
' Form or a PictureBox or an element of
' a PictureBox array or a Printer.
End If
pOffsetX = xPos: pOffsetY = yPos
If TypeOf Dest Is Printer Then
' On Forms and Picture Boxes coordinate (0, 0) by default
' points to the top left corner of the Form or Picture Box.
' However, printers do not behave like this. A printer
' usually needs a little bit of space at the top and the
' bottom of the page for mechanical reasons, and also at
' the left and right sides of the page because they usually
' have an 8 inch print width whereas sheets of printer
' paper are usually a bit wider than 8 inches (8.27 inches
' A4 and 8.5 inches US Letter, for example). The exact
' size of these "unprintable margins" is different on
' different printers (especially the top and bottom margins).
' Therefore, the printer behaves as though it had a
' "printable rectangle" that is slightly smaller than the
' physical page. Both the size and the position of this
' "printable rectangle" can be different on different
' printers. On a printer, location (0, 0) points to the
' top left corner of the "printable rectangle", which is of
' course not the same as the top left corner of the
' physical page (for the reasons I have explained). Even
' the newer inkjet printers which are capable of printing
' fully to the edges of the *** still have this "smaller
' printable rectangle" at most default settings, and only
' switch to the "full bleed edge of page" setting when the
' user specifically choses that setting in the printer
' dialog. So, if we want to position stuff accurately on
' the page we must examine the printer to find out exactly
' where the printable rectangle is on that particular
' printer at its current settings. When we have that
' information we then need to make the appropriate
' adjustments to the position coordinates supplied by
' the "caller" of this routine(pOffsetX and pOffsetY)
Dim borderLeft As Long, borderTop As Long
' find left unprintable margin in printer pixels
borderLeft = GetDeviceCaps(Printer.hdc, PHYSICALOFFSETX)
' convert it to printer scale units and adjust the
' normal pOffsetX accordingly
pOffsetX = pOffsetX - Printer.ScaleX(borderLeft, _
vbPixels, Printer.ScaleMode)
' find top unprintable margin in printer pixels
borderTop = GetDeviceCaps(Printer.hdc, PHYSICALOFFSETY)
' convert it to printer scale units and adjust the
' normal pOffsetY accordingly
pOffsetY = pOffsetY - Printer.ScaleY(borderTop, _
vbPixels, Printer.ScaleMode)
End If
' set the object we are going to draw into to the object
' that the calling routine passed to this routine:
Set currentObject = Dest
Picture1.BorderStyle = vbBSNone
Picture1.Cls
Picture1.Width = wide
Picture1.Height = high
DoEvents
If backClr <> -1 Then
' draw a backgrounnd filled rectangle
currentObject.Line (pOffsetX, pOffsetY)- _
(pOffsetX + wide, pOffsetY + high), backClr, BF
End If
' first set the line thickness
Dim dw As Long
' the drawwidth (line thickness) is always expressed
' in device pixels regardless of the device ScaleMode
' therefore we need to do a little conversion in order
' to ensure that the line thickness is the actual size
' ("inch thickness", if you like) on all devices
' regardless of their pixels per inch resolution
' The following sets the drawwidth to 0.005 inches
' (5 thousandths of an inch)
dw = Picture1.ScaleX(0.005, vbInches, vbPixels)
If dw < 1 Then dw = 1
Picture1.DrawWidth = dw
dw = currentObject.ScaleX(0.005, vbInches, vbPixels)
If dw < 1 Then dw = 1
currentObject.DrawWidth = dw
If borderClr <> -1 Then
currentObject.Line (pOffsetX, pOffsetY)- _
(pOffsetX + wide, pOffsetY + high), borderClr, B
End If
' This Scale code has changed a little bit from the previous code
Dim graphWidth As Single, gridWidth As Single
Dim leftBorder As Single, rightBorder As Single
Dim graphHeight As Single, gridHeight As Single
Dim bottomBorder As Single, topBorder As Single
'
' set up the
graphWidth = 30: gridWidth = 5
leftBorder = graphWidth / 8 ' allow some space at left side
rightBorder = graphWidth / 15 ' not too close to right edge
Picture1.ScaleWidth = graphWidth + leftBorder + rightBorder
graphHeight = 75: gridHeight = 5
bottomBorder = graphHeight / 8 ' allow some space at the bottom
topBorder = graphHeight / 15 ' not too close to top edge of window
Picture1.ScaleHeight = -(graphHeight + bottomBorder + topBorder)
Picture1.ScaleTop = -Picture1.ScaleHeight - bottomBorder
Picture1.ScaleLeft = -leftBorder
'
Dim x As Single, y As Single
' now draw horizontal lines
For y = 0 To graphHeight + 0.001 Step gridHeight
Dest.Line (pConvertX(0), pConvertY(y))- _
(pConvertX(graphWidth), pConvertY(y)), vbBlack
Next y
' now draw the vertical lines
For x = 0 To graphWidth + 0.001 Step gridWidth
Dest.Line (pConvertX(x), pConvertY(0))- _
(pConvertX(x), pConvertY(graphHeight)), vbBlack
Next x
' In the above code the picture box overall size has been
' set to the value (in inches) that you passed to the
' routine. These are "logical inches"
' and are not necessarily the same physical size on the
' screen as a real world inch. However, all logical units
' on the computer bear the same relationsip to each other
' as they do in real life. For example, there are 2.54
' logical centimetres in a logical inch in the computer's
' logical world, just as there are 2.54 real centimetres
' in a real inch in the real world. And of course there are
' 72 logical Points in a logical inch in the computer's
' logical world, just as in the real world there are 72
' real Points in a real inch. Therefore using inches or
' centimeters or twips some other unit of measurement that
' exists in the real world as a ScaleMode (instead of pixels
' which do not have a fixed "real world") helps us to more
' easily visualise what we are doing. In addition, it makes
' the font sizes we will use (which are measured in logical
' "points" match up with the logical inches in the same way
' that real world "points" match up to real world inches.
' The above is a rather long winded way of saying that the
' font sizes we use in the graph will look the same relative
' size to the rest of the graph as they would in the real
' world.
' Okay. Let's set the font name and size and weight:
Dest.Font.Name = "Arial"
Picture1.Font.Name = Dest.Font.Name
Dest.Font.size = 8
Picture1.Font.size = Dest.Font.size
Dest.Font.Bold = False
Picture1.Font.Bold = Dest.Font.Bold
' Now draw the numbers along the bottom axis.
' Note: In the following For . . . Next routines we use
' For x = 0 to graphWidth + .001 Step gridWidth, whereas
' you might expect us to use For x = 0 to GraphWidth step
' gridWidth. Why are we adding the .001? Well, the answer
' is that floating point numbers cannot be held by binary
' computers with absolute precision, and since we are using
' Singles as our For Next Loop counter (which is what we
' will need for many graphs) It is possible that after
' adding a value to another value many times the result may
' not be exactly what we expect (a trailing digit high up in
' the decimal places is quite likely because of the basic
' loss of precision. This is normal on all computers, but it
' can upset a For Next loop because the last value we expect
' in the loop might be (say) 1600 but after the repeated
' additions in the loop it might actually be held internally
' as 1600.00001. In that case the final execution of the loop
' will not occur (even though we want it to) because VB will
' think we have exceeded the "To" value. To cure this problem
' it is wise to add a small amount to the "To" value of the
' loop. The amount added should be large enough relative to
' the possible floating point "lack of precision" error that
' it causes the final loop to be executed correctly, but
' large relative to the loop counter so that it does not
' cause an extra iteration of the loop. In this case (and in
' almost all other cases) the value of 0.001 seems to be
' about right.
Dim s1 As String
For x = 0 To graphWidth + 0.001 Step gridWidth
s1 = Format(x, "0") ' convert to a string using format
' Note: the [ +Picture1.Textwidth ("anything") * 0.3 ] part
' moves the position down by a third of a character space
' (we use + instead of - even though + moves upwards on
' our graph because the TextWidth will be returned as a
' negative value instead of a positive value due to the
' fact we have made the ScaleHeight negative for our
' graph drawing purposes.
Dest.CurrentX = pConvertX(x - Picture1.TextWidth(s1) / 2)
Dest.CurrentY = pConvertY(0 + Picture1.TextHeight("anything") * 0.3)
Dest.Print s1
Next x
' Now do something similar for the numbers along the side:
For y = 0 To graphHeight + 0.001 Step gridHeight
s1 = Format(y, "0")
' Note: the [ -Picture1.Textwidth (" ") ] part moves
' the position to the left by the width of a space
' character just to move them slightly away from the line
Dest.CurrentX = pConvertX(-Picture1.TextWidth(" ") _
- Picture1.TextWidth(s1))
Dest.CurrentY = pConvertY(y - Picture1.TextHeight(s1) / 2)
Dest.Print s1
Next y
'
' * * * * * * * * * * * * * * * * * * * * * * * * * *
' * * * * * * * * * * * * * * * * * * * * * * * * * *
' Okay. Here you can place your own code to draw your various
' graph lines. All you need to do is to calculate the values
' of x and y for your lines, points or whatever and draw
' them to Dest using the appropriate conversion function
' (pConvertX for the x parameter and pConvertY for the y
' parameter). As an example, if you want to plot a point
' at position x, y you would normally use something like:
' Dest.Pset (x, y), vbBlue
' Instead of the above you would use:
' Dest.Pset (pConvertX(x), pConvertY(y)), vbBlue
' This makes it very easy to convert your existing code.
' TO MAKE ROUTINES FOR OTHER GRAPHS: The easiest way is
' to copy and paste this entire routine and to call the
- Follow-Ups:
- Re: Mike: it's Working, more explanation Please.
- From: Mike Williams
- Re: Mike: it's Working, more explanation Please.
- References:
- Re: Custom ScaleMode setting problem
- From: Mike Williams
- Re: Custom ScaleMode setting problem
- Prev by Date: Re: vb6 bcoming irrelevant?
- Next by Date: Re: Insert record code
- Previous by thread: Re: Custom ScaleMode setting problem
- Next by thread: Re: Mike: it's Working, more explanation Please.
- Index(es):