File Coverage

blib/lib/Algorithm/TimelinePacking.pm
Criterion Covered Total %
statement 47 47 100.0
branch 6 8 75.0
condition 7 9 77.7
subroutine 7 7 100.0
pod 1 1 100.0
total 68 72 94.4


line stmt bran cond sub pod time code
1             package Algorithm::TimelinePacking;
2              
3 3     3   536699 use strict;
  3         6  
  3         118  
4 3     3   14 use warnings;
  3         4  
  3         196  
5              
6 3     3   1920 use Moo;
  3         33845  
  3         16  
7 3     3   7535 use Types::Standard qw(Int Maybe);
  3         501347  
  3         79  
8 3     3   8810 use POSIX qw(floor);
  3         22489  
  3         19  
9 3     3   4255 use List::Util qw(max shuffle);
  3         4  
  3         2259  
10              
11             our $VERSION = '0.01';
12              
13             # minimum space (in units, i.e. most frequently pixels) between 2 consecutive
14             # items on a line
15             has space => (
16             is => 'rw',
17             isa => Int->where('$_ >= 0'),
18             default => 0,
19             );
20              
21             # convert all epochs to end at this maximum. undef means no constraint
22             has width => (
23             is => 'rw',
24             isa => Maybe[ Int->where('$_ >= 0') ],
25             default => undef,
26             );
27              
28             sub arrange_slices {
29 11     11 1 237641 my $self = shift;
30 11         25 my $slices = shift;
31              
32             # sort the slices by ascending start timestamp, i.e leftmost slice will be
33             # on the highest line. Also make the whole series start at 0, not the
34             # actual timestamp.
35 11 50       39 @$slices = sort { $a->[0] <=> $b->[0] || $a->[1] <=> $b->[1] } @$slices;
  14         45  
36 11         16 my $earliest = $slices->[0][0];
37 11         18 @$slices = map { $_->[0] -= $earliest, $_->[1] -= $earliest; $_ } @$slices;
  22         29  
  22         33  
38 11         36 my $latest = max map { $_->[1] } @$slices;
  22         44  
39              
40 11 100 100     194 if ($self->width && $latest > 0) {
    100 66        
41 2         50 my $factor = $self->width / $latest;
42             # rework the epochs so they fit within width
43 2         13 for (@$slices) {
44 4         28 $_->[0] = floor($_->[0] *= $factor);
45 4         11 $_->[1] = floor($_->[1] *= $factor);
46 4 50       12 $_->[1]++ if $_->[1] == $_->[0]; # no invisibles
47             }
48             }
49             elsif ($self->width && $latest == 0) {
50             # all slices are zero-duration points; give them minimum width
51 1         22 $_->[1] = 1 for @$slices;
52             }
53              
54 11         167 my $lines = [ [] ];
55              
56             # Track where each line ends. Initialize line 0 with a negative value
57             # so the first slice always fits. Without this, if space=10 and first
58             # slice starts at 0, the check (0 + 10 <= 0) would fail and create an
59             # unnecessary extra line. The -space value makes (−10 + 10 <= 0) pass.
60 11         147 my $ends = { 0 => -$self->space };
61              
62             # for every slice, try to put it in on the line that has the leftmost end
63 11         71 while ( my $slice = shift @$slices ) {
64              
65             # find which line has the leftmost end that allows placement of our
66             # slice (line must end before slice starts, with required spacing)
67 23         278 my ($line_index) = grep { $ends->{$_} + $self->space <= $slice->[0] }
68 22         44 sort { $ends->{$a} <=> $ends->{$b} } keys %$ends;
  1         3  
69              
70             # if no suitable line was found, create a new one
71 22   66     122 $line_index //= $#$lines + 1;
72              
73             # add the slice to the right line, and update the end point for that
74             # one
75 22         24 push @{ $lines->[$line_index] }, $slice;
  22         40  
76 22         55 $ends->{$line_index} = $slice->[1];
77             }
78             # randomizing the ordering gives a more balanced look
79 11         109 @$lines = shuffle @$lines;
80 11         46 return $lines, $latest;
81             }
82              
83             1;
84              
85             __END__