@@ -24,17 +24,6 @@ const sourceColors = {
24
24
Boston : '#ef4444' ,
25
25
} ;
26
26
27
- // Define your margins
28
- const margin = { top : 40 , right : 10 , bottom : 10 , left : 10 } ;
29
-
30
- // Define the size of your chart
31
- const width = 1200 ; // You can adjust this as needed
32
- const height = 600 ; // You can adjust this as needed
33
- // maxDate should be X months from today:
34
- const maxDate = new Date ( new Date ( ) . setMonth ( new Date ( ) . getMonth ( ) + 6 ) )
35
- . toISOString ( )
36
- . split ( 'T' ) [ 0 ] ;
37
-
38
27
function getBarText ( item : BaseDocument | EventDocument ) {
39
28
// get item title. if > 40 chars, truncate and add ellipsis:
40
29
const myTitle =
@@ -44,8 +33,9 @@ function getBarText(item: BaseDocument | EventDocument) {
44
33
return `${ myTitle } - ${ item . source } ` ;
45
34
}
46
35
47
- function getDomainMin ( items : ( BaseDocument | EventDocument ) [ ] ) {
48
- const min = Math . min (
36
+ function getDomainMin ( items : EventDocument [ ] ) {
37
+ if ( ! ( items ?. length > 0 ) ) return new Date ( ) . getTime ( ) ;
38
+ const min = Math . max (
49
39
...items
50
40
. filter ( ( item ) => item . date )
51
41
. map ( ( item ) => new Date ( item . date || '' ) . getTime ( ) )
@@ -54,31 +44,41 @@ function getDomainMin(items: (BaseDocument | EventDocument)[]) {
54
44
}
55
45
56
46
function getDomainMax ( items : ( BaseDocument | EventDocument ) [ ] ) {
47
+ const maxDate = new Date ( new Date ( ) . setMonth ( new Date ( ) . getMonth ( ) + 6 ) )
48
+ . toISOString ( )
49
+ . split ( 'T' ) [ 0 ] ;
50
+ if ( ! ( items ?. length > 0 ) ) return new Date ( ) . getTime ( ) ;
57
51
const max = Math . max (
58
52
...items
59
- . filter ( ( item ) : item is EventDocument => 'endDate' in item && item . endDate !== undefined )
53
+ . filter (
54
+ ( item ) : item is EventDocument =>
55
+ 'endDate' in item && item . endDate !== undefined
56
+ )
60
57
. map ( ( item ) => {
61
- // if item.endDate is greater than 5 years into future, don't use it:
62
- const endDate = new Date ( item . endDate || '' ) . getTime ( ) ;
63
- return endDate >
64
- new Date (
65
- new Date ( ) . setFullYear ( new Date ( ) . getFullYear ( ) + 5 )
66
- ) . getTime ( )
67
- ? new Date ( maxDate ) . getTime ( )
68
- : endDate ;
58
+ // if item.endDate is greater than 3 years into future, don't use it:
59
+ try {
60
+ const endDate = new Date ( item . endDate || '' ) . getTime ( ) ;
61
+ return endDate >
62
+ new Date (
63
+ new Date ( ) . setFullYear ( new Date ( ) . getFullYear ( ) + 20 )
64
+ ) . getTime ( )
65
+ ? new Date ( maxDate ) . getTime ( )
66
+ : endDate ;
67
+ } catch ( e ) {
68
+ return 0 ;
69
+ }
69
70
} )
70
71
) ;
71
72
return max ;
72
73
}
73
74
74
- interface TimelineProps {
75
- items : ( BaseDocument | EventDocument ) [ ] ;
76
- }
77
-
78
- export function Timeline ( { items } : TimelineProps ) {
79
- const dict = getDictionary ( ) ;
80
- // create a new array of items, sorted by location:
81
- const sortedItems = items . sort ( ( a , b ) => {
75
+ /**
76
+ * Sort items by location and sourceId
77
+ * @param items Array of items to sort
78
+ * @returns Sorted array of items
79
+ */
80
+ function getSortedItems ( items : ( BaseDocument | EventDocument ) [ ] ) {
81
+ return [ ...items ] . sort ( ( a , b ) => {
82
82
if ( a . sourceId && b . sourceId ) {
83
83
const locationA = a . sourceId && sources [ a . sourceId ] ?. location ;
84
84
const locationB = b . sourceId && sources [ b . sourceId ] ?. location ;
@@ -94,35 +94,44 @@ export function Timeline({ items }: TimelineProps) {
94
94
}
95
95
return 0 ;
96
96
} ) ;
97
+ }
97
98
98
- // for each item, if endDate > maxTime, set endDate to maxTime
99
- const maxTime = getDomainMax ( sortedItems ) ;
100
- const maxDate = format ( new Date ( maxTime ) , 'yyyy-MM-dd' ) ;
99
+ function getMinTimeWithinDomain ( item : EventDocument , minTime : number ) {
100
+ if ( item . date ) return new Date ( item . date ) . getTime ( ) ;
101
+ return minTime ;
102
+ }
103
+
104
+ function getMaxTimeWithinDomain ( item : EventDocument , maxTime : number ) {
105
+ if ( item . endDate && new Date ( item . endDate ) . getTime ( ) < maxTime ) {
106
+ return new Date ( item . endDate ) . getTime ( ) ;
107
+ }
108
+ return maxTime ;
109
+ }
110
+
111
+ const chartMargin = { top : 40 , right : 10 , bottom : 10 , left : 10 } ;
112
+ const chartWidth = 1200 ;
113
+ const chartHeight = 600 ;
114
+
115
+ interface TimelineProps {
116
+ items : ( BaseDocument | EventDocument ) [ ] ;
117
+ }
118
+
119
+ export function Timeline ( { items } : TimelineProps ) {
120
+ const dict = getDictionary ( ) ;
121
+ const sortedItems = getSortedItems ( items ) ;
101
122
const minTime = getDomainMin ( sortedItems ) ;
102
- const minDate = format ( new Date ( minTime ) , 'yyyy-MM-dd' ) ;
103
- sortedItems . forEach ( ( item : EventDocument ) => {
104
- // if item.endDate is greater than 5 years into future, don't use it:
105
- if ( item . endDate ) {
106
- const endDate = new Date ( item . endDate ) . getTime ( ) ;
107
- if (
108
- endDate >
109
- new Date ( new Date ( ) . setFullYear ( new Date ( ) . getFullYear ( ) + 5 ) ) . getTime ( )
110
- ) {
111
- item . endDate = format ( new Date ( maxTime ) , 'yyyy-MM-dd' ) ;
112
- }
113
- }
114
- } ) ;
123
+ const maxTime = getDomainMax ( sortedItems ) ;
115
124
116
125
const timeScale = scaleLinear ( {
117
- domain : [ getDomainMin ( sortedItems ) , getDomainMax ( sortedItems ) ] ,
118
- range : [ margin . left , width - margin . right ] , // Now for horizontal
126
+ domain : [ minTime , maxTime ] ,
127
+ range : [ chartMargin . left , chartWidth - chartMargin . right ] , // Now for horizontal
119
128
} ) ;
120
129
121
130
const itemScale = scaleBand < string > ( {
122
131
domain : sortedItems
123
132
. filter ( ( item ) => item . title )
124
133
. map ( ( item ) => item . title ) as string [ ] ,
125
- range : [ height - margin . bottom , margin . top ] ,
134
+ range : [ chartHeight - chartMargin . bottom , chartMargin . top ] ,
126
135
padding : 0.1 ,
127
136
} ) ;
128
137
@@ -141,12 +150,12 @@ export function Timeline({ items }: TimelineProps) {
141
150
return (
142
151
< >
143
152
< div className = "w-full overflow-x-auto" >
144
- < svg ref = { svgRef } width = { width } height = { height } >
153
+ < svg ref = { svgRef } width = { chartWidth } height = { chartHeight } >
145
154
< Group >
146
155
{ sortedItems . map ( ( item : EventDocument , i : Key ) => {
147
156
// Swap the usage of scales for x and y
148
- const startX = timeScale ( new Date ( item . date || minDate ) . getTime ( ) ) ;
149
- const endX = timeScale ( new Date ( item . endDate || maxDate ) . getTime ( ) ) ;
157
+ const startX = timeScale ( getMinTimeWithinDomain ( item , minTime ) ) ;
158
+ const endX = timeScale ( getMaxTimeWithinDomain ( item , maxTime ) ) ;
150
159
const barX = Math . min ( startX , endX ) ;
151
160
const barWidth = Math . abs ( endX - startX ) ;
152
161
const barY = itemScale ( item . title || '' ) ?? 0 ;
@@ -212,7 +221,7 @@ export function Timeline({ items }: TimelineProps) {
212
221
) ;
213
222
} ) }
214
223
< AxisTop
215
- top = { margin . top }
224
+ top = { chartMargin . top }
216
225
scale = { timeScale }
217
226
tickFormat = { ( value : number ) => {
218
227
const date = new Date ( value ) ;
@@ -227,8 +236,8 @@ export function Timeline({ items }: TimelineProps) {
227
236
< line
228
237
x1 = { timeScale ( currentTime ) }
229
238
x2 = { timeScale ( currentTime ) }
230
- y1 = { margin . top }
231
- y2 = { height - margin . bottom }
239
+ y1 = { chartMargin . top }
240
+ y2 = { chartHeight - chartMargin . bottom }
232
241
stroke = "red"
233
242
strokeWidth = { 2 }
234
243
/>
0 commit comments