Mercurial > repos > iuc > mmseqs2_easy_linclust_clustering
comparison test-data/taxo_result.html @ 0:9f6869226de1 draft
planemo upload for repository https://github.com/galaxyproject/tools-iuc/tree/master/tools/mmsesq2 commit 1400593429eb4e9c6e307df3621825a8b84a6fa7
| author | iuc |
|---|---|
| date | Thu, 27 Mar 2025 14:37:56 +0000 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| -1:000000000000 | 0:9f6869226de1 |
|---|---|
| 1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> | |
| 2 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> | |
| 3 <head> | |
| 4 <meta charset="utf-8"/> | |
| 5 <link rel="shortcut icon" href="data:image/x-icon;base64,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"/> | |
| 6 <script id="notfound">window.onload=function(){document.body.innerHTML=""}</script> | |
| 7 <script language="javascript" type="text/javascript"> | |
| 8 {//----------------------------------------------------------------------------- | |
| 9 // | |
| 10 // PURPOSE | |
| 11 // | |
| 12 // Krona is a flexible tool for exploring the relative proportions of | |
| 13 // hierarchical data, such as metagenomic classifications, using a | |
| 14 // radial, space-filling display. It is implemented using HTML5 and | |
| 15 // JavaScript, allowing charts to be explored locally or served over the | |
| 16 // Internet, requiring only a current version of any major web | |
| 17 // browser. Krona charts can be created using an Excel template or from | |
| 18 // common bioinformatic formats using the provided conversion scripts. | |
| 19 // | |
| 20 // | |
| 21 // COPYRIGHT LICENSE | |
| 22 // | |
| 23 // Copyright (c) 2011, Battelle National Biodefense Institute (BNBI); | |
| 24 // all rights reserved. Authored by: Brian Ondov, Nicholas Bergman, and | |
| 25 // Adam Phillippy | |
| 26 // | |
| 27 // This Software was prepared for the Department of Homeland Security | |
| 28 // (DHS) by the Battelle National Biodefense Institute, LLC (BNBI) as | |
| 29 // part of contract HSHQDC-07-C-00020 to manage and operate the National | |
| 30 // Biodefense Analysis and Countermeasures Center (NBACC), a Federally | |
| 31 // Funded Research and Development Center. | |
| 32 // | |
| 33 // Redistribution and use in source and binary forms, with or without | |
| 34 // modification, are permitted provided that the following conditions are | |
| 35 // met: | |
| 36 // | |
| 37 // * Redistributions of source code must retain the above copyright | |
| 38 // notice, this list of conditions and the following disclaimer. | |
| 39 // | |
| 40 // * Redistributions in binary form must reproduce the above copyright | |
| 41 // notice, this list of conditions and the following disclaimer in the | |
| 42 // documentation and/or other materials provided with the distribution. | |
| 43 // | |
| 44 // * Neither the name of the Battelle National Biodefense Institute nor | |
| 45 // the names of its contributors may be used to endorse or promote | |
| 46 // products derived from this software without specific prior written | |
| 47 // permission. | |
| 48 // | |
| 49 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 50 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 51 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 52 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 53 // HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 54 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 55 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 56 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 57 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 58 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 59 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 60 // | |
| 61 // | |
| 62 // TRADEMARK LICENSE | |
| 63 // | |
| 64 // KRONA(TM) is a trademark of the Department of Homeland Security, and use | |
| 65 // of the trademark is subject to the following conditions: | |
| 66 // | |
| 67 // * Distribution of the unchanged, official code/software using the | |
| 68 // KRONA(TM) mark is hereby permitted by the Department of Homeland | |
| 69 // Security, provided that the software is distributed without charge | |
| 70 // and modification. | |
| 71 // | |
| 72 // * Distribution of altered source code/software using the KRONA(TM) mark | |
| 73 // is not permitted unless written permission has been granted by the | |
| 74 // Department of Homeland Security. | |
| 75 // | |
| 76 // | |
| 77 // FOR MORE INFORMATION VISIT | |
| 78 // | |
| 79 // https://github.com/marbl/Krona/wiki/ | |
| 80 // | |
| 81 //----------------------------------------------------------------------------- | |
| 82 } | |
| 83 | |
| 84 | |
| 85 var canvas; | |
| 86 var context; | |
| 87 var svg; // for snapshot mode | |
| 88 var collapse = true; | |
| 89 var collapseCheckBox; | |
| 90 var collapseLast; | |
| 91 var compress; | |
| 92 var compressCheckBox; | |
| 93 var maxAbsoluteDepthText; | |
| 94 var maxAbsoluteDepthButtonDecrease; | |
| 95 var maxAbsoluteDepthButtonIncrease; | |
| 96 var fontSize = 11; | |
| 97 var fontSizeText; | |
| 98 var fontSizeButtonDecrease; | |
| 99 var fontSizeButtonIncrease; | |
| 100 var fontSizeLast; | |
| 101 var radiusButtonDecrease; | |
| 102 var radiusButtonIncrease; | |
| 103 var shorten; | |
| 104 var shortenCheckBox; | |
| 105 var maxAbsoluteDepth; | |
| 106 var backButton; | |
| 107 var upButton; | |
| 108 var forwardButton; | |
| 109 var snapshotButton; | |
| 110 var snapshotMode = false; | |
| 111 var details; | |
| 112 var detailsName; | |
| 113 var search; | |
| 114 var searchResults; | |
| 115 var nSearchResults; | |
| 116 var useHueCheckBox; | |
| 117 var useHueDiv; | |
| 118 var datasetDropDown; | |
| 119 var datasetButtonLast; | |
| 120 var datasetButtonPrev; | |
| 121 var datasetButtonNext; | |
| 122 var keyControl; | |
| 123 var showKeys = true; | |
| 124 var linkButton; | |
| 125 var linkText; | |
| 126 var frame; | |
| 127 | |
| 128 // Node references. Note that the meanings of 'selected' and 'focused' are | |
| 129 // swapped in the docs. | |
| 130 // | |
| 131 var head; // the root of the entire tree | |
| 132 var selectedNode = 0; // the root of the current view | |
| 133 var focusNode = 0; // a node chosen for more info (single-click) | |
| 134 var highlightedNode = 0; // mouse hover node | |
| 135 var highlightingHidden = false; | |
| 136 var nodes = new Array(); | |
| 137 var currentNodeID = 0; // to iterate while loading | |
| 138 | |
| 139 var nodeHistory = new Array(); | |
| 140 var nodeHistoryPosition = 0; | |
| 141 | |
| 142 var dataEnabled = false; // true when supplemental files are present | |
| 143 | |
| 144 // store non-Krona GET variables so they can be passed on to links | |
| 145 // | |
| 146 var getVariables = new Array(); | |
| 147 | |
| 148 // selectedNodeLast is separate from the history, since we need to check | |
| 149 // properties of the last node viewed when browsing through the history | |
| 150 // | |
| 151 var selectedNodeLast = 0; | |
| 152 var zoomOut = false; | |
| 153 | |
| 154 // temporary zoom-in while holding the mouse button on a wedge | |
| 155 // | |
| 156 var quickLook = false; // true when in quick look state | |
| 157 var mouseDown = false; | |
| 158 var mouseDownTime; // to detect mouse button hold | |
| 159 var quickLookHoldLength = 200; | |
| 160 | |
| 161 var imageWidth; | |
| 162 var imageHeight; | |
| 163 var centerX; | |
| 164 var centerY; | |
| 165 var gRadius; | |
| 166 var updateViewNeeded = false; | |
| 167 | |
| 168 // Determines the angle that the pie chart starts at. 90 degrees makes the | |
| 169 // center label consistent with the children. | |
| 170 // | |
| 171 var rotationOffset = Math.PI / 2; | |
| 172 | |
| 173 var buffer; | |
| 174 var bufferFactor = .1; | |
| 175 | |
| 176 // The maps are the small pie charts showing the current slice being viewed. | |
| 177 // | |
| 178 var mapBuffer = 10; | |
| 179 var mapRadius = 0; | |
| 180 var maxMapRadius = 25; | |
| 181 var mapWidth = 150; | |
| 182 var maxLabelOverhang = Math.PI * 4.18; | |
| 183 | |
| 184 // Keys are the labeled boxes for slices in the highest level that are too thin | |
| 185 // to label. | |
| 186 // | |
| 187 var maxKeySizeFactor = 2; // will be multiplied by font size | |
| 188 var keySize; | |
| 189 var keys; | |
| 190 var keyBuffer = 10; | |
| 191 var currentKey; | |
| 192 var keyMinTextLeft; | |
| 193 var keyMinAngle; | |
| 194 | |
| 195 var minRingWidthFactor = 5; // will be multiplied by font size | |
| 196 var maxPossibleDepth; // the theoretical max that can be displayed | |
| 197 var maxDisplayDepth; // the actual depth that will be displayed | |
| 198 var headerHeight = 0;//document.getElementById('options').clientHeight; | |
| 199 var historySpacingFactor = 1.6; // will be multiplied by font size | |
| 200 var historyAlphaDelta = .25; | |
| 201 | |
| 202 // appearance | |
| 203 // | |
| 204 var lineOpacity = 0.3; | |
| 205 var saturation = 0.5; | |
| 206 var lightnessBase = 0.6; | |
| 207 var lightnessMax = .8; | |
| 208 var thinLineWidth = .3; | |
| 209 var highlightLineWidth = 1.5; | |
| 210 var labelBoxBuffer = 6; | |
| 211 var labelBoxRounding = 15; | |
| 212 var labelWidthFudge = 1.05; // The width of unshortened labels are set slightly | |
| 213 // longer than the name width so the animation | |
| 214 // finishes faster. | |
| 215 var fontNormal; | |
| 216 var fontBold; | |
| 217 var fontFamily = 'sans-serif'; | |
| 218 //var fontFaceBold = 'bold Arial'; | |
| 219 var nodeRadius; | |
| 220 var angleFactor; | |
| 221 var tickLength; | |
| 222 var compressedRadii; | |
| 223 | |
| 224 // colors | |
| 225 // | |
| 226 var highlightFill = 'rgba(255, 255, 255, .3)'; | |
| 227 var colorUnclassified = 'rgb(220,220,220)'; | |
| 228 | |
| 229 // label staggering | |
| 230 // | |
| 231 var labelOffsets; // will store the current offset at each depth | |
| 232 // | |
| 233 // This will store pointers to the last node that had a label in each offset (or "track") of a | |
| 234 // each depth. These will be used to shorten neighboring labels that would overlap. | |
| 235 // The [nLabelNodes] index will store the last node with a radial label. | |
| 236 // labelFirstNodes is the same, but to check for going all the way around and | |
| 237 // overlapping the first labels. | |
| 238 // | |
| 239 var labelLastNodes; | |
| 240 var labelFirstNodes; | |
| 241 // | |
| 242 var nLabelOffsets = 3; // the number of offsets to use | |
| 243 | |
| 244 var mouseX = -1; | |
| 245 var mouseY = -1; | |
| 246 var mouseXRel = -1; | |
| 247 var mouseYRel = -1; | |
| 248 | |
| 249 // tweening | |
| 250 // | |
| 251 var progress = 0; // for tweening; goes from 0 to 1. | |
| 252 var progressLast = 0; | |
| 253 var tweenFactor = 0; // progress converted by a curve for a smoother effect. | |
| 254 var tweenLength = 850; // in ms | |
| 255 var tweenCurvature = 13; | |
| 256 // | |
| 257 // tweenMax is used to scale the sigmoid function so its range is [0,1] for the | |
| 258 // domain [0,1] | |
| 259 // | |
| 260 var tweenMax = 1 / (1 + Math.exp(-tweenCurvature / 2)); | |
| 261 // | |
| 262 var tweenStartTime; | |
| 263 | |
| 264 // for framerate debug | |
| 265 // | |
| 266 var tweenFrames = 0; | |
| 267 var fpsDisplay = document.getElementById('frameRate'); | |
| 268 | |
| 269 // Arrays to translate xml attribute names into displayable attribute names | |
| 270 // | |
| 271 var attributes = new Array(); | |
| 272 // | |
| 273 var magnitudeIndex; // the index of attribute arrays used for magnitude | |
| 274 var membersAssignedIndex; | |
| 275 var membersSummaryIndex; | |
| 276 | |
| 277 // For defining gradients | |
| 278 // | |
| 279 var hueDisplayName; | |
| 280 var hueStopPositions; | |
| 281 var hueStopHues; | |
| 282 var hueStopText; | |
| 283 | |
| 284 // multiple datasets | |
| 285 // | |
| 286 var currentDataset = 0; | |
| 287 var lastDataset = 0; | |
| 288 var datasets = 1; | |
| 289 var datasetNames; | |
| 290 var datasetSelectSize = 30; | |
| 291 var datasetAlpha = new Tween(0, 0); | |
| 292 var datasetWidths = new Array(); | |
| 293 var datasetChanged; | |
| 294 var datasetSelectWidth = 50; | |
| 295 | |
| 296 window.onload = load; | |
| 297 | |
| 298 var image; | |
| 299 var hiddenPattern; | |
| 300 var loadingImage; | |
| 301 var logoImage; | |
| 302 | |
| 303 function backingScale() | |
| 304 { | |
| 305 if ('devicePixelRatio' in window) | |
| 306 { | |
| 307 if (window.devicePixelRatio > 1) | |
| 308 { | |
| 309 return window.devicePixelRatio; | |
| 310 } | |
| 311 } | |
| 312 | |
| 313 return 1; | |
| 314 } | |
| 315 | |
| 316 function resize() | |
| 317 { | |
| 318 imageWidth = window.innerWidth; | |
| 319 imageHeight = window.innerHeight; | |
| 320 | |
| 321 if ( ! snapshotMode ) | |
| 322 { | |
| 323 context.canvas.width = imageWidth * backingScale(); | |
| 324 context.canvas.height = imageHeight * backingScale(); | |
| 325 context.canvas.style.width = imageWidth + "px" | |
| 326 context.canvas.style.height = imageHeight + "px" | |
| 327 context.scale(backingScale(), backingScale()); | |
| 328 } | |
| 329 | |
| 330 if ( datasetDropDown ) | |
| 331 { | |
| 332 var ratio = | |
| 333 (datasetDropDown.offsetTop + datasetDropDown.clientHeight) * 2 / | |
| 334 imageHeight; | |
| 335 | |
| 336 if ( ratio > 1 ) | |
| 337 { | |
| 338 ratio = 1; | |
| 339 } | |
| 340 | |
| 341 ratio = Math.sqrt(ratio); | |
| 342 | |
| 343 datasetSelectWidth = | |
| 344 (datasetDropDown.offsetLeft + datasetDropDown.clientWidth) * ratio; | |
| 345 } | |
| 346 var leftMargin = datasets > 1 ? datasetSelectWidth + 30 : 0; | |
| 347 var minDimension = imageWidth - mapWidth - leftMargin > imageHeight ? | |
| 348 imageHeight : | |
| 349 imageWidth - mapWidth - leftMargin; | |
| 350 | |
| 351 maxMapRadius = minDimension * .03; | |
| 352 buffer = minDimension * bufferFactor; | |
| 353 margin = minDimension * .015; | |
| 354 centerX = (imageWidth - mapWidth - leftMargin) / 2 + leftMargin; | |
| 355 centerY = imageHeight / 2; | |
| 356 gRadius = minDimension / 2 - buffer; | |
| 357 //context.font = '11px sans-serif'; | |
| 358 } | |
| 359 | |
| 360 function handleResize() | |
| 361 { | |
| 362 updateViewNeeded = true; | |
| 363 } | |
| 364 | |
| 365 function Attribute() | |
| 366 { | |
| 367 } | |
| 368 | |
| 369 function Tween(start, end) | |
| 370 { | |
| 371 this.start = start; | |
| 372 this.end = end; | |
| 373 this.current = this.start; | |
| 374 | |
| 375 this.current = function() | |
| 376 { | |
| 377 if ( progress == 1 || this.start == this.end ) | |
| 378 { | |
| 379 return this.end; | |
| 380 } | |
| 381 else | |
| 382 { | |
| 383 return this.start + tweenFactor * (this.end - this.start); | |
| 384 } | |
| 385 }; | |
| 386 | |
| 387 this.setTarget = function(target) | |
| 388 { | |
| 389 this.start = this.current(); | |
| 390 this.end = target; | |
| 391 } | |
| 392 } | |
| 393 | |
| 394 function Node() | |
| 395 { | |
| 396 this.id = currentNodeID; | |
| 397 currentNodeID++; | |
| 398 nodes[this.id] = this; | |
| 399 | |
| 400 this.angleStart = new Tween(Math.PI, 0); | |
| 401 this.angleEnd = new Tween(Math.PI, 0); | |
| 402 this.radiusInner = new Tween(1, 1); | |
| 403 this.labelRadius = new Tween(1, 1); | |
| 404 this.labelWidth = new Tween(0, 0); | |
| 405 this.scale = new Tween(1, 1); // TEMP | |
| 406 this.radiusOuter = new Tween(1, 1); | |
| 407 | |
| 408 this.r = new Tween(255, 255); | |
| 409 this.g = new Tween(255, 255); | |
| 410 this.b = new Tween(255, 255); | |
| 411 | |
| 412 this.alphaLabel = new Tween(0, 1); | |
| 413 this.alphaLine = new Tween(0, 1); | |
| 414 this.alphaArc = new Tween(0, 0); | |
| 415 this.alphaWedge = new Tween(0, 1); | |
| 416 this.alphaOther = new Tween(0, 1); | |
| 417 this.alphaPattern = new Tween(0, 0); | |
| 418 this.children = Array(); | |
| 419 this.parent = 0; | |
| 420 | |
| 421 this.attributes = new Array(attributes.length); | |
| 422 | |
| 423 this.addChild = function(child) | |
| 424 { | |
| 425 this.children.push(child); | |
| 426 }; | |
| 427 | |
| 428 this.addLabelNode = function(depth, labelOffset) | |
| 429 { | |
| 430 if ( labelHeadNodes[depth][labelOffset] == 0 ) | |
| 431 { | |
| 432 // this will become the head node for this list | |
| 433 | |
| 434 labelHeadNodes[depth][labelOffset] = this; | |
| 435 this.labelPrev = this; | |
| 436 } | |
| 437 | |
| 438 var head = labelHeadNodes[depth][labelOffset]; | |
| 439 | |
| 440 this.labelNext = head; | |
| 441 this.labelPrev = head.labelPrev; | |
| 442 head.labelPrev.labelNext = this; | |
| 443 head.labelPrev = this; | |
| 444 } | |
| 445 | |
| 446 this.canDisplayDepth = function() | |
| 447 { | |
| 448 // whether this node is at a depth that can be displayed, according | |
| 449 // to the max absolute depth | |
| 450 | |
| 451 return this.depth <= maxAbsoluteDepth; | |
| 452 } | |
| 453 | |
| 454 this.canDisplayHistory = function() | |
| 455 { | |
| 456 var radiusInner; | |
| 457 | |
| 458 if ( compress ) | |
| 459 { | |
| 460 radiusInner = compressedRadii[0]; | |
| 461 } | |
| 462 else | |
| 463 { | |
| 464 radiusInner = nodeRadius; | |
| 465 } | |
| 466 | |
| 467 return ( | |
| 468 -this.labelRadius.end * gRadius + | |
| 469 historySpacingFactor * fontSize / 2 < | |
| 470 radiusInner * gRadius | |
| 471 ); | |
| 472 } | |
| 473 | |
| 474 this.canDisplayLabelCurrent = function() | |
| 475 { | |
| 476 return ( | |
| 477 (this.angleEnd.current() - this.angleStart.current()) * | |
| 478 (this.radiusInner.current() * gRadius + gRadius) >= | |
| 479 minWidth()); | |
| 480 } | |
| 481 | |
| 482 this.checkHighlight = function() | |
| 483 { | |
| 484 if ( this.children.length == 0 && this == focusNode ) | |
| 485 { | |
| 486 //return false; | |
| 487 } | |
| 488 | |
| 489 if ( this.hide ) | |
| 490 { | |
| 491 return false; | |
| 492 } | |
| 493 | |
| 494 if ( this.radiusInner.end == 1 ) | |
| 495 { | |
| 496 // compressed to the outside; don't check | |
| 497 | |
| 498 return false; | |
| 499 } | |
| 500 | |
| 501 var highlighted = false; | |
| 502 | |
| 503 var angleStartCurrent = this.angleStart.current() + rotationOffset; | |
| 504 var angleEndCurrent = this.angleEnd.current() + rotationOffset; | |
| 505 var radiusInner = this.radiusInner.current() * gRadius; | |
| 506 | |
| 507 for ( var i = 0; i < this.children.length; i++ ) | |
| 508 { | |
| 509 highlighted = this.children[i].checkHighlight(); | |
| 510 | |
| 511 if ( highlighted ) | |
| 512 { | |
| 513 return true; | |
| 514 } | |
| 515 } | |
| 516 | |
| 517 if ( this.radial ) | |
| 518 { | |
| 519 var angleText = (angleStartCurrent + angleEndCurrent) / 2; | |
| 520 var radiusText = (gRadius + radiusInner) / 2; | |
| 521 | |
| 522 context.rotate(angleText); | |
| 523 context.beginPath(); | |
| 524 context.moveTo(radiusText, -fontSize); | |
| 525 context.lineTo(radiusText, fontSize); | |
| 526 context.lineTo(radiusText + centerX, fontSize); | |
| 527 context.lineTo(radiusText + centerX, -fontSize); | |
| 528 context.closePath(); | |
| 529 context.rotate(-angleText); | |
| 530 | |
| 531 if ( context.isPointInPath(mouseXRel, mouseYRel) ) | |
| 532 { | |
| 533 var label = String(this.getPercentage()) + '%' + ' ' + this.name; | |
| 534 | |
| 535 if ( this.searchResultChildren() ) | |
| 536 { | |
| 537 label += searchResultString(this.searchResultChildren()); | |
| 538 } | |
| 539 | |
| 540 if | |
| 541 ( | |
| 542 Math.sqrt((mouseXRel) * (mouseXRel) + (mouseYRel) * (mouseYRel)) / backingScale() < | |
| 543 radiusText + measureText(label) | |
| 544 ) | |
| 545 { | |
| 546 highlighted = true; | |
| 547 } | |
| 548 } | |
| 549 } | |
| 550 else | |
| 551 { | |
| 552 for ( var i = 0; i < this.hiddenLabels.length; i++ ) | |
| 553 { | |
| 554 var hiddenLabel = this.hiddenLabels[i]; | |
| 555 | |
| 556 context.rotate(hiddenLabel.angle); | |
| 557 context.beginPath(); | |
| 558 context.moveTo(gRadius, -fontSize); | |
| 559 context.lineTo(gRadius, fontSize); | |
| 560 context.lineTo(gRadius + centerX, fontSize); | |
| 561 context.lineTo(gRadius + centerX, -fontSize); | |
| 562 context.closePath(); | |
| 563 context.rotate(-hiddenLabel.angle); | |
| 564 | |
| 565 if ( context.isPointInPath(mouseXRel, mouseYRel) ) | |
| 566 { | |
| 567 var label = String(hiddenLabel.value) + ' more'; | |
| 568 | |
| 569 if ( hiddenLabel.search ) | |
| 570 { | |
| 571 label += searchResultString(hiddenLabel.search); | |
| 572 } | |
| 573 | |
| 574 if | |
| 575 ( | |
| 576 Math.sqrt((mouseXRel) * (mouseXRel) + (mouseYRel) * (mouseYRel)) / backingScale() < | |
| 577 gRadius + fontSize + measureText(label) | |
| 578 ) | |
| 579 { | |
| 580 highlighted = true; | |
| 581 break; | |
| 582 } | |
| 583 } | |
| 584 } | |
| 585 } | |
| 586 | |
| 587 if ( ! highlighted && this != selectedNode && ! this.getCollapse() ) | |
| 588 { | |
| 589 context.beginPath(); | |
| 590 context.arc(0, 0, radiusInner, angleStartCurrent, angleEndCurrent, false); | |
| 591 context.arc(0, 0, gRadius, angleEndCurrent, angleStartCurrent, true); | |
| 592 context.closePath(); | |
| 593 | |
| 594 if ( context.isPointInPath(mouseXRel, mouseYRel) ) | |
| 595 { | |
| 596 highlighted = true; | |
| 597 } | |
| 598 | |
| 599 if | |
| 600 ( | |
| 601 ! highlighted && | |
| 602 (angleEndCurrent - angleStartCurrent) * | |
| 603 (radiusInner + gRadius) < | |
| 604 minWidth() && | |
| 605 this.getDepth() == selectedNode.getDepth() + 1 | |
| 606 ) | |
| 607 { | |
| 608 if ( showKeys && this.checkHighlightKey() ) | |
| 609 { | |
| 610 highlighted = true; | |
| 611 } | |
| 612 } | |
| 613 } | |
| 614 | |
| 615 if ( highlighted ) | |
| 616 { | |
| 617 if ( this != highlightedNode ) | |
| 618 { | |
| 619 // document.body.style.cursor='pointer'; | |
| 620 } | |
| 621 | |
| 622 highlightedNode = this; | |
| 623 } | |
| 624 | |
| 625 return highlighted; | |
| 626 } | |
| 627 | |
| 628 this.checkHighlightCenter = function() | |
| 629 { | |
| 630 if ( ! this.canDisplayHistory() ) | |
| 631 { | |
| 632 return; | |
| 633 } | |
| 634 | |
| 635 var cx = centerX; | |
| 636 var cy = centerY - this.labelRadius.end * gRadius; | |
| 637 //var dim = context.measureText(this.name); | |
| 638 | |
| 639 var width = this.nameWidth; | |
| 640 | |
| 641 if ( this.searchResultChildren() ) | |
| 642 { | |
| 643 var results = searchResultString(this.searchResultChildren()); | |
| 644 var dim = context.measureText(results); | |
| 645 width += dim.width; | |
| 646 } | |
| 647 | |
| 648 if | |
| 649 ( | |
| 650 mouseX > cx - width / 2 && | |
| 651 mouseX < cx + width / 2 && | |
| 652 mouseY > cy - historySpacingFactor * fontSize / 2 && | |
| 653 mouseY < cy + historySpacingFactor * fontSize / 2 | |
| 654 ) | |
| 655 { | |
| 656 highlightedNode = this; | |
| 657 return; | |
| 658 } | |
| 659 | |
| 660 if ( this.getParent() ) | |
| 661 { | |
| 662 this.getParent().checkHighlightCenter(); | |
| 663 } | |
| 664 } | |
| 665 | |
| 666 this.checkHighlightKey = function() | |
| 667 { | |
| 668 var offset = keyOffset(); | |
| 669 | |
| 670 var xMin = imageWidth - keySize - margin - this.keyNameWidth - keyBuffer; | |
| 671 var xMax = imageWidth - margin; | |
| 672 var yMin = offset; | |
| 673 var yMax = offset + keySize; | |
| 674 | |
| 675 currentKey++; | |
| 676 | |
| 677 return ( | |
| 678 mouseX > xMin && | |
| 679 mouseX < xMax && | |
| 680 mouseY > yMin && | |
| 681 mouseY < yMax); | |
| 682 } | |
| 683 | |
| 684 this.checkHighlightMap = function() | |
| 685 { | |
| 686 if ( this.parent ) | |
| 687 { | |
| 688 this.parent.checkHighlightMap(); | |
| 689 } | |
| 690 | |
| 691 if ( this.getCollapse() || this == focusNode ) | |
| 692 { | |
| 693 return; | |
| 694 } | |
| 695 | |
| 696 var box = this.getMapPosition(); | |
| 697 | |
| 698 if | |
| 699 ( | |
| 700 mouseX > box.x - mapRadius && | |
| 701 mouseX < box.x + mapRadius && | |
| 702 mouseY > box.y - mapRadius && | |
| 703 mouseY < box.y + mapRadius | |
| 704 ) | |
| 705 { | |
| 706 highlightedNode = this; | |
| 707 } | |
| 708 } | |
| 709 | |
| 710 /* this.collapse = function() | |
| 711 { | |
| 712 for (var i = 0; i < this.children.length; i++ ) | |
| 713 { | |
| 714 this.children[i] = this.children[i].collapse(); | |
| 715 } | |
| 716 | |
| 717 if | |
| 718 ( | |
| 719 this.children.length == 1 && | |
| 720 this.children[0].magnitude == this.magnitude | |
| 721 ) | |
| 722 { | |
| 723 this.children[0].parent = this.parent; | |
| 724 this.children[0].getDepth() = this.parent.getDepth() + 1; | |
| 725 return this.children[0]; | |
| 726 } | |
| 727 else | |
| 728 { | |
| 729 return this; | |
| 730 } | |
| 731 } | |
| 732 */ | |
| 733 this.draw = function(labelMode, selected, searchHighlighted) | |
| 734 { | |
| 735 var depth = this.getDepth() - selectedNode.getDepth() + 1; | |
| 736 // var hidden = false; | |
| 737 | |
| 738 if ( selectedNode == this ) | |
| 739 { | |
| 740 selected = true; | |
| 741 } | |
| 742 | |
| 743 var angleStartCurrent = this.angleStart.current() + rotationOffset; | |
| 744 var angleEndCurrent = this.angleEnd.current() + rotationOffset; | |
| 745 var radiusInner = this.radiusInner.current() * gRadius; | |
| 746 var canDisplayLabelCurrent = this.canDisplayLabelCurrent(); | |
| 747 var hiddenSearchResults = false; | |
| 748 | |
| 749 /* if ( ! this.hide ) | |
| 750 { | |
| 751 for ( var i = 0; i < this.children.length; i++ ) | |
| 752 { | |
| 753 if ( this.children[i].hide && this.children[i].searchResults ) | |
| 754 { | |
| 755 hiddenSearchResults = true; | |
| 756 } | |
| 757 } | |
| 758 } | |
| 759 */ | |
| 760 var drawChildren = | |
| 761 ( ! this.hide || ! this.hidePrev && progress < 1 ) && | |
| 762 ( ! this.hideAlone || ! this.hideAlonePrev && progress < 1 ); | |
| 763 | |
| 764 // if ( this.alphaWedge.current() > 0 || this.alphaLabel.current() > 0 ) | |
| 765 { | |
| 766 var lastChildAngleEnd = angleStartCurrent; | |
| 767 | |
| 768 if ( this.hasChildren() )//canDisplayChildren ) | |
| 769 { | |
| 770 lastChildAngleEnd = | |
| 771 this.children[this.children.length - 1].angleEnd.current() | |
| 772 + rotationOffset; | |
| 773 } | |
| 774 | |
| 775 if ( labelMode ) | |
| 776 { | |
| 777 var drawRadial = | |
| 778 !( | |
| 779 this.parent && | |
| 780 this.parent != selectedNode && | |
| 781 angleEndCurrent == this.parent.angleEnd.current() + rotationOffset | |
| 782 ); | |
| 783 | |
| 784 //if ( angleStartCurrent != angleEndCurrent ) | |
| 785 { | |
| 786 this.drawLines(angleStartCurrent, angleEndCurrent, radiusInner, drawRadial, selected); | |
| 787 } | |
| 788 | |
| 789 var alphaOtherCurrent = this.alphaOther.current(); | |
| 790 var childRadiusInner; | |
| 791 | |
| 792 if ( this == selectedNode || alphaOtherCurrent ) | |
| 793 { | |
| 794 childRadiusInner = | |
| 795 this.children.length ? | |
| 796 this.children[this.children.length - 1].radiusInner.current() * gRadius | |
| 797 : radiusInner | |
| 798 } | |
| 799 | |
| 800 if ( this == selectedNode ) | |
| 801 { | |
| 802 this.drawReferenceRings(childRadiusInner); | |
| 803 } | |
| 804 | |
| 805 if | |
| 806 ( | |
| 807 selected && | |
| 808 ! searchHighlighted && | |
| 809 this != selectedNode && | |
| 810 ( | |
| 811 this.isSearchResult || | |
| 812 this.hideAlone && this.searchResultChildren() || | |
| 813 false | |
| 814 // this.hide && | |
| 815 // this.containsSearchResult | |
| 816 ) | |
| 817 ) | |
| 818 { | |
| 819 context.globalAlpha = this.alphaWedge.current(); | |
| 820 | |
| 821 drawWedge | |
| 822 ( | |
| 823 angleStartCurrent, | |
| 824 angleEndCurrent, | |
| 825 radiusInner, | |
| 826 gRadius, | |
| 827 highlightFill, | |
| 828 0, | |
| 829 true | |
| 830 ); | |
| 831 | |
| 832 if | |
| 833 ( | |
| 834 this.keyed && | |
| 835 ! showKeys && | |
| 836 this.searchResults && | |
| 837 ! searchHighlighted && | |
| 838 this != highlightedNode && | |
| 839 this != focusNode | |
| 840 ) | |
| 841 { | |
| 842 var angle = (angleEndCurrent + angleStartCurrent) / 2; | |
| 843 this.drawLabel(angle, true, false, true, true); | |
| 844 } | |
| 845 | |
| 846 //this.drawHighlight(false); | |
| 847 searchHighlighted = true; | |
| 848 } | |
| 849 | |
| 850 if | |
| 851 ( | |
| 852 this == selectedNode || | |
| 853 // true | |
| 854 //(canDisplayLabelCurrent) && | |
| 855 this != highlightedNode && | |
| 856 this != focusNode | |
| 857 ) | |
| 858 { | |
| 859 if ( this.radial != this.radialPrev && this.alphaLabel.end == 1 ) | |
| 860 { | |
| 861 context.globalAlpha = tweenFactor; | |
| 862 } | |
| 863 else | |
| 864 { | |
| 865 context.globalAlpha = this.alphaLabel.current(); | |
| 866 } | |
| 867 | |
| 868 this.drawLabel | |
| 869 ( | |
| 870 (angleStartCurrent + angleEndCurrent) / 2, | |
| 871 this.hideAlone && this.searchResultChildren() || | |
| 872 (this.isSearchResult || hiddenSearchResults) && selected, | |
| 873 this == selectedNode && ! this.radial, | |
| 874 selected, | |
| 875 this.radial | |
| 876 ); | |
| 877 | |
| 878 if ( this.radial != this.radialPrev && this.alphaLabel.start == 1 && progress < 1 ) | |
| 879 { | |
| 880 context.globalAlpha = 1 - tweenFactor; | |
| 881 | |
| 882 this.drawLabel | |
| 883 ( | |
| 884 (angleStartCurrent + angleEndCurrent) / 2, | |
| 885 (this.isSearchResult || hiddenSearchResults) && selected, | |
| 886 this == selectedNodeLast && ! this.radialPrev, | |
| 887 selected, | |
| 888 this.radialPrev | |
| 889 ); | |
| 890 } | |
| 891 } | |
| 892 | |
| 893 if | |
| 894 ( | |
| 895 alphaOtherCurrent && | |
| 896 lastChildAngleEnd != null | |
| 897 ) | |
| 898 { | |
| 899 if | |
| 900 ( | |
| 901 (angleEndCurrent - lastChildAngleEnd) * | |
| 902 (childRadiusInner + gRadius) >= | |
| 903 minWidth() | |
| 904 ) | |
| 905 { | |
| 906 //context.font = fontNormal; | |
| 907 context.globalAlpha = this.alphaOther.current(); | |
| 908 | |
| 909 drawTextPolar | |
| 910 ( | |
| 911 this.getUnclassifiedText(), | |
| 912 this.getUnclassifiedPercentage(), | |
| 913 (lastChildAngleEnd + angleEndCurrent) / 2, | |
| 914 (childRadiusInner + gRadius) / 2, | |
| 915 true, | |
| 916 false, | |
| 917 false, | |
| 918 0, | |
| 919 0 | |
| 920 ); | |
| 921 } | |
| 922 } | |
| 923 | |
| 924 if ( this == selectedNode && this.keyUnclassified && showKeys ) | |
| 925 { | |
| 926 this.drawKey | |
| 927 ( | |
| 928 (lastChildAngleEnd + angleEndCurrent) / 2, | |
| 929 false, | |
| 930 false | |
| 931 ); | |
| 932 } | |
| 933 } | |
| 934 else | |
| 935 { | |
| 936 var alphaWedgeCurrent = this.alphaWedge.current(); | |
| 937 | |
| 938 if ( alphaWedgeCurrent || this.alphaOther.current() ) | |
| 939 { | |
| 940 var currentR = this.r.current(); | |
| 941 var currentG = this.g.current(); | |
| 942 var currentB = this.b.current(); | |
| 943 | |
| 944 var fill = rgbText(currentR, currentG, currentB); | |
| 945 | |
| 946 var radiusOuter; | |
| 947 var lastChildAngle; | |
| 948 var truncateWedge = | |
| 949 ( | |
| 950 (this.hasChildren() || this == selectedNode ) && | |
| 951 ! this.keyed && | |
| 952 (compress || depth < maxDisplayDepth) && | |
| 953 drawChildren | |
| 954 ); | |
| 955 | |
| 956 if ( truncateWedge ) | |
| 957 { | |
| 958 radiusOuter = this.children.length ? this.children[0].radiusInner.current() * gRadius : radiusInner; | |
| 959 } | |
| 960 else | |
| 961 { | |
| 962 radiusOuter = gRadius; | |
| 963 } | |
| 964 /* | |
| 965 if ( this.hasChildren() ) | |
| 966 { | |
| 967 radiusOuter = this.children[0].getUncollapsed().radiusInner.current() * gRadius + 1; | |
| 968 } | |
| 969 else | |
| 970 { // TEMP | |
| 971 radiusOuter = radiusInner + nodeRadius * gRadius; | |
| 972 | |
| 973 if ( radiusOuter > gRadius ) | |
| 974 { | |
| 975 radiusOuter = gRadius; | |
| 976 } | |
| 977 } | |
| 978 */ | |
| 979 context.globalAlpha = alphaWedgeCurrent; | |
| 980 | |
| 981 if ( radiusInner != radiusOuter || truncateWedge ) | |
| 982 { | |
| 983 drawWedge | |
| 984 ( | |
| 985 angleStartCurrent, | |
| 986 angleEndCurrent, | |
| 987 radiusInner, | |
| 988 radiusOuter,//this.radiusOuter.current() * gRadius, | |
| 989 //'rgba(0, 200, 0, .1)', | |
| 990 fill, | |
| 991 this.alphaPattern.current() | |
| 992 ); | |
| 993 | |
| 994 if ( truncateWedge ) | |
| 995 { | |
| 996 // fill in the extra space if the sum of our childrens' | |
| 997 // magnitudes is less than ours | |
| 998 | |
| 999 if ( lastChildAngleEnd < angleEndCurrent )//&& false) // TEMP | |
| 1000 { | |
| 1001 if ( radiusOuter > 1 ) | |
| 1002 { | |
| 1003 // overlap slightly to hide the seam | |
| 1004 | |
| 1005 // radiusOuter -= 1; | |
| 1006 } | |
| 1007 | |
| 1008 if ( alphaWedgeCurrent < 1 ) | |
| 1009 { | |
| 1010 context.globalAlpha = this.alphaOther.current(); | |
| 1011 drawWedge | |
| 1012 ( | |
| 1013 lastChildAngleEnd, | |
| 1014 angleEndCurrent, | |
| 1015 radiusOuter, | |
| 1016 gRadius, | |
| 1017 colorUnclassified, | |
| 1018 0 | |
| 1019 ); | |
| 1020 context.globalAlpha = alphaWedgeCurrent; | |
| 1021 } | |
| 1022 | |
| 1023 drawWedge | |
| 1024 ( | |
| 1025 lastChildAngleEnd, | |
| 1026 angleEndCurrent, | |
| 1027 radiusOuter, | |
| 1028 gRadius,//this.radiusOuter.current() * gRadius, | |
| 1029 //'rgba(200, 0, 0, .1)', | |
| 1030 fill, | |
| 1031 this.alphaPattern.current() | |
| 1032 ); | |
| 1033 } | |
| 1034 } | |
| 1035 | |
| 1036 if ( radiusOuter < gRadius ) | |
| 1037 { | |
| 1038 // patch up the seam | |
| 1039 // | |
| 1040 context.beginPath(); | |
| 1041 context.arc(0, 0, radiusOuter, angleStartCurrent/*lastChildAngleEnd*/, angleEndCurrent, false); | |
| 1042 context.strokeStyle = fill; | |
| 1043 context.lineWidth = 1; | |
| 1044 context.stroke(); | |
| 1045 } | |
| 1046 } | |
| 1047 | |
| 1048 if ( this.keyed && selected && showKeys )//&& progress == 1 ) | |
| 1049 { | |
| 1050 this.drawKey | |
| 1051 ( | |
| 1052 (angleStartCurrent + angleEndCurrent) / 2, | |
| 1053 ( | |
| 1054 this == highlightedNode || | |
| 1055 this == focusNode || | |
| 1056 this.searchResults | |
| 1057 ), | |
| 1058 this == highlightedNode || this == focusNode | |
| 1059 ); | |
| 1060 } | |
| 1061 } | |
| 1062 } | |
| 1063 } | |
| 1064 | |
| 1065 this.hiddenLabels = Array(); | |
| 1066 | |
| 1067 if ( drawChildren ) | |
| 1068 { | |
| 1069 // draw children | |
| 1070 // | |
| 1071 for ( var i = 0; i < this.children.length; i++ ) | |
| 1072 { | |
| 1073 if ( this.drawHiddenChildren(i, selected, labelMode, searchHighlighted) ) | |
| 1074 { | |
| 1075 i = this.children[i].hiddenEnd; | |
| 1076 } | |
| 1077 else | |
| 1078 { | |
| 1079 this.children[i].draw(labelMode, selected, searchHighlighted); | |
| 1080 } | |
| 1081 } | |
| 1082 } | |
| 1083 }; | |
| 1084 | |
| 1085 this.drawHiddenChildren = function | |
| 1086 ( | |
| 1087 firstHiddenChild, | |
| 1088 selected, | |
| 1089 labelMode, | |
| 1090 searchHighlighted | |
| 1091 ) | |
| 1092 { | |
| 1093 var firstChild = this.children[firstHiddenChild]; | |
| 1094 | |
| 1095 if ( firstChild.hiddenEnd == null || firstChild.radiusInner.current() == 1 ) | |
| 1096 { | |
| 1097 return false; | |
| 1098 } | |
| 1099 | |
| 1100 for ( var i = firstHiddenChild; i < firstChild.hiddenEnd; i++ ) | |
| 1101 { | |
| 1102 if ( ! this.children[i].hide || ! this.children[i].hidePrev && progress < 1 ) | |
| 1103 { | |
| 1104 return false; | |
| 1105 } | |
| 1106 } | |
| 1107 | |
| 1108 var angleStart = firstChild.angleStart.current() + rotationOffset; | |
| 1109 var lastChild = this.children[firstChild.hiddenEnd]; | |
| 1110 var angleEnd = lastChild.angleEnd.current() + rotationOffset; | |
| 1111 var radiusInner = gRadius * firstChild.radiusInner.current(); | |
| 1112 var hiddenChildren = firstChild.hiddenEnd - firstHiddenChild + 1; | |
| 1113 | |
| 1114 if ( labelMode ) | |
| 1115 { | |
| 1116 var hiddenSearchResults = 0; | |
| 1117 | |
| 1118 for ( var i = firstHiddenChild; i <= firstChild.hiddenEnd; i++ ) | |
| 1119 { | |
| 1120 hiddenSearchResults += this.children[i].searchResults; | |
| 1121 | |
| 1122 if ( this.children[i].magnitude == 0 ) | |
| 1123 { | |
| 1124 hiddenChildren--; | |
| 1125 } | |
| 1126 } | |
| 1127 | |
| 1128 if | |
| 1129 ( | |
| 1130 selected && | |
| 1131 (angleEnd - angleStart) * | |
| 1132 (gRadius + gRadius) >= | |
| 1133 minWidth() || | |
| 1134 this == highlightedNode && | |
| 1135 hiddenChildren || | |
| 1136 hiddenSearchResults | |
| 1137 ) | |
| 1138 { | |
| 1139 context.globalAlpha = this.alphaWedge.current(); | |
| 1140 | |
| 1141 this.drawHiddenLabel | |
| 1142 ( | |
| 1143 angleStart, | |
| 1144 angleEnd, | |
| 1145 hiddenChildren, | |
| 1146 hiddenSearchResults | |
| 1147 ); | |
| 1148 } | |
| 1149 } | |
| 1150 | |
| 1151 var drawWedges = true; | |
| 1152 | |
| 1153 for ( var i = firstHiddenChild; i <= firstChild.hiddenEnd; i++ ) | |
| 1154 { | |
| 1155 // all hidden children must be completely hidden to draw together | |
| 1156 | |
| 1157 if ( this.children[i].alphaPattern.current() != this.children[i].alphaWedge.current() ) | |
| 1158 { | |
| 1159 drawWedges = false; | |
| 1160 break; | |
| 1161 } | |
| 1162 } | |
| 1163 | |
| 1164 if ( labelMode ) | |
| 1165 { | |
| 1166 if ( drawWedges ) | |
| 1167 { | |
| 1168 var drawRadial = (angleEnd < this.angleEnd.current() + rotationOffset); | |
| 1169 this.drawLines(angleStart, angleEnd, radiusInner, drawRadial); | |
| 1170 } | |
| 1171 | |
| 1172 if ( hiddenSearchResults && ! searchHighlighted ) | |
| 1173 { | |
| 1174 drawWedge | |
| 1175 ( | |
| 1176 angleStart, | |
| 1177 angleEnd, | |
| 1178 radiusInner, | |
| 1179 gRadius,//this.radiusOuter.current() * gRadius, | |
| 1180 highlightFill, | |
| 1181 0, | |
| 1182 true | |
| 1183 ); | |
| 1184 } | |
| 1185 } | |
| 1186 else if ( drawWedges ) | |
| 1187 { | |
| 1188 context.globalAlpha = this.alphaWedge.current(); | |
| 1189 | |
| 1190 var fill = rgbText | |
| 1191 ( | |
| 1192 firstChild.r.current(), | |
| 1193 firstChild.g.current(), | |
| 1194 firstChild.b.current() | |
| 1195 ); | |
| 1196 | |
| 1197 drawWedge | |
| 1198 ( | |
| 1199 angleStart, | |
| 1200 angleEnd, | |
| 1201 radiusInner, | |
| 1202 gRadius,//this.radiusOuter.current() * gRadius, | |
| 1203 fill, | |
| 1204 context.globalAlpha, | |
| 1205 false | |
| 1206 ); | |
| 1207 } | |
| 1208 | |
| 1209 return drawWedges; | |
| 1210 } | |
| 1211 | |
| 1212 this.drawHiddenLabel = function(angleStart, angleEnd, value, hiddenSearchResults) | |
| 1213 { | |
| 1214 var textAngle = (angleStart + angleEnd) / 2; | |
| 1215 var labelRadius = gRadius + fontSize;//(radiusInner + radius) / 2; | |
| 1216 | |
| 1217 var hiddenLabel = Array(); | |
| 1218 | |
| 1219 hiddenLabel.value = value; | |
| 1220 hiddenLabel.angle = textAngle; | |
| 1221 hiddenLabel.search = hiddenSearchResults; | |
| 1222 | |
| 1223 this.hiddenLabels.push(hiddenLabel); | |
| 1224 | |
| 1225 drawTick(gRadius - fontSize * .75, fontSize * 1.5, textAngle); | |
| 1226 drawTextPolar | |
| 1227 ( | |
| 1228 value.toString() + ' more', | |
| 1229 0, // inner text | |
| 1230 textAngle, | |
| 1231 labelRadius, | |
| 1232 true, // radial | |
| 1233 hiddenSearchResults, // bubble | |
| 1234 this == highlightedNode || this == focusNode, // bold | |
| 1235 false, | |
| 1236 hiddenSearchResults | |
| 1237 ); | |
| 1238 } | |
| 1239 | |
| 1240 this.drawHighlight = function(bold) | |
| 1241 { | |
| 1242 var angleStartCurrent = this.angleStart.current() + rotationOffset; | |
| 1243 var angleEndCurrent = this.angleEnd.current() + rotationOffset; | |
| 1244 var radiusInner = this.radiusInner.current() * gRadius; | |
| 1245 | |
| 1246 //this.setHighlightStyle(); | |
| 1247 | |
| 1248 if ( this == focusNode && this == highlightedNode && this.hasChildren() ) | |
| 1249 { | |
| 1250 // context.fillStyle = "rgba(255, 255, 255, .3)"; | |
| 1251 arrow | |
| 1252 ( | |
| 1253 angleStartCurrent, | |
| 1254 angleEndCurrent, | |
| 1255 radiusInner | |
| 1256 ); | |
| 1257 } | |
| 1258 else | |
| 1259 { | |
| 1260 drawWedge | |
| 1261 ( | |
| 1262 angleStartCurrent, | |
| 1263 angleEndCurrent, | |
| 1264 radiusInner, | |
| 1265 gRadius, | |
| 1266 highlightFill, | |
| 1267 0, | |
| 1268 true | |
| 1269 ); | |
| 1270 } | |
| 1271 | |
| 1272 // check if hidden children should be highlighted | |
| 1273 // | |
| 1274 for ( var i = 0; i < this.children.length; i++ ) | |
| 1275 { | |
| 1276 if | |
| 1277 ( | |
| 1278 this.children[i].getDepth() - selectedNode.getDepth() + 1 <= | |
| 1279 maxDisplayDepth && | |
| 1280 this.children[i].hiddenEnd != null | |
| 1281 ) | |
| 1282 { | |
| 1283 var firstChild = this.children[i]; | |
| 1284 var lastChild = this.children[firstChild.hiddenEnd]; | |
| 1285 var hiddenAngleStart = firstChild.angleStart.current() + rotationOffset; | |
| 1286 var hiddenAngleEnd = lastChild.angleEnd.current() + rotationOffset; | |
| 1287 var hiddenRadiusInner = gRadius * firstChild.radiusInner.current(); | |
| 1288 | |
| 1289 drawWedge | |
| 1290 ( | |
| 1291 hiddenAngleStart, | |
| 1292 hiddenAngleEnd, | |
| 1293 hiddenRadiusInner, | |
| 1294 gRadius, | |
| 1295 'rgba(255, 255, 255, .3)', | |
| 1296 0, | |
| 1297 true | |
| 1298 ); | |
| 1299 | |
| 1300 if ( false && ! this.searchResults ) | |
| 1301 { | |
| 1302 this.drawHiddenLabel | |
| 1303 ( | |
| 1304 hiddenAngleStart, | |
| 1305 hiddenAngleEnd, | |
| 1306 firstChild.hiddenEnd - i + 1 | |
| 1307 ); | |
| 1308 } | |
| 1309 | |
| 1310 i = firstChild.hiddenEnd; | |
| 1311 } | |
| 1312 } | |
| 1313 | |
| 1314 // context.strokeStyle = 'black'; | |
| 1315 context.fillStyle = 'black'; | |
| 1316 | |
| 1317 var highlight = ! ( progress < 1 && zoomOut && this == selectedNodeLast ); | |
| 1318 | |
| 1319 var angle = (angleEndCurrent + angleStartCurrent) / 2; | |
| 1320 | |
| 1321 if ( ! (this.keyed && showKeys) ) | |
| 1322 { | |
| 1323 this.drawLabel(angle, true, bold, true, this.radial); | |
| 1324 } | |
| 1325 } | |
| 1326 | |
| 1327 this.drawHighlightCenter = function() | |
| 1328 { | |
| 1329 if ( ! this.canDisplayHistory() ) | |
| 1330 { | |
| 1331 return; | |
| 1332 } | |
| 1333 | |
| 1334 context.lineWidth = highlightLineWidth; | |
| 1335 context.strokeStyle = 'black'; | |
| 1336 context.fillStyle = "rgba(255, 255, 255, .6)"; | |
| 1337 | |
| 1338 context.fillStyle = 'black'; | |
| 1339 this.drawLabel(3 * Math.PI / 2, true, true, false); | |
| 1340 context.font = fontNormal; | |
| 1341 } | |
| 1342 | |
| 1343 this.drawKey = function(angle, highlight, bold) | |
| 1344 { | |
| 1345 var offset = keyOffset(); | |
| 1346 var color; | |
| 1347 var colorText = this.magnitude == 0 ? 'gray' : 'black'; | |
| 1348 var patternAlpha = this.alphaPattern.end; | |
| 1349 var boxLeft = imageWidth - keySize - margin; | |
| 1350 var textY = offset + keySize / 2; | |
| 1351 | |
| 1352 var label; | |
| 1353 var keyNameWidth; | |
| 1354 | |
| 1355 if ( this == selectedNode ) | |
| 1356 { | |
| 1357 color = colorUnclassified; | |
| 1358 label = | |
| 1359 this.getUnclassifiedText() + | |
| 1360 ' ' + | |
| 1361 this.getUnclassifiedPercentage(); | |
| 1362 keyNameWidth = measureText(label, false); | |
| 1363 } | |
| 1364 else | |
| 1365 { | |
| 1366 label = this.keyLabel; | |
| 1367 color = rgbText(this.r.end, this.g.end, this.b.end); | |
| 1368 | |
| 1369 if ( highlight ) | |
| 1370 { | |
| 1371 if ( this.searchResultChildren() ) | |
| 1372 { | |
| 1373 label = label + searchResultString(this.searchResultChildren()); | |
| 1374 } | |
| 1375 | |
| 1376 keyNameWidth = measureText(label, bold); | |
| 1377 } | |
| 1378 else | |
| 1379 { | |
| 1380 keyNameWidth = this.keyNameWidth; | |
| 1381 } | |
| 1382 } | |
| 1383 | |
| 1384 var textLeft = boxLeft - keyBuffer - keyNameWidth - fontSize / 2; | |
| 1385 var labelLeft = textLeft; | |
| 1386 | |
| 1387 if ( labelLeft > keyMinTextLeft - fontSize / 2 ) | |
| 1388 { | |
| 1389 keyMinTextLeft -= fontSize / 2; | |
| 1390 | |
| 1391 if ( keyMinTextLeft < centerX - gRadius + fontSize / 2 ) | |
| 1392 { | |
| 1393 keyMinTextLeft = centerX - gRadius + fontSize / 2; | |
| 1394 } | |
| 1395 | |
| 1396 labelLeft = keyMinTextLeft; | |
| 1397 } | |
| 1398 | |
| 1399 var lineX = new Array(); | |
| 1400 var lineY = new Array(); | |
| 1401 | |
| 1402 var bendRadius; | |
| 1403 var keyAngle = Math.atan((textY - centerY) / (labelLeft - centerX)); | |
| 1404 var arcAngle; | |
| 1405 | |
| 1406 if ( keyAngle < 0 ) | |
| 1407 { | |
| 1408 keyAngle += Math.PI; | |
| 1409 } | |
| 1410 | |
| 1411 if ( keyMinAngle == 0 || angle < keyMinAngle ) | |
| 1412 { | |
| 1413 keyMinAngle = angle; | |
| 1414 } | |
| 1415 | |
| 1416 if ( angle > Math.PI && keyMinAngle > Math.PI ) | |
| 1417 { | |
| 1418 // allow lines to come underneath the chart | |
| 1419 | |
| 1420 angle -= Math.PI * 2; | |
| 1421 } | |
| 1422 | |
| 1423 lineX.push(Math.cos(angle) * gRadius); | |
| 1424 lineY.push(Math.sin(angle) * gRadius); | |
| 1425 | |
| 1426 if ( angle < keyAngle && textY > centerY + Math.sin(angle) * (gRadius + buffer * (currentKey - 1) / (keys + 1) / 2 + buffer / 2) ) | |
| 1427 { | |
| 1428 bendRadius = gRadius + buffer - buffer * currentKey / (keys + 1) / 2; | |
| 1429 } | |
| 1430 else | |
| 1431 { | |
| 1432 bendRadius = gRadius + buffer * currentKey / (keys + 1) / 2 + buffer / 2; | |
| 1433 } | |
| 1434 | |
| 1435 var outside = | |
| 1436 Math.sqrt | |
| 1437 ( | |
| 1438 Math.pow(labelLeft - centerX, 2) + | |
| 1439 Math.pow(textY - centerY, 2) | |
| 1440 ) > bendRadius; | |
| 1441 | |
| 1442 if ( ! outside ) | |
| 1443 { | |
| 1444 arcAngle = Math.asin((textY - centerY) / bendRadius); | |
| 1445 | |
| 1446 keyMinTextLeft = min(keyMinTextLeft, centerX + bendRadius * Math.cos(arcAngle) - fontSize / 2); | |
| 1447 | |
| 1448 if ( labelLeft < textLeft && textLeft > centerX + bendRadius * Math.cos(arcAngle) ) | |
| 1449 { | |
| 1450 lineX.push(textLeft - centerX); | |
| 1451 lineY.push(textY - centerY); | |
| 1452 } | |
| 1453 } | |
| 1454 else | |
| 1455 { | |
| 1456 keyMinTextLeft = min(keyMinTextLeft, labelLeft - fontSize / 2); | |
| 1457 | |
| 1458 if ( angle < keyAngle ) | |
| 1459 { | |
| 1460 // flip everything over y = x | |
| 1461 // | |
| 1462 arcAngle = Math.PI / 2 - keyLineAngle | |
| 1463 ( | |
| 1464 Math.PI / 2 - angle, | |
| 1465 Math.PI / 2 - keyAngle, | |
| 1466 bendRadius, | |
| 1467 textY - centerY, | |
| 1468 labelLeft - centerX, | |
| 1469 lineY, | |
| 1470 lineX | |
| 1471 ); | |
| 1472 | |
| 1473 } | |
| 1474 else | |
| 1475 { | |
| 1476 arcAngle = keyLineAngle | |
| 1477 ( | |
| 1478 angle, | |
| 1479 keyAngle, | |
| 1480 bendRadius, | |
| 1481 labelLeft - centerX, | |
| 1482 textY - centerY, | |
| 1483 lineX, | |
| 1484 lineY | |
| 1485 ); | |
| 1486 } | |
| 1487 } | |
| 1488 | |
| 1489 if ( labelLeft > centerX + bendRadius * Math.cos(arcAngle) || | |
| 1490 textY > centerY + bendRadius * Math.sin(arcAngle) + .01) | |
| 1491 // if ( outside || ) | |
| 1492 { | |
| 1493 lineX.push(labelLeft - centerX); | |
| 1494 lineY.push(textY - centerY); | |
| 1495 | |
| 1496 if ( textLeft != labelLeft ) | |
| 1497 { | |
| 1498 lineX.push(textLeft - centerX); | |
| 1499 lineY.push(textY - centerY); | |
| 1500 } | |
| 1501 } | |
| 1502 | |
| 1503 context.globalAlpha = this.alphaWedge.current(); | |
| 1504 | |
| 1505 if ( snapshotMode ) | |
| 1506 { | |
| 1507 var labelSVG; | |
| 1508 | |
| 1509 if ( this == selectedNode ) | |
| 1510 { | |
| 1511 labelSVG = | |
| 1512 this.getUnclassifiedText() + | |
| 1513 spacer() + | |
| 1514 this.getUnclassifiedPercentage(); | |
| 1515 } | |
| 1516 else | |
| 1517 { | |
| 1518 labelSVG = this.name + spacer() + this.getPercentage() + '%'; | |
| 1519 } | |
| 1520 | |
| 1521 svg += | |
| 1522 '<rect fill="' + color + '" ' + | |
| 1523 'x="' + boxLeft + '" y="' + offset + | |
| 1524 '" width="' + keySize + '" height="' + keySize + '"/>'; | |
| 1525 | |
| 1526 if ( patternAlpha ) | |
| 1527 { | |
| 1528 svg += | |
| 1529 '<rect fill="url(#hiddenPattern)" style="stroke:none" ' + | |
| 1530 'x="' + boxLeft + '" y="' + offset + | |
| 1531 '" width="' + keySize + '" height="' + keySize + '"/>'; | |
| 1532 } | |
| 1533 | |
| 1534 svg += | |
| 1535 '<path class="line' + | |
| 1536 (highlight ? ' highlight' : '') + | |
| 1537 '" d="M ' + (lineX[0] + centerX) + ',' + | |
| 1538 (lineY[0] + centerY); | |
| 1539 | |
| 1540 if ( angle != arcAngle ) | |
| 1541 { | |
| 1542 svg += | |
| 1543 ' L ' + (centerX + bendRadius * Math.cos(angle)) + ',' + | |
| 1544 (centerY + bendRadius * Math.sin(angle)) + | |
| 1545 ' A ' + bendRadius + ',' + bendRadius + ' 0 ' + | |
| 1546 '0,' + (angle > arcAngle ? '0' : '1') + ' ' + | |
| 1547 (centerX + bendRadius * Math.cos(arcAngle)) + ',' + | |
| 1548 (centerY + bendRadius * Math.sin(arcAngle)); | |
| 1549 } | |
| 1550 | |
| 1551 for ( var i = 1; i < lineX.length; i++ ) | |
| 1552 { | |
| 1553 svg += | |
| 1554 ' L ' + (centerX + lineX[i]) + ',' + | |
| 1555 (centerY + lineY[i]); | |
| 1556 } | |
| 1557 | |
| 1558 svg += '"/>'; | |
| 1559 | |
| 1560 if ( highlight ) | |
| 1561 { | |
| 1562 if ( this.searchResultChildren() ) | |
| 1563 { | |
| 1564 labelSVG = labelSVG + searchResultString(this.searchResultChildren()); | |
| 1565 } | |
| 1566 | |
| 1567 drawBubbleSVG | |
| 1568 ( | |
| 1569 boxLeft - keyBuffer - keyNameWidth - fontSize / 2, | |
| 1570 textY - fontSize, | |
| 1571 keyNameWidth + fontSize, | |
| 1572 fontSize * 2, | |
| 1573 fontSize, | |
| 1574 0 | |
| 1575 ); | |
| 1576 | |
| 1577 if ( this.isSearchResult ) | |
| 1578 { | |
| 1579 drawSearchHighlights | |
| 1580 ( | |
| 1581 label, | |
| 1582 boxLeft - keyBuffer - keyNameWidth, | |
| 1583 textY, | |
| 1584 0 | |
| 1585 ) | |
| 1586 } | |
| 1587 } | |
| 1588 | |
| 1589 svg += svgText(labelSVG, boxLeft - keyBuffer, textY, 'end', bold, colorText); | |
| 1590 } | |
| 1591 else | |
| 1592 { | |
| 1593 context.fillStyle = color; | |
| 1594 context.translate(-centerX, -centerY); | |
| 1595 context.strokeStyle = 'black'; | |
| 1596 context.globalAlpha = 1;//this.alphaWedge.current(); | |
| 1597 | |
| 1598 context.fillRect(boxLeft, offset, keySize, keySize); | |
| 1599 | |
| 1600 if ( patternAlpha ) | |
| 1601 { | |
| 1602 context.globalAlpha = patternAlpha; | |
| 1603 context.fillStyle = hiddenPattern; | |
| 1604 | |
| 1605 // make clipping box for Firefox performance | |
| 1606 context.beginPath(); | |
| 1607 context.moveTo(boxLeft, offset); | |
| 1608 context.lineTo(boxLeft + keySize, offset); | |
| 1609 context.lineTo(boxLeft + keySize, offset + keySize); | |
| 1610 context.lineTo(boxLeft, offset + keySize); | |
| 1611 context.closePath(); | |
| 1612 context.save(); | |
| 1613 context.clip(); | |
| 1614 | |
| 1615 context.fillRect(boxLeft, offset, keySize, keySize); | |
| 1616 context.fillRect(boxLeft, offset, keySize, keySize); | |
| 1617 | |
| 1618 context.restore(); // remove clipping region | |
| 1619 } | |
| 1620 | |
| 1621 if ( highlight ) | |
| 1622 { | |
| 1623 this.setHighlightStyle(); | |
| 1624 context.fillRect(boxLeft, offset, keySize, keySize); | |
| 1625 } | |
| 1626 else | |
| 1627 { | |
| 1628 context.lineWidth = thinLineWidth; | |
| 1629 } | |
| 1630 | |
| 1631 context.strokeRect(boxLeft, offset, keySize, keySize); | |
| 1632 | |
| 1633 if ( lineX.length ) | |
| 1634 { | |
| 1635 context.beginPath(); | |
| 1636 context.moveTo(lineX[0] + centerX, lineY[0] + centerY); | |
| 1637 | |
| 1638 context.arc(centerX, centerY, bendRadius, angle, arcAngle, angle > arcAngle); | |
| 1639 | |
| 1640 for ( var i = 1; i < lineX.length; i++ ) | |
| 1641 { | |
| 1642 context.lineTo(lineX[i] + centerX, lineY[i] + centerY); | |
| 1643 } | |
| 1644 | |
| 1645 context.globalAlpha = this == selectedNode ? | |
| 1646 this.children[0].alphaWedge.current() : | |
| 1647 this.alphaWedge.current(); | |
| 1648 context.lineWidth = highlight ? highlightLineWidth : thinLineWidth; | |
| 1649 context.stroke(); | |
| 1650 context.globalAlpha = 1; | |
| 1651 } | |
| 1652 | |
| 1653 if ( highlight ) | |
| 1654 { | |
| 1655 drawBubbleCanvas | |
| 1656 ( | |
| 1657 boxLeft - keyBuffer - keyNameWidth - fontSize / 2, | |
| 1658 textY - fontSize, | |
| 1659 keyNameWidth + fontSize, | |
| 1660 fontSize * 2, | |
| 1661 fontSize, | |
| 1662 0 | |
| 1663 ); | |
| 1664 | |
| 1665 if ( this.isSearchResult ) | |
| 1666 { | |
| 1667 drawSearchHighlights | |
| 1668 ( | |
| 1669 label, | |
| 1670 boxLeft - keyBuffer - keyNameWidth, | |
| 1671 textY, | |
| 1672 0 | |
| 1673 ) | |
| 1674 } | |
| 1675 } | |
| 1676 | |
| 1677 drawText(label, boxLeft - keyBuffer, offset + keySize / 2, 0, 'end', bold, colorText); | |
| 1678 | |
| 1679 context.translate(centerX, centerY); | |
| 1680 } | |
| 1681 | |
| 1682 currentKey++; | |
| 1683 } | |
| 1684 | |
| 1685 this.drawLabel = function(angle, bubble, bold, selected, radial) | |
| 1686 { | |
| 1687 if ( context.globalAlpha == 0 ) | |
| 1688 { | |
| 1689 return; | |
| 1690 } | |
| 1691 | |
| 1692 var innerText; | |
| 1693 var label; | |
| 1694 var radius; | |
| 1695 | |
| 1696 if ( radial ) | |
| 1697 { | |
| 1698 radius = (this.radiusInner.current() + 1) * gRadius / 2; | |
| 1699 } | |
| 1700 else | |
| 1701 { | |
| 1702 radius = this.labelRadius.current() * gRadius; | |
| 1703 } | |
| 1704 | |
| 1705 if ( radial && (selected || bubble ) ) | |
| 1706 { | |
| 1707 var percentage = this.getPercentage(); | |
| 1708 innerText = percentage + '%'; | |
| 1709 } | |
| 1710 | |
| 1711 if | |
| 1712 ( | |
| 1713 ! radial && | |
| 1714 this != selectedNode && | |
| 1715 ! bubble && | |
| 1716 ( !zoomOut || this != selectedNodeLast) | |
| 1717 ) | |
| 1718 { | |
| 1719 label = this.shortenLabel(); | |
| 1720 } | |
| 1721 else | |
| 1722 { | |
| 1723 label = this.name; | |
| 1724 } | |
| 1725 | |
| 1726 var flipped = drawTextPolar | |
| 1727 ( | |
| 1728 label, | |
| 1729 innerText, | |
| 1730 angle, | |
| 1731 radius, | |
| 1732 radial, | |
| 1733 bubble, | |
| 1734 bold, | |
| 1735 // this.isSearchResult && this.shouldAddSearchResultsString() && (!selected || this == selectedNode || highlight), | |
| 1736 this.isSearchResult && (!selected || this == selectedNode || bubble), | |
| 1737 (this.hideAlone || !selected || this == selectedNode ) ? this.searchResultChildren() : 0 | |
| 1738 ); | |
| 1739 | |
| 1740 var depth = this.getDepth() - selectedNode.getDepth() + 1; | |
| 1741 | |
| 1742 if | |
| 1743 ( | |
| 1744 ! radial && | |
| 1745 ! bubble && | |
| 1746 this != selectedNode && | |
| 1747 this.angleEnd.end != this.angleStart.end && | |
| 1748 nLabelOffsets[depth - 2] > 2 && | |
| 1749 this.labelWidth.current() > (this.angleEnd.end - this.angleStart.end) * Math.abs(radius) && | |
| 1750 ! ( zoomOut && this == selectedNodeLast ) && | |
| 1751 this.labelRadius.end > 0 | |
| 1752 ) | |
| 1753 { | |
| 1754 // name extends beyond wedge; draw tick mark towards the central | |
| 1755 // radius for easier identification | |
| 1756 | |
| 1757 var radiusCenter = compress ? | |
| 1758 (compressedRadii[depth - 1] + compressedRadii[depth - 2]) / 2 : | |
| 1759 (depth - .5) * nodeRadius; | |
| 1760 | |
| 1761 if ( this.labelRadius.end > radiusCenter ) | |
| 1762 { | |
| 1763 if ( flipped ) | |
| 1764 { | |
| 1765 drawTick(radius - tickLength * 1.4 , tickLength, angle); | |
| 1766 } | |
| 1767 else | |
| 1768 { | |
| 1769 drawTick(radius - tickLength * 1.7, tickLength, angle); | |
| 1770 } | |
| 1771 } | |
| 1772 else | |
| 1773 { | |
| 1774 if ( flipped ) | |
| 1775 { | |
| 1776 drawTick(radius + tickLength * .7, tickLength, angle); | |
| 1777 } | |
| 1778 else | |
| 1779 { | |
| 1780 drawTick(radius + tickLength * .4, tickLength, angle); | |
| 1781 } | |
| 1782 } | |
| 1783 } | |
| 1784 } | |
| 1785 | |
| 1786 this.drawLines = function(angleStart, angleEnd, radiusInner, drawRadial, selected) | |
| 1787 { | |
| 1788 if ( snapshotMode ) | |
| 1789 { | |
| 1790 if ( this != selectedNode) | |
| 1791 { | |
| 1792 if ( angleEnd == angleStart + Math.PI * 2 ) | |
| 1793 { | |
| 1794 // fudge to prevent overlap, which causes arc ambiguity | |
| 1795 // | |
| 1796 angleEnd -= .1 / gRadius; | |
| 1797 } | |
| 1798 | |
| 1799 var longArc = angleEnd - angleStart > Math.PI ? 1 : 0; | |
| 1800 | |
| 1801 var x1 = centerX + radiusInner * Math.cos(angleStart); | |
| 1802 var y1 = centerY + radiusInner * Math.sin(angleStart); | |
| 1803 | |
| 1804 var x2 = centerX + gRadius * Math.cos(angleStart); | |
| 1805 var y2 = centerY + gRadius * Math.sin(angleStart); | |
| 1806 | |
| 1807 var x3 = centerX + gRadius * Math.cos(angleEnd); | |
| 1808 var y3 = centerY + gRadius * Math.sin(angleEnd); | |
| 1809 | |
| 1810 var x4 = centerX + radiusInner * Math.cos(angleEnd); | |
| 1811 var y4 = centerY + radiusInner * Math.sin(angleEnd); | |
| 1812 | |
| 1813 if ( this.alphaArc.end ) | |
| 1814 { | |
| 1815 var dArray = | |
| 1816 [ | |
| 1817 " M ", x4, ",", y4, | |
| 1818 " A ", radiusInner, ",", radiusInner, " 0 ", longArc, | |
| 1819 " 0 ", x1, ",", y1 | |
| 1820 ]; | |
| 1821 | |
| 1822 svg += '<path class="line" d="' + dArray.join('') + '"/>'; | |
| 1823 } | |
| 1824 | |
| 1825 if ( drawRadial && this.alphaLine.end ) | |
| 1826 { | |
| 1827 svg += '<line x1="' + x3 + '" y1="' + y3 + '" x2="' + x4 + '" y2="' + y4 + '"/>'; | |
| 1828 } | |
| 1829 } | |
| 1830 } | |
| 1831 else | |
| 1832 { | |
| 1833 context.lineWidth = thinLineWidth; | |
| 1834 context.strokeStyle = 'black'; | |
| 1835 context.beginPath(); | |
| 1836 context.arc(0, 0, radiusInner, angleStart, angleEnd, false); | |
| 1837 context.globalAlpha = this.alphaArc.current(); | |
| 1838 context.stroke(); | |
| 1839 | |
| 1840 if ( drawRadial ) | |
| 1841 { | |
| 1842 var x1 = radiusInner * Math.cos(angleEnd); | |
| 1843 var y1 = radiusInner * Math.sin(angleEnd); | |
| 1844 var x2 = gRadius * Math.cos(angleEnd); | |
| 1845 var y2 = gRadius * Math.sin(angleEnd); | |
| 1846 | |
| 1847 context.beginPath(); | |
| 1848 context.moveTo(x1, y1); | |
| 1849 context.lineTo(x2, y2); | |
| 1850 | |
| 1851 // if ( this.getCollapse() )//( selected && this != selectedNode ) | |
| 1852 { | |
| 1853 context.globalAlpha = this.alphaLine.current(); | |
| 1854 } | |
| 1855 | |
| 1856 context.stroke(); | |
| 1857 } | |
| 1858 } | |
| 1859 } | |
| 1860 | |
| 1861 this.drawMap = function(child) | |
| 1862 { | |
| 1863 if ( this.parent ) | |
| 1864 { | |
| 1865 this.parent.drawMap(child); | |
| 1866 } | |
| 1867 | |
| 1868 if ( this.getCollapse() && this != child || this == focusNode ) | |
| 1869 { | |
| 1870 return; | |
| 1871 } | |
| 1872 | |
| 1873 var angleStart = | |
| 1874 (child.baseMagnitude - this.baseMagnitude) / this.magnitude * Math.PI * 2 + | |
| 1875 rotationOffset; | |
| 1876 var angleEnd = | |
| 1877 (child.baseMagnitude - this.baseMagnitude + child.magnitude) / | |
| 1878 this.magnitude * Math.PI * 2 + | |
| 1879 rotationOffset; | |
| 1880 | |
| 1881 var box = this.getMapPosition(); | |
| 1882 | |
| 1883 context.save(); | |
| 1884 context.fillStyle = 'black'; | |
| 1885 context.textAlign = 'end'; | |
| 1886 context.textBaseline = 'middle'; | |
| 1887 | |
| 1888 var textX = box.x - mapRadius - mapBuffer; | |
| 1889 var percentage = getPercentage(child.magnitude / this.magnitude); | |
| 1890 | |
| 1891 var highlight = this == selectedNode || this == highlightedNode; | |
| 1892 | |
| 1893 if ( highlight ) | |
| 1894 { | |
| 1895 context.font = fontBold; | |
| 1896 } | |
| 1897 else | |
| 1898 { | |
| 1899 context.font = fontNormal; | |
| 1900 } | |
| 1901 | |
| 1902 context.fillText(percentage + '% of', textX, box.y - mapRadius / 3); | |
| 1903 context.fillText(this.name, textX, box.y + mapRadius / 3); | |
| 1904 | |
| 1905 if ( highlight ) | |
| 1906 { | |
| 1907 context.font = fontNormal; | |
| 1908 } | |
| 1909 | |
| 1910 if ( this == highlightedNode && this != selectedNode ) | |
| 1911 { | |
| 1912 context.fillStyle = 'rgb(245, 245, 245)'; | |
| 1913 // context.fillStyle = 'rgb(200, 200, 200)'; | |
| 1914 } | |
| 1915 else | |
| 1916 { | |
| 1917 context.fillStyle = 'rgb(255, 255, 255)'; | |
| 1918 } | |
| 1919 | |
| 1920 context.beginPath(); | |
| 1921 context.arc(box.x, box.y, mapRadius, 0, Math.PI * 2, true); | |
| 1922 context.closePath(); | |
| 1923 context.fill(); | |
| 1924 | |
| 1925 if ( this == selectedNode ) | |
| 1926 { | |
| 1927 context.lineWidth = 1; | |
| 1928 context.fillStyle = 'rgb(100, 100, 100)'; | |
| 1929 } | |
| 1930 else | |
| 1931 { | |
| 1932 if ( this == highlightedNode ) | |
| 1933 { | |
| 1934 context.lineWidth = .2; | |
| 1935 context.fillStyle = 'rgb(190, 190, 190)'; | |
| 1936 } | |
| 1937 else | |
| 1938 { | |
| 1939 context.lineWidth = .2; | |
| 1940 context.fillStyle = 'rgb(200, 200, 200)'; | |
| 1941 } | |
| 1942 } | |
| 1943 | |
| 1944 var maxDepth = this.getMaxDepth(); | |
| 1945 | |
| 1946 if ( ! compress && maxDepth > maxPossibleDepth + this.getDepth() - 1 ) | |
| 1947 { | |
| 1948 maxDepth = maxPossibleDepth + this.getDepth() - 1; | |
| 1949 } | |
| 1950 | |
| 1951 if ( this.getDepth() < selectedNode.getDepth() ) | |
| 1952 { | |
| 1953 if ( child.getDepth() - 1 >= maxDepth ) | |
| 1954 { | |
| 1955 maxDepth = child.getDepth(); | |
| 1956 } | |
| 1957 } | |
| 1958 | |
| 1959 var radiusInner; | |
| 1960 | |
| 1961 if ( compress ) | |
| 1962 { | |
| 1963 radiusInner = 0; | |
| 1964 // Math.atan(child.getDepth() - this.getDepth()) / | |
| 1965 // Math.PI * 2 * .9; | |
| 1966 } | |
| 1967 else | |
| 1968 { | |
| 1969 radiusInner = | |
| 1970 (child.getDepth() - this.getDepth()) / | |
| 1971 (maxDepth - this.getDepth() + 1); | |
| 1972 } | |
| 1973 | |
| 1974 context.stroke(); | |
| 1975 context.beginPath(); | |
| 1976 | |
| 1977 if ( radiusInner == 0 ) | |
| 1978 { | |
| 1979 context.moveTo(box.x, box.y); | |
| 1980 } | |
| 1981 else | |
| 1982 { | |
| 1983 context.arc(box.x, box.y, mapRadius * radiusInner, angleEnd, angleStart, true); | |
| 1984 } | |
| 1985 | |
| 1986 context.arc(box.x, box.y, mapRadius, angleStart, angleEnd, false); | |
| 1987 context.closePath(); | |
| 1988 context.fill(); | |
| 1989 | |
| 1990 if ( this == highlightedNode && this != selectedNode ) | |
| 1991 { | |
| 1992 context.lineWidth = 1; | |
| 1993 context.stroke(); | |
| 1994 } | |
| 1995 | |
| 1996 context.restore(); | |
| 1997 } | |
| 1998 | |
| 1999 this.drawReferenceRings = function(childRadiusInner) | |
| 2000 { | |
| 2001 if ( snapshotMode ) | |
| 2002 { | |
| 2003 svg += | |
| 2004 '<circle cx="' + centerX + '" cy="' + centerY + | |
| 2005 '" r="' + childRadiusInner + '"/>'; | |
| 2006 svg += | |
| 2007 '<circle cx="' + centerX + '" cy="' + centerY + | |
| 2008 '" r="' + gRadius + '"/>'; | |
| 2009 } | |
| 2010 else | |
| 2011 { | |
| 2012 context.globalAlpha = 1 - this.alphaLine.current();//this.getUncollapsed().alphaLine.current(); | |
| 2013 context.beginPath(); | |
| 2014 context.arc(0, 0, childRadiusInner, 0, Math.PI * 2, false); | |
| 2015 context.stroke(); | |
| 2016 context.beginPath(); | |
| 2017 context.arc(0, 0, gRadius, 0, Math.PI * 2, false); | |
| 2018 context.stroke(); | |
| 2019 } | |
| 2020 } | |
| 2021 | |
| 2022 this.getCollapse = function() | |
| 2023 { | |
| 2024 return ( | |
| 2025 collapse && | |
| 2026 this.collapse && | |
| 2027 this.depth != maxAbsoluteDepth | |
| 2028 ); | |
| 2029 } | |
| 2030 | |
| 2031 this.getDepth = function() | |
| 2032 { | |
| 2033 if ( collapse ) | |
| 2034 { | |
| 2035 return this.depthCollapsed; | |
| 2036 } | |
| 2037 else | |
| 2038 { | |
| 2039 return this.depth; | |
| 2040 } | |
| 2041 } | |
| 2042 | |
| 2043 this.getMagnitude = function() | |
| 2044 { | |
| 2045 return this.attributes[magnitudeIndex][currentDataset]; | |
| 2046 } | |
| 2047 | |
| 2048 this.getMapPosition = function() | |
| 2049 { | |
| 2050 return { | |
| 2051 x : (details.offsetLeft + details.clientWidth - mapRadius), | |
| 2052 y : ((focusNode.getDepth() - this.getDepth()) * | |
| 2053 (mapBuffer + mapRadius * 2) - mapRadius) + | |
| 2054 details.clientHeight + details.offsetTop | |
| 2055 }; | |
| 2056 } | |
| 2057 | |
| 2058 this.getMaxDepth = function(limit) | |
| 2059 { | |
| 2060 var max; | |
| 2061 | |
| 2062 if ( collapse ) | |
| 2063 { | |
| 2064 return this.maxDepthCollapsed; | |
| 2065 } | |
| 2066 else | |
| 2067 { | |
| 2068 if ( this.maxDepth > maxAbsoluteDepth ) | |
| 2069 { | |
| 2070 return maxAbsoluteDepth; | |
| 2071 } | |
| 2072 else | |
| 2073 { | |
| 2074 return this.maxDepth; | |
| 2075 } | |
| 2076 } | |
| 2077 } | |
| 2078 | |
| 2079 this.getData = function(index, summary) | |
| 2080 { | |
| 2081 var files = new Array(); | |
| 2082 | |
| 2083 if | |
| 2084 ( | |
| 2085 this.attributes[index] != null && | |
| 2086 this.attributes[index][currentDataset] != null && | |
| 2087 this.attributes[index][currentDataset] != '' | |
| 2088 ) | |
| 2089 { | |
| 2090 files.push | |
| 2091 ( | |
| 2092 document.location + | |
| 2093 '.files/' + | |
| 2094 this.attributes[index][currentDataset] | |
| 2095 ); | |
| 2096 } | |
| 2097 | |
| 2098 if ( summary ) | |
| 2099 { | |
| 2100 for ( var i = 0; i < this.children.length; i++ ) | |
| 2101 { | |
| 2102 files = files.concat(this.children[i].getData(index, true)); | |
| 2103 } | |
| 2104 } | |
| 2105 | |
| 2106 return files; | |
| 2107 } | |
| 2108 | |
| 2109 this.getList = function(index, summary) | |
| 2110 { | |
| 2111 var list; | |
| 2112 | |
| 2113 if | |
| 2114 ( | |
| 2115 this.attributes[index] != null && | |
| 2116 this.attributes[index][currentDataset] != null | |
| 2117 ) | |
| 2118 { | |
| 2119 list = this.attributes[index][currentDataset]; | |
| 2120 } | |
| 2121 else | |
| 2122 { | |
| 2123 list = new Array(); | |
| 2124 } | |
| 2125 | |
| 2126 if ( summary ) | |
| 2127 { | |
| 2128 for ( var i = 0; i < this.children.length; i++ ) | |
| 2129 { | |
| 2130 list = list.concat(this.children[i].getList(index, true)); | |
| 2131 } | |
| 2132 } | |
| 2133 | |
| 2134 return list; | |
| 2135 } | |
| 2136 | |
| 2137 this.getParent = function() | |
| 2138 { | |
| 2139 // returns parent, accounting for collapsing or 0 if doesn't exist | |
| 2140 | |
| 2141 var parent = this.parent; | |
| 2142 | |
| 2143 while ( parent != 0 && parent.getCollapse() ) | |
| 2144 { | |
| 2145 parent = parent.parent; | |
| 2146 } | |
| 2147 | |
| 2148 return parent; | |
| 2149 } | |
| 2150 | |
| 2151 this.getPercentage = function() | |
| 2152 { | |
| 2153 return getPercentage(this.magnitude / selectedNode.magnitude); | |
| 2154 } | |
| 2155 | |
| 2156 this.getUnclassifiedPercentage = function() | |
| 2157 { | |
| 2158 if ( this.children.length ) | |
| 2159 { | |
| 2160 var lastChild = this.children[this.children.length - 1]; | |
| 2161 | |
| 2162 return getPercentage | |
| 2163 ( | |
| 2164 ( | |
| 2165 this.baseMagnitude + | |
| 2166 this.magnitude - | |
| 2167 lastChild.magnitude - | |
| 2168 lastChild.baseMagnitude | |
| 2169 ) / this.magnitude | |
| 2170 ) + '%'; | |
| 2171 } | |
| 2172 else | |
| 2173 { | |
| 2174 return '100%'; | |
| 2175 } | |
| 2176 } | |
| 2177 | |
| 2178 this.getUnclassifiedText = function() | |
| 2179 { | |
| 2180 return '[other '+ this.name + ']'; | |
| 2181 } | |
| 2182 | |
| 2183 this.getUncollapsed = function() | |
| 2184 { | |
| 2185 // recurse through collapsed children until uncollapsed node is found | |
| 2186 | |
| 2187 if ( this.getCollapse() ) | |
| 2188 { | |
| 2189 return this.children[0].getUncollapsed(); | |
| 2190 } | |
| 2191 else | |
| 2192 { | |
| 2193 return this; | |
| 2194 } | |
| 2195 } | |
| 2196 | |
| 2197 this.hasChildren = function() | |
| 2198 { | |
| 2199 return this.children.length && this.depth < maxAbsoluteDepth && this.magnitude; | |
| 2200 } | |
| 2201 | |
| 2202 this.hasParent = function(parent) | |
| 2203 { | |
| 2204 if ( this.parent ) | |
| 2205 { | |
| 2206 if ( this.parent == parent ) | |
| 2207 { | |
| 2208 return true; | |
| 2209 } | |
| 2210 else | |
| 2211 { | |
| 2212 return this.parent.hasParent(parent); | |
| 2213 } | |
| 2214 } | |
| 2215 else | |
| 2216 { | |
| 2217 return false; | |
| 2218 } | |
| 2219 } | |
| 2220 | |
| 2221 this.maxVisibleDepth = function(maxDepth) | |
| 2222 { | |
| 2223 var childInnerRadius; | |
| 2224 var depth = this.getDepth() - selectedNode.getDepth() + 1; | |
| 2225 var currentMaxDepth = depth; | |
| 2226 | |
| 2227 if ( this.hasChildren() && depth < maxDepth) | |
| 2228 { | |
| 2229 var lastChild = this.children[this.children.length - 1]; | |
| 2230 | |
| 2231 if ( this.name == 'Pseudomonadaceae' ) | |
| 2232 { | |
| 2233 var x = 3; | |
| 2234 } | |
| 2235 | |
| 2236 if | |
| 2237 ( | |
| 2238 lastChild.baseMagnitude + lastChild.magnitude < | |
| 2239 this.baseMagnitude + this.magnitude | |
| 2240 ) | |
| 2241 { | |
| 2242 currentMaxDepth++; | |
| 2243 } | |
| 2244 | |
| 2245 if ( compress ) | |
| 2246 { | |
| 2247 childInnerRadius = compressedRadii[depth - 1]; | |
| 2248 } | |
| 2249 else | |
| 2250 { | |
| 2251 childInnerRadius = (depth) / maxDepth; | |
| 2252 } | |
| 2253 | |
| 2254 for ( var i = 0; i < this.children.length; i++ ) | |
| 2255 { | |
| 2256 if | |
| 2257 (//true || | |
| 2258 this.children[i].magnitude * | |
| 2259 angleFactor * | |
| 2260 (childInnerRadius + 1) * | |
| 2261 gRadius >= | |
| 2262 minWidth() | |
| 2263 ) | |
| 2264 { | |
| 2265 var childMaxDepth = this.children[i].maxVisibleDepth(maxDepth); | |
| 2266 | |
| 2267 if ( childMaxDepth > currentMaxDepth ) | |
| 2268 { | |
| 2269 currentMaxDepth = childMaxDepth; | |
| 2270 } | |
| 2271 } | |
| 2272 } | |
| 2273 } | |
| 2274 | |
| 2275 return currentMaxDepth; | |
| 2276 } | |
| 2277 | |
| 2278 this.resetLabelWidth = function() | |
| 2279 { | |
| 2280 var nameWidthOld = this.nameWidth; | |
| 2281 | |
| 2282 if ( true || ! this.radial )//&& fontSize != fontSizeLast ) | |
| 2283 { | |
| 2284 var dim = context.measureText(this.name); | |
| 2285 this.nameWidth = dim.width; | |
| 2286 } | |
| 2287 | |
| 2288 if ( fontSize != fontSizeLast && this.labelWidth.end == nameWidthOld * labelWidthFudge ) | |
| 2289 { | |
| 2290 // font size changed; adjust start of tween to match | |
| 2291 | |
| 2292 this.labelWidth.start = this.nameWidth * labelWidthFudge; | |
| 2293 } | |
| 2294 else | |
| 2295 { | |
| 2296 this.labelWidth.start = this.labelWidth.current(); | |
| 2297 } | |
| 2298 | |
| 2299 this.labelWidth.end = this.nameWidth * labelWidthFudge; | |
| 2300 } | |
| 2301 | |
| 2302 this.restrictLabelWidth = function(width) | |
| 2303 { | |
| 2304 if ( width < this.labelWidth.end ) | |
| 2305 { | |
| 2306 this.labelWidth.end = width; | |
| 2307 } | |
| 2308 } | |
| 2309 | |
| 2310 this.search = function() | |
| 2311 { | |
| 2312 this.isSearchResult = false; | |
| 2313 this.searchResults = 0; | |
| 2314 | |
| 2315 if | |
| 2316 ( | |
| 2317 ! this.getCollapse() && | |
| 2318 search.value != '' && | |
| 2319 this.name.toLowerCase().indexOf(search.value.toLowerCase()) != -1 | |
| 2320 ) | |
| 2321 { | |
| 2322 this.isSearchResult = true; | |
| 2323 this.searchResults = 1; | |
| 2324 nSearchResults++; | |
| 2325 } | |
| 2326 | |
| 2327 for ( var i = 0; i < this.children.length; i++ ) | |
| 2328 { | |
| 2329 this.searchResults += this.children[i].search(); | |
| 2330 } | |
| 2331 | |
| 2332 return this.searchResults; | |
| 2333 } | |
| 2334 | |
| 2335 this.searchResultChildren = function() | |
| 2336 { | |
| 2337 if ( this.isSearchResult ) | |
| 2338 { | |
| 2339 return this.searchResults - 1; | |
| 2340 } | |
| 2341 else | |
| 2342 { | |
| 2343 return this.searchResults; | |
| 2344 } | |
| 2345 } | |
| 2346 | |
| 2347 this.setDepth = function(depth, depthCollapsed) | |
| 2348 { | |
| 2349 this.depth = depth; | |
| 2350 this.depthCollapsed = depthCollapsed; | |
| 2351 | |
| 2352 if | |
| 2353 ( | |
| 2354 this.children.length == 1 && | |
| 2355 // this.magnitude > 0 && | |
| 2356 this.children[0].magnitude == this.magnitude && | |
| 2357 ( head.children.length > 1 || this.children[0].children.length ) | |
| 2358 ) | |
| 2359 { | |
| 2360 this.collapse = true; | |
| 2361 } | |
| 2362 else | |
| 2363 { | |
| 2364 this.collapse = false; | |
| 2365 depthCollapsed++; | |
| 2366 } | |
| 2367 | |
| 2368 for ( var i = 0; i < this.children.length; i++ ) | |
| 2369 { | |
| 2370 this.children[i].setDepth(depth + 1, depthCollapsed); | |
| 2371 } | |
| 2372 } | |
| 2373 | |
| 2374 this.setHighlightStyle = function() | |
| 2375 { | |
| 2376 context.lineWidth = highlightLineWidth; | |
| 2377 | |
| 2378 if ( this.hasChildren() || this != focusNode || this != highlightedNode ) | |
| 2379 { | |
| 2380 context.strokeStyle = 'black'; | |
| 2381 context.fillStyle = "rgba(255, 255, 255, .3)"; | |
| 2382 } | |
| 2383 else | |
| 2384 { | |
| 2385 context.strokeStyle = 'rgb(90,90,90)'; | |
| 2386 context.fillStyle = "rgba(155, 155, 155, .3)"; | |
| 2387 } | |
| 2388 } | |
| 2389 | |
| 2390 this.setLabelWidth = function(node) | |
| 2391 { | |
| 2392 if ( ! shorten || this.radial ) | |
| 2393 { | |
| 2394 return; // don't need to set width | |
| 2395 } | |
| 2396 | |
| 2397 if ( node.hide ) | |
| 2398 { | |
| 2399 alert('wtf'); | |
| 2400 return; | |
| 2401 } | |
| 2402 | |
| 2403 var angle = (this.angleStart.end + this.angleEnd.end) / 2; | |
| 2404 var a; // angle difference | |
| 2405 | |
| 2406 if ( node == selectedNode ) | |
| 2407 { | |
| 2408 a = Math.abs(angle - node.angleOther); | |
| 2409 } | |
| 2410 else | |
| 2411 { | |
| 2412 a = Math.abs(angle - (node.angleStart.end + node.angleEnd.end) / 2); | |
| 2413 } | |
| 2414 | |
| 2415 if ( a == 0 ) | |
| 2416 { | |
| 2417 return; | |
| 2418 } | |
| 2419 | |
| 2420 if ( a > Math.PI ) | |
| 2421 { | |
| 2422 a = 2 * Math.PI - a; | |
| 2423 } | |
| 2424 | |
| 2425 if ( node.radial || node == selectedNode ) | |
| 2426 { | |
| 2427 var nodeLabelRadius; | |
| 2428 | |
| 2429 if ( node == selectedNode ) | |
| 2430 { | |
| 2431 // radial 'other' label | |
| 2432 | |
| 2433 nodeLabelRadius = (node.children[0].radiusInner.end + 1) / 2; | |
| 2434 } | |
| 2435 else | |
| 2436 { | |
| 2437 nodeLabelRadius = (node.radiusInner.end + 1) / 2; | |
| 2438 } | |
| 2439 | |
| 2440 if ( a < Math.PI / 2 ) | |
| 2441 { | |
| 2442 var r = this.labelRadius.end * gRadius + .5 * fontSize | |
| 2443 var hypotenuse = r / Math.cos(a); | |
| 2444 var opposite = r * Math.tan(a); | |
| 2445 var fontRadius = .8 * fontSize; | |
| 2446 | |
| 2447 if | |
| 2448 ( | |
| 2449 nodeLabelRadius * gRadius < hypotenuse && | |
| 2450 this.labelWidth.end / 2 + fontRadius > opposite | |
| 2451 ) | |
| 2452 { | |
| 2453 this.labelWidth.end = 2 * (opposite - fontRadius); | |
| 2454 } | |
| 2455 } | |
| 2456 } | |
| 2457 else if | |
| 2458 ( | |
| 2459 this.labelRadius.end == node.labelRadius.end && | |
| 2460 a < Math.PI / 4 | |
| 2461 ) | |
| 2462 { | |
| 2463 // same radius with small angle; use circumferential approximation | |
| 2464 | |
| 2465 var dist = a * this.labelRadius.end * gRadius - fontSize * (1 - a * 4 / Math.PI) * 1.3; | |
| 2466 | |
| 2467 if ( this.labelWidth.end < dist ) | |
| 2468 { | |
| 2469 node.restrictLabelWidth((dist - this.labelWidth.end / 2) * 2); | |
| 2470 } | |
| 2471 else if ( node.labelWidth.end < dist ) | |
| 2472 { | |
| 2473 this.restrictLabelWidth((dist - node.labelWidth.end / 2) * 2); | |
| 2474 } | |
| 2475 else | |
| 2476 { | |
| 2477 // both labels reach halfway point; restrict both | |
| 2478 | |
| 2479 this.labelWidth.end = dist; | |
| 2480 node.labelWidth.end = dist | |
| 2481 } | |
| 2482 } | |
| 2483 else | |
| 2484 { | |
| 2485 var r1 = this.labelRadius.end * gRadius; | |
| 2486 var r2 = node.labelRadius.end * gRadius; | |
| 2487 | |
| 2488 // first adjust the radii to account for the height of the font by shifting them | |
| 2489 // toward each other | |
| 2490 // | |
| 2491 var fontFudge = .35 * fontSize; | |
| 2492 // | |
| 2493 if ( this.labelRadius.end < node.labelRadius.end ) | |
| 2494 { | |
| 2495 r1 += fontFudge; | |
| 2496 r2 -= fontFudge; | |
| 2497 } | |
| 2498 else if ( this.labelRadius.end > node.labelRadius.end ) | |
| 2499 { | |
| 2500 r1 -= fontFudge; | |
| 2501 r2 += fontFudge; | |
| 2502 } | |
| 2503 else | |
| 2504 { | |
| 2505 r1 -= fontFudge; | |
| 2506 r2 -= fontFudge; | |
| 2507 } | |
| 2508 | |
| 2509 var r1s = r1 * r1; | |
| 2510 var r2s = r2 * r2; | |
| 2511 | |
| 2512 // distance between the centers of the two labels | |
| 2513 // | |
| 2514 var dist = Math.sqrt(r1s + r2s - 2 * r1 * r2 * Math.cos(a)); | |
| 2515 | |
| 2516 // angle at our label center between our radius and the line to the other label center | |
| 2517 // | |
| 2518 var b = Math.acos((r1s + dist * dist - r2s) / (2 * r1 * dist)); | |
| 2519 | |
| 2520 // distance from our label center to the intersection of the two tangents | |
| 2521 // | |
| 2522 var l1 = Math.sin(a + b - Math.PI / 2) * dist / Math.sin(Math.PI - a); | |
| 2523 | |
| 2524 // distance from other label center the the intersection of the two tangents | |
| 2525 // | |
| 2526 var l2 = Math.sin(Math.PI / 2 - b) * dist / Math.sin(Math.PI - a); | |
| 2527 | |
| 2528 l1 = Math.abs(l1) - .4 * fontSize; | |
| 2529 l2 = Math.abs(l2) - .4 * fontSize; | |
| 2530 /* | |
| 2531 // amount to shorten the distances because of the height of the font | |
| 2532 // | |
| 2533 var l3 = 0; | |
| 2534 var fontRadius = fontSize * .55; | |
| 2535 // | |
| 2536 if ( l1 < 0 || l2 < 0 ) | |
| 2537 { | |
| 2538 var l4 = fontRadius / Math.tan(a); | |
| 2539 l1 = Math.abs(l1); | |
| 2540 l2 = Math.abs(l2); | |
| 2541 | |
| 2542 l1 -= l4; | |
| 2543 l2 -= l4; | |
| 2544 } | |
| 2545 else | |
| 2546 { | |
| 2547 var c = Math.PI - a; | |
| 2548 | |
| 2549 l3 = fontRadius * Math.tan(c / 2); | |
| 2550 } | |
| 2551 */ | |
| 2552 if ( this.labelWidth.end / 2 > l1 && node.labelWidth.end / 2 > l2 ) | |
| 2553 { | |
| 2554 // shorten the farthest one from the intersection | |
| 2555 | |
| 2556 if ( l1 > l2 ) | |
| 2557 { | |
| 2558 this.restrictLabelWidth(2 * (l1));// - l3 - fontRadius)); | |
| 2559 } | |
| 2560 else | |
| 2561 { | |
| 2562 node.restrictLabelWidth(2 * (l2));// - l3 - fontRadius)); | |
| 2563 } | |
| 2564 }/* | |
| 2565 else if ( this.labelWidth.end / 2 > l1 + l3 && node.labelWidth.end / 2 > l2 - l3 ) | |
| 2566 { | |
| 2567 node.restrictLabelWidth(2 * (l2 - l3)); | |
| 2568 } | |
| 2569 else if ( this.labelWidth.end / 2 > l1 - l3 && node.labelWidth.end / 2 > l2 + l3 ) | |
| 2570 { | |
| 2571 this.restrictLabelWidth(2 * (l1 - l3)); | |
| 2572 }*/ | |
| 2573 } | |
| 2574 } | |
| 2575 | |
| 2576 this.setMagnitudes = function(baseMagnitude) | |
| 2577 { | |
| 2578 this.magnitude = this.getMagnitude(); | |
| 2579 this.baseMagnitude = baseMagnitude; | |
| 2580 | |
| 2581 for ( var i = 0; i < this.children.length; i++ ) | |
| 2582 { | |
| 2583 this.children[i].setMagnitudes(baseMagnitude); | |
| 2584 baseMagnitude += this.children[i].magnitude; | |
| 2585 } | |
| 2586 | |
| 2587 this.maxChildMagnitude = baseMagnitude; | |
| 2588 } | |
| 2589 | |
| 2590 this.setMaxDepths = function() | |
| 2591 { | |
| 2592 this.maxDepth = this.depth; | |
| 2593 this.maxDepthCollapsed = this.depthCollapsed; | |
| 2594 | |
| 2595 for ( i in this.children ) | |
| 2596 { | |
| 2597 var child = this.children[i]; | |
| 2598 | |
| 2599 child.setMaxDepths(); | |
| 2600 | |
| 2601 if ( child.maxDepth > this.maxDepth ) | |
| 2602 { | |
| 2603 this.maxDepth = child.maxDepth; | |
| 2604 } | |
| 2605 | |
| 2606 if | |
| 2607 ( | |
| 2608 child.maxDepthCollapsed > this.maxDepthCollapsed && | |
| 2609 (child.depth <= maxAbsoluteDepth || maxAbsoluteDepth == 0) | |
| 2610 ) | |
| 2611 { | |
| 2612 this.maxDepthCollapsed = child.maxDepthCollapsed; | |
| 2613 } | |
| 2614 } | |
| 2615 } | |
| 2616 | |
| 2617 this.setTargetLabelRadius = function() | |
| 2618 { | |
| 2619 var depth = this.getDepth() - selectedNode.getDepth() + 1; | |
| 2620 var index = depth - 2; | |
| 2621 var labelOffset = labelOffsets[index]; | |
| 2622 | |
| 2623 if ( this.radial ) | |
| 2624 { | |
| 2625 //this.labelRadius.setTarget((this.radiusInner.end + 1) / 2); | |
| 2626 var max = | |
| 2627 depth == maxDisplayDepth ? | |
| 2628 1 : | |
| 2629 compressedRadii[index + 1]; | |
| 2630 | |
| 2631 this.labelRadius.setTarget((compressedRadii[index] + max) / 2); | |
| 2632 } | |
| 2633 else | |
| 2634 { | |
| 2635 var radiusCenter; | |
| 2636 var width; | |
| 2637 | |
| 2638 if ( compress ) | |
| 2639 { | |
| 2640 if ( nLabelOffsets[index] > 1 ) | |
| 2641 { | |
| 2642 this.labelRadius.setTarget | |
| 2643 ( | |
| 2644 lerp | |
| 2645 ( | |
| 2646 labelOffset + .75, | |
| 2647 0, | |
| 2648 nLabelOffsets[index] + .5, | |
| 2649 compressedRadii[index], | |
| 2650 compressedRadii[index + 1] | |
| 2651 ) | |
| 2652 ); | |
| 2653 } | |
| 2654 else | |
| 2655 { | |
| 2656 this.labelRadius.setTarget((compressedRadii[index] + compressedRadii[index + 1]) / 2); | |
| 2657 } | |
| 2658 } | |
| 2659 else | |
| 2660 { | |
| 2661 radiusCenter = | |
| 2662 nodeRadius * (depth - 1) + | |
| 2663 nodeRadius / 2; | |
| 2664 width = nodeRadius; | |
| 2665 | |
| 2666 this.labelRadius.setTarget | |
| 2667 ( | |
| 2668 radiusCenter + width * ((labelOffset + 1) / (nLabelOffsets[index] + 1) - .5) | |
| 2669 ); | |
| 2670 } | |
| 2671 } | |
| 2672 | |
| 2673 if ( ! this.hide && ! this.keyed && nLabelOffsets[index] ) | |
| 2674 { | |
| 2675 // check last and first labels in each track for overlap | |
| 2676 | |
| 2677 for ( var i = 0; i < maxDisplayDepth - 1; i++ ) | |
| 2678 { | |
| 2679 for ( var j = 0; j <= nLabelOffsets[i]; j++ ) | |
| 2680 { | |
| 2681 var last = labelLastNodes[i][j]; | |
| 2682 var first = labelFirstNodes[i][j]; | |
| 2683 | |
| 2684 if ( last ) | |
| 2685 { | |
| 2686 if ( j == nLabelOffsets[i] ) | |
| 2687 { | |
| 2688 // last is radial | |
| 2689 this.setLabelWidth(last); | |
| 2690 } | |
| 2691 else | |
| 2692 { | |
| 2693 last.setLabelWidth(this); | |
| 2694 } | |
| 2695 } | |
| 2696 | |
| 2697 if ( first ) | |
| 2698 { | |
| 2699 if ( j == nLabelOffsets[i] ) | |
| 2700 { | |
| 2701 this.setLabelWidth(first); | |
| 2702 } | |
| 2703 else | |
| 2704 { | |
| 2705 first.setLabelWidth(this); | |
| 2706 } | |
| 2707 } | |
| 2708 } | |
| 2709 } | |
| 2710 | |
| 2711 if ( selectedNode.canDisplayLabelOther ) | |
| 2712 { | |
| 2713 this.setLabelWidth(selectedNode); // in case there is an 'other' label | |
| 2714 } | |
| 2715 | |
| 2716 if ( this.radial ) | |
| 2717 { | |
| 2718 // use the last 'track' of this depth for radial | |
| 2719 | |
| 2720 labelLastNodes[index][nLabelOffsets[index]] = this; | |
| 2721 | |
| 2722 if ( labelFirstNodes[index][nLabelOffsets[index]] == 0 ) | |
| 2723 { | |
| 2724 labelFirstNodes[index][nLabelOffsets[index]] = this; | |
| 2725 } | |
| 2726 } | |
| 2727 else | |
| 2728 { | |
| 2729 labelLastNodes[index][labelOffset] = this; | |
| 2730 | |
| 2731 // update offset | |
| 2732 | |
| 2733 labelOffsets[index] += 1; | |
| 2734 | |
| 2735 if ( labelOffsets[index] > nLabelOffsets[index] ) | |
| 2736 { | |
| 2737 labelOffsets[index] -= nLabelOffsets[index]; | |
| 2738 | |
| 2739 if ( !(nLabelOffsets[index] & 1) ) | |
| 2740 { | |
| 2741 labelOffsets[index]--; | |
| 2742 } | |
| 2743 } | |
| 2744 else if ( labelOffsets[index] == nLabelOffsets[index] ) | |
| 2745 { | |
| 2746 labelOffsets[index] -= nLabelOffsets[index]; | |
| 2747 | |
| 2748 if ( false && !(nLabelOffsets[index] & 1) ) | |
| 2749 { | |
| 2750 labelOffsets[index]++; | |
| 2751 } | |
| 2752 } | |
| 2753 | |
| 2754 if ( labelFirstNodes[index][labelOffset] == 0 ) | |
| 2755 { | |
| 2756 labelFirstNodes[index][labelOffset] = this; | |
| 2757 } | |
| 2758 } | |
| 2759 } | |
| 2760 else if ( this.hide ) | |
| 2761 { | |
| 2762 this.labelWidth.end = 0; | |
| 2763 } | |
| 2764 } | |
| 2765 | |
| 2766 this.setTargets = function() | |
| 2767 { | |
| 2768 if ( this == selectedNode ) | |
| 2769 { | |
| 2770 this.setTargetsSelected | |
| 2771 ( | |
| 2772 0, | |
| 2773 1, | |
| 2774 lightnessBase, | |
| 2775 false, | |
| 2776 false | |
| 2777 ); | |
| 2778 return; | |
| 2779 } | |
| 2780 | |
| 2781 var depthRelative = this.getDepth() - selectedNode.getDepth(); | |
| 2782 | |
| 2783 var parentOfSelected = selectedNode.hasParent(this); | |
| 2784 /* ( | |
| 2785 // ! this.getCollapse() && | |
| 2786 this.baseMagnitude <= selectedNode.baseMagnitude && | |
| 2787 this.baseMagnitude + this.magnitude >= | |
| 2788 selectedNode.baseMagnitude + selectedNode.magnitude | |
| 2789 ); | |
| 2790 */ | |
| 2791 if ( parentOfSelected ) | |
| 2792 { | |
| 2793 this.resetLabelWidth(); | |
| 2794 } | |
| 2795 else | |
| 2796 { | |
| 2797 //context.font = fontNormal; | |
| 2798 var dim = context.measureText(this.name); | |
| 2799 this.nameWidth = dim.width; | |
| 2800 //this.labelWidth.setTarget(this.labelWidth.end); | |
| 2801 this.labelWidth.setTarget(0); | |
| 2802 } | |
| 2803 | |
| 2804 // set angles | |
| 2805 // | |
| 2806 if ( this.baseMagnitude <= selectedNode.baseMagnitude ) | |
| 2807 { | |
| 2808 this.angleStart.setTarget(0); | |
| 2809 } | |
| 2810 else | |
| 2811 { | |
| 2812 this.angleStart.setTarget(Math.PI * 2); | |
| 2813 } | |
| 2814 // | |
| 2815 if | |
| 2816 ( | |
| 2817 parentOfSelected || | |
| 2818 this.baseMagnitude + this.magnitude >= | |
| 2819 selectedNode.baseMagnitude + selectedNode.magnitude | |
| 2820 ) | |
| 2821 { | |
| 2822 this.angleEnd.setTarget(Math.PI * 2); | |
| 2823 } | |
| 2824 else | |
| 2825 { | |
| 2826 this.angleEnd.setTarget(0); | |
| 2827 } | |
| 2828 | |
| 2829 // children | |
| 2830 // | |
| 2831 for ( var i = 0; i < this.children.length; i++ ) | |
| 2832 { | |
| 2833 this.children[i].setTargets(); | |
| 2834 } | |
| 2835 | |
| 2836 if ( this.getDepth() <= selectedNode.getDepth() ) | |
| 2837 { | |
| 2838 // collapse in | |
| 2839 | |
| 2840 this.radiusInner.setTarget(0); | |
| 2841 | |
| 2842 if ( parentOfSelected ) | |
| 2843 { | |
| 2844 this.labelRadius.setTarget | |
| 2845 ( | |
| 2846 (depthRelative) * | |
| 2847 historySpacingFactor * fontSize / gRadius | |
| 2848 ); | |
| 2849 //this.scale.setTarget(1 - (selectedNode.getDepth() - this.getDepth()) / 18); // TEMP | |
| 2850 } | |
| 2851 else | |
| 2852 { | |
| 2853 this.labelRadius.setTarget(0); | |
| 2854 //this.scale.setTarget(1); // TEMP | |
| 2855 } | |
| 2856 } | |
| 2857 else if ( depthRelative + 1 > maxDisplayDepth ) | |
| 2858 { | |
| 2859 // collapse out | |
| 2860 | |
| 2861 this.radiusInner.setTarget(1); | |
| 2862 this.labelRadius.setTarget(1); | |
| 2863 //this.scale.setTarget(1); // TEMP | |
| 2864 } | |
| 2865 else | |
| 2866 { | |
| 2867 // don't collapse | |
| 2868 | |
| 2869 if ( compress ) | |
| 2870 { | |
| 2871 this.radiusInner.setTarget(compressedRadii[depthRelative - 1]); | |
| 2872 } | |
| 2873 else | |
| 2874 { | |
| 2875 this.radiusInner.setTarget(nodeRadius * (depthRelative)); | |
| 2876 } | |
| 2877 | |
| 2878 //this.scale.setTarget(1); // TEMP | |
| 2879 | |
| 2880 if ( this == selectedNode ) | |
| 2881 { | |
| 2882 this.labelRadius.setTarget(0); | |
| 2883 } | |
| 2884 else | |
| 2885 { | |
| 2886 if ( compress ) | |
| 2887 { | |
| 2888 this.labelRadius.setTarget | |
| 2889 ( | |
| 2890 (compressedRadii[depthRelative - 1] + compressedRadii[depthRelative]) / 2 | |
| 2891 ); | |
| 2892 } | |
| 2893 else | |
| 2894 { | |
| 2895 this.labelRadius.setTarget(nodeRadius * (depthRelative) + nodeRadius / 2); | |
| 2896 } | |
| 2897 } | |
| 2898 } | |
| 2899 | |
| 2900 // this.r.start = this.r.end; | |
| 2901 // this.g.start = this.g.end; | |
| 2902 // this.b.start = this.b.end; | |
| 2903 | |
| 2904 this.r.setTarget(255); | |
| 2905 this.g.setTarget(255); | |
| 2906 this.b.setTarget(255); | |
| 2907 | |
| 2908 this.alphaLine.setTarget(0); | |
| 2909 this.alphaArc.setTarget(0); | |
| 2910 this.alphaWedge.setTarget(0); | |
| 2911 this.alphaPattern.setTarget(0); | |
| 2912 this.alphaOther.setTarget(0); | |
| 2913 | |
| 2914 if ( parentOfSelected && ! this.getCollapse() ) | |
| 2915 { | |
| 2916 var alpha = | |
| 2917 ( | |
| 2918 1 - | |
| 2919 (selectedNode.getDepth() - this.getDepth()) / | |
| 2920 (Math.floor((compress ? compressedRadii[0] : nodeRadius) * gRadius / (historySpacingFactor * fontSize) - .5) + 1) | |
| 2921 ); | |
| 2922 | |
| 2923 if ( alpha < 0 ) | |
| 2924 { | |
| 2925 alpha = 0; | |
| 2926 } | |
| 2927 | |
| 2928 this.alphaLabel.setTarget(alpha); | |
| 2929 this.radial = false; | |
| 2930 } | |
| 2931 else | |
| 2932 { | |
| 2933 this.alphaLabel.setTarget(0); | |
| 2934 } | |
| 2935 | |
| 2936 this.hideAlonePrev = this.hideAlone; | |
| 2937 this.hidePrev = this.hide; | |
| 2938 | |
| 2939 if ( parentOfSelected ) | |
| 2940 { | |
| 2941 this.hideAlone = false; | |
| 2942 this.hide = false; | |
| 2943 } | |
| 2944 | |
| 2945 if ( this.getParent() == selectedNode.getParent() ) | |
| 2946 { | |
| 2947 this.hiddenEnd = null; | |
| 2948 } | |
| 2949 | |
| 2950 this.radialPrev = this.radial; | |
| 2951 } | |
| 2952 | |
| 2953 this.setTargetsSelected = function(hueMin, hueMax, lightness, hide, nextSiblingHidden) | |
| 2954 { | |
| 2955 var collapse = this.getCollapse(); | |
| 2956 var depth = this.getDepth() - selectedNode.getDepth() + 1; | |
| 2957 var canDisplayChildLabels = false; | |
| 2958 var lastChild; | |
| 2959 | |
| 2960 if ( this.hasChildren() )//&& ! hide ) | |
| 2961 { | |
| 2962 lastChild = this.children[this.children.length - 1]; | |
| 2963 this.hideAlone = true; | |
| 2964 } | |
| 2965 else | |
| 2966 { | |
| 2967 this.hideAlone = false; | |
| 2968 } | |
| 2969 | |
| 2970 // set child wedges | |
| 2971 // | |
| 2972 for ( var i = 0; i < this.children.length; i++ ) | |
| 2973 { | |
| 2974 this.children[i].setTargetWedge(); | |
| 2975 | |
| 2976 if | |
| 2977 ( | |
| 2978 ! this.children[i].hide && | |
| 2979 ( collapse || depth < maxDisplayDepth ) && | |
| 2980 this.depth < maxAbsoluteDepth | |
| 2981 ) | |
| 2982 { | |
| 2983 canDisplayChildLabels = true; | |
| 2984 this.hideAlone = false; | |
| 2985 } | |
| 2986 } | |
| 2987 | |
| 2988 if ( this == selectedNode || lastChild && lastChild.angleEnd.end < this.angleEnd.end - .01) | |
| 2989 { | |
| 2990 this.hideAlone = false; | |
| 2991 } | |
| 2992 | |
| 2993 if ( this.hideAlonePrev == undefined ) | |
| 2994 { | |
| 2995 this.hideAlonePrev = this.hideAlone; | |
| 2996 } | |
| 2997 | |
| 2998 if ( this == selectedNode ) | |
| 2999 { | |
| 3000 var otherArc = | |
| 3001 this.children.length ? | |
| 3002 angleFactor * | |
| 3003 ( | |
| 3004 this.baseMagnitude + this.magnitude - | |
| 3005 lastChild.baseMagnitude - lastChild.magnitude | |
| 3006 ) | |
| 3007 : this.baseMagnitude + this.magnitude; | |
| 3008 this.canDisplayLabelOther = | |
| 3009 this.children.length ? | |
| 3010 otherArc * | |
| 3011 (this.children[0].radiusInner.end + 1) * gRadius >= | |
| 3012 minWidth() | |
| 3013 : true; | |
| 3014 | |
| 3015 this.keyUnclassified = false; | |
| 3016 | |
| 3017 if ( this.canDisplayLabelOther ) | |
| 3018 { | |
| 3019 this.angleOther = Math.PI * 2 - otherArc / 2; | |
| 3020 } | |
| 3021 else if ( otherArc > 0.0000000001 ) | |
| 3022 { | |
| 3023 this.keyUnclassified = true; | |
| 3024 keys++; | |
| 3025 } | |
| 3026 | |
| 3027 this.angleStart.setTarget(0); | |
| 3028 this.angleEnd.setTarget(Math.PI * 2); | |
| 3029 | |
| 3030 if ( this.children.length ) | |
| 3031 { | |
| 3032 this.radiusInner.setTarget(0); | |
| 3033 } | |
| 3034 else | |
| 3035 { | |
| 3036 this.radiusInner.setTarget(compressedRadii[0]); | |
| 3037 } | |
| 3038 | |
| 3039 this.hidePrev = this.hide; | |
| 3040 this.hide = false; | |
| 3041 this.hideAlonePrev = this.hideAlone; | |
| 3042 this.hideAlone = false; | |
| 3043 this.keyed = false; | |
| 3044 } | |
| 3045 | |
| 3046 if ( hueMax - hueMin > 1 / 12 ) | |
| 3047 { | |
| 3048 hueMax = hueMin + 1 / 12; | |
| 3049 } | |
| 3050 | |
| 3051 // set lightness | |
| 3052 // | |
| 3053 if ( ! ( hide || this.hideAlone ) ) | |
| 3054 { | |
| 3055 if ( useHue() ) | |
| 3056 { | |
| 3057 lightness = (lightnessBase + lightnessMax) / 2; | |
| 3058 } | |
| 3059 else | |
| 3060 { | |
| 3061 lightness = lightnessBase + (depth - 1) * lightnessFactor; | |
| 3062 | |
| 3063 if ( lightness > lightnessMax ) | |
| 3064 { | |
| 3065 lightness = lightnessMax; | |
| 3066 } | |
| 3067 } | |
| 3068 } | |
| 3069 | |
| 3070 if ( hide ) | |
| 3071 { | |
| 3072 this.hide = true; | |
| 3073 } | |
| 3074 | |
| 3075 if ( this.hidePrev == undefined ) | |
| 3076 { | |
| 3077 this.hidePrev = this.hide; | |
| 3078 } | |
| 3079 | |
| 3080 var hiddenStart = -1; | |
| 3081 var hiddenHueNumer = 0; | |
| 3082 var hiddenHueDenom = 0; | |
| 3083 var i = 0; | |
| 3084 | |
| 3085 if ( ! this.hide ) | |
| 3086 { | |
| 3087 this.hiddenEnd = null; | |
| 3088 } | |
| 3089 | |
| 3090 while ( true ) | |
| 3091 { | |
| 3092 if ( ! this.hideAlone && ! hide && ( i == this.children.length || ! this.children[i].hide ) ) | |
| 3093 { | |
| 3094 // reached a non-hidden child or the end; set targets for | |
| 3095 // previous group of hidden children (if any) using their | |
| 3096 // average hue | |
| 3097 | |
| 3098 if ( hiddenStart != -1 ) | |
| 3099 { | |
| 3100 var hiddenHue = hiddenHueDenom ? hiddenHueNumer / hiddenHueDenom : hueMin; | |
| 3101 | |
| 3102 for ( var j = hiddenStart; j < i; j++ ) | |
| 3103 { | |
| 3104 this.children[j].setTargetsSelected | |
| 3105 ( | |
| 3106 hiddenHue, | |
| 3107 null, | |
| 3108 lightness, | |
| 3109 false, | |
| 3110 j < i - 1 | |
| 3111 ); | |
| 3112 | |
| 3113 this.children[j].hiddenEnd = null; | |
| 3114 } | |
| 3115 | |
| 3116 this.children[hiddenStart].hiddenEnd = i - 1; | |
| 3117 } | |
| 3118 } | |
| 3119 | |
| 3120 if ( i == this.children.length ) | |
| 3121 { | |
| 3122 break; | |
| 3123 } | |
| 3124 | |
| 3125 var child = this.children[i]; | |
| 3126 var childHueMin; | |
| 3127 var childHueMax; | |
| 3128 | |
| 3129 if ( this.magnitude > 0 && ! this.hide && ! this.hideAlone ) | |
| 3130 { | |
| 3131 if ( useHue() ) | |
| 3132 { | |
| 3133 childHueMin = child.hues[currentDataset]; | |
| 3134 } | |
| 3135 else if ( this == selectedNode ) | |
| 3136 { | |
| 3137 var min = 0.0; | |
| 3138 var max = 1.0; | |
| 3139 | |
| 3140 if ( this.children.length > 6 ) | |
| 3141 { | |
| 3142 childHueMin = lerp((1 - Math.pow(1 - i / this.children.length, 1.4)) * .95, 0, 1, min, max); | |
| 3143 childHueMax = lerp((1 - Math.pow(1 - (i + .55) / this.children.length, 1.4)) * .95, 0, 1, min, max); | |
| 3144 } | |
| 3145 else | |
| 3146 { | |
| 3147 childHueMin = lerp(i / this.children.length, 0, 1, min, max); | |
| 3148 childHueMax = lerp((i + .55) / this.children.length, 0, 1, min, max); | |
| 3149 } | |
| 3150 } | |
| 3151 else | |
| 3152 { | |
| 3153 childHueMin = lerp | |
| 3154 ( | |
| 3155 child.baseMagnitude, | |
| 3156 this.baseMagnitude, | |
| 3157 this.baseMagnitude + this.magnitude, | |
| 3158 hueMin, | |
| 3159 hueMax | |
| 3160 ); | |
| 3161 childHueMax = lerp | |
| 3162 ( | |
| 3163 child.baseMagnitude + child.magnitude * .99, | |
| 3164 this.baseMagnitude, | |
| 3165 this.baseMagnitude + this.magnitude, | |
| 3166 hueMin, | |
| 3167 hueMax | |
| 3168 ); | |
| 3169 } | |
| 3170 } | |
| 3171 else | |
| 3172 { | |
| 3173 childHueMin = hueMin; | |
| 3174 childHueMax = hueMax; | |
| 3175 } | |
| 3176 | |
| 3177 if ( ! this.hideAlone && ! hide && ! this.hide && child.hide ) | |
| 3178 { | |
| 3179 if ( hiddenStart == -1 ) | |
| 3180 { | |
| 3181 hiddenStart = i; | |
| 3182 } | |
| 3183 | |
| 3184 if ( useHue() ) | |
| 3185 { | |
| 3186 hiddenHueNumer += childHueMin * child.magnitude; | |
| 3187 hiddenHueDenom += child.magnitude; | |
| 3188 } | |
| 3189 else | |
| 3190 { | |
| 3191 hiddenHueNumer += childHueMin; | |
| 3192 hiddenHueDenom++; | |
| 3193 } | |
| 3194 } | |
| 3195 else | |
| 3196 { | |
| 3197 hiddenStart = -1; | |
| 3198 | |
| 3199 this.children[i].setTargetsSelected | |
| 3200 ( | |
| 3201 childHueMin, | |
| 3202 childHueMax, | |
| 3203 lightness, | |
| 3204 hide || this.keyed || this.hideAlone || this.hide && ! collapse, | |
| 3205 false | |
| 3206 ); | |
| 3207 } | |
| 3208 | |
| 3209 i++; | |
| 3210 } | |
| 3211 | |
| 3212 if ( this.hue && this.magnitude ) | |
| 3213 { | |
| 3214 this.hue.setTarget(this.hues[currentDataset]); | |
| 3215 | |
| 3216 if ( this.attributes[magnitudeIndex][lastDataset] == 0 ) | |
| 3217 { | |
| 3218 this.hue.start = this.hue.end; | |
| 3219 } | |
| 3220 } | |
| 3221 | |
| 3222 this.radialPrev = this.radial; | |
| 3223 | |
| 3224 if ( this == selectedNode ) | |
| 3225 { | |
| 3226 this.resetLabelWidth(); | |
| 3227 this.labelWidth.setTarget(this.nameWidth * labelWidthFudge); | |
| 3228 this.alphaWedge.setTarget(0); | |
| 3229 this.alphaLabel.setTarget(1); | |
| 3230 this.alphaOther.setTarget(1); | |
| 3231 this.alphaArc.setTarget(0); | |
| 3232 this.alphaLine.setTarget(0); | |
| 3233 this.alphaPattern.setTarget(0); | |
| 3234 this.r.setTarget(255); | |
| 3235 this.g.setTarget(255); | |
| 3236 this.b.setTarget(255); | |
| 3237 this.radial = false; | |
| 3238 this.labelRadius.setTarget(0); | |
| 3239 } | |
| 3240 else | |
| 3241 { | |
| 3242 var rgb = hslToRgb | |
| 3243 ( | |
| 3244 hueMin, | |
| 3245 saturation, | |
| 3246 lightness | |
| 3247 ); | |
| 3248 | |
| 3249 this.r.setTarget(rgb.r); | |
| 3250 this.g.setTarget(rgb.g); | |
| 3251 this.b.setTarget(rgb.b); | |
| 3252 this.alphaOther.setTarget(0); | |
| 3253 | |
| 3254 this.alphaWedge.setTarget(1); | |
| 3255 | |
| 3256 if ( this.hide || this.hideAlone ) | |
| 3257 { | |
| 3258 this.alphaPattern.setTarget(1); | |
| 3259 } | |
| 3260 else | |
| 3261 { | |
| 3262 this.alphaPattern.setTarget(0); | |
| 3263 } | |
| 3264 | |
| 3265 // set radial | |
| 3266 // | |
| 3267 if ( ! ( hide || this.hide ) )//&& ! this.keyed ) | |
| 3268 { | |
| 3269 if ( this.hideAlone ) | |
| 3270 { | |
| 3271 this.radial = true; | |
| 3272 } | |
| 3273 else if ( false && canDisplayChildLabels ) | |
| 3274 { | |
| 3275 this.radial = false; | |
| 3276 } | |
| 3277 else | |
| 3278 { | |
| 3279 this.radial = true; | |
| 3280 | |
| 3281 if ( this.hasChildren() && depth < maxDisplayDepth ) | |
| 3282 { | |
| 3283 var lastChild = this.children[this.children.length - 1]; | |
| 3284 | |
| 3285 if | |
| 3286 ( | |
| 3287 lastChild.angleEnd.end == this.angleEnd.end || | |
| 3288 ( | |
| 3289 (this.angleStart.end + this.angleEnd.end) / 2 - | |
| 3290 lastChild.angleEnd.end | |
| 3291 ) * (this.radiusInner.end + 1) * gRadius * 2 < | |
| 3292 minWidth() | |
| 3293 ) | |
| 3294 { | |
| 3295 this.radial = false; | |
| 3296 } | |
| 3297 } | |
| 3298 } | |
| 3299 } | |
| 3300 | |
| 3301 // set alphaLabel | |
| 3302 // | |
| 3303 if | |
| 3304 ( | |
| 3305 collapse || | |
| 3306 hide || | |
| 3307 this.hide || | |
| 3308 this.keyed || | |
| 3309 depth > maxDisplayDepth || | |
| 3310 ! this.canDisplayDepth() | |
| 3311 ) | |
| 3312 { | |
| 3313 this.alphaLabel.setTarget(0); | |
| 3314 } | |
| 3315 else | |
| 3316 { | |
| 3317 if | |
| 3318 ( | |
| 3319 (this.radial || nLabelOffsets[depth - 2]) | |
| 3320 ) | |
| 3321 { | |
| 3322 this.alphaLabel.setTarget(1); | |
| 3323 } | |
| 3324 else | |
| 3325 { | |
| 3326 this.alphaLabel.setTarget(0); | |
| 3327 | |
| 3328 if ( this.radialPrev ) | |
| 3329 { | |
| 3330 this.alphaLabel.start = 0; | |
| 3331 } | |
| 3332 } | |
| 3333 } | |
| 3334 | |
| 3335 // set alphaArc | |
| 3336 // | |
| 3337 if | |
| 3338 ( | |
| 3339 collapse || | |
| 3340 hide || | |
| 3341 depth > maxDisplayDepth || | |
| 3342 ! this.canDisplayDepth() | |
| 3343 ) | |
| 3344 { | |
| 3345 this.alphaArc.setTarget(0); | |
| 3346 } | |
| 3347 else | |
| 3348 { | |
| 3349 this.alphaArc.setTarget(1); | |
| 3350 } | |
| 3351 | |
| 3352 // set alphaLine | |
| 3353 // | |
| 3354 if | |
| 3355 ( | |
| 3356 hide || | |
| 3357 this.hide && nextSiblingHidden || | |
| 3358 depth > maxDisplayDepth || | |
| 3359 ! this.canDisplayDepth() | |
| 3360 ) | |
| 3361 { | |
| 3362 this.alphaLine.setTarget(0); | |
| 3363 } | |
| 3364 else | |
| 3365 { | |
| 3366 this.alphaLine.setTarget(1); | |
| 3367 } | |
| 3368 | |
| 3369 //if ( ! this.radial ) | |
| 3370 { | |
| 3371 this.resetLabelWidth(); | |
| 3372 } | |
| 3373 | |
| 3374 // set labelRadius target | |
| 3375 // | |
| 3376 if ( collapse ) | |
| 3377 { | |
| 3378 this.labelRadius.setTarget(this.radiusInner.end); | |
| 3379 } | |
| 3380 else | |
| 3381 { | |
| 3382 if ( depth > maxDisplayDepth || ! this.canDisplayDepth() ) | |
| 3383 { | |
| 3384 this.labelRadius.setTarget(1); | |
| 3385 } | |
| 3386 else | |
| 3387 { | |
| 3388 this.setTargetLabelRadius(); | |
| 3389 } | |
| 3390 } | |
| 3391 } | |
| 3392 } | |
| 3393 | |
| 3394 this.setTargetWedge = function() | |
| 3395 { | |
| 3396 var depth = this.getDepth() - selectedNode.getDepth() + 1; | |
| 3397 | |
| 3398 // set angles | |
| 3399 // | |
| 3400 var baseMagnitudeRelative = this.baseMagnitude - selectedNode.baseMagnitude; | |
| 3401 // | |
| 3402 this.angleStart.setTarget(baseMagnitudeRelative * angleFactor); | |
| 3403 this.angleEnd.setTarget((baseMagnitudeRelative + this.magnitude) * angleFactor); | |
| 3404 | |
| 3405 // set radiusInner | |
| 3406 // | |
| 3407 if ( depth > maxDisplayDepth || ! this.canDisplayDepth() ) | |
| 3408 { | |
| 3409 this.radiusInner.setTarget(1); | |
| 3410 } | |
| 3411 else | |
| 3412 { | |
| 3413 if ( compress ) | |
| 3414 { | |
| 3415 this.radiusInner.setTarget(compressedRadii[depth - 2]); | |
| 3416 } | |
| 3417 else | |
| 3418 { | |
| 3419 this.radiusInner.setTarget(nodeRadius * (depth - 1)); | |
| 3420 } | |
| 3421 } | |
| 3422 | |
| 3423 if ( this.hide != undefined ) | |
| 3424 { | |
| 3425 this.hidePrev = this.hide; | |
| 3426 } | |
| 3427 | |
| 3428 if ( this.hideAlone != undefined ) | |
| 3429 { | |
| 3430 this.hideAlonePrev = this.hideAlone; | |
| 3431 } | |
| 3432 | |
| 3433 // set hide | |
| 3434 // | |
| 3435 if | |
| 3436 ( | |
| 3437 (this.angleEnd.end - this.angleStart.end) * | |
| 3438 (this.radiusInner.end * gRadius + gRadius) < | |
| 3439 minWidth() | |
| 3440 ) | |
| 3441 { | |
| 3442 if ( depth == 2 && ! this.getCollapse() && this.depth <= maxAbsoluteDepth ) | |
| 3443 { | |
| 3444 this.keyed = true; | |
| 3445 keys++; | |
| 3446 this.hide = false; | |
| 3447 | |
| 3448 var percentage = this.getPercentage(); | |
| 3449 this.keyLabel = this.name + ' ' + percentage + '%'; | |
| 3450 var dim = context.measureText(this.keyLabel); | |
| 3451 this.keyNameWidth = dim.width; | |
| 3452 } | |
| 3453 else | |
| 3454 { | |
| 3455 this.keyed = false; | |
| 3456 this.hide = depth > 2; | |
| 3457 } | |
| 3458 } | |
| 3459 else | |
| 3460 { | |
| 3461 this.keyed = false; | |
| 3462 this.hide = false; | |
| 3463 } | |
| 3464 } | |
| 3465 | |
| 3466 this.shortenLabel = function() | |
| 3467 { | |
| 3468 var label = this.name; | |
| 3469 | |
| 3470 var labelWidth = this.nameWidth; | |
| 3471 var maxWidth = this.labelWidth.current(); | |
| 3472 var minEndLength = 0; | |
| 3473 | |
| 3474 if ( labelWidth > maxWidth && label.length > minEndLength * 2 ) | |
| 3475 { | |
| 3476 var endLength = | |
| 3477 Math.floor((label.length - 1) * maxWidth / labelWidth / 2); | |
| 3478 | |
| 3479 if ( endLength < minEndLength ) | |
| 3480 { | |
| 3481 endLength = minEndLength; | |
| 3482 } | |
| 3483 | |
| 3484 return ( | |
| 3485 label.substring(0, endLength) + | |
| 3486 '...' + | |
| 3487 label.substring(label.length - endLength)); | |
| 3488 } | |
| 3489 else | |
| 3490 { | |
| 3491 return label; | |
| 3492 } | |
| 3493 } | |
| 3494 | |
| 3495 /* this.shouldAddSearchResultsString = function() | |
| 3496 { | |
| 3497 if ( this.isSearchResult ) | |
| 3498 { | |
| 3499 return this.searchResults > 1; | |
| 3500 } | |
| 3501 else | |
| 3502 { | |
| 3503 return this.searchResults > 0; | |
| 3504 } | |
| 3505 } | |
| 3506 */ | |
| 3507 this.sort = function() | |
| 3508 { | |
| 3509 this.children.sort(function(a, b){return b.getMagnitude() - a.getMagnitude()}); | |
| 3510 | |
| 3511 for (var i = 0; i < this.children.length; i++) | |
| 3512 { | |
| 3513 this.children[i].sort(); | |
| 3514 } | |
| 3515 } | |
| 3516 } | |
| 3517 | |
| 3518 var options; | |
| 3519 | |
| 3520 function addOptionElement(position, innerHTML, title) | |
| 3521 { | |
| 3522 var div = document.createElement("div"); | |
| 3523 // div.style.position = 'absolute'; | |
| 3524 // div.style.top = position + 'px'; | |
| 3525 div.innerHTML = innerHTML; | |
| 3526 // div.style.display = 'block'; | |
| 3527 div.style.padding = '2px'; | |
| 3528 | |
| 3529 if ( title ) | |
| 3530 { | |
| 3531 div.title = title; | |
| 3532 } | |
| 3533 | |
| 3534 options.appendChild(div); | |
| 3535 var height = 0;//div.clientHeight; | |
| 3536 return position + height; | |
| 3537 } | |
| 3538 | |
| 3539 function addOptionElements(hueName, hueDefault) | |
| 3540 { | |
| 3541 options = document.createElement('div'); | |
| 3542 options.style.position = 'absolute'; | |
| 3543 options.style.top = '0px'; | |
| 3544 options.addEventListener('mousedown', function(e) {mouseClick(e)}, false); | |
| 3545 // options.onmouseup = function(e) {mouseUp(e)} | |
| 3546 document.body.appendChild(options); | |
| 3547 | |
| 3548 document.body.style.font = '11px sans-serif'; | |
| 3549 var position = 5; | |
| 3550 | |
| 3551 details = document.createElement('div'); | |
| 3552 details.style.position = 'absolute'; | |
| 3553 details.style.top = '1%'; | |
| 3554 details.style.right = '2%'; | |
| 3555 details.style.textAlign = 'right'; | |
| 3556 document.body.insertBefore(details, canvas); | |
| 3557 // <div id="details" style="position:absolute;top:1%;right:2%;text-align:right;"> | |
| 3558 | |
| 3559 details.innerHTML = '\ | |
| 3560 <span id="detailsName" style="font-weight:bold"></span> \ | |
| 3561 <input type="button" id="detailsExpand" onclick="expand(focusNode);"\ | |
| 3562 value="↔" title="Expand this wedge to become the new focus of the chart"/><br/>\ | |
| 3563 <div id="detailsInfo" style="float:right"></div>'; | |
| 3564 | |
| 3565 keyControl = document.createElement('input'); | |
| 3566 keyControl.type = 'button'; | |
| 3567 keyControl.value = showKeys ? 'x' : '…'; | |
| 3568 keyControl.style.position = ''; | |
| 3569 keyControl.style.position = 'fixed'; | |
| 3570 keyControl.style.visibility = 'hidden'; | |
| 3571 | |
| 3572 document.body.insertBefore(keyControl, canvas); | |
| 3573 | |
| 3574 var logoElement = document.getElementById('logo'); | |
| 3575 | |
| 3576 if ( logoElement ) | |
| 3577 { | |
| 3578 logoImage = logoElement.src; | |
| 3579 } | |
| 3580 else | |
| 3581 { | |
| 3582 logoImage = 'http://marbl.github.io/Krona/img/logo-med.png'; | |
| 3583 } | |
| 3584 | |
| 3585 // document.getElementById('options').style.fontSize = '9pt'; | |
| 3586 position = addOptionElement | |
| 3587 ( | |
| 3588 position, | |
| 3589 '<a style="margin:2px" target="_blank" href="https://github.com/marbl/Krona/wiki"><img style="vertical-align:middle;width:108px;height:30px;" src="' + logoImage + '" alt="Logo of Krona"/></a><input type="button" id="back" value="←" title="Go back (Shortcut: ←)"/>\ | |
| 3590 <input type="button" id="forward" value="→" title="Go forward (Shortcut: →)"/> \ | |
| 3591 Search: <input type="text" id="search"/>\ | |
| 3592 <input id="searchClear" type="button" value="x" onclick="clearSearch()"/> \ | |
| 3593 <span id="searchResults"></span>' | |
| 3594 ); | |
| 3595 | |
| 3596 if ( datasets > 1 ) | |
| 3597 { | |
| 3598 var size = datasets < datasetSelectSize ? datasets : datasetSelectSize; | |
| 3599 | |
| 3600 var select = | |
| 3601 '<table style="border-collapse:collapse;padding:0px"><tr><td style="padding:0px">' + | |
| 3602 '<select id="datasets" style="min-width:100px" size="' + size + '" onchange="onDatasetChange()">'; | |
| 3603 | |
| 3604 for ( var i = 0; i < datasetNames.length; i++ ) | |
| 3605 { | |
| 3606 select += '<option>' + datasetNames[i] + '</option>'; | |
| 3607 } | |
| 3608 | |
| 3609 select += | |
| 3610 '</select></td><td style="vertical-align:top;padding:1px;">' + | |
| 3611 '<input style="display:block" title="Previous dataset (Shortcut: ↑)" id="prevDataset" type="button" value="↑" onclick="prevDataset()" disabled="true"/>' + | |
| 3612 '<input title="Next dataset (Shortcut: ↓)" id="nextDataset" type="button" value="↓" onclick="nextDataset()"/><br/></td>' + | |
| 3613 '<td style="padding-top:1px;vertical-align:top"><input title="Switch to the last dataset that was viewed (Shortcut: TAB)" id="lastDataset" type="button" style="font:11px Times new roman" value="last" onclick="selectLastDataset()"/></td></tr></table>'; | |
| 3614 | |
| 3615 position = addOptionElement(position + 5, select); | |
| 3616 | |
| 3617 datasetDropDown = document.getElementById('datasets'); | |
| 3618 datasetButtonLast = document.getElementById('lastDataset'); | |
| 3619 datasetButtonPrev = document.getElementById('prevDataset'); | |
| 3620 datasetButtonNext = document.getElementById('nextDataset'); | |
| 3621 | |
| 3622 position += datasetDropDown.clientHeight; | |
| 3623 } | |
| 3624 | |
| 3625 position = addOptionElement | |
| 3626 ( | |
| 3627 position + 5, | |
| 3628 '<input type="button" id="maxAbsoluteDepthDecrease" value="-"/>\ | |
| 3629 <span id="maxAbsoluteDepth"></span>\ | |
| 3630 <input type="button" id="maxAbsoluteDepthIncrease" value="+"/> Max depth', | |
| 3631 'Maximum depth to display, counted from the top level \ | |
| 3632 and including collapsed wedges.' | |
| 3633 ); | |
| 3634 | |
| 3635 position = addOptionElement | |
| 3636 ( | |
| 3637 position, | |
| 3638 '<input type="button" id="fontSizeDecrease" value="-"/>\ | |
| 3639 <span id="fontSize"></span>\ | |
| 3640 <input type="button" id="fontSizeIncrease" value="+"/> Font size' | |
| 3641 ); | |
| 3642 | |
| 3643 position = addOptionElement | |
| 3644 ( | |
| 3645 position, | |
| 3646 '<input type="button" id="radiusDecrease" value="-"/>\ | |
| 3647 <input type="button" id="radiusIncrease" value="+"/> Chart size' | |
| 3648 ); | |
| 3649 | |
| 3650 if ( hueName ) | |
| 3651 { | |
| 3652 hueDisplayName = attributes[attributeIndex(hueName)].displayName; | |
| 3653 | |
| 3654 position = addOptionElement | |
| 3655 ( | |
| 3656 position + 5, | |
| 3657 '<input type="checkbox" id="useHue" style="float:left" ' + | |
| 3658 '/><div>Color by<br/>' + hueDisplayName + | |
| 3659 '</div>' | |
| 3660 ); | |
| 3661 | |
| 3662 useHueCheckBox = document.getElementById('useHue'); | |
| 3663 useHueCheckBox.checked = hueDefault; | |
| 3664 useHueCheckBox.onclick = handleResize; | |
| 3665 useHueCheckBox.onmousedown = suppressEvent; | |
| 3666 } | |
| 3667 /* | |
| 3668 position = addOptionElement | |
| 3669 ( | |
| 3670 position + 5, | |
| 3671 ' <input type="checkbox" id="shorten" checked="checked" />Shorten labels</div>', | |
| 3672 'Prevent labels from overlapping by shortening them' | |
| 3673 ); | |
| 3674 | |
| 3675 position = addOptionElement | |
| 3676 ( | |
| 3677 position, | |
| 3678 ' <input type="checkbox" id="compress" checked="checked" />Compress', | |
| 3679 'Compress wedges if needed to show the entire depth' | |
| 3680 ); | |
| 3681 */ | |
| 3682 position = addOptionElement | |
| 3683 ( | |
| 3684 position, | |
| 3685 '<input type="checkbox" id="collapse" checked="checked" />Collapse', | |
| 3686 'Collapse wedges that are redundant (entirely composed of another wedge)' | |
| 3687 ); | |
| 3688 | |
| 3689 position = addOptionElement | |
| 3690 ( | |
| 3691 position + 5, | |
| 3692 '<input type="button" id="snapshot" value="Snapshot"/>', | |
| 3693 'Render the current view as SVG (Scalable Vector Graphics), a publication-\ | |
| 3694 quality format that can be printed and saved (see Help for browser compatibility)' | |
| 3695 ); | |
| 3696 | |
| 3697 position = addOptionElement | |
| 3698 ( | |
| 3699 position + 5, | |
| 3700 '<input type="button" id="linkButton" value="Link"/>\ | |
| 3701 <input type="text" size="30" id="linkText"/>', | |
| 3702 'Show a link to this view that can be copied for bookmarking or sharing' | |
| 3703 ); | |
| 3704 | |
| 3705 position = addOptionElement | |
| 3706 ( | |
| 3707 position + 5, | |
| 3708 '<input type="button" id="help" value="?"\ | |
| 3709 onclick="window.open(\'https://github.com/marbl/Krona/wiki/Browsing%20Krona%20charts\', \'help\')"/>', | |
| 3710 'Help' | |
| 3711 ); | |
| 3712 } | |
| 3713 | |
| 3714 function arrow(angleStart, angleEnd, radiusInner) | |
| 3715 { | |
| 3716 if ( context.globalAlpha == 0 ) | |
| 3717 { | |
| 3718 return; | |
| 3719 } | |
| 3720 | |
| 3721 var angleCenter = (angleStart + angleEnd) / 2; | |
| 3722 var radiusArrowInner = radiusInner - gRadius / 10;//nodeRadius * gRadius; | |
| 3723 var radiusArrowOuter = gRadius * 1.1;//(1 + nodeRadius); | |
| 3724 var radiusArrowCenter = (radiusArrowInner + radiusArrowOuter) / 2; | |
| 3725 var pointLength = (radiusArrowOuter - radiusArrowInner) / 5; | |
| 3726 | |
| 3727 context.fillStyle = highlightFill; | |
| 3728 context.lineWidth = highlightLineWidth; | |
| 3729 | |
| 3730 // First, mask out the first half of the arrow. This will prevent the tips | |
| 3731 // from superimposing if the arrow goes most of the way around the circle. | |
| 3732 // Masking is done by setting the clipping region to the inverse of the | |
| 3733 // half-arrow, which is defined by cutting the half-arrow out of a large | |
| 3734 // rectangle | |
| 3735 // | |
| 3736 context.beginPath(); | |
| 3737 context.arc(0, 0, radiusInner, angleCenter, angleEnd, false); | |
| 3738 context.lineTo | |
| 3739 ( | |
| 3740 radiusArrowInner * Math.cos(angleEnd), | |
| 3741 radiusArrowInner * Math.sin(angleEnd) | |
| 3742 ); | |
| 3743 context.lineTo | |
| 3744 ( | |
| 3745 radiusArrowCenter * Math.cos(angleEnd) - pointLength * Math.sin(angleEnd), | |
| 3746 radiusArrowCenter * Math.sin(angleEnd) + pointLength * Math.cos(angleEnd) | |
| 3747 ); | |
| 3748 context.lineTo | |
| 3749 ( | |
| 3750 radiusArrowOuter * Math.cos(angleEnd), | |
| 3751 radiusArrowOuter * Math.sin(angleEnd) | |
| 3752 ); | |
| 3753 context.arc(0, 0, gRadius, angleEnd, angleCenter, true); | |
| 3754 context.closePath(); | |
| 3755 context.moveTo(-imageWidth, -imageHeight); | |
| 3756 context.lineTo(imageWidth, -imageHeight); | |
| 3757 context.lineTo(imageWidth, imageHeight); | |
| 3758 context.lineTo(-imageWidth, imageHeight); | |
| 3759 context.closePath(); | |
| 3760 context.save(); | |
| 3761 context.clip(); | |
| 3762 | |
| 3763 // Next, draw the other half-arrow with the first half masked out | |
| 3764 // | |
| 3765 context.beginPath(); | |
| 3766 context.arc(0, 0, radiusInner, angleCenter, angleStart, true); | |
| 3767 context.lineTo | |
| 3768 ( | |
| 3769 radiusArrowInner * Math.cos(angleStart), | |
| 3770 radiusArrowInner * Math.sin(angleStart) | |
| 3771 ); | |
| 3772 context.lineTo | |
| 3773 ( | |
| 3774 radiusArrowCenter * Math.cos(angleStart) + pointLength * Math.sin(angleStart), | |
| 3775 radiusArrowCenter * Math.sin(angleStart) - pointLength * Math.cos(angleStart) | |
| 3776 ); | |
| 3777 context.lineTo | |
| 3778 ( | |
| 3779 radiusArrowOuter * Math.cos(angleStart), | |
| 3780 radiusArrowOuter * Math.sin(angleStart) | |
| 3781 ); | |
| 3782 context.arc(0, 0, gRadius, angleStart, angleCenter, false); | |
| 3783 context.fill(); | |
| 3784 context.stroke(); | |
| 3785 | |
| 3786 // Finally, remove the clipping region and draw the first half-arrow. This | |
| 3787 // half is extended slightly to fill the seam. | |
| 3788 // | |
| 3789 context.restore(); | |
| 3790 context.beginPath(); | |
| 3791 context.arc(0, 0, radiusInner, angleCenter - 2 / (2 * Math.PI * radiusInner), angleEnd, false); | |
| 3792 context.lineTo | |
| 3793 ( | |
| 3794 radiusArrowInner * Math.cos(angleEnd), | |
| 3795 radiusArrowInner * Math.sin(angleEnd) | |
| 3796 ); | |
| 3797 context.lineTo | |
| 3798 ( | |
| 3799 radiusArrowCenter * Math.cos(angleEnd) - pointLength * Math.sin(angleEnd), | |
| 3800 radiusArrowCenter * Math.sin(angleEnd) + pointLength * Math.cos(angleEnd) | |
| 3801 ); | |
| 3802 context.lineTo | |
| 3803 ( | |
| 3804 radiusArrowOuter * Math.cos(angleEnd), | |
| 3805 radiusArrowOuter * Math.sin(angleEnd) | |
| 3806 ); | |
| 3807 context.arc(0, 0, gRadius, angleEnd, angleCenter - 2 / (2 * Math.PI * gRadius), true); | |
| 3808 context.fill(); | |
| 3809 context.stroke(); | |
| 3810 } | |
| 3811 | |
| 3812 function attributeIndex(aname) | |
| 3813 { | |
| 3814 for ( var i = 0 ; i < attributes.length; i++ ) | |
| 3815 { | |
| 3816 if ( aname == attributes[i].name ) | |
| 3817 { | |
| 3818 return i; | |
| 3819 } | |
| 3820 } | |
| 3821 | |
| 3822 return null; | |
| 3823 } | |
| 3824 | |
| 3825 function checkHighlight() | |
| 3826 { | |
| 3827 var lastHighlightedNode = highlightedNode; | |
| 3828 var lastHighlightingHidden = highlightingHidden; | |
| 3829 | |
| 3830 highlightedNode = selectedNode; | |
| 3831 resetKeyOffset(); | |
| 3832 | |
| 3833 if ( progress == 1 ) | |
| 3834 { | |
| 3835 selectedNode.checkHighlight(); | |
| 3836 if ( selectedNode.getParent() ) | |
| 3837 { | |
| 3838 selectedNode.getParent().checkHighlightCenter(); | |
| 3839 } | |
| 3840 | |
| 3841 focusNode.checkHighlightMap(); | |
| 3842 } | |
| 3843 | |
| 3844 if ( highlightedNode != selectedNode ) | |
| 3845 { | |
| 3846 if ( highlightedNode == focusNode ) | |
| 3847 { | |
| 3848 // canvas.style.display='none'; | |
| 3849 // window.resizeBy(1,0); | |
| 3850 // canvas.style.cursor='ew-resize'; | |
| 3851 // window.resizeBy(-1,0); | |
| 3852 // canvas.style.display='inline'; | |
| 3853 } | |
| 3854 else | |
| 3855 { | |
| 3856 // canvas.style.cursor='pointer'; | |
| 3857 } | |
| 3858 } | |
| 3859 else | |
| 3860 { | |
| 3861 // canvas.style.cursor='auto'; | |
| 3862 } | |
| 3863 | |
| 3864 if | |
| 3865 ( | |
| 3866 ( | |
| 3867 true || | |
| 3868 highlightedNode != lastHighlightedNode || | |
| 3869 highlightingHidden != highlightingHiddenLast | |
| 3870 ) && | |
| 3871 progress == 1 | |
| 3872 ) | |
| 3873 { | |
| 3874 draw(); // TODO: handle in update() | |
| 3875 } | |
| 3876 } | |
| 3877 | |
| 3878 function checkSelectedCollapse() | |
| 3879 { | |
| 3880 var newNode = selectedNode; | |
| 3881 | |
| 3882 while ( newNode.getCollapse() ) | |
| 3883 { | |
| 3884 newNode = newNode.children[0]; | |
| 3885 } | |
| 3886 | |
| 3887 if ( newNode.children.length == 0 && newNode.getParent() ) | |
| 3888 { | |
| 3889 newNode = newNode.getParent(); | |
| 3890 } | |
| 3891 | |
| 3892 if ( newNode != selectedNode ) | |
| 3893 { | |
| 3894 selectNode(newNode); | |
| 3895 } | |
| 3896 } | |
| 3897 | |
| 3898 function clearSearch() | |
| 3899 { | |
| 3900 if ( search.value != '' ) | |
| 3901 { | |
| 3902 search.value = ''; | |
| 3903 onSearchChange(); | |
| 3904 } | |
| 3905 } | |
| 3906 | |
| 3907 function createSVG() | |
| 3908 { | |
| 3909 svgNS = "http://www.w3.org/2000/svg"; | |
| 3910 var SVG = {}; | |
| 3911 SVG.xlinkns = "http://www.w3.org/1999/xlink"; | |
| 3912 | |
| 3913 var newSVG = document.createElementNS(svgNS, "svg:svg"); | |
| 3914 | |
| 3915 newSVG.setAttribute("id", "canvas"); | |
| 3916 // How big is the canvas in pixels | |
| 3917 newSVG.setAttribute("width", '100%'); | |
| 3918 newSVG.setAttribute("height", '100%'); | |
| 3919 // Set the coordinates used by drawings in the canvas | |
| 3920 // newSVG.setAttribute("viewBox", "0 0 " + imageWidth + " " + imageHeight); | |
| 3921 // Define the XLink namespace that SVG uses | |
| 3922 newSVG.setAttributeNS | |
| 3923 ( | |
| 3924 "http://www.w3.org/2000/xmlns/", | |
| 3925 "xmlns:xlink", | |
| 3926 SVG.xlinkns | |
| 3927 ); | |
| 3928 | |
| 3929 return newSVG; | |
| 3930 } | |
| 3931 | |
| 3932 function degrees(radians) | |
| 3933 { | |
| 3934 return radians * 180 / Math.PI; | |
| 3935 } | |
| 3936 | |
| 3937 function draw() | |
| 3938 { | |
| 3939 tweenFrames++; | |
| 3940 //resize(); | |
| 3941 // context.fillRect(0, 0, imageWidth, imageHeight); | |
| 3942 context.clearRect(0, 0, imageWidth, imageHeight); | |
| 3943 | |
| 3944 context.font = fontNormal; | |
| 3945 context.textBaseline = 'middle'; | |
| 3946 | |
| 3947 //context.strokeStyle = 'rgba(0, 0, 0, 0.3)'; | |
| 3948 context.translate(centerX, centerY); | |
| 3949 | |
| 3950 resetKeyOffset(); | |
| 3951 | |
| 3952 head.draw(false, false); // draw pie slices | |
| 3953 head.draw(true, false); // draw labels | |
| 3954 | |
| 3955 var pathRoot = selectedNode; | |
| 3956 | |
| 3957 if ( focusNode != 0 && focusNode != selectedNode ) | |
| 3958 { | |
| 3959 context.globalAlpha = 1; | |
| 3960 focusNode.drawHighlight(true); | |
| 3961 pathRoot = focusNode; | |
| 3962 } | |
| 3963 | |
| 3964 if | |
| 3965 ( | |
| 3966 highlightedNode && | |
| 3967 highlightedNode.getDepth() >= selectedNode.getDepth() && | |
| 3968 highlightedNode != focusNode | |
| 3969 ) | |
| 3970 { | |
| 3971 if | |
| 3972 ( | |
| 3973 progress == 1 && | |
| 3974 highlightedNode != selectedNode && | |
| 3975 ( | |
| 3976 highlightedNode != focusNode || | |
| 3977 focusNode.children.length > 0 | |
| 3978 ) | |
| 3979 ) | |
| 3980 { | |
| 3981 context.globalAlpha = 1; | |
| 3982 highlightedNode.drawHighlight(true); | |
| 3983 } | |
| 3984 | |
| 3985 //pathRoot = highlightedNode; | |
| 3986 } | |
| 3987 else if | |
| 3988 ( | |
| 3989 progress == 1 && | |
| 3990 highlightedNode.getDepth() < selectedNode.getDepth() | |
| 3991 ) | |
| 3992 { | |
| 3993 context.globalAlpha = 1; | |
| 3994 highlightedNode.drawHighlightCenter(); | |
| 3995 } | |
| 3996 | |
| 3997 if ( quickLook && false) // TEMP | |
| 3998 { | |
| 3999 context.globalAlpha = 1 - progress / 2; | |
| 4000 selectedNode.drawHighlight(true); | |
| 4001 } | |
| 4002 else if ( progress < 1 )//&& zoomOut() ) | |
| 4003 { | |
| 4004 if ( !zoomOut)//() ) | |
| 4005 { | |
| 4006 context.globalAlpha = selectedNode.alphaLine.current(); | |
| 4007 selectedNode.drawHighlight(true); | |
| 4008 } | |
| 4009 else if ( selectedNodeLast ) | |
| 4010 { | |
| 4011 context.globalAlpha = 1 - 4 * Math.pow(progress - .5, 2); | |
| 4012 selectedNodeLast.drawHighlight(false); | |
| 4013 } | |
| 4014 } | |
| 4015 | |
| 4016 drawDatasetName(); | |
| 4017 | |
| 4018 //drawHistory(); | |
| 4019 | |
| 4020 context.translate(-centerX, -centerY); | |
| 4021 context.globalAlpha = 1; | |
| 4022 | |
| 4023 mapRadius = | |
| 4024 (imageHeight / 2 - details.clientHeight - details.offsetTop) / | |
| 4025 (pathRoot.getDepth() - 1) * 3 / 4 / 2; | |
| 4026 | |
| 4027 if ( mapRadius > maxMapRadius ) | |
| 4028 { | |
| 4029 mapRadius = maxMapRadius; | |
| 4030 } | |
| 4031 | |
| 4032 mapBuffer = mapRadius / 2; | |
| 4033 | |
| 4034 //context.font = fontNormal; | |
| 4035 pathRoot.drawMap(pathRoot); | |
| 4036 | |
| 4037 if ( hueDisplayName && useHue() ) | |
| 4038 { | |
| 4039 drawLegend(); | |
| 4040 } | |
| 4041 } | |
| 4042 | |
| 4043 function drawBubble(angle, radius, width, radial, flip) | |
| 4044 { | |
| 4045 var height = fontSize * 2; | |
| 4046 var x; | |
| 4047 var y; | |
| 4048 | |
| 4049 width = width + fontSize; | |
| 4050 | |
| 4051 if ( radial ) | |
| 4052 { | |
| 4053 y = -fontSize; | |
| 4054 | |
| 4055 if ( flip ) | |
| 4056 { | |
| 4057 x = radius - width + fontSize / 2; | |
| 4058 } | |
| 4059 else | |
| 4060 { | |
| 4061 x = radius - fontSize / 2; | |
| 4062 } | |
| 4063 } | |
| 4064 else | |
| 4065 { | |
| 4066 x = -width / 2; | |
| 4067 y = -radius - fontSize; | |
| 4068 } | |
| 4069 | |
| 4070 if ( snapshotMode ) | |
| 4071 { | |
| 4072 drawBubbleSVG(x + centerX, y + centerY, width, height, fontSize, angle); | |
| 4073 } | |
| 4074 else | |
| 4075 { | |
| 4076 drawBubbleCanvas(x, y, width, height, fontSize, angle); | |
| 4077 } | |
| 4078 } | |
| 4079 | |
| 4080 function drawBubbleCanvas(x, y, width, height, radius, rotation) | |
| 4081 { | |
| 4082 context.strokeStyle = 'black'; | |
| 4083 context.lineWidth = highlightLineWidth; | |
| 4084 context.fillStyle = 'rgba(255, 255, 255, .75)'; | |
| 4085 context.rotate(rotation); | |
| 4086 roundedRectangle(x, y, width, fontSize * 2, fontSize); | |
| 4087 context.fill(); | |
| 4088 context.stroke(); | |
| 4089 context.rotate(-rotation); | |
| 4090 } | |
| 4091 | |
| 4092 function drawBubbleSVG(x, y, width, height, radius, rotation) | |
| 4093 { | |
| 4094 svg += | |
| 4095 '<rect x="' + x + '" y="' + y + | |
| 4096 '" width="' + width + | |
| 4097 '" height="' + height + | |
| 4098 '" rx="' + radius + | |
| 4099 '" ry="' + radius + | |
| 4100 '" fill="rgba(255, 255, 255, .75)' + | |
| 4101 '" class="highlight" ' + | |
| 4102 'transform="rotate(' + | |
| 4103 degrees(rotation) + ',' + centerX + ',' + centerY + | |
| 4104 ')"/>'; | |
| 4105 } | |
| 4106 | |
| 4107 function drawDatasetName() | |
| 4108 { | |
| 4109 var alpha = datasetAlpha.current(); | |
| 4110 | |
| 4111 if ( alpha > 0 ) | |
| 4112 { | |
| 4113 var radius = gRadius * compressedRadii[0] / -2; | |
| 4114 | |
| 4115 if ( alpha > 1 ) | |
| 4116 { | |
| 4117 alpha = 1; | |
| 4118 } | |
| 4119 | |
| 4120 context.globalAlpha = alpha; | |
| 4121 | |
| 4122 drawBubble(0, -radius, datasetWidths[currentDataset], false, false); | |
| 4123 drawText(datasetNames[currentDataset], 0, radius, 0, 'center', true); | |
| 4124 } | |
| 4125 } | |
| 4126 | |
| 4127 function drawHistory() | |
| 4128 { | |
| 4129 var alpha = 1; | |
| 4130 context.textAlign = 'center'; | |
| 4131 | |
| 4132 for ( var i = 0; i < nodeHistoryPosition && alpha > 0; i++ ) | |
| 4133 { | |
| 4134 | |
| 4135 context.globalAlpha = alpha - historyAlphaDelta * tweenFactor; | |
| 4136 context.fillText | |
| 4137 ( | |
| 4138 nodeHistory[nodeHistoryPosition - i - 1].name, | |
| 4139 0, | |
| 4140 (i + tweenFactor) * historySpacingFactor * fontSize - 1 | |
| 4141 ); | |
| 4142 | |
| 4143 if ( alpha > 0 ) | |
| 4144 { | |
| 4145 alpha -= historyAlphaDelta; | |
| 4146 } | |
| 4147 } | |
| 4148 | |
| 4149 context.globalAlpha = 1; | |
| 4150 } | |
| 4151 | |
| 4152 function drawLegend() | |
| 4153 { | |
| 4154 var left = imageWidth * .01; | |
| 4155 var width = imageHeight * .0265; | |
| 4156 var height = imageHeight * .15; | |
| 4157 var top = imageHeight - fontSize * 3.5 - height; | |
| 4158 var textLeft = left + width + fontSize / 2; | |
| 4159 | |
| 4160 context.fillStyle = 'black'; | |
| 4161 context.textAlign = 'start'; | |
| 4162 context.font = fontNormal; | |
| 4163 // context.fillText(valueStartText, textLeft, top + height); | |
| 4164 // context.fillText(valueEndText, textLeft, top); | |
| 4165 context.fillText(hueDisplayName, left, imageHeight - fontSize * 1.5); | |
| 4166 | |
| 4167 var gradient = context.createLinearGradient(0, top + height, 0, top); | |
| 4168 | |
| 4169 for ( var i = 0; i < hueStopPositions.length; i++ ) | |
| 4170 { | |
| 4171 gradient.addColorStop(hueStopPositions[i], hueStopHsl[i]); | |
| 4172 | |
| 4173 var textY = top + (1 - hueStopPositions[i]) * height; | |
| 4174 | |
| 4175 if | |
| 4176 ( | |
| 4177 i == 0 || | |
| 4178 i == hueStopPositions.length - 1 || | |
| 4179 textY > top + fontSize && textY < top + height - fontSize | |
| 4180 ) | |
| 4181 { | |
| 4182 context.fillText(hueStopText[i], textLeft, textY); | |
| 4183 } | |
| 4184 } | |
| 4185 | |
| 4186 context.fillStyle = gradient; | |
| 4187 context.fillRect(left, top, width, height); | |
| 4188 context.lineWidth = thinLineWidth; | |
| 4189 context.strokeRect(left, top, width, height); | |
| 4190 } | |
| 4191 | |
| 4192 function drawLegendSVG() | |
| 4193 { | |
| 4194 var left = imageWidth * .01; | |
| 4195 var width = imageHeight * .0265; | |
| 4196 var height = imageHeight * .15; | |
| 4197 var top = imageHeight - fontSize * 3.5 - height; | |
| 4198 var textLeft = left + width + fontSize / 2; | |
| 4199 | |
| 4200 var text = ''; | |
| 4201 | |
| 4202 text += svgText(hueDisplayName, left, imageHeight - fontSize * 1.5); | |
| 4203 | |
| 4204 var svgtest = '<linearGradient id="gradient" x1="0%" y1="100%" x2="0%" y2="0%">'; | |
| 4205 | |
| 4206 for ( var i = 0; i < hueStopPositions.length; i++ ) | |
| 4207 { | |
| 4208 svgtest += | |
| 4209 '<stop offset="' + round(hueStopPositions[i] * 100) + | |
| 4210 '%" style="stop-color:' + hueStopHsl[i] + '"/>'; | |
| 4211 | |
| 4212 var textY = top + (1 - hueStopPositions[i]) * height; | |
| 4213 | |
| 4214 if | |
| 4215 ( | |
| 4216 i == 0 || | |
| 4217 i == hueStopPositions.length - 1 || | |
| 4218 textY > top + fontSize && textY < top + height - fontSize | |
| 4219 ) | |
| 4220 { | |
| 4221 text += svgText(hueStopText[i], textLeft, textY); | |
| 4222 } | |
| 4223 } | |
| 4224 | |
| 4225 svgtest += '</linearGradient>'; | |
| 4226 //alert(svgtest); | |
| 4227 svg += svgtest; | |
| 4228 svg += | |
| 4229 '<rect style="fill:url(#gradient)" x="' + left + '" y="' + top + | |
| 4230 '" width="' + width + '" height="' + height + '"/>'; | |
| 4231 | |
| 4232 svg += text; | |
| 4233 } | |
| 4234 | |
| 4235 function drawSearchHighlights(label, bubbleX, bubbleY, rotation, center) | |
| 4236 { | |
| 4237 var index = -1; | |
| 4238 var labelLength = label.length; | |
| 4239 | |
| 4240 bubbleX -= fontSize / 4; | |
| 4241 | |
| 4242 do | |
| 4243 { | |
| 4244 index = label.toLowerCase().indexOf(search.value.toLowerCase(), index + 1); | |
| 4245 | |
| 4246 if ( index != -1 && index < labelLength ) | |
| 4247 { | |
| 4248 var dim = context.measureText(label.substr(0, index)); | |
| 4249 var x = bubbleX + dim.width; | |
| 4250 | |
| 4251 dim = context.measureText(label.substr(index, search.value.length)); | |
| 4252 | |
| 4253 var y = bubbleY - fontSize * 3 / 4; | |
| 4254 var width = dim.width + fontSize / 2; | |
| 4255 var height = fontSize * 3 / 2; | |
| 4256 var radius = fontSize / 2; | |
| 4257 | |
| 4258 if ( snapshotMode ) | |
| 4259 { | |
| 4260 if ( center ) | |
| 4261 { | |
| 4262 x += centerX; | |
| 4263 y += centerY; | |
| 4264 } | |
| 4265 | |
| 4266 svg += | |
| 4267 '<rect x="' + x + '" y="' + y + | |
| 4268 '" width="' + width + | |
| 4269 '" height="' + height + | |
| 4270 '" rx="' + radius + | |
| 4271 '" ry="' + radius + | |
| 4272 '" class="searchHighlight' + | |
| 4273 '" transform="rotate(' + | |
| 4274 degrees(rotation) + ',' + centerX + ',' + centerY + | |
| 4275 ')"/>'; | |
| 4276 } | |
| 4277 else | |
| 4278 { | |
| 4279 context.fillStyle = 'rgb(255, 255, 100)'; | |
| 4280 context.rotate(rotation); | |
| 4281 roundedRectangle(x, y, width, height, radius); | |
| 4282 context.fill(); | |
| 4283 context.rotate(-rotation); | |
| 4284 } | |
| 4285 } | |
| 4286 } | |
| 4287 while ( index != -1 && index < labelLength ); | |
| 4288 } | |
| 4289 | |
| 4290 function drawText(text, x, y, angle, anchor, bold, color) | |
| 4291 { | |
| 4292 if ( color == undefined ) | |
| 4293 { | |
| 4294 color = 'black'; | |
| 4295 } | |
| 4296 | |
| 4297 if ( snapshotMode ) | |
| 4298 { | |
| 4299 svg += | |
| 4300 '<text x="' + (centerX + x) + '" y="' + (centerY + y) + | |
| 4301 '" text-anchor="' + anchor + '" style="font-color:' + color + ';font-weight:' + (bold ? 'bold' : 'normal') + | |
| 4302 '" transform="rotate(' + degrees(angle) + ',' + centerX + ',' + centerY + ')">' + | |
| 4303 text + '</text>'; | |
| 4304 } | |
| 4305 else | |
| 4306 { | |
| 4307 context.fillStyle = color; | |
| 4308 context.textAlign = anchor; | |
| 4309 context.font = bold ? fontBold : fontNormal; | |
| 4310 context.rotate(angle); | |
| 4311 context.fillText(text, x, y); | |
| 4312 context.rotate(-angle); | |
| 4313 } | |
| 4314 } | |
| 4315 | |
| 4316 function drawTextPolar | |
| 4317 ( | |
| 4318 text, | |
| 4319 innerText, | |
| 4320 angle, | |
| 4321 radius, | |
| 4322 radial, | |
| 4323 bubble, | |
| 4324 bold, | |
| 4325 searchResult, | |
| 4326 searchResults | |
| 4327 ) | |
| 4328 { | |
| 4329 var anchor; | |
| 4330 var textX; | |
| 4331 var textY; | |
| 4332 var spacer; | |
| 4333 var totalText = text; | |
| 4334 var flip; | |
| 4335 | |
| 4336 if ( snapshotMode ) | |
| 4337 { | |
| 4338 spacer = '   '; | |
| 4339 } | |
| 4340 else | |
| 4341 { | |
| 4342 spacer = ' '; | |
| 4343 } | |
| 4344 | |
| 4345 if ( radial ) | |
| 4346 { | |
| 4347 flip = angle < 3 * Math.PI / 2; | |
| 4348 | |
| 4349 if ( flip ) | |
| 4350 { | |
| 4351 angle -= Math.PI; | |
| 4352 radius = -radius; | |
| 4353 anchor = 'end'; | |
| 4354 | |
| 4355 if ( innerText ) | |
| 4356 { | |
| 4357 totalText = text + spacer + innerText; | |
| 4358 } | |
| 4359 } | |
| 4360 else | |
| 4361 { | |
| 4362 anchor = 'start'; | |
| 4363 | |
| 4364 if ( innerText ) | |
| 4365 { | |
| 4366 totalText = innerText + spacer + text; | |
| 4367 } | |
| 4368 } | |
| 4369 | |
| 4370 textX = radius; | |
| 4371 textY = 0; | |
| 4372 } | |
| 4373 else | |
| 4374 { | |
| 4375 flip = angle < Math.PI || angle > 2 * Math.PI; | |
| 4376 var label; | |
| 4377 | |
| 4378 anchor = snapshotMode ? 'middle' : 'center'; | |
| 4379 | |
| 4380 if ( flip ) | |
| 4381 { | |
| 4382 angle -= Math.PI; | |
| 4383 radius = -radius; | |
| 4384 } | |
| 4385 | |
| 4386 angle += Math.PI / 2; | |
| 4387 textX = 0; | |
| 4388 textY = -radius; | |
| 4389 } | |
| 4390 | |
| 4391 if ( bubble ) | |
| 4392 { | |
| 4393 var textActual = totalText; | |
| 4394 | |
| 4395 if ( innerText && snapshotMode ) | |
| 4396 { | |
| 4397 if ( flip ) | |
| 4398 { | |
| 4399 textActual = text + ' ' + innerText; | |
| 4400 } | |
| 4401 else | |
| 4402 { | |
| 4403 textActual = innerText + ' ' + text; | |
| 4404 } | |
| 4405 } | |
| 4406 | |
| 4407 if ( searchResults ) | |
| 4408 { | |
| 4409 textActual = textActual + searchResultString(searchResults); | |
| 4410 } | |
| 4411 | |
| 4412 var textWidth = measureText(textActual, bold); | |
| 4413 | |
| 4414 var x = textX; | |
| 4415 | |
| 4416 if ( anchor == 'end' ) | |
| 4417 { | |
| 4418 x -= textWidth; | |
| 4419 } | |
| 4420 else if ( anchor != 'start' ) | |
| 4421 { | |
| 4422 // centered | |
| 4423 x -= textWidth / 2; | |
| 4424 } | |
| 4425 | |
| 4426 drawBubble(angle, radius, textWidth, radial, flip); | |
| 4427 | |
| 4428 if ( searchResult ) | |
| 4429 { | |
| 4430 drawSearchHighlights | |
| 4431 ( | |
| 4432 textActual, | |
| 4433 x, | |
| 4434 textY, | |
| 4435 angle, | |
| 4436 true | |
| 4437 ) | |
| 4438 } | |
| 4439 } | |
| 4440 | |
| 4441 if ( searchResults ) | |
| 4442 { | |
| 4443 totalText = totalText + searchResultString(searchResults); | |
| 4444 } | |
| 4445 | |
| 4446 drawText(totalText, textX, textY, angle, anchor, bold); | |
| 4447 | |
| 4448 return flip; | |
| 4449 } | |
| 4450 | |
| 4451 function drawTick(start, length, angle) | |
| 4452 { | |
| 4453 if ( snapshotMode ) | |
| 4454 { | |
| 4455 svg += | |
| 4456 '<line x1="' + (centerX + start) + | |
| 4457 '" y1="' + centerY + | |
| 4458 '" x2="' + (centerX + start + length) + | |
| 4459 '" y2="' + centerY + | |
| 4460 '" class="tick" transform="rotate(' + | |
| 4461 degrees(angle) + ',' + centerX + ',' + centerY + | |
| 4462 ')"/>'; | |
| 4463 } | |
| 4464 else | |
| 4465 { | |
| 4466 context.rotate(angle); | |
| 4467 context.beginPath(); | |
| 4468 context.moveTo(start, 0); | |
| 4469 context.lineTo(start + length, 0); | |
| 4470 context.lineWidth = thinLineWidth * 2; | |
| 4471 context.stroke(); | |
| 4472 context.rotate(-angle); | |
| 4473 } | |
| 4474 } | |
| 4475 | |
| 4476 function drawWedge | |
| 4477 ( | |
| 4478 angleStart, | |
| 4479 angleEnd, | |
| 4480 radiusInner, | |
| 4481 radiusOuter, | |
| 4482 color, | |
| 4483 patternAlpha, | |
| 4484 highlight | |
| 4485 ) | |
| 4486 { | |
| 4487 if ( context.globalAlpha == 0 ) | |
| 4488 { | |
| 4489 return; | |
| 4490 } | |
| 4491 | |
| 4492 if ( snapshotMode ) | |
| 4493 { | |
| 4494 if ( angleEnd == angleStart + Math.PI * 2 ) | |
| 4495 { | |
| 4496 // fudge to prevent overlap, which causes arc ambiguity | |
| 4497 // | |
| 4498 angleEnd -= .1 / gRadius; | |
| 4499 } | |
| 4500 | |
| 4501 var longArc = angleEnd - angleStart > Math.PI ? 1 : 0; | |
| 4502 | |
| 4503 var x1 = centerX + radiusInner * Math.cos(angleStart); | |
| 4504 var y1 = centerY + radiusInner * Math.sin(angleStart); | |
| 4505 | |
| 4506 var x2 = centerX + gRadius * Math.cos(angleStart); | |
| 4507 var y2 = centerY + gRadius * Math.sin(angleStart); | |
| 4508 | |
| 4509 var x3 = centerX + gRadius * Math.cos(angleEnd); | |
| 4510 var y3 = centerY + gRadius * Math.sin(angleEnd); | |
| 4511 | |
| 4512 var x4 = centerX + radiusInner * Math.cos(angleEnd); | |
| 4513 var y4 = centerY + radiusInner * Math.sin(angleEnd); | |
| 4514 | |
| 4515 var dArray = | |
| 4516 [ | |
| 4517 " M ", x1, ",", y1, | |
| 4518 " L ", x2, ",", y2, | |
| 4519 " A ", gRadius, ",", gRadius, " 0 ", longArc, ",1 ", x3, ",", y3, | |
| 4520 " L ", x4, ",", y4, | |
| 4521 " A ", radiusInner, ",", radiusInner, " 0 ", longArc, " 0 ", x1, ",", y1, | |
| 4522 " Z " | |
| 4523 ]; | |
| 4524 | |
| 4525 svg += | |
| 4526 '<path class="'+ (highlight ? 'highlight' : 'wedge') + '" fill="' + color + | |
| 4527 '" d="' + dArray.join('') + '"/>'; | |
| 4528 | |
| 4529 if ( patternAlpha > 0 ) | |
| 4530 { | |
| 4531 svg += | |
| 4532 '<path class="wedge" fill="url(#hiddenPattern)" d="' + | |
| 4533 dArray.join('') + '"/>'; | |
| 4534 } | |
| 4535 } | |
| 4536 else | |
| 4537 { | |
| 4538 // fudge to prevent seams during animation | |
| 4539 // | |
| 4540 angleEnd += 1 / gRadius; | |
| 4541 | |
| 4542 context.fillStyle = color; | |
| 4543 context.beginPath(); | |
| 4544 context.arc(0, 0, radiusInner, angleStart, angleEnd, false); | |
| 4545 context.arc(0, 0, radiusOuter, angleEnd, angleStart, true); | |
| 4546 context.closePath(); | |
| 4547 context.fill(); | |
| 4548 | |
| 4549 if ( patternAlpha > 0 ) | |
| 4550 { | |
| 4551 context.save(); | |
| 4552 context.clip(); | |
| 4553 context.globalAlpha = patternAlpha; | |
| 4554 context.fillStyle = hiddenPattern; | |
| 4555 context.fill(); | |
| 4556 context.restore(); | |
| 4557 } | |
| 4558 | |
| 4559 if ( highlight ) | |
| 4560 { | |
| 4561 context.lineWidth = highlight ? highlightLineWidth : thinLineWidth; | |
| 4562 context.strokeStyle = 'black'; | |
| 4563 context.stroke(); | |
| 4564 } | |
| 4565 } | |
| 4566 } | |
| 4567 | |
| 4568 function expand(node) | |
| 4569 { | |
| 4570 selectNode(node); | |
| 4571 updateView(); | |
| 4572 } | |
| 4573 | |
| 4574 function focusLost() | |
| 4575 { | |
| 4576 mouseX = -1; | |
| 4577 mouseY = -1; | |
| 4578 checkHighlight(); | |
| 4579 document.body.style.cursor = 'auto'; | |
| 4580 } | |
| 4581 | |
| 4582 function fontSizeDecrease() | |
| 4583 { | |
| 4584 if ( fontSize > 1 ) | |
| 4585 { | |
| 4586 fontSize--; | |
| 4587 updateViewNeeded = true; | |
| 4588 } | |
| 4589 } | |
| 4590 | |
| 4591 function fontSizeIncrease() | |
| 4592 { | |
| 4593 fontSize++; | |
| 4594 updateViewNeeded = true; | |
| 4595 } | |
| 4596 | |
| 4597 function getGetString(name, value, bool) | |
| 4598 { | |
| 4599 return name + '=' + (bool ? value ? 'true' : 'false' : value); | |
| 4600 } | |
| 4601 | |
| 4602 function hideLink() | |
| 4603 { | |
| 4604 hide(linkText); | |
| 4605 show(linkButton); | |
| 4606 } | |
| 4607 | |
| 4608 function show(object) | |
| 4609 { | |
| 4610 object.style.display = 'inline'; | |
| 4611 } | |
| 4612 | |
| 4613 function hide(object) | |
| 4614 { | |
| 4615 object.style.display = 'none'; | |
| 4616 } | |
| 4617 | |
| 4618 function showLink() | |
| 4619 { | |
| 4620 var urlHalves = String(document.location).split('?'); | |
| 4621 var newGetVariables = new Array(); | |
| 4622 | |
| 4623 newGetVariables.push | |
| 4624 ( | |
| 4625 getGetString('dataset', currentDataset, false), | |
| 4626 getGetString('node', selectedNode.id, false), | |
| 4627 getGetString('collapse', collapse, true), | |
| 4628 getGetString('color', useHue(), true), | |
| 4629 getGetString('depth', maxAbsoluteDepth - 1, false), | |
| 4630 getGetString('font', fontSize, false), | |
| 4631 getGetString('key', showKeys, true) | |
| 4632 ); | |
| 4633 | |
| 4634 hide(linkButton); | |
| 4635 show(linkText); | |
| 4636 linkText.value = urlHalves[0] + '?' + getVariables.concat(newGetVariables).join('&'); | |
| 4637 //linkText.disabled = false; | |
| 4638 linkText.focus(); | |
| 4639 linkText.select(); | |
| 4640 //linkText.disabled = true; | |
| 4641 // document.location = urlHalves[0] + '?' + getVariables.join('&'); | |
| 4642 } | |
| 4643 | |
| 4644 function getFirstChild(element) | |
| 4645 { | |
| 4646 element = element.firstChild; | |
| 4647 | |
| 4648 if ( element && element.nodeType != 1 ) | |
| 4649 { | |
| 4650 element = getNextSibling(element); | |
| 4651 } | |
| 4652 | |
| 4653 return element; | |
| 4654 } | |
| 4655 | |
| 4656 function getNextSibling(element) | |
| 4657 { | |
| 4658 do | |
| 4659 { | |
| 4660 element = element.nextSibling; | |
| 4661 } | |
| 4662 while ( element && element.nodeType != 1 ); | |
| 4663 | |
| 4664 return element; | |
| 4665 } | |
| 4666 | |
| 4667 function getPercentage(fraction) | |
| 4668 { | |
| 4669 return round(fraction * 100); | |
| 4670 } | |
| 4671 | |
| 4672 function hslText(hue) | |
| 4673 { | |
| 4674 if ( 1 || snapshotMode ) | |
| 4675 { | |
| 4676 // Safari doesn't seem to allow hsl() in SVG | |
| 4677 | |
| 4678 var rgb = hslToRgb(hue, saturation, (lightnessBase + lightnessMax) / 2); | |
| 4679 | |
| 4680 return rgbText(rgb.r, rgb.g, rgb.b); | |
| 4681 } | |
| 4682 else | |
| 4683 { | |
| 4684 var hslArray = | |
| 4685 [ | |
| 4686 'hsl(', | |
| 4687 Math.floor(hue * 360), | |
| 4688 ',', | |
| 4689 Math.floor(saturation * 100), | |
| 4690 '%,', | |
| 4691 Math.floor((lightnessBase + lightnessMax) * 50), | |
| 4692 '%)' | |
| 4693 ]; | |
| 4694 | |
| 4695 return hslArray.join(''); | |
| 4696 } | |
| 4697 } | |
| 4698 | |
| 4699 function hslToRgb(h, s, l) | |
| 4700 { | |
| 4701 var m1, m2; | |
| 4702 var r, g, b; | |
| 4703 | |
| 4704 if (s == 0) | |
| 4705 { | |
| 4706 r = g = b = Math.floor((l * 255)); | |
| 4707 } | |
| 4708 else | |
| 4709 { | |
| 4710 if (l <= 0.5) | |
| 4711 { | |
| 4712 m2 = l * (s + 1); | |
| 4713 } | |
| 4714 else | |
| 4715 { | |
| 4716 m2 = l + s - l * s; | |
| 4717 } | |
| 4718 | |
| 4719 m1 = l * 2 - m2; | |
| 4720 | |
| 4721 r = Math.floor(hueToRgb(m1, m2, h + 1 / 3)); | |
| 4722 g = Math.floor(hueToRgb(m1, m2, h)); | |
| 4723 b = Math.floor(hueToRgb(m1, m2, h - 1/3)); | |
| 4724 } | |
| 4725 | |
| 4726 return {r: r, g: g, b: b}; | |
| 4727 } | |
| 4728 | |
| 4729 function hueToRgb(m1, m2, hue) | |
| 4730 { | |
| 4731 var v; | |
| 4732 | |
| 4733 while (hue < 0) | |
| 4734 { | |
| 4735 hue += 1; | |
| 4736 } | |
| 4737 | |
| 4738 while (hue > 1) | |
| 4739 { | |
| 4740 hue -= 1; | |
| 4741 } | |
| 4742 | |
| 4743 if (6 * hue < 1) | |
| 4744 v = m1 + (m2 - m1) * hue * 6; | |
| 4745 else if (2 * hue < 1) | |
| 4746 v = m2; | |
| 4747 else if (3 * hue < 2) | |
| 4748 v = m1 + (m2 - m1) * (2/3 - hue) * 6; | |
| 4749 else | |
| 4750 v = m1; | |
| 4751 | |
| 4752 return 255 * v; | |
| 4753 } | |
| 4754 | |
| 4755 function interpolateHue(hueStart, hueEnd, valueStart, valueEnd) | |
| 4756 { | |
| 4757 // since the gradient will be RGB based, we need to add stops to hit all the | |
| 4758 // colors in the hue spectrum | |
| 4759 | |
| 4760 hueStopPositions = new Array(); | |
| 4761 hueStopHsl = new Array(); | |
| 4762 hueStopText = new Array(); | |
| 4763 | |
| 4764 hueStopPositions.push(0); | |
| 4765 hueStopHsl.push(hslText(hueStart)); | |
| 4766 hueStopText.push(round(valueStart)); | |
| 4767 | |
| 4768 for | |
| 4769 ( | |
| 4770 var i = (hueStart > hueEnd ? 5 / 6 : 1 / 6); | |
| 4771 (hueStart > hueEnd ? i > 0 : i < 1); | |
| 4772 i += (hueStart > hueEnd ? -1 : 1) / 6 | |
| 4773 ) | |
| 4774 { | |
| 4775 if | |
| 4776 ( | |
| 4777 hueStart > hueEnd ? | |
| 4778 i > hueEnd && i < hueStart : | |
| 4779 i > hueStart && i < hueEnd | |
| 4780 ) | |
| 4781 { | |
| 4782 hueStopPositions.push(lerp(i, hueStart, hueEnd, 0, 1)); | |
| 4783 hueStopHsl.push(hslText(i)); | |
| 4784 hueStopText.push(round(lerp | |
| 4785 ( | |
| 4786 i, | |
| 4787 hueStart, | |
| 4788 hueEnd, | |
| 4789 valueStart, | |
| 4790 valueEnd | |
| 4791 ))); | |
| 4792 } | |
| 4793 } | |
| 4794 | |
| 4795 hueStopPositions.push(1); | |
| 4796 hueStopHsl.push(hslText(hueEnd)); | |
| 4797 hueStopText.push(round(valueEnd)); | |
| 4798 } | |
| 4799 | |
| 4800 function keyLineAngle(angle, keyAngle, bendRadius, keyX, keyY, pointsX, pointsY) | |
| 4801 { | |
| 4802 if ( angle < Math.PI / 2 && keyY < bendRadius * Math.sin(angle) | |
| 4803 || angle > Math.PI / 2 && keyY < bendRadius) | |
| 4804 { | |
| 4805 return Math.asin(keyY / bendRadius); | |
| 4806 } | |
| 4807 else | |
| 4808 { | |
| 4809 // find the angle of the normal to a tangent line that goes to | |
| 4810 // the label | |
| 4811 | |
| 4812 var textDist = Math.sqrt | |
| 4813 ( | |
| 4814 Math.pow(keyX, 2) + | |
| 4815 Math.pow(keyY, 2) | |
| 4816 ); | |
| 4817 | |
| 4818 var tanAngle = Math.acos(bendRadius / textDist) + keyAngle; | |
| 4819 | |
| 4820 if ( angle < tanAngle || angle < Math.PI / 2 )//|| labelLeft < centerX ) | |
| 4821 { | |
| 4822 // angle doesn't reach far enough for tangent; collapse and | |
| 4823 // connect directly to label | |
| 4824 | |
| 4825 if ( keyY / Math.tan(angle) > 0 ) | |
| 4826 { | |
| 4827 pointsX.push(keyY / Math.tan(angle)); | |
| 4828 pointsY.push(keyY); | |
| 4829 } | |
| 4830 else | |
| 4831 { | |
| 4832 pointsX.push(bendRadius * Math.cos(angle)); | |
| 4833 pointsY.push(bendRadius * Math.sin(angle)); | |
| 4834 } | |
| 4835 | |
| 4836 return angle; | |
| 4837 } | |
| 4838 else | |
| 4839 { | |
| 4840 return tanAngle; | |
| 4841 } | |
| 4842 } | |
| 4843 } | |
| 4844 | |
| 4845 function keyOffset() | |
| 4846 { | |
| 4847 return imageHeight - (keys - currentKey + 1) * (keySize + keyBuffer) + keyBuffer - margin; | |
| 4848 } | |
| 4849 | |
| 4850 function lerp(value, fromStart, fromEnd, toStart, toEnd) | |
| 4851 { | |
| 4852 return (value - fromStart) * | |
| 4853 (toEnd - toStart) / | |
| 4854 (fromEnd - fromStart) + | |
| 4855 toStart; | |
| 4856 } | |
| 4857 | |
| 4858 function createCanvas() | |
| 4859 { | |
| 4860 canvas = document.createElement('canvas'); | |
| 4861 document.body.appendChild(canvas); | |
| 4862 context = canvas.getContext('2d'); | |
| 4863 } | |
| 4864 | |
| 4865 function load() | |
| 4866 { | |
| 4867 document.body.style.overflow = "hidden"; | |
| 4868 document.body.style.margin = 0; | |
| 4869 | |
| 4870 createCanvas(); | |
| 4871 | |
| 4872 if ( context == undefined ) | |
| 4873 { | |
| 4874 document.body.innerHTML = '\ | |
| 4875 <br/>This browser does not support HTML5 (see \ | |
| 4876 <a href="https://github.com/marbl/Krona/wiki/Browser%20support">Browser support</a>).\ | |
| 4877 '; | |
| 4878 return; | |
| 4879 } | |
| 4880 | |
| 4881 if ( typeof context.fillText != 'function' ) | |
| 4882 { | |
| 4883 document.body.innerHTML = '\ | |
| 4884 <br/>This browser does not support HTML5 canvas text (see \ | |
| 4885 <a href="https://github.com/marbl/Krona/wiki/Browser%20support">Browser support</a>).\ | |
| 4886 '; | |
| 4887 return; | |
| 4888 } | |
| 4889 | |
| 4890 resize(); | |
| 4891 | |
| 4892 var kronaElement = document.getElementsByTagName('krona')[0]; | |
| 4893 | |
| 4894 var magnitudeName; | |
| 4895 var hueName; | |
| 4896 var hueDefault; | |
| 4897 var hueStart; | |
| 4898 var hueEnd; | |
| 4899 var valueStart; | |
| 4900 var valueEnd; | |
| 4901 | |
| 4902 if ( kronaElement.getAttribute('collapse') != undefined ) | |
| 4903 { | |
| 4904 collapse = kronaElement.getAttribute('collapse') == 'true'; | |
| 4905 } | |
| 4906 | |
| 4907 if ( kronaElement.getAttribute('key') != undefined ) | |
| 4908 { | |
| 4909 showKeys = kronaElement.getAttribute('key') == 'true'; | |
| 4910 } | |
| 4911 | |
| 4912 for | |
| 4913 ( | |
| 4914 var element = getFirstChild(kronaElement); | |
| 4915 element; | |
| 4916 element = getNextSibling(element) | |
| 4917 ) | |
| 4918 { | |
| 4919 switch ( element.tagName.toLowerCase() ) | |
| 4920 { | |
| 4921 case 'attributes': | |
| 4922 magnitudeName = element.getAttribute('magnitude'); | |
| 4923 // | |
| 4924 for | |
| 4925 ( | |
| 4926 var attributeElement = getFirstChild(element); | |
| 4927 attributeElement; | |
| 4928 attributeElement = getNextSibling(attributeElement) | |
| 4929 ) | |
| 4930 { | |
| 4931 var tag = attributeElement.tagName.toLowerCase(); | |
| 4932 | |
| 4933 if ( tag == 'attribute' ) | |
| 4934 { | |
| 4935 var attribute = new Attribute(); | |
| 4936 attribute.name = attributeElement.firstChild.nodeValue.toLowerCase(); | |
| 4937 attribute.displayName = attributeElement.getAttribute('display'); | |
| 4938 | |
| 4939 if ( attributeElement.getAttribute('hrefBase') ) | |
| 4940 { | |
| 4941 attribute.hrefBase = attributeElement.getAttribute('hrefBase'); | |
| 4942 } | |
| 4943 | |
| 4944 if ( attributeElement.getAttribute('target') ) | |
| 4945 { | |
| 4946 attribute.target = attributeElement.getAttribute('target'); | |
| 4947 } | |
| 4948 | |
| 4949 if ( attribute.name == magnitudeName ) | |
| 4950 { | |
| 4951 magnitudeIndex = attributes.length; | |
| 4952 } | |
| 4953 | |
| 4954 if ( attributeElement.getAttribute('listAll') ) | |
| 4955 { | |
| 4956 attribute.listAll = attributeElement.getAttribute('listAll').toLowerCase(); | |
| 4957 } | |
| 4958 else if ( attributeElement.getAttribute('listNode') ) | |
| 4959 { | |
| 4960 attribute.listNode = attributeElement.getAttribute('listNode').toLowerCase(); | |
| 4961 } | |
| 4962 else if ( attributeElement.getAttribute('dataAll') ) | |
| 4963 { | |
| 4964 attribute.dataAll = attributeElement.getAttribute('dataAll').toLowerCase(); | |
| 4965 } | |
| 4966 else if ( attributeElement.getAttribute('dataNode') ) | |
| 4967 { | |
| 4968 attribute.dataNode = attributeElement.getAttribute('dataNode').toLowerCase(); | |
| 4969 } | |
| 4970 | |
| 4971 if ( attributeElement.getAttribute('postUrl') ) | |
| 4972 { | |
| 4973 attribute.postUrl = attributeElement.getAttribute('postUrl'); | |
| 4974 } | |
| 4975 | |
| 4976 if ( attributeElement.getAttribute('postVar') ) | |
| 4977 { | |
| 4978 attribute.postVar = attributeElement.getAttribute('postVar'); | |
| 4979 } | |
| 4980 | |
| 4981 if ( attributeElement.getAttribute('mono') ) | |
| 4982 { | |
| 4983 attribute.mono = true; | |
| 4984 } | |
| 4985 | |
| 4986 attributes.push(attribute); | |
| 4987 } | |
| 4988 else if ( tag == 'list' ) | |
| 4989 { | |
| 4990 var attribute = new Attribute(); | |
| 4991 | |
| 4992 attribute.name = attributeElement.firstChild.nodeValue; | |
| 4993 attribute.list = true; | |
| 4994 attributes.push(attribute); | |
| 4995 } | |
| 4996 else if ( tag == 'data' ) | |
| 4997 { | |
| 4998 var attribute = new Attribute(); | |
| 4999 | |
| 5000 attribute.name = attributeElement.firstChild.nodeValue; | |
| 5001 attribute.data = true; | |
| 5002 attributes.push(attribute); | |
| 5003 | |
| 5004 var enableScript = document.createElement('script'); | |
| 5005 var date = new Date(); | |
| 5006 enableScript.src = | |
| 5007 attributeElement.getAttribute('enable') + '?' + | |
| 5008 date.getTime(); | |
| 5009 document.body.appendChild(enableScript); | |
| 5010 } | |
| 5011 } | |
| 5012 break; | |
| 5013 | |
| 5014 case 'color': | |
| 5015 hueName = element.getAttribute('attribute'); | |
| 5016 hueStart = Number(element.getAttribute('hueStart')) / 360; | |
| 5017 hueEnd = Number(element.getAttribute('hueEnd')) / 360; | |
| 5018 valueStart = Number(element.getAttribute('valueStart')); | |
| 5019 valueEnd = Number(element.getAttribute('valueEnd')); | |
| 5020 // | |
| 5021 interpolateHue(hueStart, hueEnd, valueStart, valueEnd); | |
| 5022 // | |
| 5023 if ( element.getAttribute('default') == 'true' ) | |
| 5024 { | |
| 5025 hueDefault = true; | |
| 5026 } | |
| 5027 break; | |
| 5028 | |
| 5029 case 'datasets': | |
| 5030 datasetNames = new Array(); | |
| 5031 // | |
| 5032 for ( j = getFirstChild(element); j; j = getNextSibling(j) ) | |
| 5033 { | |
| 5034 datasetNames.push(j.firstChild.nodeValue); | |
| 5035 } | |
| 5036 datasets = datasetNames.length; | |
| 5037 break; | |
| 5038 | |
| 5039 case 'node': | |
| 5040 head = loadTreeDOM | |
| 5041 ( | |
| 5042 element, | |
| 5043 magnitudeName, | |
| 5044 hueName, | |
| 5045 hueStart, | |
| 5046 hueEnd, | |
| 5047 valueStart, | |
| 5048 valueEnd | |
| 5049 ); | |
| 5050 break; | |
| 5051 } | |
| 5052 } | |
| 5053 | |
| 5054 // get GET options | |
| 5055 // | |
| 5056 var urlHalves = String(document.location).split('?'); | |
| 5057 var datasetDefault = 0; | |
| 5058 var maxDepthDefault; | |
| 5059 var nodeDefault = 0; | |
| 5060 // | |
| 5061 if ( urlHalves[1] ) | |
| 5062 { | |
| 5063 var vars = urlHalves[1].split('&'); | |
| 5064 | |
| 5065 for ( i = 0; i < vars.length; i++ ) | |
| 5066 { | |
| 5067 var pair = vars[i].split('='); | |
| 5068 | |
| 5069 switch ( pair[0] ) | |
| 5070 { | |
| 5071 case 'collapse': | |
| 5072 collapse = pair[1] == 'true'; | |
| 5073 break; | |
| 5074 | |
| 5075 case 'color': | |
| 5076 hueDefault = pair[1] == 'true'; | |
| 5077 break; | |
| 5078 | |
| 5079 case 'dataset': | |
| 5080 datasetDefault = Number(pair[1]); | |
| 5081 break; | |
| 5082 | |
| 5083 case 'depth': | |
| 5084 maxDepthDefault = Number(pair[1]) + 1; | |
| 5085 break; | |
| 5086 | |
| 5087 case 'key': | |
| 5088 showKeys = pair[1] == 'true'; | |
| 5089 break; | |
| 5090 | |
| 5091 case 'font': | |
| 5092 fontSize = Number(pair[1]); | |
| 5093 break; | |
| 5094 | |
| 5095 case 'node': | |
| 5096 nodeDefault = Number(pair[1]); | |
| 5097 break; | |
| 5098 | |
| 5099 default: | |
| 5100 getVariables.push(pair[0] + '=' + pair[1]); | |
| 5101 break; | |
| 5102 } | |
| 5103 } | |
| 5104 } | |
| 5105 | |
| 5106 addOptionElements(hueName, hueDefault); | |
| 5107 setCallBacks(); | |
| 5108 | |
| 5109 head.sort(); | |
| 5110 maxAbsoluteDepth = 0; | |
| 5111 selectDataset(datasetDefault); | |
| 5112 | |
| 5113 if ( maxDepthDefault && maxDepthDefault < head.maxDepth ) | |
| 5114 { | |
| 5115 maxAbsoluteDepth = maxDepthDefault; | |
| 5116 } | |
| 5117 else | |
| 5118 { | |
| 5119 maxAbsoluteDepth = head.maxDepth; | |
| 5120 } | |
| 5121 | |
| 5122 selectNode(nodes[nodeDefault]); | |
| 5123 | |
| 5124 setInterval(update, 20); | |
| 5125 | |
| 5126 window.onresize = handleResize; | |
| 5127 updateMaxAbsoluteDepth(); | |
| 5128 updateViewNeeded = true; | |
| 5129 } | |
| 5130 | |
| 5131 function loadTreeDOM | |
| 5132 ( | |
| 5133 domNode, | |
| 5134 magnitudeName, | |
| 5135 hueName, | |
| 5136 hueStart, | |
| 5137 hueEnd, | |
| 5138 valueStart, | |
| 5139 valueEnd | |
| 5140 ) | |
| 5141 { | |
| 5142 var newNode = new Node(); | |
| 5143 | |
| 5144 newNode.name = domNode.getAttribute('name'); | |
| 5145 | |
| 5146 if ( domNode.getAttribute('href') ) | |
| 5147 { | |
| 5148 newNode.href = domNode.getAttribute('href'); | |
| 5149 } | |
| 5150 | |
| 5151 if ( hueName ) | |
| 5152 { | |
| 5153 newNode.hues = new Array(); | |
| 5154 } | |
| 5155 | |
| 5156 for ( var i = getFirstChild(domNode); i; i = getNextSibling(i) ) | |
| 5157 { | |
| 5158 switch ( i.tagName.toLowerCase() ) | |
| 5159 { | |
| 5160 case 'node': | |
| 5161 var newChild = loadTreeDOM | |
| 5162 ( | |
| 5163 i, | |
| 5164 magnitudeName, | |
| 5165 hueName, | |
| 5166 hueStart, | |
| 5167 hueEnd, | |
| 5168 valueStart, | |
| 5169 valueEnd | |
| 5170 ); | |
| 5171 newChild.parent = newNode; | |
| 5172 newNode.children.push(newChild); | |
| 5173 break; | |
| 5174 | |
| 5175 default: | |
| 5176 var attributeName = i.tagName.toLowerCase(); | |
| 5177 var index = attributeIndex(attributeName); | |
| 5178 // | |
| 5179 newNode.attributes[index] = new Array(); | |
| 5180 // | |
| 5181 for ( var j = getFirstChild(i); j; j = getNextSibling(j) ) | |
| 5182 { | |
| 5183 if ( attributes[index] == undefined ) | |
| 5184 { | |
| 5185 var x = 5; | |
| 5186 } | |
| 5187 if ( attributes[index].list ) | |
| 5188 { | |
| 5189 newNode.attributes[index].push(new Array()); | |
| 5190 | |
| 5191 for ( var k = getFirstChild(j); k; k = getNextSibling(k) ) | |
| 5192 { | |
| 5193 newNode.attributes[index][newNode.attributes[index].length - 1].push(k.firstChild.nodeValue); | |
| 5194 } | |
| 5195 } | |
| 5196 else | |
| 5197 { | |
| 5198 var value = j.firstChild ? j.firstChild.nodeValue : ''; | |
| 5199 | |
| 5200 if ( j.getAttribute('href') ) | |
| 5201 { | |
| 5202 var target; | |
| 5203 | |
| 5204 if ( attributes[index].target ) | |
| 5205 { | |
| 5206 target = ' target="' + attributes[index].target + '"'; | |
| 5207 } | |
| 5208 | |
| 5209 value = '<a href="' + attributes[index].hrefBase + j.getAttribute('href') + '"' + target + '>' + value + '</a>'; | |
| 5210 } | |
| 5211 | |
| 5212 newNode.attributes[index].push(value); | |
| 5213 } | |
| 5214 } | |
| 5215 // | |
| 5216 if ( attributeName == magnitudeName || attributeName == hueName ) | |
| 5217 { | |
| 5218 for ( j = 0; j < datasets; j++ ) | |
| 5219 { | |
| 5220 var value = newNode.attributes[index][j] == undefined ? 0 : Number(newNode.attributes[index][j]); | |
| 5221 | |
| 5222 newNode.attributes[index][j] = value; | |
| 5223 | |
| 5224 if ( attributeName == hueName ) | |
| 5225 { | |
| 5226 var hue = lerp | |
| 5227 ( | |
| 5228 value, | |
| 5229 valueStart, | |
| 5230 valueEnd, | |
| 5231 hueStart, | |
| 5232 hueEnd | |
| 5233 ); | |
| 5234 | |
| 5235 if ( hue < hueStart == hueStart < hueEnd ) | |
| 5236 { | |
| 5237 hue = hueStart; | |
| 5238 } | |
| 5239 else if ( hue > hueEnd == hueStart < hueEnd ) | |
| 5240 { | |
| 5241 hue = hueEnd; | |
| 5242 } | |
| 5243 | |
| 5244 newNode.hues[j] = hue; | |
| 5245 } | |
| 5246 } | |
| 5247 | |
| 5248 if ( attributeName == hueName ) | |
| 5249 { | |
| 5250 newNode.hue = new Tween(newNode.hues[0], newNode.hues[0]); | |
| 5251 } | |
| 5252 } | |
| 5253 break; | |
| 5254 } | |
| 5255 } | |
| 5256 | |
| 5257 return newNode; | |
| 5258 } | |
| 5259 | |
| 5260 function maxAbsoluteDepthDecrease() | |
| 5261 { | |
| 5262 if ( maxAbsoluteDepth > 2 ) | |
| 5263 { | |
| 5264 maxAbsoluteDepth--; | |
| 5265 head.setMaxDepths(); | |
| 5266 handleResize(); | |
| 5267 } | |
| 5268 } | |
| 5269 | |
| 5270 function maxAbsoluteDepthIncrease() | |
| 5271 { | |
| 5272 if ( maxAbsoluteDepth < head.maxDepth ) | |
| 5273 { | |
| 5274 maxAbsoluteDepth++; | |
| 5275 head.setMaxDepths(); | |
| 5276 handleResize(); | |
| 5277 } | |
| 5278 } | |
| 5279 | |
| 5280 function measureText(text, bold) | |
| 5281 { | |
| 5282 context.font = bold ? fontBold : fontNormal; | |
| 5283 var dim = context.measureText(text); | |
| 5284 return dim.width; | |
| 5285 } | |
| 5286 | |
| 5287 function min(a, b) | |
| 5288 { | |
| 5289 return a < b ? a : b; | |
| 5290 } | |
| 5291 | |
| 5292 function minWidth() | |
| 5293 { | |
| 5294 // Min wedge width (at center) for displaying a node (or for displaying a | |
| 5295 // label if it's at the highest level being viewed, multiplied by 2 to make | |
| 5296 // further calculations simpler | |
| 5297 | |
| 5298 return (fontSize * 2.3); | |
| 5299 } | |
| 5300 | |
| 5301 function mouseMove(e) | |
| 5302 { | |
| 5303 mouseX = e.pageX; | |
| 5304 mouseY = e.pageY - headerHeight; | |
| 5305 mouseXRel = (mouseX - centerX) * backingScale() | |
| 5306 mouseYRel = (mouseY - centerY) * backingScale() | |
| 5307 | |
| 5308 if ( head && ! quickLook ) | |
| 5309 { | |
| 5310 checkHighlight(); | |
| 5311 } | |
| 5312 } | |
| 5313 | |
| 5314 function mouseClick(e) | |
| 5315 { | |
| 5316 if ( highlightedNode == focusNode && focusNode != selectedNode || selectedNode.hasParent(highlightedNode) ) | |
| 5317 { | |
| 5318 if ( highlightedNode.hasChildren() ) | |
| 5319 { | |
| 5320 expand(highlightedNode); | |
| 5321 } | |
| 5322 } | |
| 5323 else if ( progress == 1 )//( highlightedNode != selectedNode ) | |
| 5324 { | |
| 5325 setFocus(highlightedNode); | |
| 5326 // document.body.style.cursor='ew-resize'; | |
| 5327 draw(); | |
| 5328 checkHighlight(); | |
| 5329 var date = new Date(); | |
| 5330 mouseDownTime = date.getTime(); | |
| 5331 mouseDown = true; | |
| 5332 } | |
| 5333 } | |
| 5334 | |
| 5335 function mouseUp(e) | |
| 5336 { | |
| 5337 if ( quickLook ) | |
| 5338 { | |
| 5339 navigateBack(); | |
| 5340 quickLook = false; | |
| 5341 } | |
| 5342 | |
| 5343 mouseDown = false; | |
| 5344 } | |
| 5345 | |
| 5346 function navigateBack() | |
| 5347 { | |
| 5348 if ( nodeHistoryPosition > 0 ) | |
| 5349 { | |
| 5350 nodeHistory[nodeHistoryPosition] = selectedNode; | |
| 5351 nodeHistoryPosition--; | |
| 5352 | |
| 5353 if ( nodeHistory[nodeHistoryPosition].collapse ) | |
| 5354 { | |
| 5355 collapseCheckBox.checked = collapse = false; | |
| 5356 } | |
| 5357 | |
| 5358 setSelectedNode(nodeHistory[nodeHistoryPosition]); | |
| 5359 updateDatasetButtons(); | |
| 5360 updateView(); | |
| 5361 } | |
| 5362 } | |
| 5363 | |
| 5364 function navigateUp() | |
| 5365 { | |
| 5366 if ( selectedNode.getParent() ) | |
| 5367 { | |
| 5368 selectNode(selectedNode.getParent()); | |
| 5369 updateView(); | |
| 5370 } | |
| 5371 } | |
| 5372 | |
| 5373 function navigateForward() | |
| 5374 { | |
| 5375 if ( nodeHistoryPosition < nodeHistory.length - 1 ) | |
| 5376 { | |
| 5377 nodeHistoryPosition++; | |
| 5378 var newNode = nodeHistory[nodeHistoryPosition]; | |
| 5379 | |
| 5380 if ( newNode.collapse ) | |
| 5381 { | |
| 5382 collapseCheckBox.checked = collapse = false; | |
| 5383 } | |
| 5384 | |
| 5385 if ( nodeHistoryPosition == nodeHistory.length - 1 ) | |
| 5386 { | |
| 5387 // this will ensure the forward button is disabled | |
| 5388 | |
| 5389 nodeHistory.length = nodeHistoryPosition; | |
| 5390 } | |
| 5391 | |
| 5392 setSelectedNode(newNode); | |
| 5393 updateDatasetButtons(); | |
| 5394 updateView(); | |
| 5395 } | |
| 5396 } | |
| 5397 | |
| 5398 function nextDataset() | |
| 5399 { | |
| 5400 var newDataset = currentDataset; | |
| 5401 | |
| 5402 do | |
| 5403 { | |
| 5404 if ( newDataset == datasets - 1 ) | |
| 5405 { | |
| 5406 newDataset = 0; | |
| 5407 } | |
| 5408 else | |
| 5409 { | |
| 5410 newDataset++; | |
| 5411 } | |
| 5412 } | |
| 5413 while ( datasetDropDown.options[newDataset].disabled ) | |
| 5414 | |
| 5415 selectDataset(newDataset); | |
| 5416 } | |
| 5417 | |
| 5418 function onDatasetChange() | |
| 5419 { | |
| 5420 selectDataset(datasetDropDown.selectedIndex); | |
| 5421 } | |
| 5422 | |
| 5423 function onKeyDown(event) | |
| 5424 { | |
| 5425 if | |
| 5426 ( | |
| 5427 event.keyCode == 37 && | |
| 5428 document.activeElement.id != 'search' && | |
| 5429 document.activeElement.id != 'linkText' | |
| 5430 ) | |
| 5431 { | |
| 5432 navigateBack(); | |
| 5433 event.preventDefault(); | |
| 5434 } | |
| 5435 else if | |
| 5436 ( | |
| 5437 event.keyCode == 39 && | |
| 5438 document.activeElement.id != 'search' && | |
| 5439 document.activeElement.id != 'linkText' | |
| 5440 ) | |
| 5441 { | |
| 5442 navigateForward(); | |
| 5443 event.preventDefault(); | |
| 5444 } | |
| 5445 else if ( event.keyCode == 38 && datasets > 1 ) | |
| 5446 { | |
| 5447 prevDataset(); | |
| 5448 | |
| 5449 //if ( document.activeElement.id == 'datasets' ) | |
| 5450 { | |
| 5451 event.preventDefault(); | |
| 5452 } | |
| 5453 } | |
| 5454 else if ( event.keyCode == 40 && datasets > 1 ) | |
| 5455 { | |
| 5456 nextDataset(); | |
| 5457 | |
| 5458 //if ( document.activeElement.id == 'datasets' ) | |
| 5459 { | |
| 5460 event.preventDefault(); | |
| 5461 } | |
| 5462 } | |
| 5463 else if ( event.keyCode == 9 && datasets > 1 ) | |
| 5464 { | |
| 5465 selectLastDataset(); | |
| 5466 event.preventDefault(); | |
| 5467 } | |
| 5468 else if ( event.keyCode == 83 ) | |
| 5469 { | |
| 5470 progress += .2; | |
| 5471 } | |
| 5472 else if ( event.keyCode == 66 ) | |
| 5473 { | |
| 5474 progress -= .2; | |
| 5475 } | |
| 5476 else if ( event.keyCode == 70 ) | |
| 5477 { | |
| 5478 progress = 1; | |
| 5479 } | |
| 5480 } | |
| 5481 | |
| 5482 function onKeyPress(event) | |
| 5483 { | |
| 5484 if ( event.keyCode == 38 && datasets > 1 ) | |
| 5485 { | |
| 5486 // prevDataset(); | |
| 5487 | |
| 5488 //if ( document.activeElement.id == 'datasets' ) | |
| 5489 { | |
| 5490 event.preventDefault(); | |
| 5491 } | |
| 5492 } | |
| 5493 else if ( event.keyCode == 40 && datasets > 1 ) | |
| 5494 { | |
| 5495 // nextDataset(); | |
| 5496 | |
| 5497 //if ( document.activeElement.id == 'datasets' ) | |
| 5498 { | |
| 5499 event.preventDefault(); | |
| 5500 } | |
| 5501 } | |
| 5502 } | |
| 5503 | |
| 5504 function onKeyUp(event) | |
| 5505 { | |
| 5506 if ( event.keyCode == 27 && document.activeElement.id == 'search' ) | |
| 5507 { | |
| 5508 search.value = ''; | |
| 5509 onSearchChange(); | |
| 5510 } | |
| 5511 else if ( event.keyCode == 38 && datasets > 1 ) | |
| 5512 { | |
| 5513 // prevDataset(); | |
| 5514 | |
| 5515 //if ( document.activeElement.id == 'datasets' ) | |
| 5516 { | |
| 5517 event.preventDefault(); | |
| 5518 } | |
| 5519 } | |
| 5520 else if ( event.keyCode == 40 && datasets > 1 ) | |
| 5521 { | |
| 5522 // nextDataset(); | |
| 5523 | |
| 5524 //if ( document.activeElement.id == 'datasets' ) | |
| 5525 { | |
| 5526 event.preventDefault(); | |
| 5527 } | |
| 5528 } | |
| 5529 } | |
| 5530 | |
| 5531 function onSearchChange() | |
| 5532 { | |
| 5533 nSearchResults = 0; | |
| 5534 head.search(); | |
| 5535 | |
| 5536 if ( search.value == '' ) | |
| 5537 { | |
| 5538 searchResults.innerHTML = ''; | |
| 5539 } | |
| 5540 else | |
| 5541 { | |
| 5542 searchResults.innerHTML = nSearchResults + ' results'; | |
| 5543 } | |
| 5544 | |
| 5545 setFocus(selectedNode); | |
| 5546 draw(); | |
| 5547 } | |
| 5548 | |
| 5549 function post(url, variable, value, postWindow) | |
| 5550 { | |
| 5551 var form = document.createElement('form'); | |
| 5552 var input = document.createElement('input'); | |
| 5553 var inputDataset = document.createElement('input'); | |
| 5554 | |
| 5555 form.appendChild(input); | |
| 5556 form.appendChild(inputDataset); | |
| 5557 | |
| 5558 form.method = "POST"; | |
| 5559 form.action = url; | |
| 5560 | |
| 5561 if ( postWindow == undefined ) | |
| 5562 { | |
| 5563 form.target = '_blank'; | |
| 5564 postWindow = window; | |
| 5565 } | |
| 5566 | |
| 5567 input.type = 'hidden'; | |
| 5568 input.name = variable; | |
| 5569 input.value = value; | |
| 5570 | |
| 5571 inputDataset.type = 'hidden'; | |
| 5572 inputDataset.name = 'dataset'; | |
| 5573 inputDataset.value = currentDataset; | |
| 5574 | |
| 5575 postWindow.document.body.appendChild(form); | |
| 5576 form.submit(); | |
| 5577 } | |
| 5578 | |
| 5579 function prevDataset() | |
| 5580 { | |
| 5581 var newDataset = currentDataset; | |
| 5582 | |
| 5583 do | |
| 5584 { | |
| 5585 if ( newDataset == 0 ) | |
| 5586 { | |
| 5587 newDataset = datasets - 1; | |
| 5588 } | |
| 5589 else | |
| 5590 { | |
| 5591 newDataset--; | |
| 5592 } | |
| 5593 } | |
| 5594 while ( datasetDropDown.options[newDataset].disabled ); | |
| 5595 | |
| 5596 selectDataset(newDataset); | |
| 5597 } | |
| 5598 | |
| 5599 function radiusDecrease() | |
| 5600 { | |
| 5601 if ( bufferFactor < .309 ) | |
| 5602 { | |
| 5603 bufferFactor += .03; | |
| 5604 updateViewNeeded = true; | |
| 5605 } | |
| 5606 } | |
| 5607 | |
| 5608 function radiusIncrease() | |
| 5609 { | |
| 5610 if ( bufferFactor > .041 ) | |
| 5611 { | |
| 5612 bufferFactor -= .03; | |
| 5613 updateViewNeeded = true; | |
| 5614 } | |
| 5615 } | |
| 5616 | |
| 5617 function resetKeyOffset() | |
| 5618 { | |
| 5619 currentKey = 1; | |
| 5620 keyMinTextLeft = centerX + gRadius + buffer - buffer / (keys + 1) / 2 + fontSize / 2; | |
| 5621 keyMinAngle = 0; | |
| 5622 } | |
| 5623 | |
| 5624 function rgbText(r, g, b) | |
| 5625 { | |
| 5626 var rgbArray = | |
| 5627 [ | |
| 5628 "rgb(", | |
| 5629 Math.floor(r), | |
| 5630 ",", | |
| 5631 Math.floor(g), | |
| 5632 ",", | |
| 5633 Math.floor(b), | |
| 5634 ")" | |
| 5635 ]; | |
| 5636 | |
| 5637 return rgbArray.join(''); | |
| 5638 } | |
| 5639 | |
| 5640 function round(number) | |
| 5641 { | |
| 5642 if ( number >= 1 || number <= -1 ) | |
| 5643 { | |
| 5644 return number.toFixed(0); | |
| 5645 } | |
| 5646 else | |
| 5647 { | |
| 5648 return number.toPrecision(1); | |
| 5649 } | |
| 5650 } | |
| 5651 | |
| 5652 function roundedRectangle(x, y, width, height, radius) | |
| 5653 { | |
| 5654 if ( radius * 2 > width ) | |
| 5655 { | |
| 5656 radius = width / 2; | |
| 5657 } | |
| 5658 | |
| 5659 if ( radius * 2 > height ) | |
| 5660 { | |
| 5661 radius = height / 2; | |
| 5662 } | |
| 5663 | |
| 5664 context.beginPath(); | |
| 5665 context.arc(x + radius, y + radius, radius, Math.PI, Math.PI * 3 / 2, false); | |
| 5666 context.lineTo(x + width - radius, y); | |
| 5667 context.arc(x + width - radius, y + radius, radius, Math.PI * 3 / 2, Math.PI * 2, false); | |
| 5668 context.lineTo(x + width, y + height - radius); | |
| 5669 context.arc(x + width - radius, y + height - radius, radius, 0, Math.PI / 2, false); | |
| 5670 context.lineTo(x + radius, y + height); | |
| 5671 context.arc(x + radius, y + height - radius, radius, Math.PI / 2, Math.PI, false); | |
| 5672 context.lineTo(x, y + radius); | |
| 5673 } | |
| 5674 | |
| 5675 function passClick(e) | |
| 5676 { | |
| 5677 mouseClick(e); | |
| 5678 } | |
| 5679 | |
| 5680 function searchResultString(results) | |
| 5681 { | |
| 5682 var searchResults = this.searchResults; | |
| 5683 | |
| 5684 if ( this.isSearchResult ) | |
| 5685 { | |
| 5686 // don't count ourselves | |
| 5687 searchResults--; | |
| 5688 } | |
| 5689 | |
| 5690 return ' - ' + results + (results > 1 ? ' results' : ' result'); | |
| 5691 } | |
| 5692 | |
| 5693 function setCallBacks() | |
| 5694 { | |
| 5695 canvas.onselectstart = function(){return false;} // prevent unwanted highlighting | |
| 5696 options.onselectstart = function(){return false;} // prevent unwanted highlighting | |
| 5697 document.onmousemove = mouseMove; | |
| 5698 window.onblur = focusLost; | |
| 5699 window.onmouseout = focusLost; | |
| 5700 document.onkeyup = onKeyUp; | |
| 5701 document.onkeydown = onKeyDown; | |
| 5702 canvas.onmousedown = mouseClick; | |
| 5703 document.onmouseup = mouseUp; | |
| 5704 keyControl.onclick = toggleKeys; | |
| 5705 collapseCheckBox = document.getElementById('collapse'); | |
| 5706 collapseCheckBox.checked = collapse; | |
| 5707 collapseCheckBox.onclick = handleResize; | |
| 5708 collapseCheckBox.onmousedown = suppressEvent; | |
| 5709 maxAbsoluteDepthText = document.getElementById('maxAbsoluteDepth'); | |
| 5710 maxAbsoluteDepthButtonDecrease = document.getElementById('maxAbsoluteDepthDecrease'); | |
| 5711 maxAbsoluteDepthButtonIncrease = document.getElementById('maxAbsoluteDepthIncrease'); | |
| 5712 maxAbsoluteDepthButtonDecrease.onclick = maxAbsoluteDepthDecrease; | |
| 5713 maxAbsoluteDepthButtonIncrease.onclick = maxAbsoluteDepthIncrease; | |
| 5714 maxAbsoluteDepthButtonDecrease.onmousedown = suppressEvent; | |
| 5715 maxAbsoluteDepthButtonIncrease.onmousedown = suppressEvent; | |
| 5716 fontSizeText = document.getElementById('fontSize'); | |
| 5717 fontSizeButtonDecrease = document.getElementById('fontSizeDecrease'); | |
| 5718 fontSizeButtonIncrease = document.getElementById('fontSizeIncrease'); | |
| 5719 fontSizeButtonDecrease.onclick = fontSizeDecrease; | |
| 5720 fontSizeButtonIncrease.onclick = fontSizeIncrease; | |
| 5721 fontSizeButtonDecrease.onmousedown = suppressEvent; | |
| 5722 fontSizeButtonIncrease.onmousedown = suppressEvent; | |
| 5723 radiusButtonDecrease = document.getElementById('radiusDecrease'); | |
| 5724 radiusButtonIncrease = document.getElementById('radiusIncrease'); | |
| 5725 radiusButtonDecrease.onclick = radiusDecrease; | |
| 5726 radiusButtonIncrease.onclick = radiusIncrease; | |
| 5727 radiusButtonDecrease.onmousedown = suppressEvent; | |
| 5728 radiusButtonIncrease.onmousedown = suppressEvent; | |
| 5729 maxAbsoluteDepth = 0; | |
| 5730 backButton = document.getElementById('back'); | |
| 5731 backButton.onclick = navigateBack; | |
| 5732 backButton.onmousedown = suppressEvent; | |
| 5733 forwardButton = document.getElementById('forward'); | |
| 5734 forwardButton.onclick = navigateForward; | |
| 5735 forwardButton.onmousedown = suppressEvent; | |
| 5736 snapshotButton = document.getElementById('snapshot'); | |
| 5737 snapshotButton.onclick = snapshot; | |
| 5738 snapshotButton.onmousedown = suppressEvent; | |
| 5739 detailsName = document.getElementById('detailsName'); | |
| 5740 detailsExpand = document.getElementById('detailsExpand'); | |
| 5741 detailsInfo = document.getElementById('detailsInfo'); | |
| 5742 search = document.getElementById('search'); | |
| 5743 search.onkeyup = onSearchChange; | |
| 5744 search.onmousedown = suppressEvent; | |
| 5745 searchResults = document.getElementById('searchResults'); | |
| 5746 useHueDiv = document.getElementById('useHueDiv'); | |
| 5747 linkButton = document.getElementById('linkButton'); | |
| 5748 linkButton.onclick = showLink; | |
| 5749 linkButton.onmousedown = suppressEvent; | |
| 5750 linkText = document.getElementById('linkText'); | |
| 5751 linkText.onblur = hideLink; | |
| 5752 linkText.onmousedown = suppressEvent; | |
| 5753 hide(linkText); | |
| 5754 var helpButton = document.getElementById('help'); | |
| 5755 helpButton.onmousedown = suppressEvent; | |
| 5756 var searchClear = document.getElementById('searchClear'); | |
| 5757 searchClear.onmousedown = suppressEvent; | |
| 5758 if ( datasets > 1 ) | |
| 5759 { | |
| 5760 datasetDropDown.onmousedown = suppressEvent; | |
| 5761 var prevDatasetButton = document.getElementById('prevDataset'); | |
| 5762 prevDatasetButton.onmousedown = suppressEvent; | |
| 5763 var nextDatasetButton = document.getElementById('nextDataset'); | |
| 5764 nextDatasetButton.onmousedown = suppressEvent; | |
| 5765 var lastDatasetButton = document.getElementById('lastDataset'); | |
| 5766 lastDatasetButton.onmousedown = suppressEvent; | |
| 5767 } | |
| 5768 | |
| 5769 image = document.getElementById('hiddenImage'); | |
| 5770 image.onload = function() | |
| 5771 { | |
| 5772 hiddenPattern = context.createPattern(image, 'repeat'); | |
| 5773 } | |
| 5774 | |
| 5775 var loadingImageElement = document.getElementById('loadingImage'); | |
| 5776 | |
| 5777 if ( loadingImageElement ) | |
| 5778 { | |
| 5779 loadingImage = loadingImageElement.src; | |
| 5780 } | |
| 5781 } | |
| 5782 | |
| 5783 function selectDataset(newDataset) | |
| 5784 { | |
| 5785 lastDataset = currentDataset; | |
| 5786 currentDataset = newDataset | |
| 5787 if ( datasets > 1 ) | |
| 5788 { | |
| 5789 datasetDropDown.selectedIndex = currentDataset; | |
| 5790 updateDatasetButtons(); | |
| 5791 datasetAlpha.start = 1.5; | |
| 5792 datasetChanged = true; | |
| 5793 } | |
| 5794 head.setMagnitudes(0); | |
| 5795 head.setDepth(1, 1); | |
| 5796 head.setMaxDepths(); | |
| 5797 handleResize(); | |
| 5798 } | |
| 5799 | |
| 5800 function selectLastDataset() | |
| 5801 { | |
| 5802 selectDataset(lastDataset); | |
| 5803 handleResize(); | |
| 5804 } | |
| 5805 | |
| 5806 function selectNode(newNode) | |
| 5807 { | |
| 5808 if ( selectedNode != newNode ) | |
| 5809 { | |
| 5810 // truncate history at current location to create a new branch | |
| 5811 // | |
| 5812 nodeHistory.length = nodeHistoryPosition; | |
| 5813 | |
| 5814 if ( selectedNode != 0 ) | |
| 5815 { | |
| 5816 nodeHistory.push(selectedNode); | |
| 5817 nodeHistoryPosition++; | |
| 5818 } | |
| 5819 | |
| 5820 setSelectedNode(newNode); | |
| 5821 //updateView(); | |
| 5822 } | |
| 5823 | |
| 5824 updateDatasetButtons(); | |
| 5825 } | |
| 5826 | |
| 5827 function setFocus(node) | |
| 5828 { | |
| 5829 if ( node == focusNode ) | |
| 5830 { | |
| 5831 // return; | |
| 5832 } | |
| 5833 | |
| 5834 focusNode = node; | |
| 5835 | |
| 5836 if ( node.href ) | |
| 5837 { | |
| 5838 detailsName.innerHTML = | |
| 5839 '<a target="_blank" href="' + node.href + '">' + node.name + '</a>'; | |
| 5840 } | |
| 5841 else | |
| 5842 { | |
| 5843 detailsName.innerHTML = node.name; | |
| 5844 } | |
| 5845 | |
| 5846 var table = '<table>'; | |
| 5847 //TODO: use CSS margins instead of an additional column | |
| 5848 table += '<tr><td></td><td></td></tr>'; | |
| 5849 | |
| 5850 for ( var i = 0; i < node.attributes.length; i++ ) | |
| 5851 { | |
| 5852 if ( attributes[i].displayName && node.attributes[i] != undefined ) | |
| 5853 { | |
| 5854 var index = node.attributes[i].length == 1 && attributes[i].mono ? 0 : currentDataset; | |
| 5855 | |
| 5856 if ( typeof node.attributes[i][currentDataset] == 'number' || node.attributes[i][index] != undefined && node.attributes[i][currentDataset] != '' ) | |
| 5857 { | |
| 5858 var value = node.attributes[i][index]; | |
| 5859 | |
| 5860 if ( attributes[i].listNode != undefined ) | |
| 5861 { | |
| 5862 value = | |
| 5863 '<a href="" onclick="showList(' + | |
| 5864 attributeIndex(attributes[i].listNode) + ',' + i + | |
| 5865 ',false);return false;" title="Show list">' + | |
| 5866 value + '</a>'; | |
| 5867 } | |
| 5868 else if ( attributes[i].listAll != undefined ) | |
| 5869 { | |
| 5870 value = | |
| 5871 '<a href="" onclick="showList(' + | |
| 5872 attributeIndex(attributes[i].listAll) + ',' + i + | |
| 5873 ',true);return false;" title="Show list">' + | |
| 5874 value + '</a>'; | |
| 5875 } | |
| 5876 else if ( attributes[i].dataNode != undefined && dataEnabled ) | |
| 5877 { | |
| 5878 value = | |
| 5879 '<a href="" onclick="showData(' + | |
| 5880 attributeIndex(attributes[i].dataNode) + ',' + i + | |
| 5881 ',false);return false;" title="Show data">' + | |
| 5882 value + '</a>'; | |
| 5883 } | |
| 5884 else if ( attributes[i].dataAll != undefined && dataEnabled ) | |
| 5885 { | |
| 5886 value = | |
| 5887 '<a href="" onclick="showData(' + | |
| 5888 attributeIndex(attributes[i].dataAll) + ',' + i + | |
| 5889 ',true);return false;" title="Show data">' + | |
| 5890 value + '</a>'; | |
| 5891 } | |
| 5892 | |
| 5893 table += | |
| 5894 '<tr><td><strong>' + attributes[i].displayName + ':</strong></td><td>' + | |
| 5895 value + '</td></tr>'; | |
| 5896 } | |
| 5897 } | |
| 5898 } | |
| 5899 | |
| 5900 table += '</table>'; | |
| 5901 detailsInfo.innerHTML = table; | |
| 5902 | |
| 5903 detailsExpand.disabled = !focusNode.hasChildren() || focusNode == selectedNode; | |
| 5904 } | |
| 5905 | |
| 5906 function setSelectedNode(newNode) | |
| 5907 { | |
| 5908 if ( selectedNode && selectedNode.hasParent(newNode) ) | |
| 5909 { | |
| 5910 zoomOut = true; | |
| 5911 } | |
| 5912 else | |
| 5913 { | |
| 5914 zoomOut = false; | |
| 5915 } | |
| 5916 | |
| 5917 selectedNodeLast = selectedNode; | |
| 5918 selectedNode = newNode; | |
| 5919 | |
| 5920 //if ( focusNode != selectedNode ) | |
| 5921 { | |
| 5922 setFocus(selectedNode); | |
| 5923 } | |
| 5924 } | |
| 5925 | |
| 5926 function waitForData(dataWindow, target, title, time, postUrl, postVar) | |
| 5927 { | |
| 5928 if ( nodeData.length == target ) | |
| 5929 { | |
| 5930 if ( postUrl != undefined ) | |
| 5931 { | |
| 5932 for ( var i = 0; i < nodeData.length; i++ ) | |
| 5933 { | |
| 5934 nodeData[i] = nodeData[i].replace(/\n/g, ','); | |
| 5935 } | |
| 5936 | |
| 5937 var postString = nodeData.join(''); | |
| 5938 postString = postString.slice(0, -1); | |
| 5939 | |
| 5940 dataWindow.document.body.removeChild(dataWindow.document.getElementById('loading')); | |
| 5941 document.body.removeChild(document.getElementById('data')); | |
| 5942 | |
| 5943 post(postUrl, postVar, postString, dataWindow); | |
| 5944 } | |
| 5945 else | |
| 5946 { | |
| 5947 //dataWindow.document.body.removeChild(dataWindow.document.getElementById('loading')); | |
| 5948 //document.body.removeChild(document.getElementById('data')); | |
| 5949 | |
| 5950 dataWindow.document.open(); | |
| 5951 dataWindow.document.write('<pre>' + nodeData.join('') + '</pre>'); | |
| 5952 dataWindow.document.close(); | |
| 5953 } | |
| 5954 | |
| 5955 dataWindow.document.title = title; // replace after document.write() | |
| 5956 } | |
| 5957 else | |
| 5958 { | |
| 5959 var date = new Date(); | |
| 5960 | |
| 5961 if ( date.getTime() - time > 10000 ) | |
| 5962 { | |
| 5963 dataWindow.document.body.removeChild(dataWindow.document.getElementById('loading')); | |
| 5964 document.body.removeChild(document.getElementById('data')); | |
| 5965 dataWindow.document.body.innerHTML = | |
| 5966 'Timed out loading supplemental files for:<br/>' + document.location; | |
| 5967 } | |
| 5968 else | |
| 5969 { | |
| 5970 setTimeout(function() {waitForData(dataWindow, target, title, time, postUrl, postVar);}, 100); | |
| 5971 } | |
| 5972 } | |
| 5973 } | |
| 5974 | |
| 5975 function data(newData) | |
| 5976 { | |
| 5977 nodeData.push(newData); | |
| 5978 } | |
| 5979 | |
| 5980 function enableData() | |
| 5981 { | |
| 5982 dataEnabled = true; | |
| 5983 } | |
| 5984 | |
| 5985 function showData(indexData, indexAttribute, summary) | |
| 5986 { | |
| 5987 var dataWindow = window.open('', '_blank'); | |
| 5988 var title = 'Krona - ' + attributes[indexAttribute].displayName + ' - ' + focusNode.name; | |
| 5989 dataWindow.document.title = title; | |
| 5990 | |
| 5991 nodeData = new Array(); | |
| 5992 | |
| 5993 if ( dataWindow && dataWindow.document && dataWindow.document.body != null ) | |
| 5994 { | |
| 5995 //var loadImage = document.createElement('img'); | |
| 5996 //loadImage.src = "file://localhost/Users/ondovb/Krona/KronaTools/img/loading.gif"; | |
| 5997 //loadImage.id = "loading"; | |
| 5998 //loadImage.alt = "Loading..."; | |
| 5999 //dataWindow.document.body.appendChild(loadImage); | |
| 6000 dataWindow.document.body.innerHTML = | |
| 6001 '<img id="loading" src="' + loadingImage + '" alt="Loading..."></img>'; | |
| 6002 } | |
| 6003 | |
| 6004 var scripts = document.createElement('div'); | |
| 6005 scripts.id = 'data'; | |
| 6006 document.body.appendChild(scripts); | |
| 6007 | |
| 6008 var files = focusNode.getData(indexData, summary); | |
| 6009 | |
| 6010 var date = new Date(); | |
| 6011 var time = date.getTime(); | |
| 6012 | |
| 6013 for ( var i = 0; i < files.length; i++ ) | |
| 6014 { | |
| 6015 var script = document.createElement('script'); | |
| 6016 script.src = files[i] + '?' + time; | |
| 6017 scripts.appendChild(script); | |
| 6018 } | |
| 6019 | |
| 6020 waitForData(dataWindow, files.length, title, time, attributes[indexAttribute].postUrl, attributes[indexAttribute].postVar); | |
| 6021 | |
| 6022 return false; | |
| 6023 } | |
| 6024 | |
| 6025 function showList(indexList, indexAttribute, summary) | |
| 6026 { | |
| 6027 var list = focusNode.getList(indexList, summary); | |
| 6028 | |
| 6029 if ( attributes[indexAttribute].postUrl != undefined ) | |
| 6030 { | |
| 6031 post(attributes[indexAttribute].postUrl, attributes[indexAttribute].postVar, list.join(',')); | |
| 6032 } | |
| 6033 else | |
| 6034 { | |
| 6035 var dataWindow = window.open('', '_blank'); | |
| 6036 | |
| 6037 if ( true || navigator.appName == 'Microsoft Internet Explorer' ) // :( | |
| 6038 { | |
| 6039 dataWindow.document.open(); | |
| 6040 dataWindow.document.write('<pre>' + list.join('\n') + '</pre>'); | |
| 6041 dataWindow.document.close(); | |
| 6042 } | |
| 6043 else | |
| 6044 { | |
| 6045 var pre = document.createElement('pre'); | |
| 6046 dataWindow.document.body.appendChild(pre); | |
| 6047 pre.innerHTML = list; | |
| 6048 } | |
| 6049 | |
| 6050 dataWindow.document.title = 'Krona - ' + attributes[indexAttribute].displayName + ' - ' + focusNode.name; | |
| 6051 } | |
| 6052 } | |
| 6053 | |
| 6054 function snapshot() | |
| 6055 { | |
| 6056 svg = svgHeader(); | |
| 6057 | |
| 6058 resetKeyOffset(); | |
| 6059 | |
| 6060 snapshotMode = true; | |
| 6061 | |
| 6062 selectedNode.draw(false, true); | |
| 6063 selectedNode.draw(true, true); | |
| 6064 | |
| 6065 if ( focusNode != 0 && focusNode != selectedNode ) | |
| 6066 { | |
| 6067 context.globalAlpha = 1; | |
| 6068 focusNode.drawHighlight(true); | |
| 6069 } | |
| 6070 | |
| 6071 if ( hueDisplayName && useHue() ) | |
| 6072 { | |
| 6073 drawLegendSVG(); | |
| 6074 } | |
| 6075 | |
| 6076 snapshotMode = false; | |
| 6077 | |
| 6078 svg += svgFooter(); | |
| 6079 | |
| 6080 var snapshotWindow = window.open('', '_blank', '', 'replace=false'); | |
| 6081 snapshotWindow.document.write('<html><body><a href="data:image/svg+xml,' + encodeURIComponent(svg) + '" download="snapshot.svg">Download Snapshot</a></html></body>'); | |
| 6082 snapshotWindow.document.write(svg); | |
| 6083 } | |
| 6084 | |
| 6085 function save() | |
| 6086 { | |
| 6087 alert(document.body.innerHTML); | |
| 6088 } | |
| 6089 | |
| 6090 function spacer() | |
| 6091 { | |
| 6092 if ( snapshotMode ) | |
| 6093 { | |
| 6094 return '   '; | |
| 6095 } | |
| 6096 else | |
| 6097 { | |
| 6098 return ' '; | |
| 6099 } | |
| 6100 } | |
| 6101 | |
| 6102 function suppressEvent(e) | |
| 6103 { | |
| 6104 e.cancelBubble = true; | |
| 6105 if (e.stopPropagation) e.stopPropagation(); | |
| 6106 } | |
| 6107 | |
| 6108 function svgFooter() | |
| 6109 { | |
| 6110 return '</svg>'; | |
| 6111 } | |
| 6112 | |
| 6113 function svgHeader() | |
| 6114 { | |
| 6115 var patternWidth = fontSize * .6;//radius / 50; | |
| 6116 | |
| 6117 return '\ | |
| 6118 <?xml version="1.0" standalone="no"?>\ | |
| 6119 <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" \ | |
| 6120 "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\ | |
| 6121 <svg width="' + imageWidth + '" height="' + imageHeight + '" version="1.1"\ | |
| 6122 xmlns="http://www.w3.org/2000/svg">\ | |
| 6123 <title>Krona (snapshot) - ' + | |
| 6124 (datasets > 1 ? datasetNames[currentDataset] + ' - ' : '') + selectedNode.name + | |
| 6125 '</title>\ | |
| 6126 <defs>\ | |
| 6127 <style type="text/css">\ | |
| 6128 text {font-size: ' + fontSize + 'px; font-family: ' + fontFamily + '; dominant-baseline:central}\ | |
| 6129 path {stroke-width:' + thinLineWidth * fontSize / 12 + ';}\ | |
| 6130 path.wedge {stroke:none}\ | |
| 6131 path.line {fill:none;stroke:black;}\ | |
| 6132 line {stroke:black;stroke-width:' + thinLineWidth * fontSize / 12 + ';}\ | |
| 6133 line.tick {stroke-width:' + thinLineWidth * fontSize / 6 + ';}\ | |
| 6134 line.pattern {stroke-width:' + thinLineWidth * fontSize / 18 + ';}\ | |
| 6135 circle {fill:none;stroke:black;stroke-width:' + thinLineWidth * fontSize / 12 + ';}\ | |
| 6136 rect {stroke:black;stroke-width:' + thinLineWidth * fontSize / 12 + ';}\ | |
| 6137 .highlight {stroke:black;stroke-width:'+ highlightLineWidth * fontSize / 12 + ';}\ | |
| 6138 .searchHighlight {fill:rgb(255, 255, 100);stroke:none;}\ | |
| 6139 </style>\ | |
| 6140 <pattern id="hiddenPattern" patternUnits="userSpaceOnUse" \ | |
| 6141 x="0" y="0" width="' + patternWidth + '" height="' + patternWidth + '">\ | |
| 6142 <line class="pattern" x1="0" y1="0" x2="' + patternWidth / 2 + '" y2="' + patternWidth / 2 + '"/>\ | |
| 6143 <line class="pattern" x1="' + patternWidth / 2 + '" y1="' + patternWidth + | |
| 6144 '" x2="' + patternWidth + '" y2="' + patternWidth / 2 + '"/>\ | |
| 6145 </pattern>\ | |
| 6146 </defs>\ | |
| 6147 '; | |
| 6148 } | |
| 6149 | |
| 6150 function svgText(text, x, y, anchor, bold, color) | |
| 6151 { | |
| 6152 if ( typeof(anchor) == 'undefined' ) | |
| 6153 { | |
| 6154 anchor = 'start'; | |
| 6155 } | |
| 6156 | |
| 6157 if ( color == undefined ) | |
| 6158 { | |
| 6159 color = 'black'; | |
| 6160 } | |
| 6161 | |
| 6162 return '<text x="' + x + '" y="' + y + | |
| 6163 '" style="font-color:' + color + ';font-weight:' + (bold ? 'bold' : 'normal') + | |
| 6164 '" text-anchor="' + anchor + '">' + text + '</text>'; | |
| 6165 } | |
| 6166 | |
| 6167 function toggleKeys() | |
| 6168 { | |
| 6169 if ( showKeys ) | |
| 6170 { | |
| 6171 keyControl.value = '…'; | |
| 6172 showKeys = false; | |
| 6173 } | |
| 6174 else | |
| 6175 { | |
| 6176 keyControl.value = 'x'; | |
| 6177 showKeys = true; | |
| 6178 } | |
| 6179 | |
| 6180 updateKeyControl(); | |
| 6181 | |
| 6182 if ( progress == 1 ) | |
| 6183 { | |
| 6184 draw(); | |
| 6185 } | |
| 6186 } | |
| 6187 | |
| 6188 function update() | |
| 6189 { | |
| 6190 if ( ! head ) | |
| 6191 { | |
| 6192 return; | |
| 6193 } | |
| 6194 | |
| 6195 if ( mouseDown && focusNode != selectedNode ) | |
| 6196 { | |
| 6197 var date = new Date(); | |
| 6198 | |
| 6199 if ( date.getTime() - mouseDownTime > quickLookHoldLength ) | |
| 6200 { | |
| 6201 if ( focusNode.hasChildren() ) | |
| 6202 { | |
| 6203 expand(focusNode); | |
| 6204 quickLook = true; | |
| 6205 } | |
| 6206 } | |
| 6207 } | |
| 6208 | |
| 6209 if ( updateViewNeeded ) | |
| 6210 { | |
| 6211 resize(); | |
| 6212 mouseX = -1; | |
| 6213 mouseY = -1; | |
| 6214 | |
| 6215 collapse = collapseCheckBox.checked; | |
| 6216 compress = true;//compressCheckBox.checked; | |
| 6217 shorten = true;//shortenCheckBox.checked; | |
| 6218 | |
| 6219 checkSelectedCollapse(); | |
| 6220 updateMaxAbsoluteDepth(); | |
| 6221 | |
| 6222 if ( focusNode.getCollapse() || focusNode.depth > maxAbsoluteDepth ) | |
| 6223 { | |
| 6224 setFocus(selectedNode); | |
| 6225 } | |
| 6226 else | |
| 6227 { | |
| 6228 setFocus(focusNode); | |
| 6229 } | |
| 6230 | |
| 6231 updateView(); | |
| 6232 | |
| 6233 updateViewNeeded = false; | |
| 6234 } | |
| 6235 | |
| 6236 var date = new Date(); | |
| 6237 progress = (date.getTime() - tweenStartTime) / tweenLength; | |
| 6238 // progress += .01; | |
| 6239 | |
| 6240 if ( progress >= 1 ) | |
| 6241 { | |
| 6242 progress = 1; | |
| 6243 } | |
| 6244 | |
| 6245 if ( progress != progressLast ) | |
| 6246 { | |
| 6247 tweenFactor =// progress; | |
| 6248 (1 / (1 + Math.exp(-tweenCurvature * (progress - .5))) - .5) / | |
| 6249 (tweenMax - .5) / 2 + .5; | |
| 6250 | |
| 6251 if ( progress == 1 ) | |
| 6252 { | |
| 6253 snapshotButton.disabled = false; | |
| 6254 zoomOut = false; | |
| 6255 | |
| 6256 //updateKeyControl(); | |
| 6257 | |
| 6258 if ( ! quickLook ) | |
| 6259 { | |
| 6260 //checkHighlight(); | |
| 6261 } | |
| 6262 | |
| 6263 | |
| 6264 if ( fpsDisplay ) | |
| 6265 { | |
| 6266 fpsDisplay.innerHTML = 'fps: ' + Math.round(tweenFrames * 1000 / tweenLength); | |
| 6267 } | |
| 6268 } | |
| 6269 | |
| 6270 draw(); | |
| 6271 } | |
| 6272 | |
| 6273 progressLast = progress; | |
| 6274 } | |
| 6275 | |
| 6276 function updateDatasetButtons() | |
| 6277 { | |
| 6278 if ( datasets == 1 ) | |
| 6279 { | |
| 6280 return; | |
| 6281 } | |
| 6282 | |
| 6283 var node = selectedNode ? selectedNode : head; | |
| 6284 | |
| 6285 datasetButtonLast.disabled = | |
| 6286 node.attributes[magnitudeIndex][lastDataset] == 0; | |
| 6287 | |
| 6288 datasetButtonPrev.disabled = true; | |
| 6289 datasetButtonNext.disabled = true; | |
| 6290 | |
| 6291 for ( var i = 0; i < datasets; i++ ) | |
| 6292 { | |
| 6293 var disable = node.attributes[magnitudeIndex][i] == 0; | |
| 6294 | |
| 6295 datasetDropDown.options[i].disabled = disable; | |
| 6296 | |
| 6297 if ( ! disable ) | |
| 6298 { | |
| 6299 if ( i != currentDataset ) | |
| 6300 { | |
| 6301 datasetButtonPrev.disabled = false; | |
| 6302 datasetButtonNext.disabled = false; | |
| 6303 } | |
| 6304 } | |
| 6305 } | |
| 6306 } | |
| 6307 | |
| 6308 function updateDatasetWidths() | |
| 6309 { | |
| 6310 if ( datasets > 1 ) | |
| 6311 { | |
| 6312 for ( var i = 0; i < datasets; i++ ) | |
| 6313 { | |
| 6314 context.font = fontBold; | |
| 6315 var dim = context.measureText(datasetNames[i]); | |
| 6316 datasetWidths[i] = dim.width; | |
| 6317 } | |
| 6318 } | |
| 6319 } | |
| 6320 | |
| 6321 function updateKeyControl() | |
| 6322 { | |
| 6323 if ( keys == 0 )//|| progress != 1 ) | |
| 6324 { | |
| 6325 keyControl.style.visibility = 'hidden'; | |
| 6326 } | |
| 6327 else | |
| 6328 { | |
| 6329 keyControl.style.visibility = 'visible'; | |
| 6330 keyControl.style.right = margin + 'px'; | |
| 6331 | |
| 6332 if ( showKeys ) | |
| 6333 { | |
| 6334 keyControl.style.top = | |
| 6335 imageHeight - | |
| 6336 ( | |
| 6337 keys * (keySize + keyBuffer) - | |
| 6338 keyBuffer + | |
| 6339 margin + | |
| 6340 keyControl.clientHeight * 1.5 | |
| 6341 ) + 'px'; | |
| 6342 } | |
| 6343 else | |
| 6344 { | |
| 6345 keyControl.style.top = | |
| 6346 (imageHeight - margin - keyControl.clientHeight) + 'px'; | |
| 6347 } | |
| 6348 } | |
| 6349 } | |
| 6350 | |
| 6351 function updateView() | |
| 6352 { | |
| 6353 if ( selectedNode.depth > maxAbsoluteDepth - 1 ) | |
| 6354 { | |
| 6355 maxAbsoluteDepth = selectedNode.depth + 1; | |
| 6356 } | |
| 6357 | |
| 6358 highlightedNode = selectedNode; | |
| 6359 | |
| 6360 angleFactor = 2 * Math.PI / (selectedNode.magnitude); | |
| 6361 | |
| 6362 maxPossibleDepth = Math.floor(gRadius / (fontSize * minRingWidthFactor)); | |
| 6363 | |
| 6364 if ( maxPossibleDepth < 4 ) | |
| 6365 { | |
| 6366 maxPossibleDepth = 4; | |
| 6367 } | |
| 6368 | |
| 6369 var minRadiusInner = fontSize * 8 / gRadius; | |
| 6370 var minRadiusFirst = fontSize * 6 / gRadius; | |
| 6371 var minRadiusOuter = fontSize * 5 / gRadius; | |
| 6372 | |
| 6373 if ( .25 < minRadiusInner ) | |
| 6374 { | |
| 6375 minRadiusInner = .25; | |
| 6376 } | |
| 6377 | |
| 6378 if ( .15 < minRadiusFirst ) | |
| 6379 { | |
| 6380 minRadiusFirst = .15; | |
| 6381 } | |
| 6382 | |
| 6383 if ( .15 < minRadiusOuter ) | |
| 6384 { | |
| 6385 minRadiusOuter = .15; | |
| 6386 } | |
| 6387 | |
| 6388 // visibility of nodes depends on the depth they are displayed at, | |
| 6389 // so we need to set the max depth assuming they can all be displayed | |
| 6390 // and iterate it down based on the deepest child node we can display | |
| 6391 // | |
| 6392 var maxDepth; | |
| 6393 var newMaxDepth = selectedNode.getMaxDepth() - selectedNode.getDepth() + 1; | |
| 6394 // | |
| 6395 do | |
| 6396 { | |
| 6397 maxDepth = newMaxDepth; | |
| 6398 | |
| 6399 if ( ! compress && maxDepth > maxPossibleDepth ) | |
| 6400 { | |
| 6401 maxDepth = maxPossibleDepth; | |
| 6402 } | |
| 6403 | |
| 6404 if ( compress ) | |
| 6405 { | |
| 6406 compressedRadii = new Array(maxDepth); | |
| 6407 | |
| 6408 compressedRadii[0] = minRadiusInner; | |
| 6409 | |
| 6410 var offset = 0; | |
| 6411 | |
| 6412 while | |
| 6413 ( | |
| 6414 lerp | |
| 6415 ( | |
| 6416 Math.atan(offset + 2), | |
| 6417 Math.atan(offset + 1), | |
| 6418 Math.atan(maxDepth + offset - 1), | |
| 6419 minRadiusInner, | |
| 6420 1 - minRadiusOuter | |
| 6421 ) - minRadiusInner > minRadiusFirst && | |
| 6422 offset < 10 | |
| 6423 ) | |
| 6424 { | |
| 6425 offset++; | |
| 6426 } | |
| 6427 | |
| 6428 offset--; | |
| 6429 | |
| 6430 for ( var i = 1; i < maxDepth; i++ ) | |
| 6431 { | |
| 6432 compressedRadii[i] = lerp | |
| 6433 ( | |
| 6434 Math.atan(i + offset), | |
| 6435 Math.atan(offset), | |
| 6436 Math.atan(maxDepth + offset - 1), | |
| 6437 minRadiusInner, | |
| 6438 1 - minRadiusOuter | |
| 6439 ) | |
| 6440 } | |
| 6441 } | |
| 6442 else | |
| 6443 { | |
| 6444 nodeRadius = 1 / maxDepth; | |
| 6445 } | |
| 6446 | |
| 6447 newMaxDepth = selectedNode.maxVisibleDepth(maxDepth); | |
| 6448 | |
| 6449 if ( compress ) | |
| 6450 { | |
| 6451 if ( newMaxDepth <= maxPossibleDepth ) | |
| 6452 { | |
| 6453 // compress | |
| 6454 } | |
| 6455 } | |
| 6456 else | |
| 6457 { | |
| 6458 if ( newMaxDepth > maxPossibleDepth ) | |
| 6459 { | |
| 6460 newMaxDepth = maxPossibleDepth; | |
| 6461 } | |
| 6462 } | |
| 6463 } | |
| 6464 while ( newMaxDepth < maxDepth ); | |
| 6465 | |
| 6466 maxDisplayDepth = maxDepth; | |
| 6467 | |
| 6468 lightnessFactor = (lightnessMax - lightnessBase) / (maxDepth > 8 ? 8 : maxDepth); | |
| 6469 keys = 0; | |
| 6470 | |
| 6471 nLabelOffsets = new Array(maxDisplayDepth - 1); | |
| 6472 labelOffsets = new Array(maxDisplayDepth - 1); | |
| 6473 labelLastNodes = new Array(maxDisplayDepth - 1); | |
| 6474 labelFirstNodes = new Array(maxDisplayDepth - 1); | |
| 6475 | |
| 6476 for ( var i = 0; i < maxDisplayDepth - 1; i++ ) | |
| 6477 { | |
| 6478 if ( compress ) | |
| 6479 { | |
| 6480 if ( i == maxDisplayDepth - 1 ) | |
| 6481 { | |
| 6482 nLabelOffsets[i] = 0; | |
| 6483 } | |
| 6484 else | |
| 6485 { | |
| 6486 var width = | |
| 6487 (compressedRadii[i + 1] - compressedRadii[i]) * | |
| 6488 gRadius; | |
| 6489 | |
| 6490 nLabelOffsets[i] = Math.floor(width / fontSize / 1.2); | |
| 6491 | |
| 6492 if ( nLabelOffsets[i] > 2 ) | |
| 6493 { | |
| 6494 nLabelOffsets[i] = min | |
| 6495 ( | |
| 6496 Math.floor(width / fontSize / 1.75), | |
| 6497 5 | |
| 6498 ); | |
| 6499 } | |
| 6500 } | |
| 6501 } | |
| 6502 else | |
| 6503 { | |
| 6504 nLabelOffsets[i] = Math.max | |
| 6505 ( | |
| 6506 Math.floor(Math.sqrt((nodeRadius * gRadius / fontSize)) * 1.5), | |
| 6507 3 | |
| 6508 ); | |
| 6509 } | |
| 6510 | |
| 6511 labelOffsets[i] = Math.floor((nLabelOffsets[i] - 1) / 2); | |
| 6512 labelLastNodes[i] = new Array(nLabelOffsets[i] + 1); | |
| 6513 labelFirstNodes[i] = new Array(nLabelOffsets[i] + 1); | |
| 6514 | |
| 6515 for ( var j = 0; j <= nLabelOffsets[i]; j++ ) | |
| 6516 { | |
| 6517 // these arrays will allow nodes with neighboring labels to link to | |
| 6518 // each other to determine max label length | |
| 6519 | |
| 6520 labelLastNodes[i][j] = 0; | |
| 6521 labelFirstNodes[i][j] = 0; | |
| 6522 } | |
| 6523 } | |
| 6524 | |
| 6525 fontSizeText.innerHTML = fontSize; | |
| 6526 fontNormal = fontSize + 'px ' + fontFamily; | |
| 6527 context.font = fontNormal; | |
| 6528 fontBold = 'bold ' + fontSize + 'px ' + fontFamily; | |
| 6529 tickLength = fontSize * .7; | |
| 6530 | |
| 6531 head.setTargets(0); | |
| 6532 | |
| 6533 keySize = ((imageHeight - margin * 3) * 1 / 2) / keys * 3 / 4; | |
| 6534 | |
| 6535 if ( keySize > fontSize * maxKeySizeFactor ) | |
| 6536 { | |
| 6537 keySize = fontSize * maxKeySizeFactor; | |
| 6538 } | |
| 6539 | |
| 6540 keyBuffer = keySize / 3; | |
| 6541 | |
| 6542 fontSizeLast = fontSize; | |
| 6543 | |
| 6544 if ( datasetChanged ) | |
| 6545 { | |
| 6546 datasetChanged = false; | |
| 6547 } | |
| 6548 else | |
| 6549 { | |
| 6550 datasetAlpha.start = 0; | |
| 6551 } | |
| 6552 | |
| 6553 var date = new Date(); | |
| 6554 tweenStartTime = date.getTime(); | |
| 6555 progress = 0; | |
| 6556 tweenFrames = 0; | |
| 6557 | |
| 6558 updateKeyControl(); | |
| 6559 updateDatasetWidths(); | |
| 6560 | |
| 6561 document.title = 'Krona - ' + selectedNode.name; | |
| 6562 updateNavigationButtons(); | |
| 6563 snapshotButton.disabled = true; | |
| 6564 | |
| 6565 maxAbsoluteDepthText.innerHTML = maxAbsoluteDepth - 1; | |
| 6566 | |
| 6567 maxAbsoluteDepthButtonDecrease.disabled = (maxAbsoluteDepth == 2); | |
| 6568 maxAbsoluteDepthButtonIncrease.disabled = (maxAbsoluteDepth == head.maxDepth); | |
| 6569 | |
| 6570 if ( collapse != collapseLast && search.value != '' ) | |
| 6571 { | |
| 6572 onSearchChange(); | |
| 6573 collapseLast = collapse; | |
| 6574 } | |
| 6575 } | |
| 6576 | |
| 6577 function updateMaxAbsoluteDepth() | |
| 6578 { | |
| 6579 while ( maxAbsoluteDepth > 1 && selectedNode.depth > maxAbsoluteDepth - 1 ) | |
| 6580 { | |
| 6581 selectedNode = selectedNode.getParent(); | |
| 6582 } | |
| 6583 } | |
| 6584 | |
| 6585 function updateNavigationButtons() | |
| 6586 { | |
| 6587 backButton.disabled = (nodeHistoryPosition == 0); | |
| 6588 // upButton.disabled = (selectedNode.getParent() == 0); | |
| 6589 forwardButton.disabled = (nodeHistoryPosition == nodeHistory.length); | |
| 6590 } | |
| 6591 | |
| 6592 function useHue() | |
| 6593 { | |
| 6594 return useHueCheckBox && useHueCheckBox.checked; | |
| 6595 } | |
| 6596 /* | |
| 6597 function zoomOut() | |
| 6598 { | |
| 6599 return ( | |
| 6600 selectedNodeLast != 0 && | |
| 6601 selectedNodeLast.getDepth() < selectedNode.getDepth()); | |
| 6602 } | |
| 6603 */ | |
| 6604 </script> | |
| 6605 </head> | |
| 6606 <body> | |
| 6607 <img id="hiddenImage" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9oLCBQhNQwWVnsAAAAidEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVAgb24gYSBNYWOHqHdDAAABE0lEQVQYGQEIAff+AwAAABkAAAAAAAAA+gAAAAAAAAAAAAAAAAAAAAAAAAAMAwAAAAAAAAANAAAAAAAAAPoAAAAAAAAADAAAAAYAAAD0AwAAAPoAAAAAAAAAAAAAAPoAAAAMAAAADQAAAPoAAAD6AAAAAAAAAAAAAAAAAAAAAAwAAAAZAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAABkAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAGQAAAAwAAAAAAAAADAAAAAwAAAAABAAAAAAAAAAAAAAA8wAAAPQAAAAAAAAAAAAAAA0AAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAZRssKC5OpXwYAAAAASUVORK5CYII=" style="display:none"/> | |
| 6608 <img id="loadingImage" src="data:image/gif;base64,R0lGODlhEAAQAPIAAP///wAAAMLCwkJCQgAAAGJiYoKCgpKSkiH/C05FVFNDQVBFMi4wAwEAAAAh/hpDcmVhdGVkIHdpdGggYWpheGxvYWQuaW5mbwAh+QQJCgAAACwAAAAAEAAQAAADMwi63P4wyklrE2MIOggZnAdOmGYJRbExwroUmcG2LmDEwnHQLVsYOd2mBzkYDAdKa+dIAAAh+QQJCgAAACwAAAAAEAAQAAADNAi63P5OjCEgG4QMu7DmikRxQlFUYDEZIGBMRVsaqHwctXXf7WEYB4Ag1xjihkMZsiUkKhIAIfkECQoAAAAsAAAAABAAEAAAAzYIujIjK8pByJDMlFYvBoVjHA70GU7xSUJhmKtwHPAKzLO9HMaoKwJZ7Rf8AYPDDzKpZBqfvwQAIfkECQoAAAAsAAAAABAAEAAAAzMIumIlK8oyhpHsnFZfhYumCYUhDAQxRIdhHBGqRoKw0R8DYlJd8z0fMDgsGo/IpHI5TAAAIfkECQoAAAAsAAAAABAAEAAAAzIIunInK0rnZBTwGPNMgQwmdsNgXGJUlIWEuR5oWUIpz8pAEAMe6TwfwyYsGo/IpFKSAAAh+QQJCgAAACwAAAAAEAAQAAADMwi6IMKQORfjdOe82p4wGccc4CEuQradylesojEMBgsUc2G7sDX3lQGBMLAJibufbSlKAAAh+QQJCgAAACwAAAAAEAAQAAADMgi63P7wCRHZnFVdmgHu2nFwlWCI3WGc3TSWhUFGxTAUkGCbtgENBMJAEJsxgMLWzpEAACH5BAkKAAAALAAAAAAQABAAAAMyCLrc/jDKSatlQtScKdceCAjDII7HcQ4EMTCpyrCuUBjCYRgHVtqlAiB1YhiCnlsRkAAAOwAAAAAAAAAAAA==" style="display:none"/> | |
| 6609 <img id="logo" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAANgAAAA8CAYAAAAE9XR5AAAC0WlDQ1BJQ0MgUHJvZmlsZQAAOI2NlM9LFGEYx7+zjRgoQWBme4ihQ0ioTBZlROWuv9i0bVl/lBLE7Oy7u5Ozs9PM7JoiEV46ZtE9Kg8e+gM8eOiUl8LALALpblFEgpeS7Xlnxt0R7ccLM/N5nx/f53nf4X2BGlkxTT0kAXnDsZJ9Uen66JhU+xEhHEEdwqhTVNuMJBIDoMFjsWtsvofAvyute/v/OurStpoHhP1A6Eea2Sqw7xfZC1lqBBC5XsOEYzrE9zhbnv0x55TH8659KNlFvEh8QDUtHv+auEPNKWmgRiRuyQZiUgHO60XV7+cgPfXMGB6k73Hq6S6ze3wWZtJKdz9xG/HnNOvu4ZrE8xmtN0bcTM9axuod9lg4oTmxIY9DI4YeH/C5yUjFr/qaoulEk9v6dmmwZ9t+S7mcIA4TJ8cL/TymkXI7p3JD1zwW9KlcV9znd1Yxyeseo5g5U3f/F/UWeoVR6GDQYNDbgIQk+hBFK0xYKCBDHo0iNLIyN8YitjG+Z6SORIAl8q9TzrqbcxtFyuZZI4jGMdNSUZDkD/JXeVV+Ks/JX2bDxeaqZ8a6qanLD76TLq+8ret7/Z48fZXqRsirI0vWfGVNdqDTQHcZYzZcVeI12P34ZmCVLFCpFSlXadytVHJ9Nr0jgWp/2j2KXZpebKrWWhUXbqzUL03v2KvCrlWxyqp2zqtxwXwmHhVPijGxQzwHSbwkdooXxW6anRcHKhnDpKJhwlWyoVCWgUnymjv+mRcL76y5o6GPGczSVImf/4RVyGg6CxzRf7j/c/B7xaOxIvDCBg6frto2ku4dIjQuV23OFeDCN7oP3lZtzXQeDj0BFs6oRavkSwvCG4pmdxw+6SqYk5aWzTlSuyyflSJ0JTEpZqhtLZKi65LrsiWL2cwqsXQb7Mypdk+lnnal5lO5vEHnr/YRsPWwXP75rFzeek49rAEv9d/AvP1FSOihagAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB+AEEhYRJKJxkPUAACAASURBVHgBAGaAmX8B/////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAvUj3EAAAIABJREFUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////AAAAAAD+/v4AAAAAAP///wD///8AAAAAAP///wAAAAAAAQEBAAEBAQAAAAAAAQEBAAICAgAAAAAAAQEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wAAAAAA/v7+AP7+/gD9/f0A/v7+AP39/QD+/v4A/f39AP39/QD9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AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEAAwMDAAUFBQALCwsALy4vAFhbWwC47+4As+rqAAAAAAAAAAAAAAABAAEA/wAAAAAA////AP///wD//wAA/v//AP7//wAA//8AAAD/AAAAAAD/AAAAAAAAAAQCAgADAwEABQUDAAIDAgACAwEAAgEBAAEBAwADAQIAAgEAAAQCAwADAgIAFQwfAGQ5jAD77O4A8tLVAEE/PwAiIiIAEBAQAAwMDAAHBwcAAgICAAEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQACAgIACAgIAA0NDQA/Pz8A+/v7AKqpqQDw/f0AVxUVAAIA/wAAAAAAAAAAAP//AAD///4AAAABAAIBAQD+//4A////AP///wAAAAAAAAAAAAEAAAABAQEAAwMCAAAAAAAA/wAAAgECAAIDAwAAAAAAAQIAAAECAwD/AgIAAgECAP0C/ADH3rAAAwECADMzMQAsLCwAExMTABEREQAKCgoABQUFAAMDAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA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AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8A/f39APz8/AD9/f0A+fn5APr6+gD6+voA+fn5APr6+gD6+voA+fn5APj5+AD9/P0AAgICAAYGBgAHBwcABgYGAAYGBgAICAgABgYGAAcHBwAGBgYABQUFAAMDAwACAgIAAgICAAEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB/////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wAAAAAA/v7+AP7+/gD8/PwA/v7+APz8/AD8/PwA/P38AP38/QD7+/sA/Pz8AAMDAwAFBQUAAgICAAUFBQAEBAQAAwMDAAQEBAADAwMAAgICAAEBAQACAgIAAQEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8A/v7+AAAAAAD///8A////AP///wD9/f0A/v7+AAAAAAAAAAAAAQEBAAEBAQADAwMAAQEBAAEBAQABAQEAAgICAAAAAAABAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACanuzMvN2MUAAAAAElFTkSuQmCC" style="display:none"/> | |
| 6610 <noscript>Javascript must be enabled to view this page.</noscript> | |
| 6611 <div style="display:none"> | |
| 6612 <krona collapse="true" key="true"> | |
| 6613 <attributes magnitude="magnitude"> | |
| 6614 <attribute display="Total">magnitude</attribute> | |
| 6615 </attributes> | |
| 6616 <datasets> | |
| 6617 <dataset>kt</dataset> | |
| 6618 </datasets> | |
| 6619 | |
| 6620 <node name="all"><magnitude><val>15</val></magnitude><node name="root"><magnitude><val>15</val></magnitude><node name="cellular organisms"><magnitude><val>15</val></magnitude><node name="Bacteria"><magnitude><val>14</val></magnitude><node name="Pseudomonadati"><magnitude><val>13</val></magnitude><node name="Pseudomonadota"><magnitude><val>13</val></magnitude><node name="Betaproteobacteria"><magnitude><val>11</val></magnitude><node name="Burkholderiales"><magnitude><val>11</val></magnitude><node name="Burkholderiaceae"><magnitude><val>9</val></magnitude><node name="Burkholderia"><magnitude><val>5</val></magnitude><node name="Burkholderia cepacia complex"><magnitude><val>4</val></magnitude><node name="Burkholderia cepacia"><magnitude><val>2</val></magnitude></node><node name="Burkholderia cenocepacia"><magnitude><val>1</val></magnitude><node name="Burkholderia cenocepacia HI2424"><magnitude><val>1</val></magnitude></node></node><node name="Burkholderia orbicola"><magnitude><val>1</val></magnitude><node name="Burkholderia orbicola AU 1054"><magnitude><val>1</val></magnitude></node></node></node><node name="pseudomallei group"><magnitude><val>1</val></magnitude><node name="Burkholderia pseudomallei"><magnitude><val>1</val></magnitude><node name="Burkholderia pseudomallei K96243"><magnitude><val>1</val></magnitude></node></node></node></node><node name="Cupriavidus"><magnitude><val>2</val></magnitude><node name="Cupriavidus metallidurans"><magnitude><val>2</val></magnitude><node name="Cupriavidus metallidurans CH34"><magnitude><val>2</val></magnitude></node></node></node></node><node name="Comamonadaceae"><magnitude><val>2</val></magnitude><node name="Rhodoferax"><magnitude><val>1</val></magnitude><node name="Rhodoferax ferrireducens"><magnitude><val>1</val></magnitude><node name="Rhodoferax ferrireducens T118"><magnitude><val>1</val></magnitude></node></node></node><node name="Polaromonas"><magnitude><val>1</val></magnitude><node name="unclassified Polaromonas"><magnitude><val>1</val></magnitude><node name="Polaromonas sp. JS666"><magnitude><val>1</val></magnitude></node></node></node></node></node></node><node name="Gammaproteobacteria"><magnitude><val>2</val></magnitude><node name="Moraxellales"><magnitude><val>1</val></magnitude><node name="Moraxellaceae"><magnitude><val>1</val></magnitude><node name="Acinetobacter"><magnitude><val>1</val></magnitude><node name="Acinetobacter calcoaceticus/baumannii complex"><magnitude><val>1</val></magnitude><node name="Acinetobacter baumannii"><magnitude><val>1</val></magnitude><node name="Acinetobacter baumannii AB307-0294"><magnitude><val>1</val></magnitude></node></node></node></node></node></node></node></node></node><node name="Bacillati"><magnitude><val>1</val></magnitude><node name="Actinomycetota"><magnitude><val>1</val></magnitude><node name="Actinomycetes"><magnitude><val>1</val></magnitude><node name="Kitasatosporales"><magnitude><val>1</val></magnitude><node name="Streptomycetaceae"><magnitude><val>1</val></magnitude><node name="Streptomyces"><magnitude><val>1</val></magnitude><node name="Streptomyces griseus group"><magnitude><val>1</val></magnitude><node name="Streptomyces griseus subgroup"><magnitude><val>1</val></magnitude><node name="Streptomyces griseus"><magnitude><val>1</val></magnitude></node></node></node></node></node></node></node></node></node></node></node></node></node></krona></div></body></html> |
