Dynamicaly Center Multiple DrawText "Labels"
Dynamicaly Center Multiple DrawText "Labels"
Dynamicaly Center Multiple DrawText "Labels"
Dynamicaly Center Multiple DrawText "Labels"
Dynamicaly Center Multiple DrawText "Labels"
Dynamicaly Center Multiple DrawText "Labels" Dynamicaly Center Multiple DrawText "Labels" Dynamicaly Center Multiple DrawText "Labels" Dynamicaly Center Multiple DrawText "Labels" Dynamicaly Center Multiple DrawText "Labels" Dynamicaly Center Multiple DrawText "Labels" Dynamicaly Center Multiple DrawText "Labels" Dynamicaly Center Multiple DrawText "Labels"
Dynamicaly Center Multiple DrawText "Labels" Dynamicaly Center Multiple DrawText "Labels"
Dynamicaly Center Multiple DrawText "Labels"
Go Back  Xtreme Visual Basic Talk > > > Dynamicaly Center Multiple DrawText "Labels"


Reply
 
Thread Tools Display Modes
  #1  
Old 02-23-2010, 11:04 AM
drh drh is offline
Newcomer
 
Join Date: Feb 2010
Posts: 11
Default Dynamicaly Center Multiple DrawText "Labels"


I've drawn a graph where each gridline represents 15 minutes. (Every 4, 1 hour of time)

The resolution is 100 dpi per inch. Each column is 12 DPI. (Not very big)

Each gridline can have zero to multiple "Lines of data" which must be centered under the associated gridline using a vertical transform. (Each drawtext label represents an event that happened at that point in time)

This I can figure out. The problem is that there can be 3 labels at 3:00AM, then another 2 labels at 3:15 AM with more labels as time progresses through the day. If each "label takes up 24 dpi of space, I need to center 120 dpi of data under 12 dpi of space. (Drawing the lines from the gridline to the associated data should not be an issue depending on how the "label is centered)

How can I center all 5 labels at runtime. I've considered an array but each time a "label" is added, I would have to recaculate each labels position. Besides that, how can my program recgnize that the data it is coming up to (data is read one line at a time) is a group or how large the group is?

BTW. This take place at print time.

If you can help me with this simplest of possibilties I can figure out the more complicated one I'm sure.
Thanks
Reply With Quote
  #2  
Old 02-23-2010, 01:38 PM
AtmaWeapon's Avatar
AtmaWeaponDynamicaly Center Multiple DrawText "Labels" AtmaWeapon is offline
Fabulous Florist

Forum Leader
* Guru *
 
Join Date: Feb 2004
Location: Austin, TX
Posts: 9,500
Default

First, I think you need to clarify your units.

"dpi" means dots per inch, and is a ridiculous measure on its own. It actually means "pixels per inch" and you can't often guarantee it. The DPI resolution of a program depends on the display device. It's usually 96 DPI on a CRT monitor, can be much higher on an LCD, and text printers usually print between 300 and 600 DPI.

Quote:
The resolution is 100 dpi per inch.
This would be "pixels per inch per inch", or DPI^2, a measure of pixels per area unit. It makes no sense.

Quote:
Each column is 12 DPI.
Again, this is nonsensical. DPI is a measure of resolution, not width.

I think you are confusing "DPI" with "pixels"; if I make the substitution things make more sense. Still, I'm not really sure I understand what you are looking for. Could you post an image that simulates what you are trying to do? I don't know what the label taking up 24 pixels means, and can't visualize what you mean by centering 120 pixels in a 12 pixel space.
__________________
.NET Resources
My FAQ threads | Tutor's Corner | Code Library
I would bet money 2/3 of .NET questions are already answered in one of these three places.
Reply With Quote
  #3  
Old 02-23-2010, 04:29 PM
drh drh is offline
Newcomer
 
Join Date: Feb 2010
Posts: 11
Default

I've sent an attachment of an actual logsheet.

In the remarks section (immediately under the grid) you can see what I am taling about. You will see a line drop from a gridline and over at an angle to the remark for that time. I've circled what I'm talking about. BTW. It can get much worse than this with real data.

This is what I cannot figure a way to do. Can you help me now?

Thanks.
Attached Images
File Type: bmp Grid.bmp (459.8 KB, 20 views)
Reply With Quote
  #4  
Old 02-25-2010, 01:17 PM
drh drh is offline
Newcomer
 
Join Date: Feb 2010
Posts: 11
Default

Well, it looks like I'm doing this one on my own.

I was hoping there were some general vb methods I was not aware of that I could use. Guess not.

I'll post what I'm able to come up with. Maybe some of you may see something I don't, which would help.

BTW, I'm new to .net and grapgics. My specialty has been db apps. Not much graphics there.

Thanks anyways guys.
Reply With Quote
  #5  
Old 02-25-2010, 01:41 PM
AtmaWeapon's Avatar
AtmaWeaponDynamicaly Center Multiple DrawText "Labels" AtmaWeapon is offline
Fabulous Florist

Forum Leader
* Guru *
 
Join Date: Feb 2004
Location: Austin, TX
Posts: 9,500
Default

I'm fooling around with *something* but the results aren't super good so far. The vertical text is a bit of a booger and my graphics transforms are doing weird things to the coordinate system; I could throw some fudge numbers in it and make it work but I don't like fudge numbers. Progress is slow, and this is a relatively complicated request.
__________________
.NET Resources
My FAQ threads | Tutor's Corner | Code Library
I would bet money 2/3 of .NET questions are already answered in one of these three places.
Reply With Quote
  #6  
Old 03-02-2010, 12:15 PM
passel's Avatar
passelDynamicaly Center Multiple DrawText "Labels" passel is offline
Sinecure Expert

Super Moderator
* Guru *
 
Join Date: Jun 2003
Location: Upstate New York, usa
Posts: 8,026
Default

I don't have time to try something at the moment, but the first approach I would try would be to use a hidden bitmap, filled with a transparent background.
I would draw the text on this hidden bitmap.
Once the number of lines were drawn, I would determine the center X, Y of the text.
I would use that to translatetransform and rotatetransform to the destination center point, and simply use drawimage to transfer the text from the hidden bitmap.

I guess the challenge is that you're not simply centered under the time, but have overlapping text areas that have to be offset from the center so they don't overlap.
Can be an interesting exercise if I had a bit more free time at the moment.
Maybe later, but AtmaWeapon will probably get there before me.

P.S. Looking at the problem a bit more, since the vertical text is "left" justified (top justified), then we shouldn't have to find the center X of the source text, only the center Y of the source strings. The "Left"
edge (Top when rotated) will be a constant value for all the remarks.

You are going to need to read all the lines and accumlate some information in order to adjust all the groups before you do any drawing, so that text won't overlap.
I would think that probably you would accumulate the size of a given group (i.e. 3 * 24), and calculate the left and right extents of that group, based on the orignal center point.
Do this for each group (15 minute groups).
You would then check for overlaps, and if they exist, then combine the overlapped groups into a "super" group, and calculate the extents based on a center point midway between the two original center points.
You would continue until there were no overlaps.

Perhaps I'll make time to try a simple example later today.
__________________
There Is An Island Of Opportunity In The Middle of Every Difficulty.
Miss That, Though, And You're Pretty Much Doomed.

Last edited by passel; 03-02-2010 at 03:51 PM.
Reply With Quote
  #7  
Old 03-09-2010, 03:43 PM
AtmaWeapon's Avatar
AtmaWeaponDynamicaly Center Multiple DrawText "Labels" AtmaWeapon is offline
Fabulous Florist

Forum Leader
* Guru *
 
Join Date: Feb 2004
Location: Austin, TX
Posts: 9,500
Default

It's actually not as difficult as I thought, but I went down some winding roads to get to the solution.

Graphics.DrawString() has overloads that take a StringFormat. The StringFormat class is used to indicate a bunch of options about how the string should be rendered. One of the format flags is StringFormatFlags.DirectionVertical. This was the first thing I tried, and it didn't work for whatever reason. I think I was careless. Keep it in mind, because it's actually the answer to the question.

Anyway, the problem is, "Render text in such a way that it is aligned with a certain point along a number line." This has a few sub-problems:
  • Figure out where a point falls on a number line in screen/control coordinates.
  • Determine the size of a piece of text.
  • Display text vertically.

The first problem is pretty simple, particularly if you're writing a custom control. Everything's easiest if it always happens along tick marks. For example, from 0 to 10 there will be 11 tick marks. If the control is 200 pixels wide, it's easy to calculate that each tick is 18.1818... pixels apart from the last. So if we want something at position "3" on this number line, we can say 4 * 18.181818... ~= 72.72... and know that we need to align with the column 72 pixels to the right.

(It's not much more complicated to work with time. A number line from 3:00 PM to 5:00 PM is the same as going from 0 to 119 since there are 120 minutes represented. If the control is 200 pixels wide and you want to know where 3:28 is located, here's the math:
3:28 = 28 minutes past the start time.
28 minutes / 120 minutes total = 23.3...% from the origin
0.2333... * 200 pixels = 46.66... is where 3:28 is located.
)

The second problem is easier. System.Drawing.Graphics has a MeasureString() method that measures the rectangle that will be required to fit some text. Due to a lot of typography rules, this rectangle is *not* the smallest possible rectangle in which it *looks* like the text will fit, but it's usually very close. If we can get that rectangle and we know where we want to align a point on the rectangle, simple 2D geometry will help us calculate where to put the rectangle.

The third problem is the one I spent the most time on. One of my first attempts turned out to be down the right path, but I screwed something up and it didn't work so I wasted time on other approaches. My 2nd approach involved measuring the string oriented correctly, determining the center point of the text's height, then using translate and rotate transforms to cause .NET to draw the text vertically. This actually worked, but was usually a little bit off on horizontal alignment. I never could figure out what was wrong with the math. So I went back to the original approach, and it worked!

I've attached a project that demonstrates a basic case. Info is a little class I made to represent the stuff that needs to align with the labels. It has two important properties: Position is where it is on the line, and Lines consists of the lines to be displayed. DemoControl is the custom control where exciting things happen. It displays a number line from 0-10, dictated by the "Divisions" constant because I'm a doofus and focused on the space between the ticks and not the ticks themselves. OnPaint() is where the lesson lies.

Step 1 is to figure out where the ticks go; DrawTicks() does this and draws the ticks as it calculates their locations and saves them in _tickCoordinates. It looks ugly, but most of the math in the method is to account for the buffer on the edges and that I need to draw a line from above the ideal coordinate to beneath the ideal coordinate.

Step 2 is to draw a label that numbers each tick mark; DrawTickLabels() does this. The label needs to be centered under the tick, so I use Graphics.MeasureString() to see how wide the text is before drawing it. I save the tallest label height in _tickLabelHeight because the next step needs to know where to draw.

Step 3 is to draw the vertical text; this is done by DrawInfoLabels(). I'll talk about what it does in more detail than the other two. First, it determines if there's an Info that's supposed to render at this position. If so, it combines the lines from that label into a single string. After making a single string, it measures the string rendered vertically (using StringFormatFlags.DirectionVertical.) Then, it uses the width of that text combined with the location of the tick mark to determine where to put the text.

It's still not perfect. One problem is the left-to-right property of the text is preserved, so when I read the text at position 3 it looks like it says "Line Three Line Two Line One" instead of the other way around. That's easy to fix in the combination algorithm; just read the lines in reverse. Another problem is that it's not *quite* centered. I blame typography because I've been over the math a few times and can't see anything suspicious. Font height has to account for ascenders, descenders, and maybe line spacing. So the text is probably perfectly centered, but its height includes some pixels that as far as I'm concerned aren't text. I *could* put a fudge factor in that makes it look right, but it'd only work for this font at this size at this screen resolution. I'm sure there's some way I can dig into the typography information of the font and end up with something more reliable, but every time I try that I end up spending hours making things worse.
Attached Files
File Type: zip PlacementDemo.zip (13.1 KB, 4 views)
Reply With Quote
  #8  
Old 03-10-2010, 07:33 AM
passel's Avatar
passelDynamicaly Center Multiple DrawText "Labels" passel is offline
Sinecure Expert

Super Moderator
* Guru *
 
Join Date: Jun 2003
Location: Upstate New York, usa
Posts: 8,026
Default

Quote:
Originally Posted by drh View Post
...If each "label takes up 24 dpi of space, I need to center 120 dpi of data under 12 dpi of space.

How can I center all 5 labels at runtime. ...
Thanks
Unfortunately, that control doesn't address the question asked.

Each tic mark (position) of the scale is 12 pixels pixels appart.
The Height of a text label is 24 pixels.
So if you have a one line label on two adjacent positions, the label can't be simply centered under the position (because they will overlap).
Each has to be adjusted, the left one to the left by 6 pixels, the the right one to the right by 6 pixels so the labels don't overlap. (Example at time 1:00 and 1:15 in the bitmap in post 3).

Likewise, if you have multiple labels associated with a time (reference 6:30 and 6:45 in the bitmap), you have to move the text further apart to avoid overlab. In the case of multiple labels, the positions don't have to be adjacent to have overlap that needs to be dealt with.

So the object is to determine the minimum distance needed to move a group of labels left or right so collectively they are centered around the position they are associated with and don't overlap with any other labels in the log.
__________________
There Is An Island Of Opportunity In The Middle of Every Difficulty.
Miss That, Though, And You're Pretty Much Doomed.
Reply With Quote
  #9  
Old 03-10-2010, 09:53 AM
AtmaWeapon's Avatar
AtmaWeaponDynamicaly Center Multiple DrawText "Labels" AtmaWeapon is offline
Fabulous Florist

Forum Leader
* Guru *
 
Join Date: Feb 2004
Location: Austin, TX
Posts: 9,500
Default

Kind of like how at position 3 I centered the 3 lines associated with that particular time around the tick mark? I left overlapping as an exercise for the reader, because there are no details about what to expect. What I'd do to modify my example is defer rendering the text but still measure it. After measuring each label, I'd examine the rectangular bounding boxes for overlap. Then, I'd adjust overlapping rectangles and check for overlap again; when no rectangles overlap I'm "done" and can draw the text at the appropriate positions.

In the end the only way I see to satisfy both "centered with respect to a point" and "no overlap" is to change vertical displacement. If you draw a line from intended position to the labels you get some freedom with horizontal displacement, but imagine a scenario where every 15-minute period has 2 or 3 labels. You run out of horizontal space. I can also imagine scenarios where you run out of vertical space because there are just too many labels. Is this a sane scenario? No idea, that's a requirement that the OP should explore and define. The control's design should include "it is assumed that there will be no more than x labels within y minutes of each other". In my control, this isn't defined at all.
Reply With Quote
  #10  
Old 03-10-2010, 11:57 AM
passel's Avatar
passelDynamicaly Center Multiple DrawText "Labels" passel is offline
Sinecure Expert

Super Moderator
* Guru *
 
Join Date: Jun 2003
Location: Upstate New York, usa
Posts: 8,026
Default

Well, I still don't have much time to do anything nice, with good comments, etc..., but at least I'll throw this quick example together coded along the lines of what I described above.
As AtmaWeapon has noted, you do have to collect and measure all the entries.
This demo can be easily broken, and it doesn't draw a scale as you say that is all done and you're just trying to add the log entries at print time.

So, this code reads the lines, and collecting them into groups (again, not using dynamic allocation or robust code because I want this to be done in a couple hours), so since there are 96 tics on the scale, I created an array to hold 96 possible groups of text.
I figured the loop processing the groups might be a little simpler to process if I had another array acting as a list of the actual positions used. There are better ways of doing this, but this was quick and simple from my point of view (not necessarily for someone else trying to look at it cold).

Once the group information was collected, i.e. desired centerpoint, width (actually halfwidth) of the text output area required, etc. The code simply loops from first group to last, adjusting the centerpoints in each group pair of groups that overlap. It repeats the loop as many times as necessary until there is no overlap, or quits after 100 loops assuming no solution is possible (too many labels in the area allowed).
Not tested to point of failure.
The text box is initialized with the values from your bitmap example.

The output loop simply prints the text in a bitmap with transparent background, then uses DrawImage to transfer a block from the bitmap to the output position (translated and rotated).

For reference, I draw a line from the original tic position, to the computed center, so you can see the movement the labels have made.

Since you haven't been logged on to the forum since the 2nd of March, you may have proceeded on your own anyway.

By the way, is this application a coincidence, or a continuation of the work started in VB6 by WeBad over five years ago.

This example code was thrown into another little test program I had, so that is the reason for the odd name, TransparentBackground.

P.S. I really should have taken time to add a few more comments, my apologies.
Attached Files
File Type: zip TransparentBackground.zip (58.8 KB, 7 views)
__________________
There Is An Island Of Opportunity In The Middle of Every Difficulty.
Miss That, Though, And You're Pretty Much Doomed.

Last edited by passel; 03-10-2010 at 12:08 PM.
Reply With Quote
  #11  
Old 03-10-2010, 02:21 PM
AtmaWeapon's Avatar
AtmaWeaponDynamicaly Center Multiple DrawText "Labels" AtmaWeapon is offline
Fabulous Florist

Forum Leader
* Guru *
 
Join Date: Feb 2004
Location: Austin, TX
Posts: 9,500
Default

My only relevant comment is that the quality of the text on my machine is awful; completely unreadable. Part of it is it seems like the rotation transforms in GDI+ really screw text rendering up; this is another reason I rejected that approach. I tried getting rid of the rotation but the text still showed up horrible. Maybe there's some image scaling happening? It seems like it's be all-around easier to work in the coordinate space of the form. I'll see if I can't modify mine to add this important but ambiguously-defined feature.

The rest of the comments are irrelevant and would derail the thread; do consider avoiding the practice of holding references to Graphics objects for the lifetime of your application and drawing to picture boxes though. It's very wasteful compared to handling the Paint event and using the Graphics object you get that way.
__________________
.NET Resources
My FAQ threads | Tutor's Corner | Code Library
I would bet money 2/3 of .NET questions are already answered in one of these three places.
Reply With Quote
  #12  
Old 03-10-2010, 03:29 PM
passel's Avatar
passelDynamicaly Center Multiple DrawText "Labels" passel is offline
Sinecure Expert

Super Moderator
* Guru *
 
Join Date: Jun 2003
Location: Upstate New York, usa
Posts: 8,026
Default

I'm not sure that its wasteful.
I'm not writing to the picturebox, but rather a bitmap that happens to be associated with the .Image context of the picturebox.
The advantage, is that for complex graphics that are infrequently updated or updated on demand, then I only update the bitmap once, or when I need to.
When anything that windows does that will cause some portion of the picturebox to need to be redrawn, windows (or .Net, whatever) will refresh the region affected from the .Image context first, before issuing the Paint event. Since I've already done my drawing previously in the bitmap, there is nothing further for me to do in the paint event.

Of course, the bitmaps used did not have to be associated with Pictureboxes, but were in this case so you can see what is going on.
__________________
There Is An Island Of Opportunity In The Middle of Every Difficulty.
Miss That, Though, And You're Pretty Much Doomed.
Reply With Quote
  #13  
Old 03-21-2010, 07:30 PM
drh drh is offline
Newcomer
 
Join Date: Feb 2010
Posts: 11
Default

Thank you very much for your help.

With regards to not being logged on since March 2, I drive long haul truck with my wife and have been very busy on the road making delivery dead lines.

I did not post a similar question a few years ago under VB6.

My programing experience is in data base programing. I've never had to get into graphics for any reason. All the controls I needed were graphicaly available.

Now, I've not yet had a chance to look at the code samples provided by you. I will look at these later tonight after I leave the truck stop and lose my internet connection.

Reading over your submissions I see that you are using graphical controls, ie. PictureBox control or using an "image" for a picturebox control. This is interesting as I've been using the PrintDocument for creating my image. Remember, I'm new to graphics.

So what you are saying is that it would be better to use a Form, add a PictureBox and draw to the picture box than to draw using PrintDocument? I've no problem re-writting my code if it will make things easier to change at some later date. (A new Drivers log might have a different layout and therefore new drawing code).

Once again, Thanks very much for your help.
Reply With Quote
  #14  
Old 03-22-2010, 12:23 PM
AtmaWeapon's Avatar
AtmaWeaponDynamicaly Center Multiple DrawText "Labels" AtmaWeapon is offline
Fabulous Florist

Forum Leader
* Guru *
 
Join Date: Feb 2004
Location: Austin, TX
Posts: 9,500
Default

I've left this thread for a while because I feared my solution would be deemed more complicated. It's easy to understand, but the code is spread throughout more places. Abstraction helped with making the algorithms clear but hurt the overall complexity.

The attached screenshot "bad_fonts.png" shows the main thing I don't like with passel's code. I experimented with using transforms to rotate the fonts, and found similar problems with this technique. From the looks of it, the rotation transform is puking, but I think some image scaling is happening too. It all combines in a perfect storm that probably looks OK in some circumstances but I can't read it. I don't know what's up with the stray text in the top-left; this is the only time I've ever seen it so we'll pretend it's not there. Whether it's wasteful or not is actually a moot issue since the text is unreadable. (I feel like any time you draw to an in-memory bitmap and shove it in a picturebox it's a waste. You can manually draw it on a custom control and don't need an in-memory bitmap to do so. There are reasons to do it, but I don't think this case needs it.)

Anyway, here's where I pitch my solution. I attached "my_attempt.png" shows how it looks; I'm lacking some polish but it's tweaky things that take a few minutes a piece so I'm not fooling with it. I wrote my own layout algorithm because passel's self-admitted lack of comments made it look like a lot of effort to try and decipher his algorithm. The main application has quite a few buttons; each represents one of the "not ideal" arrangements of items that I used to test my layout algorithm. For example, "Leftmost overlap" ensures that an item will "naturally" appear offscreen and tests that my algorithm shuffles it back onscreen. "Crowded left" ensures that it doesn't get pushed back off the screen by items that would display near it. "Impossible" is actually possible if your monitor is big enough, but in general represents the state where the algorithm gives up because the width required to display the strings is greater than the width of the control. To counteract this, if you click an area of the control that has text a ToolTip will pop up to summarize the information. I made up for complexity with features

(*post-mortem edit*): After poking at passel's implementation I understand more about how it works, and I really want to hack something like it into my solution. The way it pulls overlap into the same group is neat, but I'm not sure how compatible with my Info-driven approach it would be. I think I might try to hack something like it later as an exercise of the extensibility point.

Let's talk about implementation. We've got 3 major responsibilities in the code:
  • Display the information.
  • Arrange items on an x-axis such that no items overlap.
  • Map DateTime values to pixel coordinates on an X axis.
  • Associate several lines of text with particular times.

I'll work in backwards order; this is the order of least complexity to most.

"Associate several lines of text with particular times" is handled by the poorly-named Info class. There's not much to it; it's got a collection of lines and a Position property that lets you tell it what time it should be associated with. Users of the class set Position and call AddLine() for each line it should display. The GetText() method returns all of the lines as one string, with newline characters in the appropriate place. I probably should have used String.Join() here instead of implementing it myself. Oh well.

"Map DateTime values to pixel coordinates on an X axis" is handled by the DataMapper class. It isn't separated cleanly from the DemoControl class, but coupling them made some things conceptually simpler. Users of this class set the properties, then call the Map function to find out where a DateTime value lies. The mapping is done such that the value specified by StartTime will return Buffer, EndTime will return Width - Buffer, and a point halfway between them will return (Width - (2 * Buffer)) \2 . That is, if Buffer is 10 and Width is 100, 8:00 AM renders at 10, noon renders at 50, and 5:00 PM renders at 90. Now we have a way to map from DateTime to an x-coordinate. We're ready to do the real work.

"Arrange items such that no items overlap" is handled by SpreadPlacementStrategy. Users pass a list of Info instances, a DataMapper, a graphics context, a font, and some height buffer to CalculatePlacement(). This method returns a collection of rectangles that represents the optimal bounds of each item. Here's how my algorithm works:
  • First, calculate the "natural" position of each item by centering it beneath its point on the x-axis. (CalculateInitialPlacement())
  • Now, adjust all items such that nothing is outside the bounds of the control and nothing overlaps (CalculateFinalPlacement()):
    • If the sum of the item widths is greater than the available width, it is impossible to avoid overlap, so leave the items where they lie and exit. (lines 78-81)
    • For each item:
      • If the item overlaps something to the left, move all items that are left of this item to the left far enough to eliminate the overlap. (90-92, ScootLeft()).
      • If the item overlaps something to the right, move all items that are right of this item to the right far enough to eliminate the overlap. (95-97, ScootRight())
      • If there is still overlap to the left, move this item and all of the items that are to the right of it to the right until no overlap remains. (100-102, ScootRight())
      • If there is overlap to the right, move this item to the left and all items to the left of it to the left until no overlap remains. (103-105, ScootLeft())

I make no claims that this algorithm is optimal. It's the first thing I thought of, and it was easy to visualize by shuffling index cards around on my desk. It's O(n^2) and probably gets shaky when there's a lot of lines. But analysis of the problem domain renders this moot. The maximum number of lines I can display on my monitor in this direction is around 250, and for reasonable window sizes this shrinks to 50 or 60. The "impossible" case renders fine, and this seems like a good maximum.

DemoControl is a control that displays an axis with a scale from 8:00 AM to 5:00 PM. It uses a DataMapper to help calculate where intermediate points lie, and has a collection of Info instances that will be displayed near the correct time. It uses the Strategy pattern to place Info instances; this makes it easy to implement a better strategy (I like how few lines passel's algorithm took) and plug it in. A SpreadPlacementStrategy is used in the demo.

All of the interesting logic takes place in the OnPaint() method called when the control raises its Paint event. If the Info collection is updated or the control is resized, it won't update until someone calls Invalidate() to repaint. OnSizeChanged() updates the DataMapper's concept of the control's size to ensure accurate mapping. Anyway, look at OnPaint(). Here's how the algorithm goes:
  • Draw a border around the control and fill it with white. (56-57)
  • Draw the "ticks" for each control (DrawTicks()): For each hour between and including the start and end time, ask the DataMapper where that hour goes and draw a line at that spot.
  • Draw the time labels beneath each tick. (DrawTickLabels()): For each hour between and including the start and end time, get the coordinate for that hour and center text that represents the time beneath it.
  • Draw the Info data. (DrawInfoLabels()): Use the IPlacementStrategy to get the bounds of each item, then draw the text using StringFormatFlags.DirectionVertical at the given location.
  • Draw a line from the axis to each Info in case it's shifted. (DrawLines()): DrawInfoLabels() caches the positions of each item. Use the DataMapper to figure out the start point of each line and this cache to figure out the end point of the line, then draw the line.

The ToolTip functionality (ShowAppropriateToolTip()) piggybacks off of the _positions cache that DrawInfoLabels() generates. When you click the control, it loops through _positions and if a position contains the click point, it adds that label to a list of labels that will be displayed (FindEligibleLabels(). Then, all eligible labels have their text combined into one string (GetToolTipText()) that is set to the tooltip text. I had a lot of trouble with the tooltips flickering and disappearing when I didn't want them to, so I added a little bit of logic that doesn't try to show the same tooltip twice (206-215). The magic number "8" on line 211 is probably a remnant of when I had ToolTips appearing on MouseMove; I had so much trouble with that implementation I gave up and settled on clicking.

So, to recap, here's why I didn't leave passel's implementation as-is even though mine is a good bit more complex:
  1. passel's has some font rendering issues that can make it unreadable.
  2. I didn't see evidence of extensive testing of overlap conditions.
  3. Mine makes it easy to plug in your own strategy for placement.
  4. Mine has a time scale; it lets you use time to express where items will end up.
  5. Mine has a stopgap solution for overlap scenarios.
  6. I feel like the delegation of tasks to three separate classes makes the algorithms used easier to understand.
  7. I spent a few hours over a couple of days writing the placement algorithm and I was proud of it.
Attached Images
File Type: png bad_fonts.png (68.2 KB, 7 views)
File Type: png my_attempt.png (47.6 KB, 7 views)
Attached Files
File Type: zip PlacementDemo.zip (20.4 KB, 2 views)
__________________
.NET Resources
My FAQ threads | Tutor's Corner | Code Library
I would bet money 2/3 of .NET questions are already answered in one of these three places.
Reply With Quote
  #15  
Old 03-25-2010, 05:34 PM
passel's Avatar
passelDynamicaly Center Multiple DrawText "Labels" passel is offline
Sinecure Expert

Super Moderator
* Guru *
 
Join Date: Jun 2003
Location: Upstate New York, usa
Posts: 8,026
Default

I'm not going to belabor the issue. AtmaWeapon has put a lot more time and effort into this, and has a much better structured approach to what he is doing.
I’m not sure what the problem with the font rendering is.
From AtmaWeapon’s attached image, it is obvious it is not a problem with the rotation or transform to position the text on the card.

You can see in the bit of text peaking out from the picturebox in back that the original un-rotated, un-transformed text already is “corrupted”. That text is simply drawn in the bitmap. Perhaps the default font doesn’t exist, so it used another font that didn’t support the point size use, but I think it should be the same default font that the textbox is using.

On my machine it looked fine, and I compared my rotated text with AtmaWeapon’s vertically drawn text and the characters matched perfectly, pixel for pixel.
Because we are rotating 90 degrees, and are not scaling from source to destination, there is no reason that there shouldn’t be a one-to-one correlation from horizontal pixels to vertical pixels. On a screen resolution that has “square” pixels there should be absolutely no distortion.
So, why the original, simple horizontal printed text is already bad, without any transformations, on AtmaWeapon’s machine is a curiosity.

Also, I wasn’t trying to write a complete control, or even the basis for a control. Just an example of one way to address the overlap problem, which I took as being the germane question posed in the thread. To that end, the textbox is provided to allow doing “what ifs”, to allow inputting different “overlapped” scenarios, and see how the overlap is handled. There is basically only one safeguard implemented, to prevent an endless loop. If you get more lines of text than can fit on the card, then the endless loop check should kick you out, and nothing will be printed.

If there is not evidence of extensive testing of overlap conditions, that is probably because the algorithm to handle overlap conditions is relatively simple, and doesn’t look as complicated as it should be. Again, use the textbox to try out different scenarios to see if the overlap handling code, handles the overlaps adequately.
__________________
There Is An Island Of Opportunity In The Middle of Every Difficulty.
Miss That, Though, And You're Pretty Much Doomed.
Reply With Quote
  #16  
Old 03-26-2010, 10:24 AM
AtmaWeapon's Avatar
AtmaWeaponDynamicaly Center Multiple DrawText "Labels" AtmaWeapon is offline
Fabulous Florist

Forum Leader
* Guru *
 
Join Date: Feb 2004
Location: Austin, TX
Posts: 9,500
Default

Quote:
Originally Posted by passel View Post
If there is not evidence of extensive testing of overlap conditions, that is probably because the algorithm to handle overlap conditions is relatively simple, and doesn’t look as complicated as it should be. Again, use the textbox to try out different scenarios to see if the overlap handling code, handles the overlaps adequately.
Had I done a deep proofread, I would have edited that part out. When I wrote the first draft, I felt like I was overly harsh on your example and removed a lot of comments about it. I poked at it for a while to see if I should give it a chance, and realized that I actually liked how you handled overlap more than my technique. I rewrote a couple of paragraphs, added the part where I'd try to think about making my example use it, and apparently missed the big fat paragraph where I said it didn't handle test cases.

I still haven't sat down to figure out what your algorithm is doing, and I really should.

Writing this post has made me want to change a bit about how I post on this forum, I feel like I'm a little too mean
__________________
.NET Resources
My FAQ threads | Tutor's Corner | Code Library
I would bet money 2/3 of .NET questions are already answered in one of these three places.
Reply With Quote
  #17  
Old 03-29-2010, 10:39 PM
passel's Avatar
passelDynamicaly Center Multiple DrawText "Labels" passel is offline
Sinecure Expert

Super Moderator
* Guru *
 
Join Date: Jun 2003
Location: Upstate New York, usa
Posts: 8,026
Default

Well, the algorithm is not well tested, it was more of an intuitive guess that it would probably work, and it seemed to.
But, just in case I decided I only did a test case of one, so I should probably re-look at it myself.
Interestingly enough, I was on a different machine this time, and I had the same corrupted text as AtmaWeapon did.
If I simply printed a normal line of text on my bitmap, using drawstring, the text was smeared, as if it had printed over itself, slightly offset horizontally.
The only thing I could think of that was unusual about the bitmap was that it was filled with TransparentColor, from the Web tab of the color selection.
This was because the project I started with, originally was drawing on a transparent background and doing DrawImage calls to accomplish layering of graphics similar to what I would do using Raster Ops and bitblt in VB6.
In this case, I don't really need a transparent background, so I changed the code to fill the bitmap with a white background, and the DrawString now draws on the bitmap without problem, on this machine.
I will have to play with that some more, since a lot of what I do with graphics, may require having a transparent background (since GDI+ doesn't support Raster Ops, and I would rather avoid interfacing to older, unmanaged GDI, to regain that option).
Perhaps if I specified the backcolor (whatever it might be) as being the "Transparent color", then it wouldn't have the same issue. I'll have to look at that in the future.

Anyway, if anyone is going to be looking at the code, I thought I should add a few more comments explaining what my design was, since although I thought it was simple, looking at the code a little cold, it took me a little bit to figure out what I was doing again.

The code is definitely not model code. Still too many literal numbers, rather than meaningful constants, and hardcoded sizes, and assumed conditions.
The adjustment of text groups is reminiscent of a bubble sort, it just keeps looping and moving pairs of groups left and right until there is no overlap.

The code is definitely incomplete. I check for the left edge, so text doesn’t get move off the left of the card, but I don’t check for the right edge, so a lot of text at the end of the card will probably have text falling off the right edge.

While I assume the existing log sheet forms already have all the scales on them, so didn’t print a scale on my first example, I went ahead and updated the example, since I was adding comments, and changing the background to white, and now print a scale similar to the one on the example sheet, just for a reference. I also thickened the line out to the groups to 3 pixels wide, and draw a light gray box around the individual groups, to make them easier to pick out in the example. I also added more default entries in the textbox, by simply copying some of the existing lines, and changing the times, so that the example showed more groups being adjusted.

As I said, not polished by any means, and I don’t expect to be doing anything further with it, but wanted to refresh the example to hopefully fix the smeared text situation, and add more comments to hopefully make it less obtuse for anyone interested in looking at the code.
Attached Images
File Type: png LogSheetExp.PNG (41.1 KB, 5 views)
Attached Files
File Type: zip TransparentBackground2.zip (24.5 KB, 3 views)
__________________
There Is An Island Of Opportunity In The Middle of Every Difficulty.
Miss That, Though, And You're Pretty Much Doomed.
Reply With Quote
  #18  
Old 03-30-2010, 11:07 AM
AtmaWeapon's Avatar
AtmaWeaponDynamicaly Center Multiple DrawText "Labels" AtmaWeapon is offline
Fabulous Florist

Forum Leader
* Guru *
 
Join Date: Feb 2004
Location: Austin, TX
Posts: 9,500
Default

Ahh, transparency is the bane of GDI+ and WinForms. I should have seen that and it should have made alarm whistles go off in my head.

Thanks for commenting it; that's an interesting way of approaching it. Brute force works when n is small! I was pleased to see that it works similarly to mine when it gets to the overlap part; at least I wasn't doing something crazy that happened to work. Good show!
__________________
.NET Resources
My FAQ threads | Tutor's Corner | Code Library
I would bet money 2/3 of .NET questions are already answered in one of these three places.
Reply With Quote
Reply


Currently Active Users Viewing This Thread: 1 (0 members and 1 guests)
 
Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is Off
HTML code is Off

Forum Jump

Advertisement:





Free Publications
The ASP.NET 2.0 Anthology
101 Essential Tips, Tricks & Hacks - Free 156 Page Preview. Learn the most practical features and best approaches for ASP.NET.
subscribe
Programmers Heaven C# School Book -Free 338 Page eBook
The Programmers Heaven C# School book covers the .NET framework and the C# language.
subscribe
Build Your Own ASP.NET 3.5 Web Site Using C# & VB, 3rd Edition - Free 219 Page Preview!
This comprehensive step-by-step guide will help get your database-driven ASP.NET web site up and running in no time..
subscribe
Dynamicaly Center Multiple DrawText "Labels"
Dynamicaly Center Multiple DrawText "Labels"
Dynamicaly Center Multiple DrawText "Labels" Dynamicaly Center Multiple DrawText "Labels"
Dynamicaly Center Multiple DrawText "Labels"
Dynamicaly Center Multiple DrawText "Labels"
Dynamicaly Center Multiple DrawText "Labels" Dynamicaly Center Multiple DrawText "Labels" Dynamicaly Center Multiple DrawText "Labels" Dynamicaly Center Multiple DrawText "Labels" Dynamicaly Center Multiple DrawText "Labels" Dynamicaly Center Multiple DrawText "Labels" Dynamicaly Center Multiple DrawText "Labels"
Dynamicaly Center Multiple DrawText "Labels"
Dynamicaly Center Multiple DrawText "Labels"
 
Dynamicaly Center Multiple DrawText "Labels"
Dynamicaly Center Multiple DrawText "Labels"
 
-->