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