Beefy Boxes and Bandwidth Generously Provided by pair Networks
There's more than one way to do things
 
PerlMonks  

algorithm help -- how to label a clock

by Anonymous Monk
on Jun 17, 2011 at 04:37 UTC ( [id://910069]=perlquestion: print w/replies, xml ) Need Help??

Anonymous Monk has asked for the wisdom of the Perl Monks concerning the following question:

Ok, this is a general algorithm question, not necessarily perl related. This isn't the actual problem I have but this is a simple analog. Basically, I need to place labels in a nice way on a circle. Suppose you have a round clockface. You want to label the clock with words instead of numbers.

So, 3 o'clock is easy. The label "three" placed with the 't' maybe just a bit to the right of the circle directly at pi/4 radians. Similarily, nine o'clock is placed at its spot but with the 'e' on the circle and the first 'n' offset to the left a bit.

Those are the easy ones though. How would one, position the "eleven"? My first thought is to consider the labels as boxes. If I define a line tangent to the circle that part of that line could define one end of the box, the other end is a line parallel to it and the sides two perpendiculars. But this seems maybe overly complicated. Then again, it would allow for the label to be any size...just adjust the dimensions of the box.

This seems like the sort of thing that there must be a well known algorithm for. Is there? Google seems a bit useless but maybe I don't know the right keyword to use here.

Any advice?

Replies are listed 'Best First'.
Re: algorithm help -- how to label a clock
by wind (Priest) on Jun 17, 2011 at 05:02 UTC

    Just use sin and cos to determine the Y and X centers of your labels respectively.

    Then depending on the size of your labels, determine their position with respect to those points.

Re: algorithm help -- how to label a clock
by zentara (Archbishop) on Jun 17, 2011 at 11:56 UTC
    A canvas widget comes to mind. Here is a start for you, all you need to add is your labels for the text time around the circle. It uses a hack to do a $mw->update inside a for(;;) loop, which should be improved; but it lays out the circular math for you to derive your text_time positions.

    A second example below, shows how to do vertical text in case you desire that feature.

    #!/usr/bin/perl use warnings; use strict; use Tk; my $pi = atan2(1,1)*4.0; # an amusing transcendental number my $main = MainWindow -> new(); # the primal widget my ($width, $height ); # canvas geometry $width = $height = 500; # change me if you don't like me my $x = int($width/2); my $y = int($height/2); my $r = int($x*0.8) >= int($y*0.8) ? int($x*0.8) : int($y*0.8); # $ra +dius my $c = $main -> Canvas('-width' => $width, '-height' => $height); $c -> create('oval', $x-$r, $y-$r, $x+$r, $y+$r, -width => 2); $c -> pack(-expand => 1, -fill => 'both'); # the following two together _prevent_ resizing: $main -> maxsize($width,$height); # fixes size $main -> minsize($width,$height); # fixes size my ($outickx, $outicky, $intickx, $inticky, $tickwidth); for (0..11) { # the tick initialization loop $outickx = int($x + sin($_*$pi/6.0)*$r*1.00); $outicky = int($y - cos($_*$pi/6.0)*$r*1.00); $intickx = int($x + sin($_*$pi/6.0)*$r*0.85); $inticky = int($y - cos($_*$pi/6.0)*$r*0.85); $tickwidth = $_ % 3 == 0 ? 3 : 1; $c->create('line', ($outickx, $outicky, $intickx, $inticky), -width => $tickwidth, ); } # end tick loop undef $outickx; undef $outicky; # cleanup undef $intickx; undef $inticky; undef $tickwidth; # time and date variables: my ($second,$minute,$hour,$date,$month,$year,$weekday,$yday,$dst); my ($hourx, $houry, $minutex, $minutey ); # clock hand geometry # my ($secondx, $secondy ); # (unused) clock hand geomet +ry for (;;) { # this is the timekeeping lo +op # feel free to make use of some of these variables: ($second,$minute,$hour,$date,$month,$year,$weekday,$yday,$dst) = localtime(); $hour = $hour % 12; ($hourx, $houry, $minutex, $minutey, # $secondx, $secondy, ) = ( int($x + sin((($hour+$minute/60.0)/6.0)*$pi) *$r*0.75 ), int($y - cos((($hour+$minute/60.0)/6.0)*$pi) *$r*0.75 ), int($x + sin( ($minute/30.0)*$pi) *$r*1.00 ), int($y - cos( ($minute/30.0)*$pi) *$r*1.00 ), # int($x + sin( ($second/30.0)*$pi) *$r ), # int($y - cos( ($second/30.0)*$pi) *$r ), ); $c -> delete('hourhand'); $c -> create('line', ($hourx, $houry, $x, $y), '-arrow' => 'first', '-tags' => 'hourhand', '-width' => 3, ); $c -> delete('minutehand'); $c -> create('line', ($minutex, $minutey, $x, $y), '-arrow' => 'first', '-tags' => 'minutehand', '-width' => 2, ); # $c -> delete('secondhand'); # $c -> create('line', ($secondx, $secondy, $x, $y), # '-tags' => 'secondhand', # ); # DoOneEvent; # <- use either this or $main -> update; # <- this (both not necessary). sleep(1); # 1 sec compromise min cpu vs. lively-X } # end timekeeping loop __END__ =head1 NAME pclock =head1 SYNOPSIS pclock & =head1 DESCRIPTION B<pclock> is a clock implemented in Perl/Tk. Here is the `date` on which this was written: Thu May 9 16:58:31 EDT 1996 It was then modified on: Fri May 10 16:31:10 EDT 1996 Thanks to Adrian Phillips and Simon Galton for helpful suggestions. The choice between DoOneEvent; and $main->update; as well as sleep(); times may be system|X-server dependent. As distributed by the author there is a second hand embedded within commented code in this program. =head1 REQUIREMENTS Perl with the Tk extension. =head1 AUTHOR Peter Prymmer pvhp@lns62.lns.cornell.edu =cut
    Vertical text example
    #!/usr/bin/perl use Tk; my $mw = new MainWindow; my $c = $mw->Canvas->pack; $c->createText(50, 50, -text => 'Hello', -width => 1, ); MainLoop;

    I'm not really a human, but I play one on earth.
    Old Perl Programmer Haiku ................... flash japh
      Here is mine Tk::Zinc version, with a little help from Advantages of Tk::Zinc over plain Canvas, Tk::Clock, -tk Moving circle, it works, but rotate is busted :)
      #!/usr/bin/perl -- use warnings; use strict; use Tk; use Tk::Zinc ; use Tk::Clock; my $mw = tkinit; my $canvas = $mw->Zinc( -width => 300, -height => 300, -backcolor => 'white', )->pack(qw/ -expand 1 -fill both -padx 10 -pady 10 /); my $clock = $mw->Clock( useAnalog => 1, )->pack; my $circle_radius = 100; my $origin_x = $canvas->cget('-height') / 2 ; my $origin_y = $canvas->cget('-width') / 2; my $cgroup = $canvas->add('group',1,-visible=> 1); #~ my $circle = $canvas->createOval( my $innercircle = $canvas->add( 'arc', $cgroup, [ $origin_x - ( $circle_radius - 10 ), $origin_y - ( $circle_radius - 10 ), $origin_x + ( $circle_radius - 10 ), $origin_y + ( $circle_radius - 10 ), ], ); my $circle = $canvas->add( 'arc', $cgroup, [ $origin_x - $circle_radius, $origin_y - $circle_radius, $origin_x + $circle_radius, $origin_y + $circle_radius, ], ); my $ix=0; my $PI=3.141592635; my $angle = -30; for my $dtick ( 1 .. 12 ){ my $d8 = 9; $angle += 30; my $cos = cos($angle*$PI/180); my $sin = sin($angle*$PI/180); my $x1 = $origin_x + ( ( $circle_radius - $d8 ) * $sin ); my $y1 = $origin_y - ( ( $circle_radius - $d8 ) * $cos ); my $x2 = $origin_x + ( ( $circle_radius + $d8 ) * $sin ); my $y2 = $origin_y - ( ( $circle_radius + $d8 ) * $cos ); #~ my $line = $canvas->createLine ( my $line = $canvas->add( 'curve', $cgroup, [ $x1, $y1, $x2, $y2, ], -tags => "tick", -linecolor => 'red', -filled => 1, -linewidth => 1.0, ); my $text = $canvas->add( 'text', $cgroup, -position => [ $x1,$y1 ], -text => ixHour( $ix++) , -color => 'blue', ); # no work, disappeared or crash #~ $canvas->rotate( $text, $angle , 'degree' ); } MainLoop; sub ixHour { return [qw/ one two three four five six seven eight nine ten eleven twelve /]->[ -1 + shift ] } __END__
Re: algorithm help -- how to label a clock
by Anonymous Monk on Jun 17, 2011 at 05:15 UTC
    But this seems maybe overly complicated.

    It may seem that way, but this is the only way to accomplish it.

    You mark X points (12) on the circle, and use each point as the starting coordinates for drawing your box-labels.

    You rotate the box-labels based on the angle, so it points in a direction (3 o'clock would point west)

    You shift the box-label in the opposite direction ( 3 o'clock shifts east), based on the width of a letter.

    See ASCII analog clock, it uses a grid of boxes a character wide.

Re: algorithm help -- how to label a clock
by toro (Beadle) on Jun 17, 2011 at 07:15 UTC

    I'm not sure I understand your question. It sounds like you know how to calculate the angle of where the text will be placed.

    If that's the case, then divide the clock into four uneven parts: T = (−20deg, 20deg), R = (20deg, 160deg), B = (160deg, 200deg), L = (200deg, 340deg). If the text is in T or B, calculate the height of the text, and place the text above/below the circle by that height + margin. If the text is in L, then calculate the length of the text and start the text len($string)+$margin to the left of the angle you want to place it at. If the text is in R, then start the text $margin to the right of the circle.

    You might also find a good answer in the R source code for plot.

Re: algorithm help -- how to label a clock
by thargas (Deacon) on Jun 17, 2011 at 11:56 UTC
      Rotating the labels also makes the hour hand point at a "9" when it is six o'clock.
Re: algorithm help -- how to label a clock
by hawtin (Prior) on Jun 17, 2011 at 07:54 UTC

    Your description seems too vague to me. There are a number of different approaches.

    Assuming you are not going to rotate the text then each "label" consumes a rectangle of space, and has a well defined point that it is trying to identify. A simple approach would be to position the centre of the rectangle on the location. Alternately you could define 8 cardinal points of the text rectangles (top left, top middle etc), select an appropriate one (for eleven the 'bottom centre' for example) and place that on the point you want.

    The issue is that the size of the rectangles and the aesthetic effect you want will determine what works for you.

    One approach I've used (in the distant past) is to place all the labels exactly where I want them (with the centre of the label in the desired location for example). Define a "goodness measure" that uses various rules (no two labels may overlap, the distance from the desired point can't be too great etc), try randomly moving one of the labels and if that increases the goodness keep it, if not try again. If you start with quite a big move size then gradually decrease it the various labels will end up in a configuration that best suits your defined measure.

    This is essentially a "Simulated Annealing" approach

Re: algorithm help -- how to label a clock
by Tux (Canon) on Jun 17, 2011 at 13:01 UTC

    You could steal the code from Tk::Clock (look at the _where function in Clock.pm. This is - as zentara - using a Canvas.

    I however think that most posted solutions here would be almost the same.


    Enjoy, Have FUN! H.Merijn
Re: algorithm help -- how to label a clock
by Anonymous Monk on Jun 17, 2011 at 12:56 UTC
    The only "secret" is that you are determining the location of the center of the label ... not its upper-left edge ... and "zero degrees" points straight up. The numbers are 360 / 12 = 30 degrees apart. There are 2*pi radians in a circle. The diameter is some known quantity. The X and Y offsets are calculated by cos() and sin().
      When it comes down to it the only real difficulty is deciding how you are going to justify the labels -- and you aren't necessarily going to key off the center of the labels. It depends if you are placing the number inside the circle, with the hands sweeping over them, outside the circle, or something in between. The look and "best" spacing will also be affected by what labels you use (arabic, roman, etc...)

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://910069]
Approved by planetscape
Front-paged by davido
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others perusing the Monastery: (3)
As of 2024-04-19 19:44 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found