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?
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.
| [reply] |
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;
| [reply] [d/l] [select] |
|
#!/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__
| [reply] [d/l] |
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.
| [reply] |
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.
| [reply] [d/l] [select] |
Re: algorithm help -- how to label a clock
by thargas (Deacon) on Jun 17, 2011 at 11:56 UTC
|
| [reply] |
|
Rotating the labels also makes the hour hand point at a "9" when it is six o'clock.
| [reply] |
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
| [reply] |
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
| [reply] [d/l] |
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(). | [reply] |
|
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...)
| [reply] |
|
|