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. |