1<?php 2 3use Fisharebest\Webtrees\Date; 4use Fisharebest\Webtrees\Family; 5use Fisharebest\Webtrees\Functions\FunctionsDate; 6use Fisharebest\Webtrees\Functions\FunctionsPrint; 7use Fisharebest\Webtrees\I18N; 8use Fisharebest\Webtrees\Individual; 9 10?> 11 12<script> 13 var bottomy = <?= json_encode(($topyear - $baseyear) * $scale - 5) ?>; 14 var topy = 0; 15 var baseyear = <?= $baseyear - (25 / $scale) ?>; 16 var birthyears = []; 17 var birthmonths = []; 18 var birthdays = []; 19 <?php foreach ($individuals as $c => $indi) : ?> 20 birthyears [<?= json_encode($c) ?>] = <?= json_encode($birthyears[$indi->xref()] ?? null) ?>; 21 birthmonths[<?= json_encode($c) ?>] = <?= json_encode($birthmonths[$indi->xref()] ?? null) ?>; 22 birthdays [<?= json_encode($c) ?>] = <?= json_encode($birthdays[$indi->xref()] ?? null) ?>; 23 <?php endforeach ?> 24 25 var bheight = <?= json_encode($bheight) ?>; 26 var scale = <?= json_encode($scale) ?>; 27 28 timeline_chart_div = document.getElementById("timeline_chart"); 29 timeline_chart_div.style.height = '<?= json_encode(0 + ($topyear - $baseyear) * $scale * 1.1) ?>px'; 30 31 /** 32 * Find the position of an event, relative to an element. 33 * 34 * @param event 35 * @param element 36 */ 37 function clickPosition(event, element) { 38 var xpos = event.pageX; 39 var ypos = event.pageY; 40 41 if (element.offsetParent) { 42 do { 43 xpos -= element.offsetLeft; 44 ypos -= element.offsetTop; 45 } while (element = element.offsetParent); 46 } 47 48 return {x: xpos, y: ypos} 49 } 50 51 var ob = null; 52 var Y = 0; 53 var X = 0; 54 var oldx = 0; 55 var oldlinew = 0; 56 var personnum = 0; 57 var type = 0; 58 var boxmean = 0; 59 60 function ageCursorMouseDown(divbox, num) { 61 ob = divbox; 62 personnum = num; 63 type = 0; 64 X = ob.offsetLeft; 65 Y = ob.offsetTop; 66 } 67 68 function factMouseDown(divbox, num, mean) { 69 ob = divbox; 70 personnum = num; 71 boxmean = mean; 72 type = 1; 73 oldx = ob.offsetLeft; 74 oldlinew = 0; 75 } 76 77 document.onmousemove = function (e) { 78 var textDirection = document.documentElement.dir; 79 80 if (ob === null) { 81 return true; 82 } 83 var newx = 0; 84 var newy = 0; 85 if (type === 0) { 86 // age boxes 87 newPosition = clickPosition(e, document.getElementById("timeline_chart")); 88 newx = newPosition.x; 89 newy = newPosition.y; 90 91 if (oldx === 0) { 92 oldx = newx; 93 } 94 if (newy < topy - bheight / 2) { 95 newy = topy - bheight / 2; 96 } 97 if (newy > bottomy) { 98 newy = bottomy - 1; 99 } 100 ob.style.top = newy + "px"; 101 var tyear = (newy + bheight - 4 - topy + scale) / scale + baseyear; 102 var year = Math.floor(tyear); 103 var month = Math.floor(tyear * 12 - year * 12); 104 var day = Math.floor(tyear * 365 - year * 365 - month * 30); 105 var mstamp = year * 365 + month * 30 + day; 106 var bdstamp = birthyears[personnum] * 365 + birthmonths[personnum] * 30 + birthdays[personnum]; 107 var daydiff = mstamp - bdstamp; 108 var ba = 1; 109 if (daydiff < 0) { 110 ba = -1; 111 daydiff = (bdstamp - mstamp); 112 } 113 var yage = Math.floor(daydiff / 365); 114 var mage = Math.floor((daydiff - yage * 365) / 30); 115 var dage = Math.floor(daydiff - yage * 365 - mage * 30); 116 if (dage < 0) { 117 mage = mage - 1; 118 } 119 if (dage < -30) { 120 dage = 30 + dage; 121 } 122 if (mage < 0) { 123 yage = yage - 1; 124 } 125 if (mage < -11) { 126 mage = 12 + mage; 127 } 128 var yearform = document.getElementById('yearform' + personnum); 129 var ageform = document.getElementById('ageform' + personnum); 130 yearform.innerHTML = year + " " + month + " <?= mb_substr(I18N::translate('Month:'), 0, 1) ?> " + day + " <?= mb_substr(I18N::translate('Day:'), 0, 1) ?>"; 131 if (ba * yage > 1 || ba * yage < -1 || ba * yage === 0) { 132 ageform.innerHTML = (ba * yage) + " <?= mb_substr(I18N::translate('years'), 0, 1) ?> " + (ba * mage) + " <?= mb_substr(I18N::translate('Month:'), 0, 1) ?> " + (ba * dage) + " <?= mb_substr(I18N::translate('Day:'), 0, 1) ?>"; 133 } else { 134 ageform.innerHTML = (ba * yage) + " <?= mb_substr(I18N::translate('Year:'), 0, 1) ?> " + (ba * mage) + " <?= mb_substr(I18N::translate('Month:'), 0, 1) ?> " + (ba * dage) + " <?= mb_substr(I18N::translate('Day:'), 0, 1) ?>"; 135 } 136 var line = document.getElementById('ageline' + personnum); 137 var temp = newx - oldx; 138 139 if (textDirection === 'rtl') { 140 temp = temp * -1; 141 } 142 line.style.width = (line.width + temp) + "px"; 143 oldx = newx; 144 return false; 145 } else { 146 // fact boxes 147 var linewidth; 148 newPosition = clickPosition(e, document.getElementById("timeline_chart")); 149 newx = newPosition.x; 150 newy = newPosition.y; 151 if (oldx === 0) { 152 oldx = newx; 153 } 154 linewidth = e.pageX; 155 156 // get diagnal line box 157 var dbox = document.getElementById('dbox' + personnum); 158 var etopy; 159 var ebottomy; 160 // set up limits 161 if (boxmean - 175 < topy) { 162 etopy = topy; 163 } else { 164 etopy = boxmean - 175; 165 } 166 if (boxmean + 175 > bottomy) { 167 ebottomy = bottomy; 168 } else { 169 ebottomy = boxmean + 175; 170 } 171 // check if in the bounds of the limits 172 if (newy < etopy) { 173 newy = etopy; 174 } 175 if (newy > ebottomy) { 176 newy = ebottomy; 177 } 178 // calculate the change in Y position 179 var dy = newy - ob.offsetTop; 180 // check if we are above the starting point and switch the background image 181 182 if (newy < boxmean) { 183 if (textDirection === 'rtl') { 184 dbox.style.backgroundImage = "url('<?= asset('css/images/dline2.png') ?>')"; 185 dbox.style.backgroundPosition = "0% 0%"; 186 } else { 187 dbox.style.backgroundImage = "url('<?= asset('css/images/dline.png') ?>')"; 188 dbox.style.backgroundPosition = "0% 100%"; 189 } 190 dy = -dy; 191 dbox.style.top = (newy + bheight / 3) + "px"; 192 } else { 193 if (textDirection === 'rtl') { 194 dbox.style.backgroundImage = "url('<?= asset('css/images/dline.png') ?>')"; 195 dbox.style.backgroundPosition = "0% 100%"; 196 } else { 197 dbox.style.backgroundImage = "url('<?= asset('css/images/dline2.png') ?>')"; 198 dbox.style.backgroundPosition = "0% 0%"; 199 } 200 201 dbox.style.top = (boxmean + bheight / 3) + "px"; 202 } 203 // the new X posistion moves the same as the y position 204 if (textDirection === 'rtl') { 205 newx = dbox.offsetRight + Math.abs(newy - boxmean); 206 } else { 207 newx = dbox.offsetLeft + Math.abs(newy - boxmean); 208 } 209 // set the X position of the box 210 if (textDirection === 'rtl') { 211 ob.style.right = newx + "px"; 212 } else { 213 ob.style.left = newx + "px"; 214 } 215 // set new top positions 216 ob.style.top = newy + "px"; 217 // get the width for the diagnal box 218 var newwidth = (ob.offsetLeft - dbox.offsetLeft); 219 // set the width 220 dbox.style.width = newwidth + "px"; 221 if (textDirection === 'rtl') { 222 dbox.style.right = (dbox.offsetRight - newwidth) + 'px'; 223 } 224 dbox.style.height = newwidth + "px"; 225 // change the line width to the change in the mouse X position 226 line = document.getElementById('boxline' + personnum); 227 if (oldlinew !== 0) { 228 line.width = line.width + (linewidth - oldlinew); 229 } 230 oldlinew = linewidth; 231 oldx = newx; 232 return false; 233 } 234 }; 235 236 document.onmouseup = function () { 237 ob = null; 238 oldx = 0; 239 } 240</script> 241 242<div id="timeline_chart"> 243 <!-- print the timeline line image --> 244 <div id="line" style="position:absolute; <?= I18N::direction() === 'ltr' ? 'left:22px;' : 'right:22px;' ?> top:0;"> 245 <img src="<?= e(asset('css/images/vline.png')) ?>" width="3" height="<?= 0 + ($topyear - $baseyear) * $scale ?>"> 246 </div> 247 248 <!-- print divs for the grid --> 249 <div id="scale<?= e($baseyear) ?>" style="position:absolute; <?= I18N::direction() === 'ltr' ? 'left' : 'right' ?>:0; top:-5px; font-size: 7pt; text-align: <?= I18N::direction() === 'ltr' ? 'left' : 'right' ?>;"> 250 <?= $baseyear ?> 251 </div> 252 <?php 253 // at a scale of 25 or higher, show every year 254 $mod = 25 / $scale; 255 if ($mod < 1) { 256 $mod = 1; 257 } 258 for ($i = $baseyear + 1; $i < $topyear; $i++) { 259 if ($i % $mod === 0) { 260 echo '<div id="scale' . $i . '" style="position:absolute; ' . (I18N::direction() === 'ltr' ? 'left:0;' : 'right:0;') . ' top:' . ((($i - $baseyear) * $scale) - $scale / 2) . 'px; font-size: 7pt; text-align:' . (I18N::direction() === 'ltr' ? 'left' : 'right') . ';">'; 261 echo $i; 262 echo '</div>'; 263 } 264 } 265 echo ''; 266 ?> 267 <div id="scale<?= e($topyear) ?>" style="position:absolute; <?= I18N::direction() === 'ltr' ? 'left' : 'right' ?>:0; top:<?= ($topyear - $baseyear) * $scale ?>px; font-size: 7pt; text-align:<?= I18N::direction() === 'ltr' ? 'left' : 'right' ?>;"> 268 <?= e($topyear) ?> 269 </div> 270 271 <?php foreach ($indifacts as $factcount => $event) : ?> 272 <?php 273 $desc = $event->value(); 274 $gdate = $event->date(); 275 $date = $gdate->minimumDate(); 276 $date = $date->convertToCalendar('gregorian'); 277 $year = $date->year(); 278 $month = max(1, $date->month()); 279 $day = max(1, $date->day()); 280 $xoffset = 0 + 22; 281 $yoffset = 0 + (($year - $baseyear) * $scale) - $scale; 282 $yoffset = $yoffset + (($month / 12) * $scale); 283 $yoffset = $yoffset + (($day / 30) * ($scale / 12)); 284 $yoffset = (int) $yoffset; 285 $place = (int) ($yoffset / $bheight); 286 $i = 1; 287 $j = 0; 288 $tyoffset = 0; 289 while (isset($placements[$place])) { 290 if ($i === $j) { 291 $tyoffset = $bheight * $i; 292 $i++; 293 } else { 294 $tyoffset = -1 * $bheight * $j; 295 $j++; 296 } 297 $place = (int) (($yoffset + $tyoffset) / $bheight); 298 } 299 $yoffset += $tyoffset; 300 $xoffset += abs($tyoffset); 301 $placements[$place] = $yoffset; 302 303 echo "<div id=\"fact$factcount\" style=\"position:absolute; " . (I18N::direction() === 'ltr' ? 'left: ' . $xoffset : 'right: ' . $xoffset) . 'px; top:' . $yoffset . 'px; font-size: 8pt; height: ' . $bheight . "px;\" onmousedown=\"factMouseDown(this, '" . $factcount . "', " . ($yoffset - $tyoffset) . ');">'; 304 echo '<table cellspacing="0" cellpadding="0" border="0" style="cursor: grab;"><tr><td>'; 305 echo '<img src="' . e(asset('css/images/hline.png')) . '" id="boxline' . $factcount . '" height="3" width="10" style="padding-'; 306 if (I18N::direction() === 'ltr') { 307 echo 'left: 3px;">'; 308 } else { 309 echo 'right: 3px;">'; 310 } 311 312 $col = array_search($event->record(), $individuals, true); 313 if ($col === false) { 314 // Marriage event - use the color of the husband 315 $col = array_search($event->record()->husband(), $individuals, true); 316 } 317 if ($col === false) { 318 // Marriage event - use the color of the wife 319 $col = array_search($event->record()->wife(), $individuals, true); 320 } 321 $col = $col % 6; 322 echo '</td><td class="person' . $col . '">'; 323 if (count($individuals) > 6) { 324 // We only have six colours, so show naes if more than this number 325 echo $event->record()->fullName() . ' — '; 326 } 327 $record = $event->record(); 328 echo $event->label(); 329 echo ' — '; 330 if ($record instanceof Individual) { 331 echo FunctionsPrint::formatFactDate($event, $record, false, false); 332 } elseif ($record instanceof Family) { 333 echo $gdate->display(); 334 if ($record->husband() && $record->husband()->getBirthDate()->isOK()) { 335 $ageh = FunctionsDate::getAgeAtEvent(Date::getAgeGedcom($record->husband()->getBirthDate(), $gdate)); 336 } else { 337 $ageh = null; 338 } 339 if ($record->wife() && $record->wife()->getBirthDate()->isOK()) { 340 $agew = FunctionsDate::getAgeAtEvent(Date::getAgeGedcom($record->wife()->getBirthDate(), $gdate)); 341 } else { 342 $agew = null; 343 } 344 if ($ageh && $agew) { 345 echo '<span class="age"> ', I18N::translate('Husband’s age'), ' ', $ageh, ' ', I18N::translate('Wife’s age'), ' ', $agew, '</span>'; 346 } elseif ($ageh) { 347 echo '<span class="age"> ', I18N::translate('Age'), ' ', $ageh, '</span>'; 348 } elseif ($agew) { 349 echo '<span class="age"> ', I18N::translate('Age'), ' ', $ageh, '</span>'; 350 } 351 } 352 echo ' ' . e($desc); 353 if ($event->place()->gedcomName() !== '') { 354 echo ' — ' . $event->place()->shortName(); 355 } 356 // Print spouses names for family events 357 if ($event->record() instanceof Family) { 358 echo ' — <a href="', e($event->record()->url()), '">', $event->record()->fullName(), '</a>'; 359 } 360 echo '</td></tr></table>'; 361 echo '</div>'; 362 if (I18N::direction() === 'ltr') { 363 $img = asset('css/images/dline2.png'); 364 $ypos = '0%'; 365 } else { 366 $img = asset('css/images/dline.png'); 367 $ypos = '100%'; 368 } 369 $dyoffset = ($yoffset - $tyoffset) + $bheight / 3; 370 if ($tyoffset < 0) { 371 $dyoffset = $yoffset + $bheight / 3; 372 if (I18N::direction() === 'ltr') { 373 $img = asset('css/images/dline.png'); 374 $ypos = '100%'; 375 } else { 376 $img = asset('css/images/dline2.png'); 377 $ypos = '0%'; 378 } 379 } 380 ?> 381 382 <!-- diagonal line --> 383 <div id="dbox<?= $factcount ?>" style="position:absolute; <?= (I18N::direction() === 'ltr' ? 'left: ' . (0 + 25) : 'right: ' . (0 + 25)) ?>px; top:<?= ($dyoffset) ?>px; font-size: 8pt; height: <?= abs($tyoffset) ?>px; width: <?= abs($tyoffset) ?>px; background-image: url('<?= e($img) ?>'); background-position: 0% <?= $ypos ?>;"> 384 </div> 385 <?php endforeach ?> 386 387 <!-- age cursors --> 388 <?php foreach ($individuals as $p => $indi) : ?> 389 <?php $ageyoffset = 0 + ($bheight * $p); ?> 390 <div id="agebox<?= $p ?>" style="cursor:move; position:absolute; <?= I18N::direction() === 'ltr' ? 'left:20px;' : 'right:20px;' ?> top:<?= $ageyoffset ?>px; height:<?= $bheight ?>px; display:none;" onmousedown="ageCursorMouseDown(this, <?= $p ?>);"> 391 <table cellspacing="0" cellpadding="0"> 392 <tr> 393 <td> 394 <img src="<?= e(asset('css/images/hline.png')) ?>" id="ageline<?= $p ?>" width="25" height="3"> 395 </td> 396 <td> 397 <?php if (!empty($birthyears[$indi->xref()])) : ?> 398 <?php $tyear = round(($ageyoffset + ($bheight / 2)) / $scale) + $baseyear; ?> 399 <table class="person<?= $p % 6 ?>" style="cursor: grab;"> 400 <tr> 401 <td> 402 <?= I18N::translate('Year:') ?> 403 <span id="yearform<?= $p ?>" class="field"> 404 <?= $tyear ?> 405 </span> 406 </td> 407 <td> 408 (<?= I18N::translate('Age') ?> <span id="ageform<?= $p ?>" class="field"><?= $tyear - $birthyears[$indi->xref()] ?></span>) 409 </td> 410 </tr> 411 </table> 412 <?php endif ?> 413 </td> 414 </tr> 415 </table> 416 <br> 417 <br> 418 <br> 419 </div> 420 <br> 421 <br> 422 <br> 423 <br> 424 <?php endforeach ?> 425</div> 426