/** * Startup activity */ public function __construct() { parent::__construct(); // Extract the request parameters $this->show_thumbs = Filter::getBool('show_thumbs'); if ($this->root && $this->root->canShowName()) { $this->setPageTitle(I18N::translate('Compact tree of %s', $this->root->getFullName())); } else { $this->setPageTitle(I18N::translate('Compact tree')); } $this->treeid = $this->sosaAncestors(5); }
/** * Create the descendancy controller */ public function __construct() { global $WT_TREE; parent::__construct(); // Extract parameters from form $this->chart_style = Filter::getInteger('chart_style', 0, 3, 0); $this->generations = Filter::getInteger('generations', 2, $WT_TREE->getPreference('MAX_DESCENDANCY_GENERATIONS'), $WT_TREE->getPreference('DEFAULT_PEDIGREE_GENERATIONS')); if ($this->root && $this->root->canShowName()) { $this->setPageTitle(I18N::translate('Descendants of %s', $this->root->getFullName())); } else { $this->setPageTitle(I18N::translate('Descendants')); } }
/** * Startup activity */ public function __construct() { global $WT_TREE; parent::__construct(); // Extract form parameters $this->show_cousins = Filter::getInteger('show_cousins', 0, 1); $this->chart_style = Filter::getInteger('chart_style', 0, 3); $this->generations = Filter::getInteger('PEDIGREE_GENERATIONS', 2, $WT_TREE->getPreference('MAX_PEDIGREE_GENERATIONS'), $WT_TREE->getPreference('DEFAULT_PEDIGREE_GENERATIONS')); if ($this->root && $this->root->canShowName()) { $this->setPageTitle(I18N::translate('Ancestors of %s', $this->root->getFullName())); } else { $this->setPageTitle(I18N::translate('Ancestors')); } }
/** * Create the controller */ public function __construct() { global $WT_TREE; parent::__construct(); $default_generations = $WT_TREE->getPreference('DEFAULT_PEDIGREE_GENERATIONS'); // Extract the request parameters $this->fan_style = Filter::getInteger('fan_style', 2, 4, 3); $this->fan_width = Filter::getInteger('fan_width', 50, 500, 100); $this->generations = Filter::getInteger('generations', 2, 9, $default_generations); if ($this->root && $this->root->canShowName()) { $this->setPageTitle(I18N::translate('Fan chart of %s', $this->root->getFullName())); } else { $this->setPageTitle(I18N::translate('Fan chart')); } }
/** * Create a family-book controller */ public function __construct() { global $WT_TREE; parent::__construct(); // Extract the request parameters $this->show_spouse = Filter::getInteger('show_spouse', 0, 1); $this->descent = Filter::getInteger('descent', 0, 9, 5); $this->generations = Filter::getInteger('generations', 2, $WT_TREE->getPreference('MAX_DESCENDANCY_GENERATIONS'), 2); $this->bhalfheight = $this->getBoxDimensions()->height / 2; if ($this->root && $this->root->canShowName()) { $this->setPageTitle(I18N::translate('Family book of %s', $this->root->getFullName())); } else { $this->setPageTitle(I18N::translate('Family book')); } //Checks how many generations of descendency is for the person for formatting purposes $this->dgenerations = $this->maxDescendencyGenerations($this->root->getXref(), 0); if ($this->dgenerations < 1) { $this->dgenerations = 1; } }
/** * Create the hourglass controller. * * @param string $rootid * @param int $show_full * @param bool $loadJS */ public function __construct($rootid = '', $show_full = 1, $loadJS = true) { global $WT_TREE; parent::__construct($show_full); // Extract parameters from $this->show_spouse = Filter::getInteger('show_spouse', 0, 1, 0); $this->generations = Filter::getInteger('generations', 2, $WT_TREE->getPreference('MAX_DESCENDANCY_GENERATIONS'), 3); $this->canLoadJS = $loadJS; //-- flip the arrows for RTL languages if (I18N::direction() === 'ltr') { $this->left_arrow = 'icon-larrow'; $this->right_arrow = 'icon-rarrow'; } else { $this->left_arrow = 'icon-rarrow'; $this->right_arrow = 'icon-larrow'; } $this->bhalfheight = (int) ($this->getBoxDimensions()->height / 2); //Checks how many generations of descendency is for the person for formatting purposes $this->dgenerations = $this->maxDescendencyGenerations($this->root, 0); if ($this->dgenerations < 1) { $this->dgenerations = 1; } $this->setPageTitle(I18N::translate('Hourglass chart of %s', $this->root->getFullName())); }
/** * This is a general purpose hook, allowing modules to respond to routes * of the form module.php?mod=FOO&mod_action=BAR * * @param string $mod_action */ public function modAction($mod_action) { global $controller, $WT_TREE; switch ($mod_action) { case 'treeview': $controller = new ChartController(); $tv = new TreeView('tv'); ob_start(); $person = $controller->getSignificantIndividual(); list($html, $js) = $tv->drawViewport($person, 4); $controller->setPageTitle(I18N::translate('Interactive tree of %s', $person->getFullName()))->pageHeader()->addExternalJavascript($this->js())->addExternalJavascript(WT_JQUERYUI_TOUCH_PUNCH_URL)->addInlineJavascript($js)->addInlineJavascript(' if (document.createStyleSheet) { document.createStyleSheet("' . $this->css() . '"); // For Internet Explorer } else { jQuery("head").append(\'<link rel="stylesheet" type="text/css" href="' . $this->css() . '">\'); } '); echo $html; break; case 'getDetails': header('Content-Type: text/html; charset=UTF-8'); $pid = Filter::get('pid', WT_REGEX_XREF); $i = Filter::get('instance'); $tv = new TreeView($i); $individual = Individual::getInstance($pid, $WT_TREE); if ($individual) { echo $tv->getDetails($individual); } break; case 'getPersons': header('Content-Type: text/html; charset=UTF-8'); $q = Filter::get('q'); $i = Filter::get('instance'); $tv = new TreeView($i); echo $tv->getPersons($q); break; default: http_response_code(404); break; } }
/** * Display a map showing the originas of ones ancestors. */ private function pedigreeMap() { global $controller, $WT_TREE; $MAX_PEDIGREE_GENERATIONS = $WT_TREE->getPreference('MAX_PEDIGREE_GENERATIONS'); $controller = new ChartController(); $this->generations = Filter::getInteger('PEDIGREE_GENERATIONS', 2, $WT_TREE->getPreference('MAX_PEDIGREE_GENERATIONS'), $WT_TREE->getPreference('DEFAULT_PEDIGREE_GENERATIONS')); $this->treesize = pow(2, $this->generations) - 1; $this->ancestors = array_values($controller->sosaAncestors($this->generations)); // Start of internal configuration variables // Limit this to match available number of icons. // 8 generations equals 255 individuals $MAX_PEDIGREE_GENERATIONS = min($MAX_PEDIGREE_GENERATIONS, 8); // End of internal configuration variables $controller->setPageTitle(I18N::translate('Pedigree map of %s', $controller->root->getFullName()))->pageHeader()->addExternalJavascript(WT_AUTOCOMPLETE_JS_URL)->addInlineJavascript('autocomplete();'); echo '<link type="text/css" href="', WT_STATIC_URL, WT_MODULES_DIR, 'googlemap/css/wt_v3_googlemap.css" rel="stylesheet">'; echo '<div id="pedigreemap-page"> <h2>', $controller->getPageTitle(), '</h2>'; // -- print the form to change the number of displayed generations ?> <form name="people" method="get" action="?"> <input type="hidden" name="ged" value="<?php echo $WT_TREE->getNameHtml(); ?> "> <input type="hidden" name="mod" value="googlemap"> <input type="hidden" name="mod_action" value="pedigree_map"> <table class="list_table" width="555"> <tr> <td class="descriptionbox wrap"> <?php echo I18N::translate('Individual'); ?> </td> <td class="optionbox"> <input class="pedigree_form" data-autocomplete-type="INDI" type="text" id="rootid" name="rootid" size="3" value="<?php echo $controller->root->getXref(); ?> "> <?php echo FunctionsPrint::printFindIndividualLink('rootid'); ?> </td> <td class="topbottombar" rowspan="2"> <input type="submit" value="<?php echo I18N::translate('View'); ?> "> </td> </tr> <tr> <td class="descriptionbox wrap"> <?php echo I18N::translate('Generations'); ?> </td> <td class="optionbox"> <select name="PEDIGREE_GENERATIONS"> <?php for ($p = 3; $p <= $MAX_PEDIGREE_GENERATIONS; $p++) { echo '<option value="', $p, '" '; if ($p == $this->generations) { echo 'selected'; } echo '>', $p, '</option>'; } ?> </select> </td> </tr> </table> </form> <!-- end of form --> <!-- count records by type --> <?php $curgen = 1; $priv = 0; $count = 0; $miscount = 0; $missing = ''; $latlongval = array(); $lat = array(); $lon = array(); for ($i = 0; $i < $this->treesize; $i++) { // -- check to see if we have moved to the next generation if ($i + 1 >= pow(2, $curgen)) { $curgen++; } $person = $this->ancestors[$i]; if (!empty($person)) { $name = $person->getFullName(); if ($name == I18N::translate('Private')) { $priv++; } $place = $person->getBirthPlace(); if (empty($place)) { $latlongval[$i] = null; } else { $latlongval[$i] = $this->getLatitudeAndLongitudeFromPlaceLocation($person->getBirthPlace()); } if ($latlongval[$i]) { $lat[$i] = str_replace(array('N', 'S', ','), array('', '-', '.'), $latlongval[$i]->pl_lati); $lon[$i] = str_replace(array('E', 'W', ','), array('', '-', '.'), $latlongval[$i]->pl_long); if ($lat[$i] && $lon[$i]) { $count++; } else { // The place is in the table but has empty values if ($name) { if ($missing) { $missing .= ', '; } $missing .= '<a href="' . $person->getHtmlUrl() . '">' . $name . '</a>'; $miscount++; } } } else { // There was no place, or not listed in the map table if ($name) { if ($missing) { $missing .= ', '; } $missing .= '<a href="' . $person->getHtmlUrl() . '">' . $name . '</a>'; $miscount++; } } } } //<!-- end of count records by type --> //<!-- start of map display --> echo '<div id="pedigreemap_chart">'; echo '<table class="tabs_table" cellspacing="0" cellpadding="0" border="0" width="100%">'; echo '<tr>'; echo '<td valign="top">'; echo '<div id="pm_map" style="border: 1px solid gray; height: ', $this->getSetting('GM_YSIZE'), 'px; font-size: 0.9em;'; echo '"><i class="icon-loading-large"></i></div>'; if (Auth::isAdmin()) { echo '<table width="100%">'; echo '<tr><td align="left">'; echo '<a href="module.php?mod=googlemap&mod_action=admin_config">', I18N::translate('Google Maps™ preferences'), '</a>'; echo '</td>'; echo '<td align="center">'; echo '<a href="module.php?mod=googlemap&mod_action=admin_places">', I18N::translate('Geographic data'), '</a>'; echo '</td>'; echo '<td align="right">'; echo '<a href="module.php?mod=googlemap&mod_action=admin_placecheck">', I18N::translate('Place check'), '</a>'; echo '</td></tr>'; echo '</table>'; } echo '</td><td width="15px"></td>'; echo '<td width="310px" valign="top">'; echo '<div id="side_bar" style="width:300px; font-size:0.9em; overflow:auto; overflow-x:hidden; overflow-y:auto; height:', $this->getSetting('GM_YSIZE'), 'px;"></div></td>'; echo '</tr>'; echo '</table>'; // display info under map echo '<hr>'; echo '<table cellspacing="0" cellpadding="0" border="0" width="100%">'; echo '<tr>'; echo '<td valign="top">'; // print summary statistics if (isset($curgen)) { $total = pow(2, $curgen) - 1; echo I18N::plural('%1$s individual displayed, out of the normal total of %2$s, from %3$s generations.', '%1$s individuals displayed, out of the normal total of %2$s, from %3$s generations.', $count, I18N::number($count), I18N::number($total), I18N::number($curgen)), '<br>'; echo '</td>'; echo '</tr>'; echo '<tr>'; echo '<td valign="top">'; if ($priv) { echo I18N::plural('%s individual is private.', '%s individuals are private.', $priv, $priv), '<br>'; } if ($count + $priv != $total) { if ($miscount == 0) { echo I18N::translate('No ancestors in the database.'), "<br>"; } else { echo I18N::plural('%1$s individual is missing birthplace map coordinates: %2$s.', '%1$s individuals are missing birthplace map coordinates: %2$s.', $miscount, I18N::number($miscount), $missing), '<br>'; } } } echo '</td>'; echo '</tr>'; echo '</table>'; echo '</div>'; // close #pedigreemap_chart echo '</div>'; // close #pedigreemap-page ?> <!-- end of map display --> <!-- Start of map scripts --> <?php echo '<script src="', $this->googleMapsScript(), '"></script>'; $controller->addInlineJavascript($this->pedigreeMapJavascript()); }
/** * Display a map showing the origins of ones ancestors. */ private function pedigreeMap() { global $controller, $WT_TREE; $MAX_PEDIGREE_GENERATIONS = $WT_TREE->getPreference('MAX_PEDIGREE_GENERATIONS'); // Limit this to match available number of icons. // 8 generations equals 255 individuals $MAX_PEDIGREE_GENERATIONS = min($MAX_PEDIGREE_GENERATIONS, 8); $controller = new ChartController(); $generations = Filter::getInteger('PEDIGREE_GENERATIONS', 2, $MAX_PEDIGREE_GENERATIONS, $WT_TREE->getPreference('DEFAULT_PEDIGREE_GENERATIONS')); $this->treesize = pow(2, $generations) - 1; $this->ancestors = array_values($controller->sosaAncestors($generations)); $controller->setPageTitle(I18N::translate('Pedigree map of %s', $controller->root->getFullName()))->pageHeader()->addExternalJavascript(WT_AUTOCOMPLETE_JS_URL)->addInlineJavascript("\n\t\t\t\tjQuery('head').prepend('<link type=\"text/css\" href =\"" . WT_STATIC_URL . WT_MODULES_DIR . "googlemap/css/wt_v3_googlemap.css\" rel=\"stylesheet\">');\n\t\t\t\tautocomplete();" . $this->pedigreeMapJavascript()); echo '<div id="pedigreemap-page"><h2>', $controller->getPageTitle(), '</h2>'; // -- print the form to change the number of displayed generations ?> <form name="people" method="get" action="?"> <input type="hidden" name="ged" value="<?php echo $WT_TREE->getNameHtml(); ?> "> <input type="hidden" name="mod" value="googlemap"> <input type="hidden" name="mod_action" value="pedigree_map"> <table class="list_table"> <tr> <td class="descriptionbox wrap"> <label for="rootid"> <?php echo I18N::translate('Individual'); ?> </label> </td> <td class="optionbox"> <input class="pedigree_form" data-autocomplete-type="INDI" type="text" id="rootid" name="rootid" size="3" value="<?php echo $controller->root->getXref(); ?> "> <?php echo FunctionsPrint::printFindIndividualLink('rootid'); ?> </td> <td class="topbottombar" rowspan="2"> <input type="submit" value="<?php echo I18N::translate('view'); ?> "> </td> </tr> <tr> <td class="descriptionbox wrap"> <label for="PEDIGREE_GENERATIONS"> <?php echo I18N::translate('Generations'); ?> </label> </td> <td class="optionbox"> <select name="PEDIGREE_GENERATIONS" id="PEDIGREE_GENERATIONS"> <?php for ($p = 3; $p <= $MAX_PEDIGREE_GENERATIONS; $p++) { echo '<option value="', $p, '" '; if ($p == $generations) { echo 'selected'; } echo '>', $p, '</option>'; } ?> </select> </td> </tr> </table> </form> <!-- end of form --> <!-- count records by type --> <?php $curgen = 1; $priv = 0; $count = 0; $miscount = 0; $missing = array(); $latlongval = array(); $lat = array(); $lon = array(); for ($i = 0; $i < $this->treesize; $i++) { // -- check to see if we have moved to the next generation if ($i + 1 >= pow(2, $curgen)) { $curgen++; } $person = $this->ancestors[$i]; if (!empty($person)) { $name = $person->getFullName(); if ($name == I18N::translate('Private')) { $priv++; } $place = $person->getBirthPlace(); if (empty($place)) { $latlongval[$i] = null; } else { $latlongval[$i] = $this->getLatitudeAndLongitudeFromPlaceLocation($person->getBirthPlace()); } if ($latlongval[$i]) { $lat[$i] = strtr($latlongval[$i]->pl_lati, array('N' => '', 'S' => '-', ',' => '.')); $lon[$i] = strtr($latlongval[$i]->pl_long, array('N' => '', 'S' => '-', ',' => '.')); if ($lat[$i] && $lon[$i]) { $count++; } else { // The place is in the table but has empty values if ($name) { $missing[] = '<a href="' . $person->getHtmlUrl() . '">' . $name . '</a>'; $miscount++; } } } else { // There was no place, or not listed in the map table if ($name) { $missing[] = '<a href="' . $person->getHtmlUrl() . '">' . $name . '</a>'; $miscount++; } } } } //<!-- end of count records by type --> //<!-- start of map display --> echo '<div class="gm-pedigree-map">'; echo '<div class="gm-wrapper">'; echo '<div class="gm-map"><i class="icon-loading-large"></i></div>'; echo '<div class="gm-ancestors"></div>'; echo '</div>'; if (Auth::isAdmin()) { echo '<div class="gm-options noprint">'; echo '<a href="module.php?mod=' . $this->getName() . '&mod_action=admin_config">' . I18N::translate('Google Maps™ preferences') . '</a>'; echo ' | <a href="module.php?mod=' . $this->getName() . '&mod_action=admin_places">' . I18N::translate('Geographic data') . '</a>'; echo ' | <a href="module.php?mod=' . $this->getName() . '&mod_action=admin_placecheck">' . I18N::translate('Place check') . '</a>'; echo '</div>'; } // display info under map echo '<hr>'; // print summary statistics if (isset($curgen)) { $total = pow(2, $curgen) - 1; echo '<div>'; echo I18N::plural('%1$s individual displayed, out of the normal total of %2$s, from %3$s generations.', '%1$s individuals displayed, out of the normal total of %2$s, from %3$s generations.', $count, I18N::number($count), I18N::number($total), I18N::number($curgen)); echo '</div>'; if ($priv) { echo '<div>' . I18N::plural('%s individual is private.', '%s individuals are private.', $priv, $priv), '</div>'; } if ($count + $priv != $total) { if ($miscount == 0) { echo '<div>' . I18N::translate('No ancestors in the database.'), '</div>'; } else { echo '<div>' . I18N::plural('%1$s individual is missing birthplace map coordinates: %2$s.', '%1$s individuals are missing birthplace map coordinates: %2$s.', $miscount, I18N::number($miscount), implode(I18N::$list_separator, $missing)), '</div>'; } } } echo '</div>'; echo '</div>'; echo '<script src="', $this->googleMapsScript(), '"></script>'; }
/** * Create a pedigree controller */ public function __construct() { global $WT_TREE; parent::__construct(); $this->orientation = Filter::getInteger('orientation', 0, 3, $WT_TREE->getPreference('PEDIGREE_LAYOUT')); $this->generations = Filter::getInteger('PEDIGREE_GENERATIONS', 2, $WT_TREE->getPreference('MAX_PEDIGREE_GENERATIONS'), $WT_TREE->getPreference('DEFAULT_PEDIGREE_GENERATIONS')); $bxspacing = Theme::theme()->parameter('chart-spacing-x'); $byspacing = Theme::theme()->parameter('chart-spacing-y'); $curgen = 1; // -- track which generation the algorithm is currently working on $addoffset = array(); // With more than 8 generations, we run out of pixels on the <canvas> if ($this->generations > 8) { $this->generations = 8; } if ($this->root && $this->root->canShowName()) { $this->setPageTitle(I18N::translate('Pedigree tree of %s', $this->root->getFullName())); } else { $this->setPageTitle(I18N::translate('Pedigree')); } $this->treesize = pow(2, $this->generations) - 1; // sosaAncestors() starts array at index 1 we need to start at 0 $this->nodes = array_map(function ($item) { return array('indi' => $item, 'x' => 0, 'y' => 0); }, array_values($this->sosaAncestors($this->generations))); //check earliest generation for any ancestors for ($i = (int) ceil($this->treesize / 2); $i < $this->treesize; $i++) { $this->chartHasAncestors = $this->chartHasAncestors || $this->nodes[$i]['indi'] && $this->nodes[$i]['indi']->getChildFamilies(); } $this->arrows = new \stdClass(); switch ($this->orientation) { case self::PORTRAIT: //drop through //drop through case self::LANDSCAPE: $this->arrows->prevGen = I18N::direction() === 'rtl' ? 'icon-larrow' : 'icon-rarrow'; $this->arrows->menu = I18N::direction() === 'rtl' ? 'icon-rarrow' : 'icon-larrow'; $addoffset['x'] = $this->chartHasAncestors ? self::ARROW_SIZE : 0; $addoffset['y'] = 0; break; case self::OLDEST_AT_TOP: $this->arrows->prevGen = 'icon-uarrow'; $this->arrows->menu = 'icon-darrow'; $addoffset['x'] = 0; $addoffset['y'] = $this->root->getSpouseFamilies() ? self::ARROW_SIZE : 0; break; case self::OLDEST_AT_BOTTOM: $this->arrows->prevGen = 'icon-darrow'; $this->arrows->menu = 'icon-uarrow'; $addoffset['x'] = 0; $addoffset['y'] = $this->chartHasAncestors ? self::ARROW_SIZE : 0; break; } // -- this next section will create and position the DIV layers for the pedigree tree // -- loop through all of IDs in the array from last to first // -- calculating the box positions for ($i = $this->treesize - 1; $i >= 0; $i--) { // -- check to see if we have moved to the next generation if ($i < (int) ($this->treesize / pow(2, $curgen))) { $curgen++; } // -- box position in current generation $boxpos = $i - pow(2, $this->generations - $curgen); // -- offset multiple for current generation if ($this->orientation < self::OLDEST_AT_TOP) { $genoffset = pow(2, $curgen - $this->orientation); $boxspacing = $this->getBoxDimensions()->height + $byspacing; } else { $genoffset = pow(2, $curgen - 1); $boxspacing = $this->getBoxDimensions()->width + $byspacing; } // -- calculate the yoffset position in the generation put child between parents $yoffset = $boxpos * ($boxspacing * $genoffset) + $boxspacing / 2 * $genoffset + $boxspacing * $genoffset; // -- calculate the xoffset switch ($this->orientation) { case self::PORTRAIT: $xoffset = ($this->generations - $curgen) * (($this->getBoxDimensions()->width + $bxspacing) / 1.8); if (!$i && $this->root->getSpouseFamilies()) { $xoffset -= self::ARROW_SIZE; } // -- compact the tree if ($curgen < $this->generations) { if ($i % 2 == 0) { $yoffset = $yoffset - $boxspacing / 2 * ($curgen - 1); } else { $yoffset = $yoffset + $boxspacing / 2 * ($curgen - 1); } $parent = (int) (($i - 1) / 2); $pgen = $curgen; while ($parent > 0) { if ($parent % 2 == 0) { $yoffset = $yoffset - $boxspacing / 2 * $pgen; } else { $yoffset = $yoffset + $boxspacing / 2 * $pgen; } $pgen++; if ($pgen > 3) { $temp = 0; for ($j = 1; $j < $pgen - 2; $j++) { $temp += pow(2, $j) - 1; } if ($parent % 2 == 0) { $yoffset = $yoffset - $boxspacing / 2 * $temp; } else { $yoffset = $yoffset + $boxspacing / 2 * $temp; } } $parent = (int) (($parent - 1) / 2); } if ($curgen > 3) { $temp = 0; for ($j = 1; $j < $curgen - 2; $j++) { $temp += pow(2, $j) - 1; } if ($i % 2 == 0) { $yoffset = $yoffset - $boxspacing / 2 * $temp; } else { $yoffset = $yoffset + $boxspacing / 2 * $temp; } } } $yoffset -= $boxspacing / 2 * pow(2, $this->generations - 2) - $boxspacing / 2; break; case self::LANDSCAPE: $xoffset = ($this->generations - $curgen) * ($this->getBoxDimensions()->width + $bxspacing); if ($curgen == 1) { $xoffset += 10; } break; case self::OLDEST_AT_TOP: //swap x & y offsets as chart is rotated $xoffset = $yoffset; $yoffset = $curgen * ($this->getBoxDimensions()->height + $byspacing * 4); break; case self::OLDEST_AT_BOTTOM: //swap x & y offsets as chart is rotated $xoffset = $yoffset; $yoffset = ($this->generations - $curgen) * ($this->getBoxDimensions()->height + $byspacing * 2); if ($i && $this->root->getSpouseFamilies()) { $yoffset += self::ARROW_SIZE; } break; } $this->nodes[$i]["x"] = (int) $xoffset; $this->nodes[$i]["y"] = (int) $yoffset; } // find the minimum x & y offsets and deduct that number from // each value in the array so that offsets start from zero $min_xoffset = min(array_map(function ($item) { return $item['x']; }, $this->nodes)); $min_yoffset = min(array_map(function ($item) { return $item['y']; }, $this->nodes)); array_walk($this->nodes, function (&$item) use($min_xoffset, $min_yoffset) { $item['x'] -= $min_xoffset; $item['y'] -= $min_yoffset; }); // calculate chart & canvas dimensions $max_xoffset = max(array_map(function ($item) { return $item['x']; }, $this->nodes)); $max_yoffset = max(array_map(function ($item) { return $item['y']; }, $this->nodes)); $this->chartsize['x'] = $max_xoffset + $bxspacing + $this->getBoxDimensions()->width + $addoffset['x']; $this->chartsize['y'] = $max_yoffset + $byspacing + $this->getBoxDimensions()->height + $addoffset['y']; }