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