diff options
| author | David Drury <david@drury.me.uk> | 2015-06-07 11:16:21 +0100 |
|---|---|---|
| committer | Greg Roach <fisharebest@gmail.com> | 2015-06-07 18:01:49 +0100 |
| commit | e3d81ebafd91ad4b59c34a3495fe9aca687c7a6e (patch) | |
| tree | c8e7fadbf0ed21755b57cd7c9c7b7bdf26e42470 /app/Controller | |
| parent | 3e51b7d0bd1ac512a8672eb0dfa574ad368e32f9 (diff) | |
| download | webtrees-e3d81ebafd91ad4b59c34a3495fe9aca687c7a6e.tar.gz webtrees-e3d81ebafd91ad4b59c34a3495fe9aca687c7a6e.tar.bz2 webtrees-e3d81ebafd91ad4b59c34a3495fe9aca687c7a6e.zip | |
Rework lifespan chart
Made look and feel more like other charts
Works for RTL languages
Added support for different calendars
Simplified chart by removing the pan/scroll arrows and replaced with scrollbars
Removed birth and death markers and replaced with a popup showing key events when the sex-image/name/lifespan on the bar is hovered over - this also replaces the existing (non functional) code which should have placed key event markers (and a popup) along the length of the bar.
popups use code structure found elsewhere in webtrees
Generate bar colors on the fly using the hsla model (doesn't work for ie8 which gets pink for a girl, blue for a boy)
For short lifespans sensibly trucate the info on the bar
The popup always includes vital events and then only other events that meets the search criteria
Hide form on print
If searching a place, now include marriage events
Diffstat (limited to 'app/Controller')
| -rw-r--r-- | app/Controller/LifespanController.php | 785 |
1 files changed, 333 insertions, 452 deletions
diff --git a/app/Controller/LifespanController.php b/app/Controller/LifespanController.php index bcd062a384..94a587e2a3 100644 --- a/app/Controller/LifespanController.php +++ b/app/Controller/LifespanController.php @@ -16,54 +16,64 @@ namespace Fisharebest\Webtrees\Controller; * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -use Fisharebest\ExtCalendar\GregorianCalendar; +use Fisharebest\Webtrees\ColorGenerator; use Fisharebest\Webtrees\Database; use Fisharebest\Webtrees\Date; +use Fisharebest\Webtrees\Fact; use Fisharebest\Webtrees\Filter; use Fisharebest\Webtrees\Functions\Functions; use Fisharebest\Webtrees\GedcomTag; use Fisharebest\Webtrees\I18N; use Fisharebest\Webtrees\Individual; +use Fisharebest\Webtrees\Family; use Fisharebest\Webtrees\Place; use Fisharebest\Webtrees\Session; +use Fisharebest\Webtrees\Date\FrenchDate; +use Fisharebest\Webtrees\Date\GregorianDate; +use Fisharebest\Webtrees\Date\HijriDate; +use Fisharebest\Webtrees\Date\JalaliDate; +use Fisharebest\Webtrees\Date\JewishDate; +use Fisharebest\Webtrees\Date\JulianDate; /** * Class LifespanController - Controller for the timeline chart */ class LifespanController extends PageController { - private $pids = array(); - public $people = array(); - public $place = ''; - public $beginYear = 0; - public $endYear = 0; - public $YrowLoc = 125; - // The following colours are deliberately omitted from the $colors list: - // Blue, Red, Black, White, Green - private $colors = array('Aliceblue', 'Antiquewhite', 'Aqua', 'Aquamarine', 'Azure', 'Beige', 'Bisque', 'Blanchedalmond', 'Blueviolet', 'Brown', 'Burlywood', 'Cadetblue', 'Chartreuse', 'Chocolate', 'Coral', 'Cornflowerblue', 'Cornsilk', 'Crimson', 'Cyan', 'Darkcyan', 'Darkgoldenrod', 'Darkgray', 'Darkgreen', 'Darkkhaki', 'Darkmagenta', 'Darkolivegreen', 'Darkorange', 'Darkorchid', 'Darkred', 'Darksalmon', 'Darkseagreen', 'Darkslateblue', 'Darkturquoise', 'Darkviolet', 'Deeppink', 'Deepskyblue', 'Dimgray', 'Dodgerblue', 'Firebrick', 'Floralwhite', 'Forestgreen', 'Fuchsia', 'Gainsboro', 'Ghostwhite', 'Gold', 'Goldenrod', 'Gray', 'Greenyellow', 'Honeydew', 'Hotpink', 'Indianred', 'Ivory', 'Khaki', 'Lavender', 'Lavenderblush', 'Lawngreen', 'Lemonchiffon', 'Lightblue', 'Lightcoral', 'Lightcyan', 'Lightgoldenrodyellow', 'Lightgreen', 'Lightgrey', 'Lightpink', 'Lightsalmon', 'Lightseagreen', 'Lightskyblue', 'Lightslategray', 'Lightsteelblue', 'Lightyellow', 'Lime', 'Limegreen', 'Linen', 'Magenta', 'Maroon', 'Mediumaqamarine', ' Mediumblue', 'Mediumorchid', 'Mediumpurple', 'Mediumseagreen', 'Mediumslateblue', 'Mediumspringgreen', 'Mediumturquoise', 'Mediumvioletred', 'Mintcream', 'Mistyrose', 'Moccasin', 'Navajowhite', 'Oldlace', 'Olive', 'Olivedrab', 'Orange', 'Orangered', 'Orchid', 'Palegoldenrod', 'Palegreen', 'Paleturquoise', 'Palevioletred', 'Papayawhip', 'Peachpuff', 'Peru', 'Pink', 'Plum', 'Powderblue', 'Purple', 'Rosybrown', 'Royalblue', 'Saddlebrown', 'Salmon', 'Sandybrown', 'Seagreen', 'Seashell', 'Sienna', 'Silver', 'Skyblue', 'Slateblue', 'Slategray', 'Snow', 'Springgreen', 'Steelblue', 'Tan', 'Teal', 'Thistle', 'Tomato', 'Turquoise', 'Violet', 'Wheat', 'Whitesmoke', 'Yellow', 'YellowGreen'); - private $malecolorR = array(' 100', ' 110', ' 120', ' 130', ' 140', ' 150', ' 160', ' 170', ' 180', ' 190', ' 200', ' 210', ' 220', ' 230', ' 240', ' 250'); - private $malecolorG = array(' 100', ' 110', ' 120', ' 130', ' 140', ' 150', ' 160', ' 170', ' 180', ' 190', ' 200', ' 210', ' 220', ' 230', ' 240', ' 250'); - private $malecolorB = 255; - private $femalecolorR = 255; - private $femalecolorG = array(' 100', ' 110', ' 120', ' 130', ' 140', ' 150', ' 160', ' 170', ' 180', ' 190', ' 200', ' 210', ' 220', ' 230', ' 240', ' 250'); - private $femalecolorB = array('250', ' 240', ' 230', ' 220', ' 210', ' 200', ' 190', ' 180', ' 170', ' 160', ' 150', ' 140', ' 130', ' 120', ' 110', ' 100'); - private $color; - private $colorindex; - private $Fcolorindex; - private $Mcolorindex; - private $zoomfactor; - public $timelineMinYear; - public $timelineMaxYear; - private $birthMod; - private $deathMod; - private $endMod = 0; - private $modTest; - private $currentYear; - private $endDate; - private $currentsex; + // Base color parameters + const range = 120; // degrees + const saturation = 100; // percent + const lightness = 30; // percent + const alpha = 0.25; + + const chartTop = 10; // px + const barSpacing = 22; // px + const yearSpan = 10; // No. years per scale section + const pixelsPerYear = 7; // No. pixels to shift per year + const sessionData = 'lifespan_data'; - private $nonfacts = array( - 'FAMS', 'FAMC', 'MAY', 'BLOB', 'OBJE', 'SEX', 'NAME', 'SOUR', 'NOTE', 'BAPL', 'ENDL', 'SLGC', 'SLGS', '_TODO', '_WT_OBJE_SORT', 'CHAN', 'HUSB', 'WIFE', 'CHIL', 'BIRT', 'DEAT', 'BURI', + // Used in lifespan.php + public $place = null; + public $beginYear = null; + public $endYear = null; + public $subtitle = ' '; + public $showDetails; + + private $people = array(); + private $defaultCalendar; + private $calendar; + private $calendarEscape; + private $timelineMinYear; + private $timelineMaxYear; + private $currentYear; + private $colors = array(); + private $place_obj = null; + private $startDate = null; + private $endDate = null; + private $strictDate; + private $facts; + private $nonfacts = array( + 'FAMS', 'FAMC', 'MAY', 'BLOB', 'OBJE', 'SEX', 'NAME', 'SOUR', 'NOTE', 'BAPL', 'ENDL', 'SLGC', 'SLGS', '_TODO', '_WT_OBJE_SORT', 'CHAN', 'HUSB', 'WIFE', 'CHIL', 'OCCU', 'ASSO' ); /** @@ -74,541 +84,412 @@ class LifespanController extends PageController { parent::__construct(); $this->setPageTitle(I18N::translate('Lifespans')); + $this->showDetails = $WT_TREE->getPreference('PEDIGREE_FULL_DETAILS', true) ? 'checked' : ''; - $this->colorindex = 0; - $this->Fcolorindex = 0; - $this->Mcolorindex = 0; - $this->zoomfactor = 10; - $this->color = '#0000FF'; - $this->currentYear = (int) date('Y'); - $this->deathMod = 0; - $this->endDate = $this->currentYear; + $this->facts = explode('|', WT_EVENTS_BIRT . '|' . WT_EVENTS_DEAT . '|' . WT_EVENTS_MARR . '|' . WT_EVENTS_DIV); + $tmp = explode('\\', get_class(I18N::defaultCalendar())); + $cal = strtolower(array_pop($tmp)); + $this->defaultCalendar = str_replace('calendar', '', $cal); + $filterPids = false; // Request parameters - $newpid = Filter::get('newpid', WT_REGEX_XREF); - $remove = Filter::get('remove', WT_REGEX_XREF); - $pids = Filter::getArray('pids', WT_REGEX_XREF); - $clear = Filter::getBool('clear'); - $addfam = Filter::getBool('addFamily'); - $place = Filter::get('place'); - $beginYear = Filter::getInteger('beginYear', 0, $this->currentYear + 100, 0); - $endYear = Filter::getInteger('endYear', 0, $this->currentYear + 100, 0); + $clear = Filter::postBool('clear'); + $newpid = Filter::post('newpid', WT_REGEX_XREF); + $addfam = Filter::postBool('addFamily'); + $this->place = Filter::post('place'); + $this->beginYear = Filter::postInteger('beginYear', 0, PHP_INT_MAX, null); + $this->endYear = Filter::postInteger('endYear', 0, PHP_INT_MAX, null); + $this->calendar = Filter::post('calendar', null, $this->defaultCalendar); + $this->strictDate = Filter::postBool('strictDate'); - $new_person = Individual::getInstance($newpid, $WT_TREE); + // Set up base color parameters + $this->colors['M'] = new ColorGenerator(240, self::saturation, self::lightness, self::alpha, self::range * -1); + $this->colors['F'] = new ColorGenerator(000, self::saturation, self::lightness, self::alpha, self::range); + // Build a list of people based on the input parameters if ($clear) { - // Empty list - $this->pids = array(); - } elseif ($pids) { - // List of specified records - $this->pids = $pids; - } elseif ($place) { - // All records found in a place - $wt_place = new Place($place, $WT_TREE); - $this->pids = Database::prepare( - "SELECT DISTINCT pl_gid FROM `##placelinks` WHERE pl_p_id = ? AND pl_file = ?" - )->execute(array($wt_place->getPlaceId(), $WT_TREE->getTreeId()))->fetchOneColumn(); - $this->place = $place; + // Empty list & reset form + $xrefs = array(); + $this->place = null; + $this->beginYear = null; + $this->endYear = null; + $this->calendar = $this->defaultCalendar; + } elseif ($this->place) { + // Get all individual & family records found for a place + $this->place_obj = new Place($this->place, $WT_TREE); + $xrefs = Database::prepare( + "SELECT DISTINCT `i_id` FROM `##placelinks`" . + " JOIN `##individuals` ON `pl_gid`=`i_id` AND `pl_file`=`i_file`" . + " WHERE `i_file`=:tree_id" . + " AND `pl_p_id`=:place_id" . + " UNION" . + " SELECT DISTINCT `f_id` FROM `##placelinks`" . + " JOIN `##families` ON `pl_gid`=`f_id` AND `pl_file`=`f_file`" . + " WHERE `f_file`=:tree_id" . + " AND `pl_p_id`=:place_id" + )->execute(array( + 'tree_id' => $WT_TREE->getTreeId(), + 'place_id' => $this->place_obj->getPlaceId() + ))->fetchOneColumn(); } else { // Modify an existing list of records - if (is_array(Session::get('timeline_pids'))) { - $this->pids = Session::get('timeline_pids'); - } else { - $this->pids = array(); - } - if ($remove) { - foreach ($this->pids as $key => $value) { - if ($value == $remove) { - unset($this->pids[$key]); - } - } - } elseif ($new_person) { - $this->addFamily($new_person, $addfam); - } elseif (!$this->pids) { - $this->addFamily($this->getSignificantIndividual(), false); + $xrefs = Session::get(self::sessionData, array()); + if ($newpid) { + $xrefs = array_merge($xrefs, $this->addFamily(Individual::getInstance($newpid, $WT_TREE), $addfam)); + $xrefs = array_unique($xrefs); + } elseif (!$xrefs) { + $xrefs = $this->addFamily($this->getSignificantIndividual(), false); } } - Session::put('timeline_pids', $this->pids); - $this->beginYear = $beginYear; - $this->endYear = $endYear; - if ($beginYear == 0 || $endYear == 0) { - //-- cleanup user input - $this->pids = array_unique($this->pids); //removes duplicates - foreach ($this->pids as $key => $value) { - if ($value != $remove) { - $this->pids[$key] = $value; - $person = Individual::getInstance($value, $WT_TREE); - // list of linked records includes families as well as individuals. - if ($person) { - $bdate = $person->getEstimatedBirthDate(); - if ($bdate->isOK() && $person->canShow()) { - $this->people[] = $person; - } - } + $tmp = $this->getCalendarDate(unixtojd()); + $this->currentYear = $tmp->today()->y; + + $tmp = strtoupper(strtr($this->calendar, + array('jewish' => 'hebrew', + 'french' => 'french r' + ))); + $this->calendarEscape = sprintf("@#D%s@", $tmp); + + if ($xrefs) { + // ensure date ranges are valid in preparation for filtering list + if ($this->beginYear || $this->endYear) { + $filterPids = true; + if (!$this->beginYear) { + $tmp = new Date($this->calendarEscape . ' 1'); + $this->beginYear = $tmp->minimumDate()->y; + } + if (!$this->endYear) { + $this->endYear = $this->currentYear; } + $this->startDate = new Date($this->calendarEscape . $this->beginYear); + $this->endDate = new Date($this->calendarEscape . $this->endYear); } - } else { - //--Finds if the begin year and end year textboxes are not empty - //-- reset the people array when doing a year range search - $this->people = array(); - //Takes the begining year and end year passed by the postback and modifies them and uses them to populate - //the time line - - //Variables to restrict the person boxes to the year searched. - //--Searches for individuals who had an even between the year begin and end years - $indis = self::searchIndividualsInYearRange($beginYear, $endYear); - //--Populates an array of people that had an event within those years - foreach ($indis as $person) { - if (empty($searchplace) || in_array($person->getXref(), $this->pids)) { - $bdate = $person->getEstimatedBirthDate(); - if ($bdate->isOK() && $person->canShow()) { - $this->people[] = $person; + // Test each xref to see if the search criteria are met + foreach ($xrefs as $key => $xref) { + $valid = false; + $person = Individual::getInstance($xref, $WT_TREE); + if ($person) { + if ($person->canShow()) { + foreach ($person->getFacts() as $fact) { + if ($this->checkFact($fact)) { + $this->people[] = $person; + $valid = true; + break; + } + } } + } else { + $family = Family::getInstance($xref, $WT_TREE); + if ($family && $family->canShow() && $this->checkFact($family->getMarriage())) { + $valid = true; + $this->people[] = $family->getHusband(); + $this->people[] = $family->getWife(); + } + } + if (!$valid) { + unset($xrefs[$key]); // no point in storing a xref if we can't use it } } - Session::forget('timeline_pids'); + Session::put(self::sessionData, $xrefs); + } else { + Session::forget(self::sessionData); } - // Sort the array in order of birth year - uasort($this->people, function (Individual $a, Individual $b) { - return Date::compare($a->getEstimatedBirthDate(), $b->getEstimatedBirthDate()); - }); - //If there is people in the array posted back this if occurs - if (isset($this->people[0])) { - //Find the maximum Death year and mimimum Birth year for each individual returned in the array. - $bdate = $this->people[0]->getEstimatedBirthDate(); - $ddate = $this->people[0]->getEstimatedDeathDate(); - $this->timelineMinYear = $bdate->gregorianYear(); - $this->timelineMaxYear = $ddate->gregorianYear() ? $ddate->gregorianYear() : date('Y'); - foreach ($this->people as $value) { - $bdate = $value->getEstimatedBirthDate(); - $ddate = $value->getEstimatedDeathDate(); - $this->timelineMinYear = min($this->timelineMinYear, $bdate->gregorianYear()); - $this->timelineMaxYear = max($this->timelineMaxYear, $ddate->gregorianYear() ? $ddate->gregorianYear() : date('Y')); + $this->people = array_filter(array_unique($this->people)); + $count = count($this->people); + if ($count) { + // Build the subtitle + if ($this->place && $filterPids) { + $this->subtitle = I18N::plural( + '%s person with events in %s between %s and %s', + '%s people with events in %s between %s and %s', + $count, I18N::number($count), + $this->place, $this->startDate->display(false, '%Y'), $this->endDate->display(false, '%Y') + ); + } elseif ($this->place) { + $this->subtitle = I18N::plural( + '%s person with events in %s', + '%s people with events in %s', + $count, I18N::number($count), + $this->place + ); + } elseif ($filterPids) { + $this->subtitle = I18N::plural( + '%s person with events between %s and %s', + '%s people with events between %s and %s', + $count, I18N::number($count), + $this->startDate->display(false, '%Y'), $this->endDate->display(false, '%Y') + ); + } else { + $this->subtitle = I18N::plural( + '%s person', + '%s people', + $count, I18N::number($count)); } - if ($this->timelineMaxYear > $this->currentYear) { - $this->timelineMaxYear = $this->currentYear; - } + // Sort the array in order of birth year + usort($this->people, function (Individual $a, Individual $b) { + return Date::compare($a->getEstimatedBirthDate(), $b->getEstimatedBirthDate()); + }); + + //Find the mimimum birth year and maximum death year from the individuals in the array. + $bdate = $this->getCalendarDate($this->people[0]->getEstimatedBirthDate()->minimumJulianDay()); + $minyear = $bdate->y; + + $maxyear = array_reduce($this->people, function ($carry, $item) { + $date = $this->getCalendarDate($item->getEstimatedDeathDate()->maximumJulianDay()); + return max($carry, $date->y); + }, 0); + } elseif ($filterPids) { + $minyear = $this->endYear; + $maxyear = $this->endYear; } else { - // Sets the default timeline length - $this->timelineMinYear = date("Y") - 101; - $this->timelineMaxYear = date("Y"); + $minyear = $this->currentYear; + $maxyear = $this->currentYear; } + + $maxyear = min($maxyear, $this->currentYear); // Limit maximum year to current year as we can't forecast the future + $minyear = min($minyear, $maxyear - $WT_TREE->getPreference('MAX_ALIVE_AGE')); // Set default minimum chart length + + $this->timelineMinYear = (int)floor($minyear / 10) * 10; // round down to start of the decade + $this->timelineMaxYear = (int) ceil($maxyear / 10) * 10; // round up to start of next decade } /** * Add a person (and optionally their immediate family members) to the pids array * * @param Individual $person - * @param bool $add_family + * @param boolean $add_family + * @return array */ private function addFamily(Individual $person, $add_family) { - $this->pids[] = $person->getXref(); + $xrefs = array(); + $xrefs[] = $person->getXref(); if ($add_family) { foreach ($person->getSpouseFamilies() as $family) { $spouse = $family->getSpouse($person); if ($spouse) { - $this->pids[] = $spouse->getXref(); + $xrefs[] = $spouse->getXref(); foreach ($family->getChildren() as $child) { - $this->pids[] = $child->getXref(); + $xrefs[] = $child->getXref(); } } } foreach ($person->getChildFamilies() as $family) { foreach ($family->getSpouses() as $parent) { - $this->pids[] = $parent->getXref(); + $xrefs[] = $parent->getXref(); } foreach ($family->getChildren() as $sibling) { if ($person !== $sibling) { - $this->pids[] = $sibling->getXref(); + $xrefs[] = $sibling->getXref(); } } } } + return $xrefs; } /** - * Sets the start year and end year to a factor of 5 - * - * @param int $year - * @param int $key + * Prints the time line scale * - * @return int */ - private function modifyYear($year, $key) { - $temp = $year; - switch ($key) { - case 1 : // rounds beginning year - $this->birthMod = $year % 5; - $year = $year - $this->birthMod; - if ($temp == $year) { - $this->modTest = 0; - } else { - $this->modTest = 1; - } - break; - case 2 : // rounds end year - $this->deathMod = $year % 5; - // Only executed if the year needs to be modified - if ($this->deathMod > 0) { - $this->endMod = 5 - $this->deathMod; - } else { - $this->endMod = 0; - } - $year = $year + $this->endMod; - break; + public function printTimeline() { + $startYear = $this->timelineMinYear; + while ($startYear < $this->timelineMaxYear) { + $date = new Date($this->calendarEscape . $startYear); + echo $date->display(false, '%Y', false); + $startYear += self::yearSpan; } - - return $year; } /** - * Prints the time line + * Populate the timeline * - * @param int $startYear - * @param int $endYear + * @return integer */ - public function printTimeline($startYear, $endYear) { - $leftPosition = 14; //start point - $tickDistance = 50; //length of one timeline section - $top = 65; //top starting position - $yearSpan = 5; //default zoom level - $newStartYear = $this->modifyYear($startYear, 1); //starting date for timeline - $this->timelineMinYear = $newStartYear; - $newEndYear = $this->modifyYear($endYear, 2); //ending date for timeline - $totalYears = $newEndYear - $newStartYear; //length of timeline - $timelineTick = $totalYears / $yearSpan; //calculates the length of the timeline + public function fillTimeline() { - for ($i = 0; $i < $timelineTick; $i++) { - echo "<div class=\"sublinks_cell\" style=\"text-align: left; position: absolute; top: ", $top, "px; left: ", $leftPosition, "px; width: ", $tickDistance, "px;\">$newStartYear<i class=\"icon-lifespan-chunk\"></i></div>"; //onclick="zoomToggle('100px', '100px', '200px', '200px', this);" - $leftPosition += $tickDistance; - $newStartYear += $yearSpan; - } - echo "<div class=\"sublinks_cell\" style=\"text-align: left; position: absolute; top: ", $top, "px; left: ", $leftPosition, "px; width: ", $tickDistance, "px;\">$newStartYear</div>"; - } - - /** - * Method used to place the person boxes onto the timeline - * - * @param Individual[] $ar - * @param int $top - * - * @return int - */ - public function fillTimeline($ar, $top) { - global $maxX, $zindex; - - $zindex = count($ar); - - $rows = array(); - $modFix = 0; - if ($this->modTest == 1) { - $modFix = (9 * $this->birthMod); - } + $rows = array(); + $maxY = self::chartTop; //base case - if (count($ar) == 0) { - return $top; + if (!$this->people) { + return $maxY; } - $maxY = $top; - foreach ($ar as $value) { - //Creates appropriate color scheme to show relationships - $this->currentsex = $value->getSex(); - if ($this->currentsex == "M") { - $this->Mcolorindex++; - if (!isset($this->malecolorR[$this->Mcolorindex])) { - $this->Mcolorindex = 0; - } - $this->malecolorR[$this->Mcolorindex]; - $this->Mcolorindex++; - if (!isset($this->malecolorG[$this->Mcolorindex])) { - $this->Mcolorindex = 0; - } - $this->malecolorG[$this->Mcolorindex]; - $red = dechex($this->malecolorR[$this->Mcolorindex]); - $green = dechex($this->malecolorR[$this->Mcolorindex]); - if (strlen($red) < 2) { - $red = "0" . $red; - } - if (strlen($green) < 2) { - $green = "0" . $green; - } + foreach ($this->people as $person) { - $this->color = "#" . $red . $green . dechex($this->malecolorB); - } elseif ($this->currentsex == "F") { - $this->Fcolorindex++; - if (!isset($this->femalecolorG[$this->Fcolorindex])) { - $this->Fcolorindex = 0; - } - $this->femalecolorG[$this->Fcolorindex]; - $this->Fcolorindex++; - if (!isset($this->femalecolorB[$this->Fcolorindex])) { - $this->Fcolorindex = 0; - } - $this->femalecolorB[$this->Fcolorindex]; - $this->color = "#" . dechex($this->femalecolorR) . dechex($this->femalecolorG[$this->Fcolorindex]) . dechex($this->femalecolorB[$this->Fcolorindex]); - } else { - $this->color = $this->colors[$this->colorindex]; - } - - //set start position and size of person-box according to zoomfactor - $bdate = $value->getEstimatedBirthDate(); - $ddate = $value->getEstimatedDeathDate(); - $birthYear = $bdate->gregorianYear(); - $deathYear = $ddate->gregorianYear() ? $ddate->gregorianYear() : date('Y'); - - $width = ($deathYear - $birthYear) * $this->zoomfactor; - $height = 2 * $this->zoomfactor; - - $startPos = (($birthYear - $this->timelineMinYear) * $this->zoomfactor) + 14 + $modFix; - $minlength = mb_strlen(strip_tags($value->getFullName())) * $this->zoomfactor; - - if ($startPos > 15) { - $startPos = (($birthYear - $this->timelineMinYear) * $this->zoomfactor) + 15; - $width = (($deathYear - $birthYear) * $this->zoomfactor) - 2; - } - - //set minimum width for single year lifespans - if ($width < 10) { - $width = 10; - } - - $lifespan = "<span dir=\"ltr\">$birthYear-</span>"; - $deathReal = $value->getDeathDate()->isOK(); - $birthReal = $value->getBirthDate()->isOK(); - if ($value->isDead() && $deathReal) { - $lifespan .= "<span dir=\"ltr\">$deathYear</span>"; - } - $lifespannumeral = $deathYear - $birthYear; + $bdate = $this->getCalendarDate($person->getEstimatedBirthDate()->minimumJulianDay()); + $ddate = $this->getCalendarDate($person->getEstimatedDeathDate()->maximumJulianDay()); + $birthYear = $bdate->y; + $age = min($ddate->y, $this->currentYear) - $birthYear; // truncate the bar at the current year + $width = max(9, $age * self::pixelsPerYear); // min width is width of sex icon + $startPos = ($birthYear - $this->timelineMinYear) * self::pixelsPerYear; //-- calculate a good Y top value - $Y = $top; - $Z = $zindex; + $Y = self::chartTop; $ready = false; while (!$ready) { if (!isset($rows[$Y])) { $ready = true; $rows[$Y]["x1"] = $startPos; $rows[$Y]["x2"] = $startPos + $width; - $rows[$Y]["z"] = $zindex; } else { if ($rows[$Y]["x1"] > $startPos + $width) { $ready = true; $rows[$Y]["x1"] = $startPos; - $Z = $rows[$Y]["z"]; } elseif ($rows[$Y]["x2"] < $startPos) { $ready = true; $rows[$Y]["x2"] = $startPos + $width; - $Z = $rows[$Y]["z"]; } else { - //move down 25 pixels - if ($this->zoomfactor > 10) { - $Y += 25 + $this->zoomfactor; - } else { - $Y += 25; - } + //move down a line + $Y += self::barSpacing; } } } - //Need to calculate each event and the spacing between them - // event1 distance will be event - birthyear that will be the distance. then each distance will chain off that - - //$event[][] = {"Cell 1 will hold events"}{"cell2 will hold time between that and the next value"}; - $facts = $value->getFacts(); - foreach ($value->getSpouseFamilies() as $family) { + $facts = $person->getFacts(); + foreach ($person->getSpouseFamilies() as $family) { foreach ($family->getFacts() as $fact) { $facts[] = $fact; } } - $unparsedEvents = array(); + Functions::sortFacts($facts); - foreach ($facts as $fact) { - if (!in_array($fact->getTag(), $this->nonfacts)) { - $unparsedEvents[] = $fact; - } - } - Functions::sortFacts($unparsedEvents); + $acceptedFacts = array_filter($facts, function ($fact) { + return (in_array($fact->getTag(), $this->facts) && $fact->getDate()->isOK()) || + (($this->place_obj || $this->startDate) && $this->checkFact($fact)); + }); - $eventinformation = array(); - foreach ($unparsedEvents as $val) { - $date = $val->getDate(); - if (!empty($date)) { - $fact = $val->getTag(); - $yearsin = $date->minimumDate()->y - $birthYear; - if ($lifespannumeral == 0) { - $lifespannumeral = 1; - } - $eventwidth = ($yearsin / $lifespannumeral) * 100; // percent of the lifespan before the event occured used for determining div spacing - // figure out some schema - $evntwdth = $eventwidth . "%"; - //-- if the fact is a generic EVENt then get the qualifying TYPE - if ($fact == "EVEN") { - $fact = $val->getAttribute('TYPE'); - } - $trans = GedcomTag::getLabel($fact); - if (isset($eventinformation[$evntwdth])) { - $eventinformation[$evntwdth] .= '<br>' . $trans . '<br>' . strip_tags($date->display()) . ' ' . $val->getPlace()->getFullName(); - } else { - $eventinformation[$evntwdth] = $fact . '-fact, ' . $trans . '<br>' . strip_tags($date->display()) . ' ' . $val->getPlace()->getFullName(); - } + $eventList = array(); + foreach ($acceptedFacts as $fact) { + $tag = $fact->getTag(); + //-- if the fact is a generic EVENt then get the qualifying TYPE + if ($tag == "EVEN") { + $tag = $fact->getAttribute('TYPE'); } + $eventList[] = array('label' => GedcomTag::getLabel($tag), + 'date' => $fact->getDate()->display(), + 'place' => $fact->getPlace()->getFullName() + ); } + $direction = I18N::direction() === 'ltr' ? 'left' : 'right'; + $lifespan = ' ' . $person->getLifeSpan(); // put the space here so its included in the length calcs + $sex = $person->getSex(); + $popupClass = strtr($sex, array('M' => '', 'U' => 'NN')); + $color = $sex === 'U' ? '' : sprintf("background-color: %s", $this->colors[$sex]->getNextColor()); - $bdate = $value->getEstimatedBirthDate(); - $ddate = $value->getEstimatedDeathDate(); - if ($width > ($minlength + 110)) { - echo "<div id=\"bar_", $value->getXref(), "\" style=\"position: absolute; top:", $Y, "px; left:", $startPos, "px; width:", $width, "px; height:", $height, "px; background-color:", $this->color, "; border: solid blue 1px; z-index:$Z;\">"; - foreach ($eventinformation as $evtwidth => $val) { - echo "<div style=\"position:absolute; left:", $evtwidth, ";\"><a class=\"showit\" href=\"#\" style=\"top:-2px; font-size:10px;\"><b>"; - $text = explode("-fact, ", $val); - $fact = $text[0]; - echo '</b><span>', self::getAbbreviation($fact), '</span></a></div>'; - } - $indiName = $value->getFullName(); - echo '<table><tr><td width="15"><a class="showit" href="#"><b>'; - echo self::getAbbreviation('BIRT'); - echo '</b><span>', $value->getSexImage(), $indiName, '<br>', GedcomTag::getLabel('BIRT'), ' ', strip_tags($bdate->display()), ' ', $value->getBirthPlace(), '</span></a>', - '<td align="left" width="100%"><a href="', $value->getHtmlUrl(), '">', $value->getSexImage(), $indiName, ' ', $lifespan, ' </a></td>', - '<td width="15">'; - if ($value->isDead()) { - if ($deathReal || $value->isDead()) { - echo '<a class="showit" href="#"><b>'; - echo self::getAbbreviation('DEAT'); - if (!$deathReal) { - echo '*'; - } - echo '</b><span>' . $value->getSexImage() . $indiName . '<br>' . GedcomTag::getLabel('DEAT') . ' ' . strip_tags($ddate->display()) . ' ' . $value->getDeathPlace() . '</span></a>'; - } - } - echo '</td></tr></table>'; - echo '</div>'; + // following lines are a nasty method of approximating + // the width of a string in pixels from the character count + $name_length = mb_strlen(strip_tags($person->getFullName())) * 6.5; + $short_name_length = mb_strlen(strip_tags($person->getShortName())) * 6.5; + $lifespan_length = mb_strlen(strip_tags($lifespan)) * 6.5; + + if ($width > $name_length + $lifespan_length) { + $printName = $person->getFullName(); + $abbrLifespan = $lifespan; + } elseif ($width > $name_length) { + $printName = $person->getFullName(); + $abbrLifespan = '…'; + } elseif ($width > $short_name_length) { + $printName = $person->getShortName(); + $abbrLifespan = ''; } else { - if ($width > $minlength + 5) { - echo '<div style="text-align: left; position: absolute; top:', $Y, 'px; left:', $startPos, 'px; width:', $width, 'px; height:', $height, 'px; background-color:', $this->color, '; border: solid blue 1px; z-index:', $Z, '">'; - foreach ($eventinformation as $evtwidth => $val) { - echo '<div style="position:absolute; left:', $evtwidth, ' "><a class="showit" href="#" style="top:-2px; font-size:10px;"><b>'; - $text = explode("-fact,", $val); - $fact = $text[0]; - echo '</b><span>' . self::getAbbreviation($fact) . '</span></a></div>'; - } - $indiName = $value->getFullName(); - echo '<table dir="ltr"><tr><td width="15"><a class="showit" href="#"><b>'; - echo self::getAbbreviation('BIRT'); - if (!$birthReal) { - echo '*'; - } - echo '</b><span>' . $value->getSexImage() . $indiName . '<br>' . GedcomTag::getLabel('BIRT') . ' ' . strip_tags($bdate->display(false)) . ' ' . $value->getBirthPlace() . '</span></a></td>' . '<td align="left" width="100%"><a href="' . $value->getHtmlUrl() . '">' . $value->getSexImage() . $indiName . '</a></td>' . - '<td width="15">'; - if ($value->isDead()) { - if ($deathReal || $value->isDead()) { - echo '<a class="showit" href="#"><b>'; - echo self::getAbbreviation('DEAT'); - if (!$deathReal) { - echo "*"; - } - echo '</b><span>' . $value->getSexImage() . $indiName . '<br>' . GedcomTag::getLabel('DEAT') . ' ' . strip_tags($ddate->display()) . ' ' . $value->getDeathPlace() . '</span></a>'; - } - } - echo '</td></tr></table>'; - echo '</div>'; - } else { - echo '<div style="text-align: left; position: absolute;top:', $Y, 'px; left:', $startPos, 'px;width:', $width, 'px; height:', $height, 'px; background-color:', $this->color, '; border: solid blue 1px; z-index:', $Z, '">'; - $indiName = $value->getFullName(); - echo '<a class="showit" href="' . $value->getHtmlUrl() . '"><b>'; - echo self::getAbbreviation('BIRT'); - echo '</b><span>' . $value->getSexImage() . $indiName . '<br>' . GedcomTag::getLabel('BIRT') . ' ' . strip_tags($bdate->display()) . ' ' . $value->getBirthPlace() . '<br>'; - foreach ($eventinformation as $val) { - $text = explode('-fact,', $val); - $val = $text[1]; - echo $val . "<br>"; - } - if ($value->isDead() && $deathReal) { - echo GedcomTag::getLabel('DEAT') . " " . strip_tags($ddate->display()) . " " . $value->getDeathPlace(); - } - echo '</span></a>'; - echo '</div>'; - } + $printName = ''; + $abbrLifespan = ''; } - $zindex--; - if ($maxX < $startPos + $width) { - $maxX = $startPos + $width; - } - if ($maxY < $Y) { - $maxY = $Y; + // Bar framework + printf(" + <div class='person_box%s' style='top:%spx; %s:%spx; width:%spx; %s'> + <div class='itr'>%s %s %s + <div class='popup person_box%s'> + <div> + <a href='%s'>%s%s</a> + </div>", + $popupClass, $Y, $direction, $startPos, $width, $color, + $person->getSexImage(), $printName, $abbrLifespan, + $popupClass, + $person->getHtmlUrl(), $person->getFullName(), $lifespan + ); + + // Add events to popup + foreach ($eventList as $event) { + printf("<div>%s: %s %s</div>", $event['label'], $event['date'], $event['place']); } - } + echo + "</div>" . // class='popup' + "</div>" . // class='itr' + "</div>"; // class=$popupclass + $maxY = max($maxY, $Y); + } return $maxY; } /** - * The significant individual on this page is the first one. + * Function checkFact * - * @return Individual + * Does this fact meet the search criteria? + * @param Fact $fact + * @return bool */ - public function getSignificantIndividual() { - if ($this->people) { - return $this->people[0]; - } else { - return parent::getSignificantIndividual(); + private function checkFact(Fact $fact) { + $valid = !in_array($fact->getTag(), $this->nonfacts); + if ($valid && $this->place_obj) { + $valid = stripos($fact->getPlace()->getGedcomName(), $this->place_obj->getGedcomName()) !== false; } + if ($valid && $this->startDate) { + if ($this->strictDate && $this->calendar !== $this->defaultCalendar) { + $valid = stripos($fact->getAttribute('DATE'), $this->calendar) !== false; + } + if ($valid) { + $date = $fact->getDate(); + $valid = $date->isOK() && Date::compare($date, $this->startDate) >= 0 && Date::compare($date, $this->endDate) <= 0; + } + } + return $valid; } /** - * Search for people who had events in a given year range - * - * @param int $startyear - * @param int $endyear - * - * @return Individual[] + * Function getCalendarDate + * @param int $date + * @return object */ - private static function searchIndividualsInYearRange($startyear, $endyear) { - global $WT_TREE; - - // At present, the lifespan chart is driven by Gregorian years. - // We ought to allow it to work with other calendars... - $gregorian_calendar = new GregorianCalendar; - - $startjd = $gregorian_calendar->ymdToJd($startyear, 1, 1); - $endjd = $gregorian_calendar->ymdToJd($endyear, 12, 31); - - $rows = Database::prepare( -"SELECT DISTINCT i_id AS xref, i_file AS gedcom_id, i_gedcom AS gedcom" . - " FROM `##individuals`" . - " JOIN `##dates` ON i_id=d_gid AND i_file=d_file" . - " WHERE i_file=? AND d_julianday1 BETWEEN ? AND ?" - )->execute(array($WT_TREE->getTreeId(), $startjd, $endjd))->fetchAll(); - - $list = array(); - foreach ($rows as $row) { - $list[] = Individual::getInstance($row->xref, $WT_TREE, $row->gedcom); + private function getCalendarDate($date) { + switch ($this->calendar) { + case 'julian': + $caldate = new JulianDate($date); + break; + case 'french': + $caldate = new FrenchDate($date); + break; + case 'jewish': + $caldate = new JewishDate($date); + break; + case 'hijri': + $caldate = new HijriDate($date); + break; + case 'jalali': + $caldate = new JalaliDate($date); + break; + default: + $caldate = new GregorianDate($date); } - - return $list; + return $caldate; } /** - * Generate a very short abbreviation for birth/marriage/death - * - * @param string $tag - * + * Function getCalendarOptionList * @return string */ - private static function getAbbreviation($tag) { - switch ($tag) { - case 'BIRT': - return I18N::translateContext('Abbreviation for birth', 'b.'); - case 'MARR': - return I18N::translateContext('Abbreviation for marriage', 'm.'); - case 'DEAT': - return I18N::translateContext('Abbreviation for death', 'd.'); - default: - return mb_substr(GedcomTag::getLabel($tag), 0, 1); // Just use the first letter of the full fact + public function getCalendarOptionList() { + $html = ''; + foreach (Date::calendarNames() as $calendar => $name) { + $selected = $this->calendar === $calendar ? 'selected' : ''; + $html .= sprintf("<option dir='auto' value='%s' %s>%s</option>", $calendar, $selected, $name); } + return $html; } } |
