├── README └── familytree.js /README: -------------------------------------------------------------------------------- 1 | ============================== LICENSE ============================== 2 | 3 | Copyright (c) 2011 Chandler Prall, chandler.prall@gmail.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | 25 | ============================== Instruction ============================== 26 | 27 | FamilyTreeJS is very easy to use. Of course, the first step is to include 28 | the Javascript file in the section of your HTML document. 29 | 30 | After familytree.js has been included you include the code to build your own 31 | family tree . You will need to wrap your code in some way to prevent its 32 | execution until after the DOM has loaded. For example, with jQuery you would 33 | wrap your family tree code with: 34 | 35 | $(document).ready(function() { 36 | // [ ... you code here ... ] 37 | }); 38 | 39 | 40 | 41 | ** Example: 42 | 43 | var tree = new FamilyTreeJS.FamilyTree(); 44 | var chandler = tree.AddPerson('Chandler'); 45 | var moriah = tree.AddPerson('Moriah'); 46 | chandler.birth('Roan', {partner: moriah}); 47 | tree.Render(document.getElementById('dom_element')); 48 | 49 | This will generate a tree showing Chandler and Moriah having a child named Roan. 50 | Any person on the tree who does not have a parent should be added by tree.AddPerson(). 51 | 52 | To avoid having to instantiate a variable for each person in the tree you 53 | can chain any number of births together: 54 | 55 | var roan = tree.AddPerson('Robert'). 56 | birth('Allen'). 57 | birth('Chandler'). 58 | birth('Roan'); 59 | 60 | Obviously this is a pretty boring family tree. There is nothing defining 61 | any of the mothers. It is simple to add them in, however. 62 | 63 | var roan = tree.AddPerson('Robert'). 64 | birth('Allen', {partner: tree.AddPerson('Arlene')}). 65 | birth('Chandler', {partner: tree.AddPerson('Leslie')}). 66 | birth('Roan', {partner: tree.AddPerson('Roan')}); 67 | 68 | Admittedly this syntax is a little confusing because, at first glance, it's easy 69 | to think Chandler was born to Allen and Arlene when his parents are 70 | actually Allen and Leslie. It just takes getting used to. 71 | 72 | Those are some simple trees, now for something a bit 73 | more complex: joining two branches. 74 | 75 | var tree = new FamilyTreeJS.FamilyTree(); 76 | 77 | var henry_i = tree.AddPerson('Robert the 1st'). 78 | birth('William the Conqueror'). 79 | birth('Henry the 1st'); 80 | 81 | var edith = tree.AddPerson('Duncan the 1st'). 82 | birth('Malcolm the 3rd'). 83 | birth('Edith'); 84 | 85 | var matilda = henry_i.birth('Matilda', {partner: edith}); 86 | 87 | 88 | A full example of the library's use can be viewed at 89 | http://chandler.prallfamily.com/2011/05/my-family-tree/ 90 | 91 | Code for that example is located at 92 | http://chandler.prallfamily.com/wp-content/js/pralls.js 93 | 94 | All bugs, questions, or suggestions are welcome at 95 | FamilyTreeJS's Github repo, 96 | https://github.com/chandlerprall/FamilyTreeJS -------------------------------------------------------------------------------- /familytree.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | var merge = function() { 4 | 5 | var merged = {}; 6 | 7 | for (var i = 0; i < arguments.length; i++) { 8 | var source = arguments[i]; 9 | if (typeof source !== 'object') { continue; } // Each argument must be an object 10 | 11 | for (var key in source) { 12 | var value = source[key]; 13 | if (typeof value !== 'object') { 14 | // Simple value 15 | merged[key] = value; 16 | } else { 17 | // Value is an object 18 | if (typeof merged[key] === 'undefined') { 19 | merged[key] = deep_copy(value); 20 | } else { 21 | merged[key] = merge(merged[key], value); 22 | } 23 | } 24 | } 25 | } 26 | 27 | return merged; 28 | 29 | }; 30 | 31 | window['merge'] = merge; 32 | 33 | var deep_copy = function(source) { 34 | 35 | var copied = {}; 36 | 37 | if (typeof source !== 'object') { 38 | return copied; 39 | } 40 | 41 | for (var key in source) { 42 | var value = source[key]; 43 | if (typeof value === 'object') { 44 | value = deep_copy(value); 45 | } 46 | copied[key] = value; 47 | } 48 | 49 | return copied; 50 | 51 | }; 52 | 53 | var nextNodeID = (function() { 54 | var nodes = 0; 55 | return function() { 56 | return 'ft_node_' + nodes++; 57 | } 58 | })() 59 | 60 | window['FamilyTreeJS'] = { 61 | 62 | AUTHOR: 'Chandler Prall ', 63 | VERSION: '.1', 64 | 65 | FamilyTree: function() { 66 | 67 | /** 68 | * FamilyTree private members 69 | */ 70 | var tree_element = null; 71 | var scroll_info = { 'scrolling':false, 'x1':0, 'x2':0, 'y1':0, 'y2':0 }; 72 | 73 | this.config = { 74 | compressable: true, 75 | node: { 76 | fontcolor: 'black', 77 | background: 'white', 78 | height: 30, 79 | width: 100, 80 | borderwidth: 1, 81 | bordercolor: 'black', 82 | spacingVertical: 40, 83 | spacingHorizontal: 15 84 | }, 85 | line: { 86 | offsetY: 0, 87 | width: 2, 88 | color: 'random' 89 | } 90 | }; 91 | 92 | var people = []; // Holds all people in the family tree 93 | 94 | 95 | /** 96 | * Class Person 97 | */ 98 | var Person = function(identity, config, details) { 99 | 100 | this.node_id = nextNodeID(); 101 | this.identity = identity; 102 | this.details = (typeof details !== 'undefined') ? details : {}; 103 | 104 | this.parents = []; 105 | this.children = []; 106 | 107 | this.leveled = false; 108 | this.level = null; 109 | this.rendered = false; 110 | this.on_grid = false; 111 | this.starting_pos = null; 112 | this.connected = false; 113 | 114 | this.node = null; 115 | this.config = config; 116 | this.line_color = null; 117 | 118 | this.birth = function(identity, details) { 119 | 120 | var config = deep_copy(this.config); 121 | if (typeof details !== 'undefined' && typeof details.config !== 'undefined') { 122 | config = merge(config, details.config); 123 | } 124 | 125 | // Create new person 126 | var person = new Person(identity, deep_copy(config), details); 127 | 128 | // Add parent/child relatonship 129 | person.parents.push(this); 130 | this.children.push(person); 131 | if (typeof details !== 'undefined' && typeof details.partner !== 'undefined') { 132 | person.parents.push(details.partner); 133 | details.partner.children.push(person); 134 | } 135 | 136 | people.push(person); 137 | 138 | return person; 139 | 140 | }; 141 | 142 | this.Level = function(level) { 143 | 144 | this.level = level; 145 | this.leveled = true; 146 | 147 | for (var i = 0; i < this.parents.length; i++) { 148 | if (!this.parents[i].leveled) { 149 | this.parents[i].Level(level - 1); 150 | } 151 | } 152 | 153 | for (var i = 0; i < this.children.length; i++) { 154 | if (!this.children[i].leveled) { 155 | this.children[i].Level(level + 1); 156 | } 157 | } 158 | 159 | }; 160 | 161 | this.GetMaxNodeWidth = function() { 162 | 163 | var width = 0; 164 | 165 | var has_children = false; 166 | 167 | for (var i = 0; i < this.children.length; i++) { 168 | if (this.children[i].parents[0] === this) { 169 | has_children = true; 170 | width += this.children[i].GetMaxNodeWidth(); 171 | } 172 | } 173 | 174 | return width + ((has_children) ? 0 : 1); 175 | 176 | }; 177 | 178 | this.FillGrid = function(grid, starting_pos) { 179 | 180 | if (this.on_grid) { 181 | return grid; 182 | } 183 | 184 | if (typeof grid[this.level] === 'undefined') { 185 | grid[this.level] = []; 186 | }; 187 | 188 | this.on_grid = true; 189 | 190 | // Make sure our main parent is on the grid 191 | if (this.parents.length > 0) { 192 | if (typeof grid[this.level-1] === 'undefined') { 193 | grid[this.level-1] = []; 194 | } 195 | //grid = this.parents[0].FillGrid(grid, grid[this.level-1].length); 196 | grid = this.parents[0].FillGrid(grid, starting_pos); 197 | if (this.parents[0].starting_pos > starting_pos) { 198 | starting_pos = this.parents[0].starting_pos; 199 | } 200 | } 201 | 202 | while (typeof grid[this.level][starting_pos] !== 'undefined' && grid[this.level][starting_pos] !== null) { 203 | starting_pos++; 204 | } 205 | 206 | this.starting_pos = starting_pos; 207 | 208 | while (starting_pos > grid[this.level].length) { 209 | grid[this.level].push(null); 210 | } 211 | 212 | grid[this.level][starting_pos] = this; 213 | for (var i = 0; i < this.GetMaxNodeWidth() - 1; i++) { 214 | grid[this.level].push(null); 215 | } 216 | 217 | // Lists of partners & children 218 | var partners = []; 219 | var children = []; 220 | for (var i = 0; i < this.children.length; i++) { 221 | if (this === this.children[i].parents[0]) { 222 | children.push(this.children[i]); 223 | } 224 | 225 | for (var j = 0; j < this.children[i].parents.length; j++) { 226 | if (this !== this.children[i].parents[j]) { 227 | var found = false; 228 | for (var k = 0; k < partners.length; k++) { 229 | if (partners[k] === this.children[i].parents[j]) { 230 | found = true; 231 | } 232 | } 233 | if (!found) { 234 | partners.push(this.children[i].parents[j]); 235 | } 236 | } 237 | } 238 | } 239 | 240 | // Put the partners on our grid 241 | for (var i = 0; i < partners.length; i++) { 242 | grid = partners[i].FillGrid(grid, starting_pos + this.GetMaxNodeWidth()); 243 | } 244 | 245 | // Add our children 246 | var children_aggregate_size = 0; 247 | for (var i = 0; i < children.length; i++) { 248 | grid = children[i].FillGrid(grid, starting_pos + children_aggregate_size); 249 | children_aggregate_size += children[i].GetMaxNodeWidth(); 250 | } 251 | 252 | return grid; 253 | }; 254 | 255 | this.IsChildNo = function() { 256 | 257 | if (this.parents.length === 0) { 258 | return 0; 259 | } 260 | 261 | parent = this.parents[0]; 262 | for (var i = 0; i < parent.children.length; i++) { 263 | if (parent.children[i].node_id === this.node_id) { 264 | return i; 265 | } 266 | } 267 | 268 | }; 269 | 270 | this.DrawConnections = function(target) { 271 | 272 | this.connected = true; 273 | 274 | for (var i = 0; i < this.children.length; i++) { 275 | var child = this.children[i]; 276 | if (!child.connected) { 277 | for (var j = 0; j < child.parents.length; j++) { 278 | var parent = child.parents[j]; 279 | 280 | if (this.line_color === null) { 281 | switch (this.config.line.color) { 282 | case 'random': 283 | this.line_color = 'rgb(' + parseInt(Math.random() * 255) + ',' + parseInt(Math.random() * 255) + ',' + parseInt(Math.random() * 255) + ')'; 284 | break; 285 | case 'inherit': 286 | this.line_color = this.parents[0].line_color; 287 | this.config.line.color = this.line_color; 288 | default: 289 | this.line_color = this.config.line.color; 290 | } 291 | } 292 | 293 | if (child.parents.length > 0 || this !== parent) { 294 | 295 | if (child.parents.length > 1) { 296 | var other_parent = parent; 297 | } 298 | 299 | // Draw line down from me 300 | var shape = AutoshapeJS.createShape({ 301 | 'shape': 'Box', 302 | 'color': this.line_color, 303 | 'width': this.config.line.width + 'px', 304 | 'height': ((this.config.node.spacingVertical / 2) + this.config.line.offsetY) + 'px', 305 | 'position': 'absolute', 306 | 'left': ((this.config.node.borderwidth * 2) + this.node.element.offsetLeft + (this.config.node.width / 2)) + 'px', 307 | 'top': ((this.config.node.borderwidth * 2) + this.node.element.offsetTop + this.config.node.height)+1 + 'px' 308 | }); 309 | shape.attachTo(target); 310 | 311 | if (typeof other_parent !== 'undefined') { 312 | 313 | // Draw line down from other parent 314 | var shape = AutoshapeJS.createShape({ 315 | 'shape': 'Box', 316 | 'color': this.line_color, 317 | 'width': this.config.line.width + 'px', 318 | 'height': (((this.level - other_parent.level)*this.config.node.spacingVertical) + ((this.level - other_parent.level) * (this.config.node.spacingVertical / 2))) + ((this.config.node.spacingVertical / 2) + this.config.line.offsetY) + 'px', 319 | 'position': 'absolute', 320 | 'left': ((this.config.node.borderwidth * 2) + other_parent.node.element.offsetLeft + (this.config.node.width / 2)) + 'px', 321 | 'top': ((this.config.node.borderwidth * 2) + other_parent.node.element.offsetTop + this.config.node.height)+1 + 'px' 322 | }); 323 | shape.attachTo(target); 324 | 325 | // Draw line across to other parent 326 | var shape = AutoshapeJS.createShape({ 327 | 'shape': 'Box', 328 | 'color': this.line_color, 329 | 'width': (this.node.element.offsetLeft < other_parent.node.element.offsetLeft) ? 330 | ((this.config.node.borderwidth * 2) + other_parent.node.element.offsetLeft - this.node.element.offsetLeft) + 'px' 331 | : ((this.config.node.borderwidth * 2) + this.node.element.offsetLeft - other_parent.node.element.offsetLeft) + 'px', 332 | 'height': this.config.line.width + 'px', 333 | 'position': 'absolute', 334 | 'left': (this.node.element.offsetLeft < other_parent.node.element.offsetLeft) ? 335 | ((this.config.node.borderwidth * 2) + this.node.element.offsetLeft + (this.config.node.width / 2)) + 'px' 336 | : ((this.config.node.borderwidth * 2) + other_parent.node.element.offsetLeft + (this.config.node.width / 2)) + 'px', 337 | 'top': ((this.config.node.borderwidth * 2) + other_parent.node.element.offsetTop + this.config.node.height + (this.config.node.spacingVertical / 2) + this.config.line.offsetY) + 'px' 338 | }); 339 | 340 | shape.attachTo(target); 341 | } 342 | 343 | // Draw line across to child 344 | var shape_width = child.node.element.offsetLeft - this.node.element.offsetLeft; 345 | if (shape_width < 0) shape_width *= -1; 346 | var shape_left = (this.node.element.offsetLeft < child.node.element.offsetLeft) ? this.node.element.offsetLeft + (this.config.node.borderwidth*2) + (this.config.node.width / 2) : child.node.element.offsetLeft + (this.config.node.borderwidth*2) + (this.config.node.width / 2) ; 347 | var shape = AutoshapeJS.createShape({ 348 | 'shape': 'Box', 349 | 'color': this.line_color, 350 | 'width': shape_width + 'px', 351 | 'height': this.config.line.width + 'px', 352 | 'position': 'absolute', 353 | 'left': shape_left + 'px', 354 | 'top': ((this.config.node.borderwidth * 2) + this.node.element.offsetTop + this.config.node.height + (this.config.node.spacingVertical / 2) + this.config.line.offsetY) + 'px' 355 | }); 356 | shape.attachTo(target); 357 | 358 | // Draw line down to child 359 | var shape = AutoshapeJS.createShape({ 360 | 'shape': 'Box', 361 | 'color': this.line_color, 362 | 'width': this.config.line.width + 'px', 363 | 'height': (this.config.node.spacingVertical / 2) + 1 + this.config.node.height - (this.config.line.offsetY) + 'px', 364 | 'position': 'absolute', 365 | 'left': ((this.config.node.borderwidth * 2) + child.node.element.offsetLeft + (this.config.node.width / 2)) + 'px', 366 | 'top': ((this.config.node.borderwidth * 2) + child.node.element.offsetTop - (this.config.node.spacingVertical / 2) + this.config.line.offsetY) + 'px' 367 | }); 368 | shape.attachTo(target); 369 | 370 | 371 | child.DrawConnections(target); 372 | 373 | } 374 | } 375 | } 376 | } 377 | } 378 | 379 | }; 380 | 381 | 382 | /** 383 | * AddPerson function 384 | * 385 | * used to add a 'parentless' person to the family tree (no higher-level nodes) 386 | */ 387 | this.AddPerson = function(identity, details) { 388 | 389 | var config = deep_copy(this.config); 390 | if (typeof details !== 'undefined' && typeof details.config !== 'undefined') { 391 | config = merge(config, details.config); 392 | } 393 | 394 | var person = new Person(identity, config, details); 395 | people.push(person); 396 | return person; 397 | 398 | }; 399 | 400 | 401 | var MouseCoordinatesFromEvent = function(e) { 402 | var pos = {x:null, y:null}; 403 | if (e.pageX || e.pageY) { 404 | pos.x = e.pageX; 405 | pos.y = e.pageY; 406 | } else if (e.clientX || e.clientY) { 407 | pos.x = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft; 408 | pos.y = e.clientY + document.body.scrollTop + document.documentElement.scrollTop; 409 | } 410 | return pos; 411 | }; 412 | 413 | 414 | /** 415 | * Render function 416 | * 417 | * renders the family tree 418 | */ 419 | this.Render = function(element) { 420 | 421 | tree_element = element; 422 | tree_element.style.position = 'relative'; 423 | tree_element.innerHTML = ''; 424 | 425 | // Generate all the people's levels so we know where they are at 426 | people[0].Level(0); 427 | 428 | min_level = 0; 429 | for (var i = 0; i < people.length; i++) { 430 | if (people[i].level < min_level) { 431 | min_level = people[i].level; 432 | } 433 | } 434 | 435 | if (min_level < 0) { 436 | var level_increase = min_level * -1 437 | for (var i = 0; i < people.length; i++) { 438 | people[i].level += level_increase; 439 | } 440 | } 441 | 442 | var grid = {}; 443 | 444 | grid = people[0].FillGrid(grid, 0); 445 | 446 | // Compress the grid 447 | while_loop: 448 | while (true) { 449 | for (var i in grid) { 450 | level = grid[i]; 451 | 452 | for (var j = 1; j < level.length; j++) { 453 | node = level[j]; 454 | 455 | if (node !== null && (level[j-1] === null) && node.config.compressable === true) { 456 | // It's a candidate to move, nothing to it's left. Is anything around it may conflict with? 457 | 458 | // Are we already above our first parent? If so we shouldn't wander away 459 | if (node.parents.length) { 460 | if (grid[node.level-1] === null || typeof grid[node.level-1] !== 'undefined') { 461 | if (grid[node.level-1][j] !== null && grid[node.level-1][j] === node.parents[0]) { 462 | continue; 463 | } 464 | } 465 | } 466 | 467 | var clear_above = true; 468 | var clear_below = true; 469 | 470 | /* 471 | if (typeof grid[node.level-1] !== 'undefined') { 472 | if (grid[node.level-1][j-1] !== null && typeof grid[node.level-1][j-1] !== 'undefined' && grid[node.level-1][j-1].children.length > 0) { 473 | // We're blocked if the thing above isn't our parent 474 | 475 | clear_above = false; 476 | 477 | for (var k = 0; k < node.parents.length; k++) { 478 | if (grid[node.level-1][j-1] === node.parents[k]) { 479 | clear_above = true; 480 | } 481 | } 482 | 483 | } 484 | } 485 | */ 486 | 487 | if (typeof grid[node.level+1] !== 'undefined') { 488 | if (grid[node.level+1][j-1] !== null && typeof grid[node.level+1][j-1] !== 'undefined' && grid[node.level+1][j-1].parents.length > 0) { 489 | // We're blocked if the thing below isn't our child 490 | 491 | clear_below = false; 492 | 493 | for (var k = 0; k < node.children.length; k++) { 494 | if (grid[node.level+1][j-1] === node.children[k]) { 495 | clear_below = true; 496 | } 497 | } 498 | 499 | } 500 | } 501 | 502 | if (clear_above && clear_below) { 503 | grid[i][j-1] = node; 504 | grid[i][j] = null; 505 | continue while_loop; 506 | } 507 | } 508 | } 509 | 510 | } 511 | 512 | break; 513 | } 514 | 515 | var all_nodes = []; 516 | 517 | for (var level in grid) { 518 | var nodes = grid[level]; 519 | for (var i = 0; i < nodes.length; i++) { 520 | var node = nodes[i]; 521 | if (node !== null) { 522 | this.RenderNode(node, tree_element, level, i); 523 | all_nodes.push(node); 524 | } 525 | } 526 | } 527 | 528 | for (var node in all_nodes) { 529 | node = all_nodes[node]; 530 | node.DrawConnections(tree_element); 531 | } 532 | 533 | // Setup the drag ability 534 | tree_element.onselectstart = function() { return false; }; 535 | tree_element.unselectable = 'on'; 536 | tree_element.style.MozUserSelect = 'none'; 537 | 538 | tree_element.style.cursor = 'move'; 539 | tree_element.onmousedown = function(e) { 540 | scroll_info.scrolling = true; 541 | 542 | e = e || window.event; 543 | var mouse_coordinates = MouseCoordinatesFromEvent(e); 544 | 545 | scroll_info.x = mouse_coordinates.x; 546 | scroll_info.y = mouse_coordinates.y; 547 | }; 548 | tree_element.onmouseup = function(e) { 549 | if (scroll_info.scrolling) { 550 | scroll_info.scrolling = false; 551 | 552 | e = e || window.event; 553 | var mouse_coordinates = MouseCoordinatesFromEvent(e); 554 | 555 | if (scroll_info.x !== null && scroll_info.y !== null) { 556 | 557 | delta = { 558 | x: scroll_info.x - mouse_coordinates.x, 559 | y: scroll_info.y - mouse_coordinates.y 560 | } 561 | 562 | tree_element.scrollLeft += delta.x; 563 | tree_element.scrollTop += delta.y; 564 | 565 | } 566 | } 567 | }; 568 | tree_element.onmousemove = function(e) { 569 | if (scroll_info.scrolling) { 570 | 571 | e = e || window.event; 572 | var mouse_coordinates = MouseCoordinatesFromEvent(e); 573 | 574 | if (scroll_info.x !== null && scroll_info.y !== null) { 575 | 576 | delta = { 577 | x: scroll_info.x - mouse_coordinates.x, 578 | y: scroll_info.y - mouse_coordinates.y 579 | } 580 | 581 | tree_element.scrollLeft += delta.x; 582 | tree_element.scrollTop += delta.y; 583 | 584 | } 585 | 586 | scroll_info.x = mouse_coordinates.x; 587 | scroll_info.y = mouse_coordinates.y; 588 | 589 | } 590 | } 591 | tree_element.onmouseout = function(e) { 592 | e = e || window.event; 593 | 594 | // Check to see if we are, in fact, still in the tree_element 595 | var element = e.relatedTarget || e.toElement; 596 | 597 | if (typeof element !== 'undefined') { 598 | 599 | while (element !== null && element !== tree_element) { 600 | element = element.parentNode; 601 | } 602 | 603 | if (element !== tree_element) { 604 | scroll_info.scrolling = false; 605 | } 606 | 607 | } 608 | } 609 | 610 | }; 611 | 612 | this.RenderNode = function(person, element, level, position) { 613 | 614 | var node_left = position * (this.config.node.width + this.config.node.spacingHorizontal); 615 | 616 | var shape = AutoshapeJS.createShape({ 617 | 'shape': 'Box', 618 | 'color': person.config.node.background, 619 | 'borderwidth': person.config.node.borderwidth + 'px', 620 | 'bordercolor': person.config.node.bordercolor, 621 | 'width': this.config.node.width + 'px', 622 | 'height': this.config.node.height + 'px', 623 | 'position': 'absolute', 624 | 'left': node_left + 'px', 625 | 'top': (level * (this.config.node.height + this.config.node.spacingVertical)) + 'px', 626 | 'opacity': (person.identity.length > 0) ? 1 : 0 627 | }); 628 | shape.attachTo(element); 629 | shape.element.innerHTML = person.identity; 630 | if (typeof person.details.blurb !== 'undefined') { 631 | shape.element.innerHTML += '
' + person.details.blurb + '
'; 632 | } 633 | shape.element.style.color = person.config.node.fontcolor; 634 | shape.element.style.textAlign = 'center'; 635 | shape.element.className = 'familytree-node'; 636 | 637 | person.node = shape; 638 | 639 | }; 640 | 641 | this.center = function() { 642 | 643 | var view_center = { 644 | x: tree_element.clientWidth / 2, 645 | y: tree_element.clientHeight / 2 646 | } 647 | 648 | if (arguments.length == 0) { 649 | 650 | // Center the whole tree 651 | var center = { 652 | x: tree_element.scrollWidth / 2, 653 | y: tree_element.scrollHeight / 2 654 | } 655 | 656 | } else { 657 | 658 | // Center on a specific node 659 | var node = arguments[0]; 660 | var center = { 661 | x: node.node.element.offsetLeft + (node.config.node.width / 2), 662 | y: node.node.element.offsetTop + (node.config.node.height / 2), 663 | } 664 | 665 | } 666 | 667 | tree_element.scrollLeft = center.x - view_center.x; 668 | tree_element.scrollTop = center.y - view_center.y; 669 | 670 | } 671 | 672 | } 673 | 674 | }; 675 | 676 | })(); --------------------------------------------------------------------------------