xref: /webtrees/resources/views/modules/timeline-chart/chart.phtml (revision 54f9ed18c67402425839d8ad7a6ffd425b6361b8)
1d70512abSGreg Roach<?php
2d70512abSGreg Roach
310e06497SGreg Roachdeclare(strict_types=1);
410e06497SGreg Roach
5054771e9SGreg Roachuse Fisharebest\Webtrees\Age;
6054771e9SGreg Roachuse Fisharebest\Webtrees\Fact;
754c1ab5eSGreg Roachuse Fisharebest\Webtrees\Family;
854c1ab5eSGreg Roachuse Fisharebest\Webtrees\I18N;
9d70512abSGreg Roachuse Fisharebest\Webtrees\Individual;
10d70512abSGreg Roach
112cebb4b4SGreg Roach/**
122cebb4b4SGreg Roach * @var int                   $baseyear
132cebb4b4SGreg Roach * @var int                   $bheight
1436779af1SGreg Roach * @var array<int>            $birthdays
1536779af1SGreg Roach * @var array<int>            $birthmonths
1636779af1SGreg Roach * @var array<int>            $birthyears
172cebb4b4SGreg Roach * @var int                   $scale
182cebb4b4SGreg Roach * @var int                   $topyear
19*54f9ed18SGreg Roach * @var array<int,Fact>       $indifacts
20*54f9ed18SGreg Roach * @var array<int,Individual> $individuals
212cebb4b4SGreg Roach */
222cebb4b4SGreg Roach
23d70512abSGreg Roach?>
24eca4a663SGreg Roach
25eca4a663SGreg Roach<script>
26728c8c27SGreg Roach    let bottomy = <?= json_encode(($topyear - $baseyear) * $scale - 5, JSON_THROW_ON_ERROR) ?>;
272cebb4b4SGreg Roach    let topy = 0;
282cebb4b4SGreg Roach    let baseyear = <?= $baseyear - 25 / $scale ?>;
292cebb4b4SGreg Roach    let birthyears = [];
302cebb4b4SGreg Roach    let birthmonths = [];
312cebb4b4SGreg Roach    let birthdays = [];
3254c1ab5eSGreg Roach    <?php foreach ($individuals as $c => $indi) : ?>
33728c8c27SGreg Roach    birthyears [<?= json_encode($c, JSON_THROW_ON_ERROR) ?>] = <?= json_encode($birthyears[$indi->xref()] ?? null, JSON_THROW_ON_ERROR) ?>;
34728c8c27SGreg Roach    birthmonths[<?= json_encode($c, JSON_THROW_ON_ERROR) ?>] = <?= json_encode($birthmonths[$indi->xref()] ?? null, JSON_THROW_ON_ERROR) ?>;
35728c8c27SGreg Roach    birthdays  [<?= json_encode($c, JSON_THROW_ON_ERROR) ?>] = <?= json_encode($birthdays[$indi->xref()] ?? null, JSON_THROW_ON_ERROR) ?>;
3654c1ab5eSGreg Roach    <?php endforeach ?>
37eca4a663SGreg Roach
38728c8c27SGreg Roach    let bheight = <?= json_encode($bheight, JSON_THROW_ON_ERROR) ?>;
39728c8c27SGreg Roach    let scale = <?= json_encode($scale, JSON_THROW_ON_ERROR) ?>;
40eca4a663SGreg Roach
41eca4a663SGreg Roach    timeline_chart_div = document.getElementById("timeline_chart");
42728c8c27SGreg Roach    timeline_chart_div.style.height = '<?= json_encode(0 + ($topyear - $baseyear) * $scale * 1.1, JSON_THROW_ON_ERROR) ?>px';
43eca4a663SGreg Roach
44eca4a663SGreg Roach    /**
45eca4a663SGreg Roach     * Find the position of an event, relative to an element.
46eca4a663SGreg Roach     *
47eca4a663SGreg Roach     * @param event
48eca4a663SGreg Roach     * @param element
49eca4a663SGreg Roach     */
50eca4a663SGreg Roach    function clickPosition(event, element) {
512cebb4b4SGreg Roach        let xpos = event.pageX;
522cebb4b4SGreg Roach        let ypos = event.pageY;
53eca4a663SGreg Roach
54eca4a663SGreg Roach        if (element.offsetParent) {
55eca4a663SGreg Roach            do {
56eca4a663SGreg Roach                xpos -= element.offsetLeft;
57eca4a663SGreg Roach                ypos -= element.offsetTop;
58eca4a663SGreg Roach            } while (element = element.offsetParent);
59eca4a663SGreg Roach        }
60eca4a663SGreg Roach
61eca4a663SGreg Roach        return {x: xpos, y: ypos}
62eca4a663SGreg Roach    }
63eca4a663SGreg Roach
642cebb4b4SGreg Roach    let ob = null;
652cebb4b4SGreg Roach    let Y = 0;
662cebb4b4SGreg Roach    let X = 0;
672cebb4b4SGreg Roach    let oldx = 0;
682cebb4b4SGreg Roach    let oldlinew = 0;
692cebb4b4SGreg Roach    let personnum = 0;
702cebb4b4SGreg Roach    let type = 0;
712cebb4b4SGreg Roach    let boxmean = 0;
72eca4a663SGreg Roach
73eca4a663SGreg Roach    function ageCursorMouseDown(divbox, num) {
74eca4a663SGreg Roach        ob = divbox;
75eca4a663SGreg Roach        personnum = num;
76eca4a663SGreg Roach        type = 0;
77eca4a663SGreg Roach        X = ob.offsetLeft;
78eca4a663SGreg Roach        Y = ob.offsetTop;
79eca4a663SGreg Roach    }
80eca4a663SGreg Roach
81eca4a663SGreg Roach    function factMouseDown(divbox, num, mean) {
82eca4a663SGreg Roach        ob = divbox;
83eca4a663SGreg Roach        personnum = num;
84eca4a663SGreg Roach        boxmean = mean;
85eca4a663SGreg Roach        type = 1;
86eca4a663SGreg Roach        oldx = ob.offsetLeft;
87eca4a663SGreg Roach        oldlinew = 0;
88eca4a663SGreg Roach    }
89eca4a663SGreg Roach
90eca4a663SGreg Roach    document.onmousemove = function (e) {
912cebb4b4SGreg Roach        let textDirection = document.documentElement.dir;
92eca4a663SGreg Roach
93eca4a663SGreg Roach        if (ob === null) {
94eca4a663SGreg Roach            return true;
95eca4a663SGreg Roach        }
962cebb4b4SGreg Roach        let newx = 0;
972cebb4b4SGreg Roach        let newy = 0;
98eca4a663SGreg Roach        if (type === 0) {
99eca4a663SGreg Roach            // age boxes
100eca4a663SGreg Roach            newPosition = clickPosition(e, document.getElementById("timeline_chart"));
101eca4a663SGreg Roach            newx = newPosition.x;
102eca4a663SGreg Roach            newy = newPosition.y;
103eca4a663SGreg Roach
104eca4a663SGreg Roach            if (oldx === 0) {
105eca4a663SGreg Roach                oldx = newx;
106eca4a663SGreg Roach            }
107eca4a663SGreg Roach            if (newy < topy - bheight / 2) {
108eca4a663SGreg Roach                newy = topy - bheight / 2;
109eca4a663SGreg Roach            }
110eca4a663SGreg Roach            if (newy > bottomy) {
111eca4a663SGreg Roach                newy = bottomy - 1;
112eca4a663SGreg Roach            }
113eca4a663SGreg Roach            ob.style.top = newy + "px";
1142cebb4b4SGreg Roach            let tyear = (newy + bheight - 4 - topy + scale) / scale + baseyear;
1152cebb4b4SGreg Roach            let year = Math.floor(tyear);
1162cebb4b4SGreg Roach            let month = Math.floor(tyear * 12 - year * 12);
1172cebb4b4SGreg Roach            let day = Math.floor(tyear * 365 - year * 365 - month * 30);
1182cebb4b4SGreg Roach            let mstamp = year * 365 + month * 30 + day;
1192cebb4b4SGreg Roach            let bdstamp = birthyears[personnum] * 365 + birthmonths[personnum] * 30 + birthdays[personnum];
1202cebb4b4SGreg Roach            let daydiff = mstamp - bdstamp;
1212cebb4b4SGreg Roach            let ba = 1;
122eca4a663SGreg Roach            if (daydiff < 0) {
123eca4a663SGreg Roach                ba = -1;
124eca4a663SGreg Roach                daydiff = (bdstamp - mstamp);
125eca4a663SGreg Roach            }
1262cebb4b4SGreg Roach            let yage = Math.floor(daydiff / 365);
1272cebb4b4SGreg Roach            let mage = Math.floor((daydiff - yage * 365) / 30);
1282cebb4b4SGreg Roach            let dage = Math.floor(daydiff - yage * 365 - mage * 30);
129eca4a663SGreg Roach            if (dage < 0) {
130eca4a663SGreg Roach                mage = mage - 1;
131eca4a663SGreg Roach            }
132eca4a663SGreg Roach            if (dage < -30) {
133eca4a663SGreg Roach                dage = 30 + dage;
134eca4a663SGreg Roach            }
135eca4a663SGreg Roach            if (mage < 0) {
136eca4a663SGreg Roach                yage = yage - 1;
137eca4a663SGreg Roach            }
138eca4a663SGreg Roach            if (mage < -11) {
139eca4a663SGreg Roach                mage = 12 + mage;
140eca4a663SGreg Roach            }
1412cebb4b4SGreg Roach            let yearform = document.getElementById('yearform' + personnum);
1422cebb4b4SGreg Roach            let ageform = document.getElementById('ageform' + personnum);
143eca4a663SGreg Roach            yearform.innerHTML = year + "      " + month + " <?= mb_substr(I18N::translate('Month:'), 0, 1) ?>   " + day + " <?= mb_substr(I18N::translate('Day:'), 0, 1) ?>";
144eca4a663SGreg Roach            if (ba * yage > 1 || ba * yage < -1 || ba * yage === 0) {
145eca4a663SGreg Roach                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) ?>";
146eca4a663SGreg Roach            } else {
147eca4a663SGreg Roach                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) ?>";
148eca4a663SGreg Roach            }
1492cebb4b4SGreg Roach            let line = document.getElementById('ageline' + personnum);
1502cebb4b4SGreg Roach            let temp = newx - oldx;
151eca4a663SGreg Roach
152eca4a663SGreg Roach            if (textDirection === 'rtl') {
153eca4a663SGreg Roach                temp = temp * -1;
154eca4a663SGreg Roach            }
155eca4a663SGreg Roach            line.style.width = (line.width + temp) + "px";
156eca4a663SGreg Roach            oldx = newx;
157eca4a663SGreg Roach            return false;
158eca4a663SGreg Roach        } else {
159eca4a663SGreg Roach            // fact boxes
1602cebb4b4SGreg Roach            let linewidth;
161eca4a663SGreg Roach            newPosition = clickPosition(e, document.getElementById("timeline_chart"));
162eca4a663SGreg Roach            newx = newPosition.x;
163eca4a663SGreg Roach            newy = newPosition.y;
164eca4a663SGreg Roach            if (oldx === 0) {
165eca4a663SGreg Roach                oldx = newx;
166eca4a663SGreg Roach            }
167eca4a663SGreg Roach            linewidth = e.pageX;
168eca4a663SGreg Roach
169eca4a663SGreg Roach            // get diagnal line box
1702cebb4b4SGreg Roach            let dbox = document.getElementById('dbox' + personnum);
1712cebb4b4SGreg Roach            let etopy;
1722cebb4b4SGreg Roach            let ebottomy;
173eca4a663SGreg Roach            // set up limits
174eca4a663SGreg Roach            if (boxmean - 175 < topy) {
175eca4a663SGreg Roach                etopy = topy;
176eca4a663SGreg Roach            } else {
177eca4a663SGreg Roach                etopy = boxmean - 175;
178eca4a663SGreg Roach            }
179eca4a663SGreg Roach            if (boxmean + 175 > bottomy) {
180eca4a663SGreg Roach                ebottomy = bottomy;
181eca4a663SGreg Roach            } else {
182eca4a663SGreg Roach                ebottomy = boxmean + 175;
183eca4a663SGreg Roach            }
184eca4a663SGreg Roach            // check if in the bounds of the limits
185eca4a663SGreg Roach            if (newy < etopy) {
186eca4a663SGreg Roach                newy = etopy;
187eca4a663SGreg Roach            }
188eca4a663SGreg Roach            if (newy > ebottomy) {
189eca4a663SGreg Roach                newy = ebottomy;
190eca4a663SGreg Roach            }
191eca4a663SGreg Roach            // calculate the change in Y position
1922cebb4b4SGreg Roach            let dy = newy - ob.offsetTop;
193eca4a663SGreg Roach            // check if we are above the starting point and switch the background image
194eca4a663SGreg Roach
195eca4a663SGreg Roach            if (newy < boxmean) {
196eca4a663SGreg Roach                if (textDirection === 'rtl') {
197e837ff07SGreg Roach                    dbox.style.backgroundImage = "url('<?= asset('css/images/dline2.png') ?>')";
198eca4a663SGreg Roach                    dbox.style.backgroundPosition = "0% 0%";
199eca4a663SGreg Roach                } else {
200e837ff07SGreg Roach                    dbox.style.backgroundImage = "url('<?= asset('css/images/dline.png') ?>')";
201eca4a663SGreg Roach                    dbox.style.backgroundPosition = "0% 100%";
202eca4a663SGreg Roach                }
203eca4a663SGreg Roach                dy = -dy;
204eca4a663SGreg Roach                dbox.style.top = (newy + bheight / 3) + "px";
205eca4a663SGreg Roach            } else {
206eca4a663SGreg Roach                if (textDirection === 'rtl') {
207e837ff07SGreg Roach                    dbox.style.backgroundImage = "url('<?= asset('css/images/dline.png') ?>')";
208eca4a663SGreg Roach                    dbox.style.backgroundPosition = "0% 100%";
209eca4a663SGreg Roach                } else {
210e837ff07SGreg Roach                    dbox.style.backgroundImage = "url('<?= asset('css/images/dline2.png') ?>')";
211eca4a663SGreg Roach                    dbox.style.backgroundPosition = "0% 0%";
212eca4a663SGreg Roach                }
213eca4a663SGreg Roach
214eca4a663SGreg Roach                dbox.style.top = (boxmean + bheight / 3) + "px";
215eca4a663SGreg Roach            }
216a2c8afeaSAlejandro Criado-Pérez            // the new X position moves the same as the y position
217eca4a663SGreg Roach            if (textDirection === 'rtl') {
218eca4a663SGreg Roach                newx = dbox.offsetRight + Math.abs(newy - boxmean);
219eca4a663SGreg Roach            } else {
220eca4a663SGreg Roach                newx = dbox.offsetLeft + Math.abs(newy - boxmean);
221eca4a663SGreg Roach            }
222eca4a663SGreg Roach            // set the X position of the box
223eca4a663SGreg Roach            if (textDirection === 'rtl') {
224eca4a663SGreg Roach                ob.style.right = newx + "px";
225eca4a663SGreg Roach            } else {
226eca4a663SGreg Roach                ob.style.left = newx + "px";
227eca4a663SGreg Roach            }
228eca4a663SGreg Roach            // set new top positions
229eca4a663SGreg Roach            ob.style.top = newy + "px";
230eca4a663SGreg Roach            // get the width for the diagnal box
2312cebb4b4SGreg Roach            let newwidth = (ob.offsetLeft - dbox.offsetLeft);
232eca4a663SGreg Roach            // set the width
233eca4a663SGreg Roach            dbox.style.width = newwidth + "px";
234eca4a663SGreg Roach            if (textDirection === 'rtl') {
235eca4a663SGreg Roach                dbox.style.right = (dbox.offsetRight - newwidth) + 'px';
236eca4a663SGreg Roach            }
237eca4a663SGreg Roach            dbox.style.height = newwidth + "px";
238eca4a663SGreg Roach            // change the line width to the change in the mouse X position
239eca4a663SGreg Roach            line = document.getElementById('boxline' + personnum);
240eca4a663SGreg Roach            if (oldlinew !== 0) {
241eca4a663SGreg Roach                line.width = line.width + (linewidth - oldlinew);
242eca4a663SGreg Roach            }
243eca4a663SGreg Roach            oldlinew = linewidth;
244eca4a663SGreg Roach            oldx = newx;
245eca4a663SGreg Roach            return false;
246eca4a663SGreg Roach        }
247eca4a663SGreg Roach    };
248eca4a663SGreg Roach
249eca4a663SGreg Roach    document.onmouseup = function () {
250eca4a663SGreg Roach        ob = null;
251eca4a663SGreg Roach        oldx = 0;
252eca4a663SGreg Roach    }
253eca4a663SGreg Roach</script>
254eca4a663SGreg Roach
255eca4a663SGreg Roach<div id="timeline_chart">
256eca4a663SGreg Roach    <!-- print the timeline line image -->
257eca4a663SGreg Roach    <div id="line" style="position:absolute; <?= I18N::direction() === 'ltr' ? 'left:22px;' : 'right:22px;' ?> top:0;">
2582cebb4b4SGreg Roach        <img src="<?= e(asset('css/images/vline.png')) ?>" width="3"
2592cebb4b4SGreg Roach             height="<?= 0 + ($topyear - $baseyear) * $scale ?>">
260eca4a663SGreg Roach    </div>
261eca4a663SGreg Roach
262eca4a663SGreg Roach    <!-- print divs for the grid -->
2632cebb4b4SGreg Roach    <div id="scale<?= e($baseyear) ?>"
2642cebb4b4SGreg Roach         style="position:absolute; <?= I18N::direction() === 'ltr' ? 'left' : 'right' ?>:0; top:-5px; font-size: 7pt; text-align: <?= I18N::direction() === 'ltr' ? 'left' : 'right' ?>;">
265eca4a663SGreg Roach        <?= $baseyear ?>
266eca4a663SGreg Roach    </div>
267eca4a663SGreg Roach    <?php
268eca4a663SGreg Roach    // at a scale of 25 or higher, show every year
2691a2589c7SGreg Roach    $mod = intdiv(25, $scale);
270eca4a663SGreg Roach    if ($mod < 1) {
271eca4a663SGreg Roach        $mod = 1;
272eca4a663SGreg Roach    }
273eca4a663SGreg Roach    for ($i = $baseyear + 1; $i < $topyear; $i++) {
274eca4a663SGreg Roach        if ($i % $mod === 0) {
275dd71ff6bSGreg Roach            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') . ';">';
276eca4a663SGreg Roach            echo $i;
277eca4a663SGreg Roach            echo '</div>';
278eca4a663SGreg Roach        }
279eca4a663SGreg Roach    }
280eca4a663SGreg Roach    echo '';
281eca4a663SGreg Roach    ?>
2822cebb4b4SGreg Roach    <div id="scale<?= e($topyear) ?>"
2832cebb4b4SGreg Roach         style="position:absolute; <?= I18N::direction() === 'ltr' ? 'left' : 'right' ?>:0; top:<?= ($topyear - $baseyear) * $scale ?>px; font-size: 7pt; text-align:<?= I18N::direction() === 'ltr' ? 'left' : 'right' ?>;">
284eca4a663SGreg Roach        <?= e($topyear) ?>
285eca4a663SGreg Roach    </div>
286eca4a663SGreg Roach
287eca4a663SGreg Roach    <?php foreach ($indifacts as $factcount => $event) : ?>
288eca4a663SGreg Roach        <?php
289eca4a663SGreg Roach        $desc     = $event->value();
290eca4a663SGreg Roach        $gdate    = $event->date();
291eca4a663SGreg Roach        $date     = $gdate->minimumDate();
292eca4a663SGreg Roach        $date     = $date->convertToCalendar('gregorian');
293eca4a663SGreg Roach        $year     = $date->year();
294eca4a663SGreg Roach        $month    = max(1, $date->month());
295eca4a663SGreg Roach        $day      = max(1, $date->day());
296eca4a663SGreg Roach        $xoffset  = 0 + 22;
2972cebb4b4SGreg Roach        $yoffset  = 0 + ($year - $baseyear) * $scale - $scale;
2982cebb4b4SGreg Roach        $yoffset  = $yoffset + $month / 12 * $scale;
2992cebb4b4SGreg Roach        $yoffset  = $yoffset + $day / 30 * ($scale / 12);
300261d6e48SGreg Roach        $yoffset  = (int) $yoffset;
301eca4a663SGreg Roach        $place    = (int) ($yoffset / $bheight);
302eca4a663SGreg Roach        $i        = 1;
303eca4a663SGreg Roach        $j        = 0;
304eca4a663SGreg Roach        $tyoffset = 0;
305eca4a663SGreg Roach        while (isset($placements[$place])) {
306eca4a663SGreg Roach            if ($i === $j) {
307eca4a663SGreg Roach                $tyoffset = $bheight * $i;
308eca4a663SGreg Roach                $i++;
309eca4a663SGreg Roach            } else {
310eca4a663SGreg Roach                $tyoffset = -1 * $bheight * $j;
311eca4a663SGreg Roach                $j++;
312eca4a663SGreg Roach            }
313eca4a663SGreg Roach            $place = (int) (($yoffset + $tyoffset) / $bheight);
314eca4a663SGreg Roach        }
315eca4a663SGreg Roach        $yoffset            += $tyoffset;
316eca4a663SGreg Roach        $xoffset            += abs($tyoffset);
317eca4a663SGreg Roach        $placements[$place] = $yoffset;
318eca4a663SGreg Roach
319261d6e48SGreg Roach        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) . ');">';
320ca8f62c9SGreg Roach        echo '<table cellspacing="0" cellpadding="0" border="0" style="cursor: grab;"><tr><td>';
321f220ef57SGreg Roach        echo '<img src="' . e(asset('css/images/hline.png')) . '" id="boxline' . $factcount . '" height="3" width="10" style="padding-';
322eca4a663SGreg Roach        if (I18N::direction() === 'ltr') {
323eca4a663SGreg Roach            echo 'left: 3px;">';
324eca4a663SGreg Roach        } else {
325eca4a663SGreg Roach            echo 'right: 3px;">';
326eca4a663SGreg Roach        }
327eca4a663SGreg Roach
32822d65e5aSGreg Roach        $col = array_search($event->record(), $individuals, true);
329eca4a663SGreg Roach        if ($col === false) {
330eca4a663SGreg Roach            // Marriage event - use the color of the husband
33122d65e5aSGreg Roach            $col = array_search($event->record()->husband(), $individuals, true);
332eca4a663SGreg Roach        }
333eca4a663SGreg Roach        if ($col === false) {
334eca4a663SGreg Roach            // Marriage event - use the color of the wife
33522d65e5aSGreg Roach            $col = array_search($event->record()->wife(), $individuals, true);
336eca4a663SGreg Roach        }
3372cebb4b4SGreg Roach        $col %= 6;
338eca4a663SGreg Roach        echo '</td><td class="person' . $col . '">';
339eca4a663SGreg Roach        if (count($individuals) > 6) {
340eca4a663SGreg Roach            // We only have six colours, so show naes if more than this number
34139ca88baSGreg Roach            echo $event->record()->fullName() . ' — ';
342eca4a663SGreg Roach        }
343eca4a663SGreg Roach        $record = $event->record();
344eca4a663SGreg Roach        echo $event->label();
345eca4a663SGreg Roach        echo ' — ';
346eca4a663SGreg Roach        if ($record instanceof Individual) {
347b315f3e1SGreg Roach            echo view('fact-date', ['cal_link' => 'false', 'fact' => $event, 'record' => $record, 'time' => false]);
348eca4a663SGreg Roach        } elseif ($record instanceof Family) {
349eca4a663SGreg Roach            echo $gdate->display();
350054771e9SGreg Roach
351054771e9SGreg Roach            foreach ($record->spouses() as $spouse) {
352054771e9SGreg Roach                if ($spouse->getBirthDate()->isOK()) {
353a5fd6d7cSGreg Roach                    $age = (string) new Age($spouse->getBirthDate(), $gdate);
354054771e9SGreg Roach                    if ($spouse->sex() === 'F') {
355a5fd6d7cSGreg Roach                        echo '<span class="age"> ', I18N::translate('Wife’s age'), ' ', $age, '</span>';
356eca4a663SGreg Roach                    } else {
357a5fd6d7cSGreg Roach                        echo '<span class="age"> ', I18N::translate('Husband’s age'), ' ', $age, '</span>';
358eca4a663SGreg Roach                    }
359eca4a663SGreg Roach                }
360eca4a663SGreg Roach            }
361eca4a663SGreg Roach        }
362eca4a663SGreg Roach        echo ' ' . e($desc);
36329e68993SGreg Roach        if ($event->place()->gedcomName() !== '') {
364392561bbSGreg Roach            echo ' — ' . $event->place()->shortName();
365eca4a663SGreg Roach        }
366eca4a663SGreg Roach        // Print spouses names for family events
367eca4a663SGreg Roach        if ($event->record() instanceof Family) {
36839ca88baSGreg Roach            echo ' — <a href="', e($event->record()->url()), '">', $event->record()->fullName(), '</a>';
369eca4a663SGreg Roach        }
370eca4a663SGreg Roach        echo '</td></tr></table>';
371eca4a663SGreg Roach        echo '</div>';
372eca4a663SGreg Roach        if (I18N::direction() === 'ltr') {
373e837ff07SGreg Roach            $img  = asset('css/images/dline2.png');
374eca4a663SGreg Roach            $ypos = '0%';
375eca4a663SGreg Roach        } else {
376e837ff07SGreg Roach            $img  = asset('css/images/dline.png');
377eca4a663SGreg Roach            $ypos = '100%';
378eca4a663SGreg Roach        }
379dd71ff6bSGreg Roach        $dyoffset = $yoffset - $tyoffset + $bheight / 3;
380eca4a663SGreg Roach        if ($tyoffset < 0) {
381eca4a663SGreg Roach            $dyoffset = $yoffset + $bheight / 3;
382eca4a663SGreg Roach            if (I18N::direction() === 'ltr') {
383e837ff07SGreg Roach                $img  = asset('css/images/dline.png');
384eca4a663SGreg Roach                $ypos = '100%';
385eca4a663SGreg Roach            } else {
386e837ff07SGreg Roach                $img  = asset('css/images/dline2.png');
387eca4a663SGreg Roach                $ypos = '0%';
388eca4a663SGreg Roach            }
389eca4a663SGreg Roach        }
390eca4a663SGreg Roach        ?>
391eca4a663SGreg Roach
392eca4a663SGreg Roach        <!-- diagonal line -->
393dd71ff6bSGreg Roach        <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 ?>;">
394eca4a663SGreg Roach        </div>
395eca4a663SGreg Roach    <?php endforeach ?>
396eca4a663SGreg Roach
397eca4a663SGreg Roach    <!-- age cursors -->
398eca4a663SGreg Roach    <?php foreach ($individuals as $p => $indi) : ?>
399dd71ff6bSGreg Roach        <?php $ageyoffset = 0 + $bheight * $p; ?>
400eca4a663SGreg Roach        <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 ?>);">
401eca4a663SGreg Roach            <table cellspacing="0" cellpadding="0">
402eca4a663SGreg Roach                <tr>
403eca4a663SGreg Roach                    <td>
404f220ef57SGreg Roach                        <img src="<?= e(asset('css/images/hline.png')) ?>" id="ageline<?= $p ?>" width="25" height="3">
405eca4a663SGreg Roach                    </td>
406eca4a663SGreg Roach                    <td>
407eca4a663SGreg Roach                        <?php if (!empty($birthyears[$indi->xref()])) : ?>
408dd71ff6bSGreg Roach                            <?php $tyear = round(($ageyoffset + $bheight / 2) / $scale) + $baseyear; ?>
409ca8f62c9SGreg Roach                            <table class="person<?= $p % 6 ?>" style="cursor: grab;">
410eca4a663SGreg Roach                                <tr>
411eca4a663SGreg Roach                                    <td>
412eca4a663SGreg Roach                                        <?= I18N::translate('Year:') ?>
413eca4a663SGreg Roach                                        <span id="yearform<?= $p ?>" class="field">
414eca4a663SGreg Roach                                            <?= $tyear ?>
415eca4a663SGreg Roach                                        </span>
416eca4a663SGreg Roach                                    </td>
417eca4a663SGreg Roach                                    <td>
4182cebb4b4SGreg Roach                                        (<?= I18N::translate('Age') ?>
4192cebb4b4SGreg Roach                                        <span id="ageform<?= $p ?>" class="field"><?= $tyear - $birthyears[$indi->xref()] ?></span>)
420eca4a663SGreg Roach                                    </td>
421eca4a663SGreg Roach                                </tr>
422eca4a663SGreg Roach                            </table>
423eca4a663SGreg Roach                        <?php endif ?>
424eca4a663SGreg Roach                    </td>
425eca4a663SGreg Roach                </tr>
426eca4a663SGreg Roach            </table>
427eca4a663SGreg Roach            <br>
428eca4a663SGreg Roach            <br>
429eca4a663SGreg Roach            <br>
430eca4a663SGreg Roach        </div>
431eca4a663SGreg Roach        <br>
432eca4a663SGreg Roach        <br>
433eca4a663SGreg Roach        <br>
434eca4a663SGreg Roach        <br>
435eca4a663SGreg Roach    <?php endforeach ?>
436eca4a663SGreg Roach</div>
437