| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
package Graph::Timeline; |
|
2
|
|
|
|
|
|
|
|
|
3
|
1
|
|
|
1
|
|
1133
|
use strict; |
|
|
1
|
|
|
|
|
2
|
|
|
|
1
|
|
|
|
|
49
|
|
|
4
|
1
|
|
|
1
|
|
5
|
use warnings; |
|
|
1
|
|
|
|
|
2
|
|
|
|
1
|
|
|
|
|
51
|
|
|
5
|
|
|
|
|
|
|
|
|
6
|
1
|
|
|
1
|
|
880
|
use Date::Calc; |
|
|
1
|
|
|
|
|
36482
|
|
|
|
1
|
|
|
|
|
2313
|
|
|
7
|
|
|
|
|
|
|
|
|
8
|
|
|
|
|
|
|
our $VERSION = '1.5'; |
|
9
|
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
sub new { |
|
11
|
2
|
|
|
2
|
1
|
827
|
my ($class) = @_; |
|
12
|
|
|
|
|
|
|
|
|
13
|
2
|
100
|
|
|
|
20
|
die "Timeline->new() takes no arguments" if scalar(@_) != 1; |
|
14
|
|
|
|
|
|
|
|
|
15
|
1
|
|
|
|
|
2
|
my $self = {}; |
|
16
|
|
|
|
|
|
|
|
|
17
|
1
|
|
|
|
|
3
|
$self->{_pool} = (); |
|
18
|
|
|
|
|
|
|
|
|
19
|
1
|
|
|
|
|
5
|
return bless $self, $class; |
|
20
|
|
|
|
|
|
|
} |
|
21
|
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
sub add_interval { |
|
23
|
15
|
100
|
|
15
|
1
|
5099
|
die "Timeline->add_interval() expected HASH as parameter" unless scalar(@_) % 2 == 1; |
|
24
|
|
|
|
|
|
|
|
|
25
|
14
|
|
|
|
|
50
|
my ( $self, %data ) = @_; |
|
26
|
|
|
|
|
|
|
|
|
27
|
14
|
|
|
|
|
41
|
%data = $self->_lowercase_keys(%data); |
|
28
|
14
|
|
|
|
|
44
|
$self->_required_keys( 'add_interval', \%data, (qw/start end label/) ); |
|
29
|
11
|
|
|
|
|
29
|
$self->_valid_keys( 'add_interval', \%data, (qw/start end label group id url/) ); |
|
30
|
|
|
|
|
|
|
|
|
31
|
7
|
|
|
|
|
9
|
$data{type} = 'interval'; |
|
32
|
|
|
|
|
|
|
|
|
33
|
7
|
|
|
|
|
19
|
$self->_add_to_pool(%data); |
|
34
|
|
|
|
|
|
|
} |
|
35
|
|
|
|
|
|
|
|
|
36
|
|
|
|
|
|
|
sub add_point { |
|
37
|
8
|
100
|
|
8
|
1
|
3575
|
die "Timeline->add_point() expected HASH as parameter" unless scalar(@_) % 2 == 1; |
|
38
|
|
|
|
|
|
|
|
|
39
|
7
|
|
|
|
|
19
|
my ( $self, %data ) = @_; |
|
40
|
|
|
|
|
|
|
|
|
41
|
7
|
|
|
|
|
21
|
%data = $self->_lowercase_keys(%data); |
|
42
|
7
|
|
|
|
|
30
|
$self->_required_keys( 'add_point', \%data, (qw/start label/) ); |
|
43
|
5
|
|
|
|
|
17
|
$self->_valid_keys( 'add_point', \%data, (qw/start label group id/) ); |
|
44
|
|
|
|
|
|
|
|
|
45
|
1
|
|
|
|
|
2
|
$data{type} = 'point'; |
|
46
|
1
|
|
|
|
|
3
|
$data{end} = $data{start}; |
|
47
|
|
|
|
|
|
|
|
|
48
|
1
|
|
|
|
|
6
|
$self->_add_to_pool(%data); |
|
49
|
|
|
|
|
|
|
} |
|
50
|
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
sub window { |
|
52
|
17
|
|
|
17
|
1
|
20226
|
my ( $self, %data ) = @_; |
|
53
|
|
|
|
|
|
|
|
|
54
|
|
|
|
|
|
|
# Default values for our parameters |
|
55
|
|
|
|
|
|
|
|
|
56
|
17
|
|
|
|
|
24
|
$self->{_window_start} = undef; |
|
57
|
17
|
|
|
|
|
22
|
$self->{_window_end} = undef; |
|
58
|
|
|
|
|
|
|
|
|
59
|
17
|
|
|
|
|
18
|
$self->{_window_start_in} = undef; |
|
60
|
17
|
|
|
|
|
18
|
$self->{_window_end_in} = undef; |
|
61
|
|
|
|
|
|
|
|
|
62
|
17
|
|
|
|
|
15
|
$self->{_window_span} = undef; |
|
63
|
|
|
|
|
|
|
|
|
64
|
17
|
|
|
|
|
18
|
$self->{_window_callback} = undef; |
|
65
|
|
|
|
|
|
|
|
|
66
|
17
|
|
|
|
|
47
|
%data = $self->_lowercase_keys(%data); |
|
67
|
17
|
|
|
|
|
54
|
$self->_valid_keys( 'window', \%data, (qw/start end start_in end_in span callback/) ); |
|
68
|
|
|
|
|
|
|
|
|
69
|
|
|
|
|
|
|
# Additional validation |
|
70
|
|
|
|
|
|
|
|
|
71
|
14
|
100
|
|
|
|
32
|
if ( $data{span} ) { |
|
72
|
4
|
100
|
100
|
|
|
29
|
die "Timeline->window() 'span' can only be defined with a 'start' and 'end'" unless $data{start} and $data{end}; |
|
73
|
|
|
|
|
|
|
} |
|
74
|
|
|
|
|
|
|
|
|
75
|
12
|
100
|
|
|
|
24
|
if ( $data{callback} ) { |
|
76
|
2
|
100
|
|
|
|
10
|
die "Timeline->window() 'callback' can only be a CODE reference" unless ref( $data{callback} ) eq 'CODE'; |
|
77
|
|
|
|
|
|
|
} |
|
78
|
|
|
|
|
|
|
|
|
79
|
11
|
|
|
|
|
21
|
foreach my $key ( keys %data ) { |
|
80
|
24
|
|
|
|
|
62
|
$self->{"_window_$key"} = $data{$key}; |
|
81
|
|
|
|
|
|
|
} |
|
82
|
|
|
|
|
|
|
} |
|
83
|
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
sub data { |
|
85
|
13
|
|
|
13
|
1
|
726
|
my ($self) = @_; |
|
86
|
|
|
|
|
|
|
|
|
87
|
13
|
100
|
|
|
|
33
|
die "Timeline->data() takes no arguments" if scalar(@_) != 1; |
|
88
|
|
|
|
|
|
|
|
|
89
|
|
|
|
|
|
|
# Set the start and end, this make things easier |
|
90
|
|
|
|
|
|
|
|
|
91
|
12
|
100
|
|
|
|
28
|
my $start = ( $self->{_window_start} ? $self->{_window_start} : '0000/00/00T00:00:00' ); |
|
92
|
12
|
100
|
|
|
|
22
|
my $end = ( $self->{_window_end} ? $self->{_window_end} : '9999/99/99T23:59:59' ); |
|
93
|
|
|
|
|
|
|
|
|
94
|
12
|
|
|
|
|
13
|
my @results; |
|
95
|
|
|
|
|
|
|
|
|
96
|
12
|
100
|
|
|
|
23
|
if ( $self->{_window_start} ) { |
|
97
|
7
|
|
|
|
|
7
|
my $x; |
|
98
|
7
|
|
|
|
|
14
|
$x->{start} = $self->{_window_start}; |
|
99
|
7
|
|
|
|
|
12
|
$x->{start_start} = $self->{_window_start}; |
|
100
|
7
|
|
|
|
|
10
|
$x->{start_end} = $self->{_window_start}; |
|
101
|
|
|
|
|
|
|
|
|
102
|
7
|
|
|
|
|
10
|
$x->{end} = $self->{_window_start}; |
|
103
|
7
|
|
|
|
|
10
|
$x->{end_start} = $self->{_window_start}; |
|
104
|
7
|
|
|
|
|
10
|
$x->{end_end} = $self->{_window_start}; |
|
105
|
|
|
|
|
|
|
|
|
106
|
7
|
|
|
|
|
7
|
$x->{type} = 'marker'; |
|
107
|
|
|
|
|
|
|
|
|
108
|
7
|
|
|
|
|
10
|
push( @results, $x ); |
|
109
|
|
|
|
|
|
|
} |
|
110
|
|
|
|
|
|
|
|
|
111
|
12
|
100
|
|
|
|
25
|
if ( $self->{_window_end} ) { |
|
112
|
7
|
|
|
|
|
6
|
my $x; |
|
113
|
7
|
|
|
|
|
12
|
$x->{start} = $self->{_window_end}; |
|
114
|
7
|
|
|
|
|
9
|
$x->{start_start} = $self->{_window_end}; |
|
115
|
7
|
|
|
|
|
10
|
$x->{start_end} = $self->{_window_end}; |
|
116
|
|
|
|
|
|
|
|
|
117
|
7
|
|
|
|
|
9
|
$x->{end} = $self->{_window_end}; |
|
118
|
7
|
|
|
|
|
10
|
$x->{end_start} = $self->{_window_end}; |
|
119
|
7
|
|
|
|
|
8
|
$x->{end_end} = $self->{_window_end}; |
|
120
|
|
|
|
|
|
|
|
|
121
|
7
|
|
|
|
|
9
|
$x->{type} = 'marker'; |
|
122
|
|
|
|
|
|
|
|
|
123
|
7
|
|
|
|
|
9
|
push( @results, $x ); |
|
124
|
|
|
|
|
|
|
} |
|
125
|
|
|
|
|
|
|
|
|
126
|
12
|
|
|
|
|
13
|
foreach my $record ( @{ $self->{_pool} } ) { |
|
|
12
|
|
|
|
|
21
|
|
|
127
|
85
|
100
|
|
|
|
249
|
if ( $record->{start} lt $start ) { |
|
|
|
100
|
|
|
|
|
|
|
128
|
15
|
100
|
|
|
|
30
|
if ( $record->{end} lt $start ) { |
|
|
|
100
|
|
|
|
|
|
|
129
|
7
|
|
|
|
|
10
|
next; |
|
130
|
|
|
|
|
|
|
} |
|
131
|
|
|
|
|
|
|
elsif ( $record->{end} lt $end ) { |
|
132
|
3
|
100
|
|
|
|
8
|
next unless $self->{_window_end_in}; |
|
133
|
|
|
|
|
|
|
} |
|
134
|
|
|
|
|
|
|
else { |
|
135
|
5
|
100
|
|
|
|
12
|
next unless $self->{_window_span}; |
|
136
|
|
|
|
|
|
|
} |
|
137
|
|
|
|
|
|
|
} |
|
138
|
|
|
|
|
|
|
elsif ( $record->{start} lt $end ) { |
|
139
|
56
|
100
|
|
|
|
102
|
if ( $record->{end} gt $end ) { |
|
140
|
2
|
100
|
|
|
|
6
|
next unless $self->{_window_start_in}; |
|
141
|
|
|
|
|
|
|
} |
|
142
|
|
|
|
|
|
|
} |
|
143
|
|
|
|
|
|
|
else { |
|
144
|
14
|
|
|
|
|
16
|
next; |
|
145
|
|
|
|
|
|
|
} |
|
146
|
|
|
|
|
|
|
|
|
147
|
57
|
100
|
|
|
|
93
|
if ( $self->{_window_callback} ) { |
|
148
|
7
|
100
|
|
|
|
6
|
next unless &{ $self->{_window_callback} }($record); |
|
|
7
|
|
|
|
|
16
|
|
|
149
|
|
|
|
|
|
|
} |
|
150
|
|
|
|
|
|
|
|
|
151
|
54
|
100
|
|
|
|
104
|
if ( $record->{start} lt $start ) { |
|
152
|
2
|
|
|
|
|
4
|
$record->{start} = $start; |
|
153
|
2
|
|
|
|
|
4
|
$record->{start_start} = $start; |
|
154
|
2
|
|
|
|
|
3
|
$record->{start_end} = $start; |
|
155
|
|
|
|
|
|
|
} |
|
156
|
|
|
|
|
|
|
|
|
157
|
54
|
100
|
|
|
|
88
|
if ( $record->{end} gt $end ) { |
|
158
|
2
|
|
|
|
|
5
|
$record->{end} = $end; |
|
159
|
2
|
|
|
|
|
3
|
$record->{end_start} = $end; |
|
160
|
2
|
|
|
|
|
3
|
$record->{end_end} = $end; |
|
161
|
|
|
|
|
|
|
} |
|
162
|
|
|
|
|
|
|
|
|
163
|
54
|
|
|
|
|
67
|
push( @results, $record ); |
|
164
|
|
|
|
|
|
|
} |
|
165
|
|
|
|
|
|
|
|
|
166
|
12
|
|
|
|
|
47
|
return @results; |
|
167
|
|
|
|
|
|
|
} |
|
168
|
|
|
|
|
|
|
|
|
169
|
|
|
|
|
|
|
sub _add_to_pool { |
|
170
|
8
|
|
|
8
|
|
21
|
my ( $self, %data ) = @_; |
|
171
|
|
|
|
|
|
|
|
|
172
|
8
|
|
|
|
|
23
|
my @newpool; |
|
173
|
8
|
|
|
|
|
10
|
my $todo = 1; |
|
174
|
|
|
|
|
|
|
|
|
175
|
8
|
|
|
|
|
25
|
%data = $self->_set_range( 'start', %data ); |
|
176
|
8
|
|
|
|
|
28
|
%data = $self->_set_range( 'end', %data ); |
|
177
|
|
|
|
|
|
|
|
|
178
|
8
|
50
|
|
|
|
33
|
$data{group} = '--unknown--' unless $data{group}; |
|
179
|
|
|
|
|
|
|
|
|
180
|
8
|
|
|
|
|
8
|
foreach my $record ( @{ $self->{_pool} } ) { |
|
|
8
|
|
|
|
|
18
|
|
|
181
|
28
|
100
|
100
|
|
|
83
|
if ( $todo and $record->{start} gt $data{start} ) { |
|
182
|
5
|
|
|
|
|
6
|
push @newpool, \%data; |
|
183
|
5
|
|
|
|
|
6
|
$todo = undef; |
|
184
|
|
|
|
|
|
|
} |
|
185
|
28
|
|
|
|
|
40
|
push @newpool, $record; |
|
186
|
|
|
|
|
|
|
} |
|
187
|
|
|
|
|
|
|
|
|
188
|
8
|
100
|
|
|
|
20
|
push @newpool, \%data if $todo; |
|
189
|
|
|
|
|
|
|
|
|
190
|
8
|
|
|
|
|
30
|
$self->{_pool} = \@newpool; |
|
191
|
|
|
|
|
|
|
} |
|
192
|
|
|
|
|
|
|
|
|
193
|
|
|
|
|
|
|
sub _valid_keys { |
|
194
|
33
|
|
|
33
|
|
73
|
my ( $self, $caller, $data, @keys ) = @_; |
|
195
|
|
|
|
|
|
|
|
|
196
|
33
|
|
|
|
|
36
|
my @testkeys = keys %{$data}; |
|
|
33
|
|
|
|
|
74
|
|
|
197
|
33
|
|
|
|
|
47
|
my %validkeys = map { $_ => $_ } @keys; |
|
|
188
|
|
|
|
|
321
|
|
|
198
|
|
|
|
|
|
|
|
|
199
|
33
|
|
|
|
|
58
|
foreach my $key (@testkeys) { |
|
200
|
90
|
100
|
|
|
|
230
|
die "Timeline->$caller() invalid key '$key' passed as data" unless $validkeys{$key}; |
|
201
|
|
|
|
|
|
|
} |
|
202
|
|
|
|
|
|
|
|
|
203
|
30
|
|
|
|
|
45
|
foreach my $key ( (qw/start end/) ) { |
|
204
|
55
|
100
|
|
|
|
144
|
if ( $data->{$key} ) { |
|
205
|
44
|
100
|
|
|
|
95
|
$data->{$key} = $self->_today() if $data->{$key} eq 'present'; |
|
206
|
44
|
100
|
|
|
|
78
|
die "Timeline->$caller() invalid date for '$key'" unless $self->_date_valid( $data->{$key} ); |
|
207
|
|
|
|
|
|
|
} |
|
208
|
|
|
|
|
|
|
} |
|
209
|
|
|
|
|
|
|
|
|
210
|
23
|
100
|
100
|
|
|
120
|
if ( $data->{start} and $data->{end} ) { |
|
211
|
14
|
100
|
|
|
|
67
|
die "Timeline->$caller() 'start' and 'end' are in the wrong order" if $data->{start} gt $data->{end}; |
|
212
|
|
|
|
|
|
|
} |
|
213
|
|
|
|
|
|
|
} |
|
214
|
|
|
|
|
|
|
|
|
215
|
|
|
|
|
|
|
sub _date_valid { |
|
216
|
44
|
|
|
44
|
|
61
|
my ( $self, $date ) = @_; |
|
217
|
|
|
|
|
|
|
|
|
218
|
44
|
|
|
|
|
92
|
my ( $date_part, $time_part ) = split( 'T', $date ); |
|
219
|
44
|
|
|
|
|
142
|
my ( $year, $month, $day ) = split( '[\/-]', $date_part ); |
|
220
|
|
|
|
|
|
|
|
|
221
|
|
|
|
|
|
|
## Check the date first |
|
222
|
|
|
|
|
|
|
|
|
223
|
44
|
100
|
|
|
|
83
|
$month = '01' unless $month; |
|
224
|
44
|
100
|
|
|
|
59
|
$day = '01' unless $day; |
|
225
|
|
|
|
|
|
|
|
|
226
|
44
|
100
|
|
|
|
209
|
return unless $year =~ m/^\d+$/; |
|
227
|
39
|
100
|
|
|
|
101
|
return unless $month =~ m/^\d+$/; |
|
228
|
38
|
100
|
|
|
|
91
|
return unless $day =~ m/^\d+$/; |
|
229
|
|
|
|
|
|
|
|
|
230
|
37
|
|
|
|
|
32
|
my $valid; |
|
231
|
37
|
|
|
|
|
37
|
eval { $valid = Date::Calc::check_date( $year, $month, $day ); }; |
|
|
37
|
|
|
|
|
86
|
|
|
232
|
|
|
|
|
|
|
|
|
233
|
37
|
50
|
|
|
|
495
|
return unless $valid; |
|
234
|
|
|
|
|
|
|
|
|
235
|
|
|
|
|
|
|
## Check the optional time part |
|
236
|
|
|
|
|
|
|
|
|
237
|
37
|
50
|
|
|
|
57
|
if ($time_part) { |
|
238
|
0
|
|
|
|
|
0
|
my ( $hours, $minutes, $seconds ) = split( ':', $time_part ); |
|
239
|
|
|
|
|
|
|
|
|
240
|
0
|
0
|
0
|
|
|
0
|
return unless 0 <= $hours and $hours <= 23; |
|
241
|
0
|
0
|
0
|
|
|
0
|
return unless 0 <= $minutes and $minutes <= 59; |
|
242
|
0
|
0
|
0
|
|
|
0
|
return unless 0 <= $seconds and $seconds <= 59; |
|
243
|
|
|
|
|
|
|
} |
|
244
|
|
|
|
|
|
|
|
|
245
|
37
|
|
|
|
|
114
|
return 1; |
|
246
|
|
|
|
|
|
|
} |
|
247
|
|
|
|
|
|
|
|
|
248
|
|
|
|
|
|
|
sub _required_keys { |
|
249
|
21
|
|
|
21
|
|
47
|
my ( $self, $caller, $data, @keys ) = @_; |
|
250
|
|
|
|
|
|
|
|
|
251
|
21
|
|
|
|
|
32
|
foreach my $key (@keys) { |
|
252
|
52
|
100
|
|
|
|
165
|
die "Timeline->$caller() missing key '$key'" unless $data->{$key}; |
|
253
|
|
|
|
|
|
|
} |
|
254
|
|
|
|
|
|
|
} |
|
255
|
|
|
|
|
|
|
|
|
256
|
|
|
|
|
|
|
sub _lowercase_keys { |
|
257
|
38
|
|
|
38
|
|
83
|
my ( $self, %data ) = @_; |
|
258
|
|
|
|
|
|
|
|
|
259
|
38
|
|
|
|
|
64
|
my %newdata = map { lc($_) => $data{$_} } keys %data; |
|
|
106
|
|
|
|
|
261
|
|
|
260
|
|
|
|
|
|
|
|
|
261
|
38
|
|
|
|
|
195
|
return %newdata; |
|
262
|
|
|
|
|
|
|
} |
|
263
|
|
|
|
|
|
|
|
|
264
|
|
|
|
|
|
|
sub _today { |
|
265
|
1
|
|
|
1
|
|
237
|
my ( $year, $month, $day ) = ( localtime() )[ 5, 4, 3 ]; |
|
266
|
|
|
|
|
|
|
|
|
267
|
1
|
|
|
|
|
3
|
$year += 1900; |
|
268
|
1
|
|
|
|
|
2
|
$month += 1; |
|
269
|
|
|
|
|
|
|
|
|
270
|
1
|
|
|
|
|
7
|
return sprintf( "%4d/%02d/%02d", $year, $month, $day ); |
|
271
|
|
|
|
|
|
|
} |
|
272
|
|
|
|
|
|
|
|
|
273
|
|
|
|
|
|
|
sub _set_range { |
|
274
|
16
|
|
|
16
|
|
37
|
my ( $self, $label, %record ) = @_; |
|
275
|
|
|
|
|
|
|
|
|
276
|
16
|
|
|
|
|
32
|
my ( $date_part, $time_part ) = split( 'T', $record{$label} ); |
|
277
|
16
|
|
|
|
|
50
|
my ( $year, $month, $day ) = split( '[\/-]', $date_part ); |
|
278
|
|
|
|
|
|
|
|
|
279
|
16
|
100
|
|
|
|
32
|
if ($day) { |
|
|
|
100
|
|
|
|
|
|
|
280
|
14
|
|
|
|
|
23
|
$record{"${label}_start"} = $date_part; |
|
281
|
14
|
|
|
|
|
25
|
$record{"${label}_end"} = $date_part; |
|
282
|
|
|
|
|
|
|
} |
|
283
|
|
|
|
|
|
|
elsif ($month) { |
|
284
|
1
|
|
|
|
|
4
|
$record{"${label}_start"} = "$year/$month/01"; |
|
285
|
1
|
|
|
|
|
5
|
$record{"${label}_end"} = "$year/$month/" . Date::Calc::Days_in_Month( $year, $month ); |
|
286
|
|
|
|
|
|
|
} |
|
287
|
|
|
|
|
|
|
else { |
|
288
|
1
|
|
|
|
|
3
|
$record{"${label}_start"} = "$year/01/01"; |
|
289
|
1
|
|
|
|
|
2
|
$record{"${label}_end"} = "$year/12/31"; |
|
290
|
|
|
|
|
|
|
} |
|
291
|
|
|
|
|
|
|
|
|
292
|
16
|
50
|
|
|
|
39
|
if($time_part) { |
|
293
|
0
|
|
|
|
|
0
|
$record{"${label}_start"} .= "T" . $time_part; |
|
294
|
0
|
|
|
|
|
0
|
$record{"${label}_end"} .= "T" . $time_part; |
|
295
|
|
|
|
|
|
|
} |
|
296
|
|
|
|
|
|
|
else { |
|
297
|
16
|
|
|
|
|
21
|
$record{"${label}_start"} .= "T00:00:00"; |
|
298
|
16
|
|
|
|
|
26
|
$record{"${label}_end"} .= "T23:59:59"; |
|
299
|
|
|
|
|
|
|
} |
|
300
|
|
|
|
|
|
|
|
|
301
|
16
|
|
|
|
|
110
|
return %record; |
|
302
|
|
|
|
|
|
|
} |
|
303
|
|
|
|
|
|
|
|
|
304
|
|
|
|
|
|
|
1; |
|
305
|
|
|
|
|
|
|
|
|
306
|
|
|
|
|
|
|
=head1 NAME |
|
307
|
|
|
|
|
|
|
|
|
308
|
|
|
|
|
|
|
Graph::Timeline - Render timeline data |
|
309
|
|
|
|
|
|
|
|
|
310
|
|
|
|
|
|
|
=head1 VERSION |
|
311
|
|
|
|
|
|
|
|
|
312
|
|
|
|
|
|
|
This document refers to verion 1.5 of Graph::Timeline, released September 29, 2009 |
|
313
|
|
|
|
|
|
|
|
|
314
|
|
|
|
|
|
|
=head1 SYNOPSIS |
|
315
|
|
|
|
|
|
|
|
|
316
|
|
|
|
|
|
|
This class takes a list of events and processes them so that they can be rendered in |
|
317
|
|
|
|
|
|
|
various graphical formats by subclasses of this class. |
|
318
|
|
|
|
|
|
|
|
|
319
|
|
|
|
|
|
|
=head1 DESCRIPTION |
|
320
|
|
|
|
|
|
|
|
|
321
|
|
|
|
|
|
|
=head2 Overview |
|
322
|
|
|
|
|
|
|
|
|
323
|
|
|
|
|
|
|
The purpose of this class is to organise the data that will be used to render a timeline. Events fall into two types. |
|
324
|
|
|
|
|
|
|
Intervals, which has a start and an end. For example Albert Einstein was born on 1879/03/14 and died on 1955/04/18, this would be |
|
325
|
|
|
|
|
|
|
stored as an interval. His works were publicly burned by the Nazi's on 1933/05/10 for being 'of un-German spirit', I guess |
|
326
|
|
|
|
|
|
|
being Jewish didn't help either. So this event would be marked as a point. |
|
327
|
|
|
|
|
|
|
|
|
328
|
|
|
|
|
|
|
You feed events into the class using add_interval( ) and add_point( ), then use window( ) to select which events you want to |
|
329
|
|
|
|
|
|
|
render and then call data( ) to get the relevant events. This last bit will be done in the subclass. |
|
330
|
|
|
|
|
|
|
|
|
331
|
|
|
|
|
|
|
=head2 Constructors and initialisation |
|
332
|
|
|
|
|
|
|
|
|
333
|
|
|
|
|
|
|
=over 4 |
|
334
|
|
|
|
|
|
|
|
|
335
|
|
|
|
|
|
|
=item new( ) |
|
336
|
|
|
|
|
|
|
|
|
337
|
|
|
|
|
|
|
The constructor takes no arguments and just initialises a few basic variables. |
|
338
|
|
|
|
|
|
|
|
|
339
|
|
|
|
|
|
|
=back |
|
340
|
|
|
|
|
|
|
|
|
341
|
|
|
|
|
|
|
=head2 Public methods |
|
342
|
|
|
|
|
|
|
|
|
343
|
|
|
|
|
|
|
=over 4 |
|
344
|
|
|
|
|
|
|
|
|
345
|
|
|
|
|
|
|
=item add_interval( HASH ) |
|
346
|
|
|
|
|
|
|
|
|
347
|
|
|
|
|
|
|
Inserts an event that has a start and an end point into the list at the corrct position. The |
|
348
|
|
|
|
|
|
|
hash contains the following keys, some of which are required. |
|
349
|
|
|
|
|
|
|
|
|
350
|
|
|
|
|
|
|
=over 4 |
|
351
|
|
|
|
|
|
|
|
|
352
|
|
|
|
|
|
|
=item start [ REQUIRED ] |
|
353
|
|
|
|
|
|
|
|
|
354
|
|
|
|
|
|
|
The start date for the interval in the for 'YYYY/MM/DD' or the word 'present' which will be converted into todays date. |
|
355
|
|
|
|
|
|
|
Dates in the format YYYY will be taken to span YYYY/01/01 until YYYY/12/31 and dates of the format YYYY/MM will span |
|
356
|
|
|
|
|
|
|
YYYY/MM/01 until YYYY/MM/xx where xx is the last day of MM in YYYY. |
|
357
|
|
|
|
|
|
|
|
|
358
|
|
|
|
|
|
|
=item end [ REQUIRED ] |
|
359
|
|
|
|
|
|
|
|
|
360
|
|
|
|
|
|
|
The start end for the interval in the for 'YYYY/MM/DD' or the word 'present' which will be converted into todays date. |
|
361
|
|
|
|
|
|
|
Dates in the format YYYY will be taken to span YYYY/01/01 until YYYY/12/31 and dates of the format YYYY/MM will span |
|
362
|
|
|
|
|
|
|
YYYY/MM/01 until YYYY/MM/xx where xx is the last day of MM in YYYY. |
|
363
|
|
|
|
|
|
|
|
|
364
|
|
|
|
|
|
|
=item label [ REQUIRED ] |
|
365
|
|
|
|
|
|
|
|
|
366
|
|
|
|
|
|
|
The text string that will be displayed when the event is rendered |
|
367
|
|
|
|
|
|
|
|
|
368
|
|
|
|
|
|
|
=item id [ OPTIONAL ] |
|
369
|
|
|
|
|
|
|
|
|
370
|
|
|
|
|
|
|
A unique id for the render, Graph::Timeline does not validate this field for uniqueness |
|
371
|
|
|
|
|
|
|
|
|
372
|
|
|
|
|
|
|
=item group [ OPTIONAL ] |
|
373
|
|
|
|
|
|
|
|
|
374
|
|
|
|
|
|
|
A string is used to group related events together, Graph::Timeline does not validate this field |
|
375
|
|
|
|
|
|
|
|
|
376
|
|
|
|
|
|
|
=back |
|
377
|
|
|
|
|
|
|
|
|
378
|
|
|
|
|
|
|
=item add_point( HASH ) |
|
379
|
|
|
|
|
|
|
|
|
380
|
|
|
|
|
|
|
The same as add_interval( ) except that the event occurs on just one day and therefore does not require an end date. Interval and point events are rendered differently. |
|
381
|
|
|
|
|
|
|
|
|
382
|
|
|
|
|
|
|
=item window( HASH ) |
|
383
|
|
|
|
|
|
|
|
|
384
|
|
|
|
|
|
|
Set up the data to be selected from the event pool. To reset the defaults just call without any parameters. |
|
385
|
|
|
|
|
|
|
|
|
386
|
|
|
|
|
|
|
=over 4 |
|
387
|
|
|
|
|
|
|
|
|
388
|
|
|
|
|
|
|
=item start |
|
389
|
|
|
|
|
|
|
|
|
390
|
|
|
|
|
|
|
Select only record that start on or after this date. Takes a valid date or the word 'present' which is |
|
391
|
|
|
|
|
|
|
translated to the current date. |
|
392
|
|
|
|
|
|
|
|
|
393
|
|
|
|
|
|
|
=item end |
|
394
|
|
|
|
|
|
|
|
|
395
|
|
|
|
|
|
|
Select only record that end on or before this date. Takes a valid date or the word 'present' which is |
|
396
|
|
|
|
|
|
|
translated to the current date. |
|
397
|
|
|
|
|
|
|
|
|
398
|
|
|
|
|
|
|
=item start_in |
|
399
|
|
|
|
|
|
|
|
|
400
|
|
|
|
|
|
|
If end is set then include records that start before the end date but ends after the end date. |
|
401
|
|
|
|
|
|
|
|
|
402
|
|
|
|
|
|
|
=item end_in |
|
403
|
|
|
|
|
|
|
|
|
404
|
|
|
|
|
|
|
If start is set then include records that start before the start date but end after the start date. |
|
405
|
|
|
|
|
|
|
|
|
406
|
|
|
|
|
|
|
=item span |
|
407
|
|
|
|
|
|
|
|
|
408
|
|
|
|
|
|
|
If start and end are both set then additionally report events that start before the start date and end |
|
409
|
|
|
|
|
|
|
after the end date. |
|
410
|
|
|
|
|
|
|
|
|
411
|
|
|
|
|
|
|
=item callback |
|
412
|
|
|
|
|
|
|
|
|
413
|
|
|
|
|
|
|
A code reference to provide additionaly custom filtering. The callback will be passed a hash reference with the |
|
414
|
|
|
|
|
|
|
following keys: start, end, label, group, id and type ('interval' and 'point'). |
|
415
|
|
|
|
|
|
|
|
|
416
|
|
|
|
|
|
|
=back |
|
417
|
|
|
|
|
|
|
|
|
418
|
|
|
|
|
|
|
=item data( ) |
|
419
|
|
|
|
|
|
|
|
|
420
|
|
|
|
|
|
|
This returns a list of the events from the pool that got passed the parameters from the window( ) method. |
|
421
|
|
|
|
|
|
|
|
|
422
|
|
|
|
|
|
|
=back |
|
423
|
|
|
|
|
|
|
|
|
424
|
|
|
|
|
|
|
=head2 Private methods |
|
425
|
|
|
|
|
|
|
|
|
426
|
|
|
|
|
|
|
=over 4 |
|
427
|
|
|
|
|
|
|
|
|
428
|
|
|
|
|
|
|
=item _add_to_pool |
|
429
|
|
|
|
|
|
|
|
|
430
|
|
|
|
|
|
|
Used to add the event into the pool which is sorted by start date |
|
431
|
|
|
|
|
|
|
|
|
432
|
|
|
|
|
|
|
=item _valid_keys |
|
433
|
|
|
|
|
|
|
|
|
434
|
|
|
|
|
|
|
Validate that the keys supplied in the hash are valid |
|
435
|
|
|
|
|
|
|
|
|
436
|
|
|
|
|
|
|
=item _date_valid |
|
437
|
|
|
|
|
|
|
|
|
438
|
|
|
|
|
|
|
Validate a date |
|
439
|
|
|
|
|
|
|
|
|
440
|
|
|
|
|
|
|
=item _required_keys |
|
441
|
|
|
|
|
|
|
|
|
442
|
|
|
|
|
|
|
Check that the required keys have been supplied in the hash |
|
443
|
|
|
|
|
|
|
|
|
444
|
|
|
|
|
|
|
=item _lowercase_keys |
|
445
|
|
|
|
|
|
|
|
|
446
|
|
|
|
|
|
|
Lowercase the keys in a hash |
|
447
|
|
|
|
|
|
|
|
|
448
|
|
|
|
|
|
|
=item _today |
|
449
|
|
|
|
|
|
|
|
|
450
|
|
|
|
|
|
|
Return todays date for use with the 'present' word |
|
451
|
|
|
|
|
|
|
|
|
452
|
|
|
|
|
|
|
=item _set_range |
|
453
|
|
|
|
|
|
|
|
|
454
|
|
|
|
|
|
|
Set the xxx_start and xxx_end values from the xxx data |
|
455
|
|
|
|
|
|
|
|
|
456
|
|
|
|
|
|
|
=back |
|
457
|
|
|
|
|
|
|
|
|
458
|
|
|
|
|
|
|
=head1 ENVIRONMENT |
|
459
|
|
|
|
|
|
|
|
|
460
|
|
|
|
|
|
|
None |
|
461
|
|
|
|
|
|
|
|
|
462
|
|
|
|
|
|
|
=head1 DIAGNOSTICS |
|
463
|
|
|
|
|
|
|
|
|
464
|
|
|
|
|
|
|
=over 4 |
|
465
|
|
|
|
|
|
|
|
|
466
|
|
|
|
|
|
|
=item Timeline->new() takes no arguments |
|
467
|
|
|
|
|
|
|
|
|
468
|
|
|
|
|
|
|
When the constructor is initialised it requires no arguments. This message is given if |
|
469
|
|
|
|
|
|
|
some arguments were supplied. |
|
470
|
|
|
|
|
|
|
|
|
471
|
|
|
|
|
|
|
=item Timeline->add_interval() expected HASH as parameter |
|
472
|
|
|
|
|
|
|
|
|
473
|
|
|
|
|
|
|
The parameter is a hash describing an event |
|
474
|
|
|
|
|
|
|
|
|
475
|
|
|
|
|
|
|
=item Timeline->add_point() expected HASH as parameter |
|
476
|
|
|
|
|
|
|
|
|
477
|
|
|
|
|
|
|
The parameter is a hash describing an event |
|
478
|
|
|
|
|
|
|
|
|
479
|
|
|
|
|
|
|
=item Timeline->window() 'span' can only be defined with a 'start' and 'end' |
|
480
|
|
|
|
|
|
|
|
|
481
|
|
|
|
|
|
|
To define 'span' then you must also define 'start' and 'end' |
|
482
|
|
|
|
|
|
|
|
|
483
|
|
|
|
|
|
|
=item Timeline->window() 'callback' can only be a CODE reference |
|
484
|
|
|
|
|
|
|
|
|
485
|
|
|
|
|
|
|
You must pass a code reference for the callback |
|
486
|
|
|
|
|
|
|
|
|
487
|
|
|
|
|
|
|
=item Timeline->data() takes no arguments |
|
488
|
|
|
|
|
|
|
|
|
489
|
|
|
|
|
|
|
When the method is called it requires no arguments. This message is given if |
|
490
|
|
|
|
|
|
|
some arguments were supplied. |
|
491
|
|
|
|
|
|
|
|
|
492
|
|
|
|
|
|
|
=item Timeline->add_interval() invalid key '...' passed as data |
|
493
|
|
|
|
|
|
|
|
|
494
|
|
|
|
|
|
|
The only valid keys are 'start', 'end', 'label', 'group' and 'id'. Something else was supplied. |
|
495
|
|
|
|
|
|
|
|
|
496
|
|
|
|
|
|
|
=item Timeline->add_interval() invalid date for '...' |
|
497
|
|
|
|
|
|
|
|
|
498
|
|
|
|
|
|
|
The date supplied for '...' is invalid |
|
499
|
|
|
|
|
|
|
|
|
500
|
|
|
|
|
|
|
=item Timeline->add_interval() 'start' and 'end' are in the wrong order |
|
501
|
|
|
|
|
|
|
|
|
502
|
|
|
|
|
|
|
The values for 'start' and 'end' are in the wrong order |
|
503
|
|
|
|
|
|
|
|
|
504
|
|
|
|
|
|
|
=item Timeline->add_interval() missing key '...' |
|
505
|
|
|
|
|
|
|
|
|
506
|
|
|
|
|
|
|
A required key was not supplied. Required keys are 'start', 'end' and 'label' |
|
507
|
|
|
|
|
|
|
|
|
508
|
|
|
|
|
|
|
=item Timeline->add_point() invalid key '...' passed as data |
|
509
|
|
|
|
|
|
|
|
|
510
|
|
|
|
|
|
|
The only valid keys are 'start', 'label', 'group' and 'id'. Something else was supplied. |
|
511
|
|
|
|
|
|
|
|
|
512
|
|
|
|
|
|
|
=item Timeline->add_point() invalid date for '...' |
|
513
|
|
|
|
|
|
|
|
|
514
|
|
|
|
|
|
|
The date supplied for '...' is invalid |
|
515
|
|
|
|
|
|
|
|
|
516
|
|
|
|
|
|
|
=item Timeline->add_point() missing key '...' |
|
517
|
|
|
|
|
|
|
|
|
518
|
|
|
|
|
|
|
A required key was not supplied. Required keys are 'start' and 'label' |
|
519
|
|
|
|
|
|
|
|
|
520
|
|
|
|
|
|
|
=item Timeline->window() invalid key '...' passed as data |
|
521
|
|
|
|
|
|
|
|
|
522
|
|
|
|
|
|
|
The only valid keys are 'start', 'end', 'start_in', 'end_in', 'span' and 'callback'. Something else was supplied. |
|
523
|
|
|
|
|
|
|
|
|
524
|
|
|
|
|
|
|
=item Timeline->window() invalid date for '...' |
|
525
|
|
|
|
|
|
|
|
|
526
|
|
|
|
|
|
|
The date supplied for '...' is invalid |
|
527
|
|
|
|
|
|
|
|
|
528
|
|
|
|
|
|
|
=item Timeline->window() 'start' and 'end' are in the wrong order |
|
529
|
|
|
|
|
|
|
|
|
530
|
|
|
|
|
|
|
The values for 'start' and 'end' are in the wrong order |
|
531
|
|
|
|
|
|
|
|
|
532
|
|
|
|
|
|
|
=back |
|
533
|
|
|
|
|
|
|
|
|
534
|
|
|
|
|
|
|
=head1 BUGS |
|
535
|
|
|
|
|
|
|
|
|
536
|
|
|
|
|
|
|
None |
|
537
|
|
|
|
|
|
|
|
|
538
|
|
|
|
|
|
|
=head1 FILES |
|
539
|
|
|
|
|
|
|
|
|
540
|
|
|
|
|
|
|
See the Timeline.t file in the test directory |
|
541
|
|
|
|
|
|
|
|
|
542
|
|
|
|
|
|
|
=head1 SEE ALSO |
|
543
|
|
|
|
|
|
|
|
|
544
|
|
|
|
|
|
|
Graph::Timeline::GD - Use GD to render the timeline |
|
545
|
|
|
|
|
|
|
|
|
546
|
|
|
|
|
|
|
=head1 AUTHORS |
|
547
|
|
|
|
|
|
|
|
|
548
|
|
|
|
|
|
|
Peter Hickman (peterhi@ntlworld.com) |
|
549
|
|
|
|
|
|
|
|
|
550
|
|
|
|
|
|
|
=head1 COPYRIGHT |
|
551
|
|
|
|
|
|
|
|
|
552
|
|
|
|
|
|
|
Copyright (c) 2003, Peter Hickman. All rights reserved. |
|
553
|
|
|
|
|
|
|
|
|
554
|
|
|
|
|
|
|
This module is free software. It may be used, redistributed and/or |
|
555
|
|
|
|
|
|
|
modified under the same terms as Perl itself. |