├── README.md ├── d3.js ├── d3.layout.js ├── iOSDeveloperRoadMap.png ├── iOSMap.jpg ├── route.html └── route.json /README.md: -------------------------------------------------------------------------------- 1 | # ios-developer-roadmap
2 | 本路线图参考:http://ios.skyfox.org/route.html ,在此基础上,删除很多过时的内容,并新增包含SwiftUI、Swift、Objective-C等大量知识点。 3 |

4 | **网页版(无法看到图片的请查看网页版)
5 | [查看网页版 >](http://hdjc8.com/iOSRoadMap/) 6 | 7 | **预览图
8 | ![image](iOSDeveloperRoadMap.png) 9 | 10 | **预览图
11 | ![image](iOSMap.jpg) 12 | -------------------------------------------------------------------------------- /d3.layout.js: -------------------------------------------------------------------------------- 1 | (function(){d3.layout = {}; 2 | // Implements hierarchical edge bundling using Holten's algorithm. For each 3 | // input link, a path is computed that travels through the tree, up the parent 4 | // hierarchy to the least common ancestor, and then back down to the destination 5 | // node. Each path is simply an array of nodes. 6 | d3.layout.bundle = function() { 7 | return function(links) { 8 | var paths = [], 9 | i = -1, 10 | n = links.length; 11 | while (++i < n) paths.push(d3_layout_bundlePath(links[i])); 12 | return paths; 13 | }; 14 | }; 15 | 16 | function d3_layout_bundlePath(link) { 17 | var start = link.source, 18 | end = link.target, 19 | lca = d3_layout_bundleLeastCommonAncestor(start, end), 20 | points = [start]; 21 | while (start !== lca) { 22 | start = start.parent; 23 | points.push(start); 24 | } 25 | var k = points.length; 26 | while (end !== lca) { 27 | points.splice(k, 0, end); 28 | end = end.parent; 29 | } 30 | return points; 31 | } 32 | 33 | function d3_layout_bundleAncestors(node) { 34 | var ancestors = [], 35 | parent = node.parent; 36 | while (parent != null) { 37 | ancestors.push(node); 38 | node = parent; 39 | parent = parent.parent; 40 | } 41 | ancestors.push(node); 42 | return ancestors; 43 | } 44 | 45 | function d3_layout_bundleLeastCommonAncestor(a, b) { 46 | if (a === b) return a; 47 | var aNodes = d3_layout_bundleAncestors(a), 48 | bNodes = d3_layout_bundleAncestors(b), 49 | aNode = aNodes.pop(), 50 | bNode = bNodes.pop(), 51 | sharedNode = null; 52 | while (aNode === bNode) { 53 | sharedNode = aNode; 54 | aNode = aNodes.pop(); 55 | bNode = bNodes.pop(); 56 | } 57 | return sharedNode; 58 | } 59 | d3.layout.chord = function() { 60 | var chord = {}, 61 | chords, 62 | groups, 63 | matrix, 64 | n, 65 | padding = 0, 66 | sortGroups, 67 | sortSubgroups, 68 | sortChords; 69 | 70 | function relayout() { 71 | var subgroups = {}, 72 | groupSums = [], 73 | groupIndex = d3.range(n), 74 | subgroupIndex = [], 75 | k, 76 | x, 77 | x0, 78 | i, 79 | j; 80 | 81 | chords = []; 82 | groups = []; 83 | 84 | // Compute the sum. 85 | k = 0, i = -1; while (++i < n) { 86 | x = 0, j = -1; while (++j < n) { 87 | x += matrix[i][j]; 88 | } 89 | groupSums.push(x); 90 | subgroupIndex.push(d3.range(n)); 91 | k += x; 92 | } 93 | 94 | // Sort groups… 95 | if (sortGroups) { 96 | groupIndex.sort(function(a, b) { 97 | return sortGroups(groupSums[a], groupSums[b]); 98 | }); 99 | } 100 | 101 | // Sort subgroups… 102 | if (sortSubgroups) { 103 | subgroupIndex.forEach(function(d, i) { 104 | d.sort(function(a, b) { 105 | return sortSubgroups(matrix[i][a], matrix[i][b]); 106 | }); 107 | }); 108 | } 109 | 110 | // Convert the sum to scaling factor for [0, 2pi]. 111 | // TODO Allow start and end angle to be specified. 112 | // TODO Allow padding to be specified as percentage? 113 | k = (2 * Math.PI - padding * n) / k; 114 | 115 | // Compute the start and end angle for each group and subgroup. 116 | x = 0, i = -1; while (++i < n) { 117 | x0 = x, j = -1; while (++j < n) { 118 | var di = groupIndex[i], 119 | dj = subgroupIndex[i][j], 120 | v = matrix[di][dj]; 121 | subgroups[di + "-" + dj] = { 122 | index: di, 123 | subindex: dj, 124 | startAngle: x, 125 | endAngle: x += v * k, 126 | value: v 127 | }; 128 | } 129 | groups.push({ 130 | index: di, 131 | startAngle: x0, 132 | endAngle: x, 133 | value: (x - x0) / k 134 | }); 135 | x += padding; 136 | } 137 | 138 | // Generate chords for each (non-empty) subgroup-subgroup link. 139 | i = -1; while (++i < n) { 140 | j = i - 1; while (++j < n) { 141 | var source = subgroups[i + "-" + j], 142 | target = subgroups[j + "-" + i]; 143 | if (source.value || target.value) { 144 | chords.push(source.value < target.value 145 | ? {source: target, target: source} 146 | : {source: source, target: target}); 147 | } 148 | } 149 | } 150 | 151 | if (sortChords) resort(); 152 | } 153 | 154 | function resort() { 155 | chords.sort(function(a, b) { 156 | return sortChords(a.target.value, b.target.value); 157 | }); 158 | } 159 | 160 | chord.matrix = function(x) { 161 | if (!arguments.length) return matrix; 162 | n = (matrix = x) && matrix.length; 163 | chords = groups = null; 164 | return chord; 165 | }; 166 | 167 | chord.padding = function(x) { 168 | if (!arguments.length) return padding; 169 | padding = x; 170 | chords = groups = null; 171 | return chord; 172 | }; 173 | 174 | chord.sortGroups = function(x) { 175 | if (!arguments.length) return sortGroups; 176 | sortGroups = x; 177 | chords = groups = null; 178 | return chord; 179 | }; 180 | 181 | chord.sortSubgroups = function(x) { 182 | if (!arguments.length) return sortSubgroups; 183 | sortSubgroups = x; 184 | chords = null; 185 | return chord; 186 | }; 187 | 188 | chord.sortChords = function(x) { 189 | if (!arguments.length) return sortChords; 190 | sortChords = x; 191 | if (chords) resort(); 192 | return chord; 193 | }; 194 | 195 | chord.chords = function() { 196 | if (!chords) relayout(); 197 | return chords; 198 | }; 199 | 200 | chord.groups = function() { 201 | if (!groups) relayout(); 202 | return groups; 203 | }; 204 | 205 | return chord; 206 | }; 207 | // A rudimentary force layout using Gauss-Seidel. 208 | d3.layout.force = function() { 209 | var force = {}, 210 | event = d3.dispatch("tick"), 211 | size = [1, 1], 212 | drag, 213 | alpha, 214 | friction = .9, 215 | linkDistance = d3_layout_forceLinkDistance, 216 | linkStrength = d3_layout_forceLinkStrength, 217 | charge = -30, 218 | gravity = .1, 219 | theta = .8, 220 | interval, 221 | nodes = [], 222 | links = [], 223 | distances, 224 | strengths, 225 | charges; 226 | 227 | function repulse(node) { 228 | return function(quad, x1, y1, x2, y2) { 229 | if (quad.point !== node) { 230 | var dx = quad.cx - node.x, 231 | dy = quad.cy - node.y, 232 | dn = 1 / Math.sqrt(dx * dx + dy * dy); 233 | 234 | /* Barnes-Hut criterion. */ 235 | if ((x2 - x1) * dn < theta) { 236 | var k = quad.charge * dn * dn; 237 | node.px -= dx * k; 238 | node.py -= dy * k; 239 | return true; 240 | } 241 | 242 | if (quad.point && isFinite(dn)) { 243 | var k = quad.pointCharge * dn * dn; 244 | node.px -= dx * k; 245 | node.py -= dy * k; 246 | } 247 | } 248 | return !quad.charge; 249 | }; 250 | } 251 | 252 | function tick() { 253 | var n = nodes.length, 254 | m = links.length, 255 | q, 256 | i, // current index 257 | o, // current object 258 | s, // current source 259 | t, // current target 260 | l, // current distance 261 | k, // current force 262 | x, // x-distance 263 | y; // y-distance 264 | 265 | // gauss-seidel relaxation for links 266 | for (i = 0; i < m; ++i) { 267 | o = links[i]; 268 | s = o.source; 269 | t = o.target; 270 | x = t.x - s.x; 271 | y = t.y - s.y; 272 | if (l = (x * x + y * y)) { 273 | l = alpha * strengths[i] * ((l = Math.sqrt(l)) - distances[i]) / l; 274 | x *= l; 275 | y *= l; 276 | t.x -= x * (k = s.weight / (t.weight + s.weight)); 277 | t.y -= y * k; 278 | s.x += x * (k = 1 - k); 279 | s.y += y * k; 280 | } 281 | } 282 | 283 | // apply gravity forces 284 | if (k = alpha * gravity) { 285 | x = size[0] / 2; 286 | y = size[1] / 2; 287 | i = -1; if (k) while (++i < n) { 288 | o = nodes[i]; 289 | o.x += (x - o.x) * k; 290 | o.y += (y - o.y) * k; 291 | } 292 | } 293 | 294 | // compute quadtree center of mass and apply charge forces 295 | if (charge) { 296 | d3_layout_forceAccumulate(q = d3.geom.quadtree(nodes), alpha, charges); 297 | i = -1; while (++i < n) { 298 | if (!(o = nodes[i]).fixed) { 299 | q.visit(repulse(o)); 300 | } 301 | } 302 | } 303 | 304 | // position verlet integration 305 | i = -1; while (++i < n) { 306 | o = nodes[i]; 307 | if (o.fixed) { 308 | o.x = o.px; 309 | o.y = o.py; 310 | } else { 311 | o.x -= (o.px - (o.px = o.x)) * friction; 312 | o.y -= (o.py - (o.py = o.y)) * friction; 313 | } 314 | } 315 | 316 | event.tick.dispatch({type: "tick", alpha: alpha}); 317 | 318 | // simulated annealing, basically 319 | return (alpha *= .99) < .005; 320 | } 321 | 322 | force.on = function(type, listener) { 323 | event[type].add(listener); 324 | return force; 325 | }; 326 | 327 | force.nodes = function(x) { 328 | if (!arguments.length) return nodes; 329 | nodes = x; 330 | return force; 331 | }; 332 | 333 | force.links = function(x) { 334 | if (!arguments.length) return links; 335 | links = x; 336 | return force; 337 | }; 338 | 339 | force.size = function(x) { 340 | if (!arguments.length) return size; 341 | size = x; 342 | return force; 343 | }; 344 | 345 | force.linkDistance = function(x) { 346 | if (!arguments.length) return linkDistance; 347 | linkDistance = d3.functor(x); 348 | return force; 349 | }; 350 | 351 | // For backwards-compatibility. 352 | force.distance = force.linkDistance; 353 | 354 | force.linkStrength = function(x) { 355 | if (!arguments.length) return linkStrength; 356 | linkStrength = d3.functor(x); 357 | return force; 358 | }; 359 | 360 | force.friction = function(x) { 361 | if (!arguments.length) return friction; 362 | friction = x; 363 | return force; 364 | }; 365 | 366 | force.charge = function(x) { 367 | if (!arguments.length) return charge; 368 | charge = typeof x === "function" ? x : +x; 369 | return force; 370 | }; 371 | 372 | force.gravity = function(x) { 373 | if (!arguments.length) return gravity; 374 | gravity = x; 375 | return force; 376 | }; 377 | 378 | force.theta = function(x) { 379 | if (!arguments.length) return theta; 380 | theta = x; 381 | return force; 382 | }; 383 | 384 | force.start = function() { 385 | var i, 386 | j, 387 | n = nodes.length, 388 | m = links.length, 389 | w = size[0], 390 | h = size[1], 391 | neighbors, 392 | o; 393 | 394 | for (i = 0; i < n; ++i) { 395 | (o = nodes[i]).index = i; 396 | o.weight = 0; 397 | } 398 | 399 | distances = []; 400 | strengths = []; 401 | for (i = 0; i < m; ++i) { 402 | o = links[i]; 403 | if (typeof o.source == "number") o.source = nodes[o.source]; 404 | if (typeof o.target == "number") o.target = nodes[o.target]; 405 | distances[i] = linkDistance.call(this, o, i); 406 | strengths[i] = linkStrength.call(this, o, i); 407 | ++o.source.weight; 408 | ++o.target.weight; 409 | } 410 | 411 | for (i = 0; i < n; ++i) { 412 | o = nodes[i]; 413 | if (isNaN(o.x)) o.x = position("x", w); 414 | if (isNaN(o.y)) o.y = position("y", h); 415 | if (isNaN(o.px)) o.px = o.x; 416 | if (isNaN(o.py)) o.py = o.y; 417 | } 418 | 419 | charges = []; 420 | if (typeof charge === "function") { 421 | for (i = 0; i < n; ++i) { 422 | charges[i] = +charge.call(this, nodes[i], i); 423 | } 424 | } else { 425 | for (i = 0; i < n; ++i) { 426 | charges[i] = charge; 427 | } 428 | } 429 | 430 | // initialize node position based on first neighbor 431 | function position(dimension, size) { 432 | var neighbors = neighbor(i), 433 | j = -1, 434 | m = neighbors.length, 435 | x; 436 | while (++j < m) if (!isNaN(x = neighbors[j][dimension])) return x; 437 | return Math.random() * size; 438 | } 439 | 440 | // initialize neighbors lazily 441 | function neighbor() { 442 | if (!neighbors) { 443 | neighbors = []; 444 | for (j = 0; j < n; ++j) { 445 | neighbors[j] = []; 446 | } 447 | for (j = 0; j < m; ++j) { 448 | var o = links[j]; 449 | neighbors[o.source.index].push(o.target); 450 | neighbors[o.target.index].push(o.source); 451 | } 452 | } 453 | return neighbors[i]; 454 | } 455 | 456 | return force.resume(); 457 | }; 458 | 459 | force.resume = function() { 460 | alpha = .1; 461 | d3.timer(tick); 462 | return force; 463 | }; 464 | 465 | force.stop = function() { 466 | alpha = 0; 467 | return force; 468 | }; 469 | 470 | // use `node.call(force.drag)` to make nodes draggable 471 | force.drag = function() { 472 | if (!drag) drag = d3.behavior.drag() 473 | .on("dragstart", dragstart) 474 | .on("drag", d3_layout_forceDrag) 475 | .on("dragend", d3_layout_forceDragEnd); 476 | 477 | this.on("mouseover.force", d3_layout_forceDragOver) 478 | .on("mouseout.force", d3_layout_forceDragOut) 479 | .call(drag); 480 | }; 481 | 482 | function dragstart(d) { 483 | d3_layout_forceDragOver(d3_layout_forceDragNode = d); 484 | d3_layout_forceDragForce = force; 485 | } 486 | 487 | return force; 488 | }; 489 | 490 | var d3_layout_forceDragForce, 491 | d3_layout_forceDragNode; 492 | 493 | function d3_layout_forceDragOver(d) { 494 | d.fixed |= 2; 495 | } 496 | 497 | function d3_layout_forceDragOut(d) { 498 | if (d !== d3_layout_forceDragNode) d.fixed &= 1; 499 | } 500 | 501 | function d3_layout_forceDragEnd() { 502 | d3_layout_forceDrag(); 503 | d3_layout_forceDragNode.fixed &= 1; 504 | d3_layout_forceDragForce = d3_layout_forceDragNode = null; 505 | } 506 | 507 | function d3_layout_forceDrag() { 508 | d3_layout_forceDragNode.px += d3.event.dx; 509 | d3_layout_forceDragNode.py += d3.event.dy; 510 | d3_layout_forceDragForce.resume(); // restart annealing 511 | } 512 | 513 | function d3_layout_forceAccumulate(quad, alpha, charges) { 514 | var cx = 0, 515 | cy = 0; 516 | quad.charge = 0; 517 | if (!quad.leaf) { 518 | var nodes = quad.nodes, 519 | n = nodes.length, 520 | i = -1, 521 | c; 522 | while (++i < n) { 523 | c = nodes[i]; 524 | if (c == null) continue; 525 | d3_layout_forceAccumulate(c, alpha, charges); 526 | quad.charge += c.charge; 527 | cx += c.charge * c.cx; 528 | cy += c.charge * c.cy; 529 | } 530 | } 531 | if (quad.point) { 532 | // jitter internal nodes that are coincident 533 | if (!quad.leaf) { 534 | quad.point.x += Math.random() - .5; 535 | quad.point.y += Math.random() - .5; 536 | } 537 | var k = alpha * charges[quad.point.index]; 538 | quad.charge += quad.pointCharge = k; 539 | cx += k * quad.point.x; 540 | cy += k * quad.point.y; 541 | } 542 | quad.cx = cx / quad.charge; 543 | quad.cy = cy / quad.charge; 544 | } 545 | 546 | function d3_layout_forceLinkDistance(link) { 547 | return 20; 548 | } 549 | 550 | function d3_layout_forceLinkStrength(link) { 551 | return 1; 552 | } 553 | d3.layout.partition = function() { 554 | var hierarchy = d3.layout.hierarchy(), 555 | size = [1, 1]; // width, height 556 | 557 | function position(node, x, dx, dy) { 558 | var children = node.children; 559 | node.x = x; 560 | node.y = node.depth * dy; 561 | node.dx = dx; 562 | node.dy = dy; 563 | if (children && (n = children.length)) { 564 | var i = -1, 565 | n, 566 | c, 567 | d; 568 | dx = node.value ? dx / node.value : 0; 569 | while (++i < n) { 570 | position(c = children[i], x, d = c.value * dx, dy); 571 | x += d; 572 | } 573 | } 574 | } 575 | 576 | function depth(node) { 577 | var children = node.children, 578 | d = 0; 579 | if (children && (n = children.length)) { 580 | var i = -1, 581 | n; 582 | while (++i < n) d = Math.max(d, depth(children[i])); 583 | } 584 | return 1 + d; 585 | } 586 | 587 | function partition(d, i) { 588 | var nodes = hierarchy.call(this, d, i); 589 | position(nodes[0], 0, size[0], size[1] / depth(nodes[0])); 590 | return nodes; 591 | } 592 | 593 | partition.size = function(x) { 594 | if (!arguments.length) return size; 595 | size = x; 596 | return partition; 597 | }; 598 | 599 | return d3_layout_hierarchyRebind(partition, hierarchy); 600 | }; 601 | d3.layout.pie = function() { 602 | var value = Number, 603 | sort = null, 604 | startAngle = 0, 605 | endAngle = 2 * Math.PI; 606 | 607 | function pie(data, i) { 608 | 609 | // Compute the start angle. 610 | var a = +(typeof startAngle === "function" 611 | ? startAngle.apply(this, arguments) 612 | : startAngle); 613 | 614 | // Compute the angular range (end - start). 615 | var k = (typeof endAngle === "function" 616 | ? endAngle.apply(this, arguments) 617 | : endAngle) - startAngle; 618 | 619 | // Optionally sort the data. 620 | var index = d3.range(data.length); 621 | if (sort != null) index.sort(function(i, j) { 622 | return sort(data[i], data[j]); 623 | }); 624 | 625 | // Compute the numeric values for each data element. 626 | var values = data.map(value); 627 | 628 | // Convert k into a scale factor from value to angle, using the sum. 629 | k /= values.reduce(function(p, d) { return p + d; }, 0); 630 | 631 | // Compute the arcs! 632 | var arcs = index.map(function(i) { 633 | return { 634 | data: data[i], 635 | value: d = values[i], 636 | startAngle: a, 637 | endAngle: a += d * k 638 | }; 639 | }); 640 | 641 | // Return the arcs in the original data's order. 642 | return data.map(function(d, i) { 643 | return arcs[index[i]]; 644 | }); 645 | } 646 | 647 | /** 648 | * Specifies the value function *x*, which returns a nonnegative numeric value 649 | * for each datum. The default value function is `Number`. The value function 650 | * is passed two arguments: the current datum and the current index. 651 | */ 652 | pie.value = function(x) { 653 | if (!arguments.length) return value; 654 | value = x; 655 | return pie; 656 | }; 657 | 658 | /** 659 | * Specifies a sort comparison operator *x*. The comparator is passed two data 660 | * elements from the data array, a and b; it returns a negative value if a is 661 | * less than b, a positive value if a is greater than b, and zero if a equals 662 | * b. 663 | */ 664 | pie.sort = function(x) { 665 | if (!arguments.length) return sort; 666 | sort = x; 667 | return pie; 668 | }; 669 | 670 | /** 671 | * Specifies the overall start angle of the pie chart. Defaults to 0. The 672 | * start angle can be specified either as a constant or as a function; in the 673 | * case of a function, it is evaluated once per array (as opposed to per 674 | * element). 675 | */ 676 | pie.startAngle = function(x) { 677 | if (!arguments.length) return startAngle; 678 | startAngle = x; 679 | return pie; 680 | }; 681 | 682 | /** 683 | * Specifies the overall end angle of the pie chart. Defaults to 2π. The 684 | * end angle can be specified either as a constant or as a function; in the 685 | * case of a function, it is evaluated once per array (as opposed to per 686 | * element). 687 | */ 688 | pie.endAngle = function(x) { 689 | if (!arguments.length) return endAngle; 690 | endAngle = x; 691 | return pie; 692 | }; 693 | 694 | return pie; 695 | }; 696 | // data is two-dimensional array of x,y; we populate y0 697 | d3.layout.stack = function() { 698 | var values = Object, 699 | order = d3_layout_stackOrders["default"], 700 | offset = d3_layout_stackOffsets["zero"], 701 | out = d3_layout_stackOut, 702 | x = d3_layout_stackX, 703 | y = d3_layout_stackY; 704 | 705 | function stack(data, index) { 706 | 707 | // Convert series to canonical two-dimensional representation. 708 | var series = data.map(function(d, i) { 709 | return values.call(stack, d, i); 710 | }); 711 | 712 | // Convert each series to canonical [[x,y]] representation. 713 | var points = series.map(function(d, i) { 714 | return d.map(function(v, i) { 715 | return [x.call(stack, v, i), y.call(stack, v, i)]; 716 | }); 717 | }); 718 | 719 | // Compute the order of series, and permute them. 720 | var orders = order.call(stack, points, index); 721 | series = d3.permute(series, orders); 722 | points = d3.permute(points, orders); 723 | 724 | // Compute the baseline… 725 | var offsets = offset.call(stack, points, index); 726 | 727 | // And propagate it to other series. 728 | var n = series.length, 729 | m = series[0].length, 730 | i, 731 | j, 732 | o; 733 | for (j = 0; j < m; ++j) { 734 | out.call(stack, series[0][j], o = offsets[j], points[0][j][1]); 735 | for (i = 1; i < n; ++i) { 736 | out.call(stack, series[i][j], o += points[i - 1][j][1], points[i][j][1]); 737 | } 738 | } 739 | 740 | return data; 741 | } 742 | 743 | stack.values = function(x) { 744 | if (!arguments.length) return values; 745 | values = x; 746 | return stack; 747 | }; 748 | 749 | stack.order = function(x) { 750 | if (!arguments.length) return order; 751 | order = typeof x === "function" ? x : d3_layout_stackOrders[x]; 752 | return stack; 753 | }; 754 | 755 | stack.offset = function(x) { 756 | if (!arguments.length) return offset; 757 | offset = typeof x === "function" ? x : d3_layout_stackOffsets[x]; 758 | return stack; 759 | }; 760 | 761 | stack.x = function(z) { 762 | if (!arguments.length) return x; 763 | x = z; 764 | return stack; 765 | }; 766 | 767 | stack.y = function(z) { 768 | if (!arguments.length) return y; 769 | y = z; 770 | return stack; 771 | }; 772 | 773 | stack.out = function(z) { 774 | if (!arguments.length) return out; 775 | out = z; 776 | return stack; 777 | }; 778 | 779 | return stack; 780 | } 781 | 782 | function d3_layout_stackX(d) { 783 | return d.x; 784 | } 785 | 786 | function d3_layout_stackY(d) { 787 | return d.y; 788 | } 789 | 790 | function d3_layout_stackOut(d, y0, y) { 791 | d.y0 = y0; 792 | d.y = y; 793 | } 794 | 795 | var d3_layout_stackOrders = { 796 | 797 | "inside-out": function(data) { 798 | var n = data.length, 799 | i, 800 | j, 801 | max = data.map(d3_layout_stackMaxIndex), 802 | sums = data.map(d3_layout_stackReduceSum), 803 | index = d3.range(n).sort(function(a, b) { return max[a] - max[b]; }), 804 | top = 0, 805 | bottom = 0, 806 | tops = [], 807 | bottoms = []; 808 | for (i = 0; i < n; ++i) { 809 | j = index[i]; 810 | if (top < bottom) { 811 | top += sums[j]; 812 | tops.push(j); 813 | } else { 814 | bottom += sums[j]; 815 | bottoms.push(j); 816 | } 817 | } 818 | return bottoms.reverse().concat(tops); 819 | }, 820 | 821 | "reverse": function(data) { 822 | return d3.range(data.length).reverse(); 823 | }, 824 | 825 | "default": function(data) { 826 | return d3.range(data.length); 827 | } 828 | 829 | }; 830 | 831 | var d3_layout_stackOffsets = { 832 | 833 | "silhouette": function(data) { 834 | var n = data.length, 835 | m = data[0].length, 836 | sums = [], 837 | max = 0, 838 | i, 839 | j, 840 | o, 841 | y0 = []; 842 | for (j = 0; j < m; ++j) { 843 | for (i = 0, o = 0; i < n; i++) o += data[i][j][1]; 844 | if (o > max) max = o; 845 | sums.push(o); 846 | } 847 | for (j = 0; j < m; ++j) { 848 | y0[j] = (max - sums[j]) / 2; 849 | } 850 | return y0; 851 | }, 852 | 853 | "wiggle": function(data) { 854 | var n = data.length, 855 | x = data[0], 856 | m = x.length, 857 | max = 0, 858 | i, 859 | j, 860 | k, 861 | s1, 862 | s2, 863 | s3, 864 | dx, 865 | o, 866 | o0, 867 | y0 = []; 868 | y0[0] = o = o0 = 0; 869 | for (j = 1; j < m; ++j) { 870 | for (i = 0, s1 = 0; i < n; ++i) s1 += data[i][j][1]; 871 | for (i = 0, s2 = 0, dx = x[j][0] - x[j - 1][0]; i < n; ++i) { 872 | for (k = 0, s3 = (data[i][j][1] - data[i][j - 1][1]) / (2 * dx); k < i; ++k) { 873 | s3 += (data[k][j][1] - data[k][j - 1][1]) / dx; 874 | } 875 | s2 += s3 * data[i][j][1]; 876 | } 877 | y0[j] = o -= s1 ? s2 / s1 * dx : 0; 878 | if (o < o0) o0 = o; 879 | } 880 | for (j = 0; j < m; ++j) y0[j] -= o0; 881 | return y0; 882 | }, 883 | 884 | "expand": function(data) { 885 | var n = data.length, 886 | m = data[0].length, 887 | k = 1 / n, 888 | i, 889 | j, 890 | o, 891 | y0 = []; 892 | for (j = 0; j < m; ++j) { 893 | for (i = 0, o = 0; i < n; i++) o += data[i][j][1]; 894 | if (o) for (i = 0; i < n; i++) data[i][j][1] /= o; 895 | else for (i = 0; i < n; i++) data[i][j][1] = k; 896 | } 897 | for (j = 0; j < m; ++j) y0[j] = 0; 898 | return y0; 899 | }, 900 | 901 | "zero": function(data) { 902 | var j = -1, 903 | m = data[0].length, 904 | y0 = []; 905 | while (++j < m) y0[j] = 0; 906 | return y0; 907 | } 908 | 909 | }; 910 | 911 | function d3_layout_stackMaxIndex(array) { 912 | var i = 1, 913 | j = 0, 914 | v = array[0][1], 915 | k, 916 | n = array.length; 917 | for (; i < n; ++i) { 918 | if ((k = array[i][1]) > v) { 919 | j = i; 920 | v = k; 921 | } 922 | } 923 | return j; 924 | } 925 | 926 | function d3_layout_stackReduceSum(d) { 927 | return d.reduce(d3_layout_stackSum, 0); 928 | } 929 | 930 | function d3_layout_stackSum(p, d) { 931 | return p + d[1]; 932 | } 933 | d3.layout.histogram = function() { 934 | var frequency = true, 935 | valuer = Number, 936 | ranger = d3_layout_histogramRange, 937 | binner = d3_layout_histogramBinSturges; 938 | 939 | function histogram(data, i) { 940 | var bins = [], 941 | values = data.map(valuer, this), 942 | range = ranger.call(this, values, i), 943 | thresholds = binner.call(this, range, values, i), 944 | bin, 945 | i = -1, 946 | n = values.length, 947 | m = thresholds.length - 1, 948 | k = frequency ? 1 : 1 / n, 949 | x; 950 | 951 | // Initialize the bins. 952 | while (++i < m) { 953 | bin = bins[i] = []; 954 | bin.dx = thresholds[i + 1] - (bin.x = thresholds[i]); 955 | bin.y = 0; 956 | } 957 | 958 | // Fill the bins, ignoring values outside the range. 959 | i = -1; while(++i < n) { 960 | x = values[i]; 961 | if ((x >= range[0]) && (x <= range[1])) { 962 | bin = bins[d3.bisect(thresholds, x, 1, m) - 1]; 963 | bin.y += k; 964 | bin.push(data[i]); 965 | } 966 | } 967 | 968 | return bins; 969 | } 970 | 971 | // Specifies how to extract a value from the associated data. The default 972 | // value function is `Number`, which is equivalent to the identity function. 973 | histogram.value = function(x) { 974 | if (!arguments.length) return valuer; 975 | valuer = x; 976 | return histogram; 977 | }; 978 | 979 | // Specifies the range of the histogram. Values outside the specified range 980 | // will be ignored. The argument `x` may be specified either as a two-element 981 | // array representing the minimum and maximum value of the range, or as a 982 | // function that returns the range given the array of values and the current 983 | // index `i`. The default range is the extent (minimum and maximum) of the 984 | // values. 985 | histogram.range = function(x) { 986 | if (!arguments.length) return ranger; 987 | ranger = d3.functor(x); 988 | return histogram; 989 | }; 990 | 991 | // Specifies how to bin values in the histogram. The argument `x` may be 992 | // specified as a number, in which case the range of values will be split 993 | // uniformly into the given number of bins. Or, `x` may be an array of 994 | // threshold values, defining the bins; the specified array must contain the 995 | // rightmost (upper) value, thus specifying n + 1 values for n bins. Or, `x` 996 | // may be a function which is evaluated, being passed the range, the array of 997 | // values, and the current index `i`, returning an array of thresholds. The 998 | // default bin function will divide the values into uniform bins using 999 | // Sturges' formula. 1000 | histogram.bins = function(x) { 1001 | if (!arguments.length) return binner; 1002 | binner = typeof x === "number" 1003 | ? function(range) { return d3_layout_histogramBinFixed(range, x); } 1004 | : d3.functor(x); 1005 | return histogram; 1006 | }; 1007 | 1008 | // Specifies whether the histogram's `y` value is a count (frequency) or a 1009 | // probability (density). The default value is true. 1010 | histogram.frequency = function(x) { 1011 | if (!arguments.length) return frequency; 1012 | frequency = !!x; 1013 | return histogram; 1014 | }; 1015 | 1016 | return histogram; 1017 | }; 1018 | 1019 | function d3_layout_histogramBinSturges(range, values) { 1020 | return d3_layout_histogramBinFixed(range, Math.ceil(Math.log(values.length) / Math.LN2 + 1)); 1021 | } 1022 | 1023 | function d3_layout_histogramBinFixed(range, n) { 1024 | var x = -1, 1025 | b = +range[0], 1026 | m = (range[1] - b) / n, 1027 | f = []; 1028 | while (++x <= n) f[x] = m * x + b; 1029 | return f; 1030 | } 1031 | 1032 | function d3_layout_histogramRange(values) { 1033 | return [d3.min(values), d3.max(values)]; 1034 | } 1035 | d3.layout.hierarchy = function() { 1036 | var sort = d3_layout_hierarchySort, 1037 | children = d3_layout_hierarchyChildren, 1038 | value = d3_layout_hierarchyValue; 1039 | 1040 | // Recursively compute the node depth and value. 1041 | // Also converts the data representation into a standard hierarchy structure. 1042 | function recurse(data, depth, nodes) { 1043 | var childs = children.call(hierarchy, data, depth), 1044 | node = d3_layout_hierarchyInline ? data : {data: data}; 1045 | node.depth = depth; 1046 | nodes.push(node); 1047 | if (childs && (n = childs.length)) { 1048 | var i = -1, 1049 | n, 1050 | c = node.children = [], 1051 | v = 0, 1052 | j = depth + 1; 1053 | while (++i < n) { 1054 | d = recurse(childs[i], j, nodes); 1055 | d.parent = node; 1056 | c.push(d); 1057 | v += d.value; 1058 | } 1059 | if (sort) c.sort(sort); 1060 | if (value) node.value = v; 1061 | } else if (value) { 1062 | node.value = +value.call(hierarchy, data, depth) || 0; 1063 | } 1064 | return node; 1065 | } 1066 | 1067 | // Recursively re-evaluates the node value. 1068 | function revalue(node, depth) { 1069 | var children = node.children, 1070 | v = 0; 1071 | if (children && (n = children.length)) { 1072 | var i = -1, 1073 | n, 1074 | j = depth + 1; 1075 | while (++i < n) v += revalue(children[i], j); 1076 | } else if (value) { 1077 | v = +value.call(hierarchy, d3_layout_hierarchyInline ? node : node.data, depth) || 0; 1078 | } 1079 | if (value) node.value = v; 1080 | return v; 1081 | } 1082 | 1083 | function hierarchy(d) { 1084 | var nodes = []; 1085 | recurse(d, 0, nodes); 1086 | return nodes; 1087 | } 1088 | 1089 | hierarchy.sort = function(x) { 1090 | if (!arguments.length) return sort; 1091 | sort = x; 1092 | return hierarchy; 1093 | }; 1094 | 1095 | hierarchy.children = function(x) { 1096 | if (!arguments.length) return children; 1097 | children = x; 1098 | return hierarchy; 1099 | }; 1100 | 1101 | hierarchy.value = function(x) { 1102 | if (!arguments.length) return value; 1103 | value = x; 1104 | return hierarchy; 1105 | }; 1106 | 1107 | // Re-evaluates the `value` property for the specified hierarchy. 1108 | hierarchy.revalue = function(root) { 1109 | revalue(root, 0); 1110 | return root; 1111 | }; 1112 | 1113 | return hierarchy; 1114 | }; 1115 | 1116 | // A method assignment helper for hierarchy subclasses. 1117 | function d3_layout_hierarchyRebind(object, hierarchy) { 1118 | object.sort = d3.rebind(object, hierarchy.sort); 1119 | object.children = d3.rebind(object, hierarchy.children); 1120 | object.links = d3_layout_hierarchyLinks; 1121 | object.value = d3.rebind(object, hierarchy.value); 1122 | 1123 | // If the new API is used, enabling inlining. 1124 | object.nodes = function(d) { 1125 | d3_layout_hierarchyInline = true; 1126 | return (object.nodes = object)(d); 1127 | }; 1128 | 1129 | return object; 1130 | } 1131 | 1132 | function d3_layout_hierarchyChildren(d) { 1133 | return d.children; 1134 | } 1135 | 1136 | function d3_layout_hierarchyValue(d) { 1137 | return d.value; 1138 | } 1139 | 1140 | function d3_layout_hierarchySort(a, b) { 1141 | return b.value - a.value; 1142 | } 1143 | 1144 | // Returns an array source+target objects for the specified nodes. 1145 | function d3_layout_hierarchyLinks(nodes) { 1146 | return d3.merge(nodes.map(function(parent) { 1147 | return (parent.children || []).map(function(child) { 1148 | return {source: parent, target: child}; 1149 | }); 1150 | })); 1151 | } 1152 | 1153 | // For backwards-compatibility, don't enable inlining by default. 1154 | var d3_layout_hierarchyInline = false; 1155 | d3.layout.pack = function() { 1156 | var hierarchy = d3.layout.hierarchy().sort(d3_layout_packSort), 1157 | size = [1, 1]; 1158 | 1159 | function pack(d, i) { 1160 | var nodes = hierarchy.call(this, d, i), 1161 | root = nodes[0]; 1162 | 1163 | // Recursively compute the layout. 1164 | root.x = 0; 1165 | root.y = 0; 1166 | d3_layout_packTree(root); 1167 | 1168 | // Scale the layout to fit the requested size. 1169 | var w = size[0], 1170 | h = size[1], 1171 | k = 1 / Math.max(2 * root.r / w, 2 * root.r / h); 1172 | d3_layout_packTransform(root, w / 2, h / 2, k); 1173 | 1174 | return nodes; 1175 | } 1176 | 1177 | pack.size = function(x) { 1178 | if (!arguments.length) return size; 1179 | size = x; 1180 | return pack; 1181 | }; 1182 | 1183 | return d3_layout_hierarchyRebind(pack, hierarchy); 1184 | }; 1185 | 1186 | function d3_layout_packSort(a, b) { 1187 | return a.value - b.value; 1188 | } 1189 | 1190 | function d3_layout_packInsert(a, b) { 1191 | var c = a._pack_next; 1192 | a._pack_next = b; 1193 | b._pack_prev = a; 1194 | b._pack_next = c; 1195 | c._pack_prev = b; 1196 | } 1197 | 1198 | function d3_layout_packSplice(a, b) { 1199 | a._pack_next = b; 1200 | b._pack_prev = a; 1201 | } 1202 | 1203 | function d3_layout_packIntersects(a, b) { 1204 | var dx = b.x - a.x, 1205 | dy = b.y - a.y, 1206 | dr = a.r + b.r; 1207 | return (dr * dr - dx * dx - dy * dy) > .001; // within epsilon 1208 | } 1209 | 1210 | function d3_layout_packCircle(nodes) { 1211 | var xMin = Infinity, 1212 | xMax = -Infinity, 1213 | yMin = Infinity, 1214 | yMax = -Infinity, 1215 | n = nodes.length, 1216 | a, b, c, j, k; 1217 | 1218 | function bound(node) { 1219 | xMin = Math.min(node.x - node.r, xMin); 1220 | xMax = Math.max(node.x + node.r, xMax); 1221 | yMin = Math.min(node.y - node.r, yMin); 1222 | yMax = Math.max(node.y + node.r, yMax); 1223 | } 1224 | 1225 | // Create node links. 1226 | nodes.forEach(d3_layout_packLink); 1227 | 1228 | // Create first node. 1229 | a = nodes[0]; 1230 | a.x = -a.r; 1231 | a.y = 0; 1232 | bound(a); 1233 | 1234 | // Create second node. 1235 | if (n > 1) { 1236 | b = nodes[1]; 1237 | b.x = b.r; 1238 | b.y = 0; 1239 | bound(b); 1240 | 1241 | // Create third node and build chain. 1242 | if (n > 2) { 1243 | c = nodes[2]; 1244 | d3_layout_packPlace(a, b, c); 1245 | bound(c); 1246 | d3_layout_packInsert(a, c); 1247 | a._pack_prev = c; 1248 | d3_layout_packInsert(c, b); 1249 | b = a._pack_next; 1250 | 1251 | // Now iterate through the rest. 1252 | for (var i = 3; i < n; i++) { 1253 | d3_layout_packPlace(a, b, c = nodes[i]); 1254 | 1255 | // Search for the closest intersection. 1256 | var isect = 0, s1 = 1, s2 = 1; 1257 | for (j = b._pack_next; j !== b; j = j._pack_next, s1++) { 1258 | if (d3_layout_packIntersects(j, c)) { 1259 | isect = 1; 1260 | break; 1261 | } 1262 | } 1263 | if (isect == 1) { 1264 | for (k = a._pack_prev; k !== j._pack_prev; k = k._pack_prev, s2++) { 1265 | if (d3_layout_packIntersects(k, c)) { 1266 | if (s2 < s1) { 1267 | isect = -1; 1268 | j = k; 1269 | } 1270 | break; 1271 | } 1272 | } 1273 | } 1274 | 1275 | // Update node chain. 1276 | if (isect == 0) { 1277 | d3_layout_packInsert(a, c); 1278 | b = c; 1279 | bound(c); 1280 | } else if (isect > 0) { 1281 | d3_layout_packSplice(a, j); 1282 | b = j; 1283 | i--; 1284 | } else { // isect < 0 1285 | d3_layout_packSplice(j, b); 1286 | a = j; 1287 | i--; 1288 | } 1289 | } 1290 | } 1291 | } 1292 | 1293 | // Re-center the circles and return the encompassing radius. 1294 | var cx = (xMin + xMax) / 2, 1295 | cy = (yMin + yMax) / 2, 1296 | cr = 0; 1297 | for (var i = 0; i < n; i++) { 1298 | var node = nodes[i]; 1299 | node.x -= cx; 1300 | node.y -= cy; 1301 | cr = Math.max(cr, node.r + Math.sqrt(node.x * node.x + node.y * node.y)); 1302 | } 1303 | 1304 | // Remove node links. 1305 | nodes.forEach(d3_layout_packUnlink); 1306 | 1307 | return cr; 1308 | } 1309 | 1310 | function d3_layout_packLink(node) { 1311 | node._pack_next = node._pack_prev = node; 1312 | } 1313 | 1314 | function d3_layout_packUnlink(node) { 1315 | delete node._pack_next; 1316 | delete node._pack_prev; 1317 | } 1318 | 1319 | function d3_layout_packTree(node) { 1320 | var children = node.children; 1321 | if (children && children.length) { 1322 | children.forEach(d3_layout_packTree); 1323 | node.r = d3_layout_packCircle(children); 1324 | } else { 1325 | node.r = Math.sqrt(node.value); 1326 | } 1327 | } 1328 | 1329 | function d3_layout_packTransform(node, x, y, k) { 1330 | var children = node.children; 1331 | node.x = (x += k * node.x); 1332 | node.y = (y += k * node.y); 1333 | node.r *= k; 1334 | if (children) { 1335 | var i = -1, n = children.length; 1336 | while (++i < n) d3_layout_packTransform(children[i], x, y, k); 1337 | } 1338 | } 1339 | 1340 | function d3_layout_packPlace(a, b, c) { 1341 | var db = a.r + c.r, 1342 | dx = b.x - a.x, 1343 | dy = b.y - a.y; 1344 | if (db && (dx || dy)) { 1345 | var da = b.r + c.r, 1346 | dc = Math.sqrt(dx * dx + dy * dy), 1347 | cos = Math.max(-1, Math.min(1, (db * db + dc * dc - da * da) / (2 * db * dc))), 1348 | theta = Math.acos(cos), 1349 | x = cos * (db /= dc), 1350 | y = Math.sin(theta) * db; 1351 | c.x = a.x + x * dx + y * dy; 1352 | c.y = a.y + x * dy - y * dx; 1353 | } else { 1354 | c.x = a.x + db; 1355 | c.y = a.y; 1356 | } 1357 | } 1358 | // Implements a hierarchical layout using the cluster (or dendogram) algorithm. 1359 | d3.layout.cluster = function() { 1360 | var hierarchy = d3.layout.hierarchy().sort(null).value(null), 1361 | separation = d3_layout_treeSeparation, 1362 | size = [1, 1]; // width, height 1363 | 1364 | function cluster(d, i) { 1365 | var nodes = hierarchy.call(this, d, i), 1366 | root = nodes[0], 1367 | previousNode, 1368 | x = 0, 1369 | kx, 1370 | ky; 1371 | 1372 | // First walk, computing the initial x & y values. 1373 | d3_layout_treeVisitAfter(root, function(node) { 1374 | var children = node.children; 1375 | if (children && children.length) { 1376 | node.x = d3_layout_clusterX(children); 1377 | node.y = d3_layout_clusterY(children); 1378 | } else { 1379 | node.x = previousNode ? x += separation(node, previousNode) : 0; 1380 | node.y = 0; 1381 | previousNode = node; 1382 | } 1383 | }); 1384 | 1385 | // Compute the left-most, right-most, and depth-most nodes for extents. 1386 | var left = d3_layout_clusterLeft(root), 1387 | right = d3_layout_clusterRight(root), 1388 | x0 = left.x - separation(left, right) / 2, 1389 | x1 = right.x + separation(right, left) / 2; 1390 | 1391 | // Second walk, normalizing x & y to the desired size. 1392 | d3_layout_treeVisitAfter(root, function(node) { 1393 | node.x = (node.x - x0) / (x1 - x0) * size[0]; 1394 | node.y = (1 - node.y / root.y) * size[1]; 1395 | }); 1396 | 1397 | return nodes; 1398 | } 1399 | 1400 | cluster.separation = function(x) { 1401 | if (!arguments.length) return separation; 1402 | separation = x; 1403 | return cluster; 1404 | }; 1405 | 1406 | cluster.size = function(x) { 1407 | if (!arguments.length) return size; 1408 | size = x; 1409 | return cluster; 1410 | }; 1411 | 1412 | return d3_layout_hierarchyRebind(cluster, hierarchy); 1413 | }; 1414 | 1415 | function d3_layout_clusterY(children) { 1416 | return 1 + d3.max(children, function(child) { 1417 | return child.y; 1418 | }); 1419 | } 1420 | 1421 | function d3_layout_clusterX(children) { 1422 | return children.reduce(function(x, child) { 1423 | return x + child.x; 1424 | }, 0) / children.length; 1425 | } 1426 | 1427 | function d3_layout_clusterLeft(node) { 1428 | var children = node.children; 1429 | return children && children.length ? d3_layout_clusterLeft(children[0]) : node; 1430 | } 1431 | 1432 | function d3_layout_clusterRight(node) { 1433 | var children = node.children, n; 1434 | return children && (n = children.length) ? d3_layout_clusterRight(children[n - 1]) : node; 1435 | } 1436 | // Node-link tree diagram using the Reingold-Tilford "tidy" algorithm 1437 | d3.layout.tree = function() { 1438 | var hierarchy = d3.layout.hierarchy().sort(null).value(null), 1439 | separation = d3_layout_treeSeparation, 1440 | size = [1, 1]; // width, height 1441 | 1442 | function tree(d, i) { 1443 | var nodes = hierarchy.call(this, d, i), 1444 | root = nodes[0]; 1445 | 1446 | function firstWalk(node, previousSibling) { 1447 | var children = node.children, 1448 | layout = node._tree; 1449 | if (children && (n = children.length)) { 1450 | var n, 1451 | firstChild = children[0], 1452 | previousChild, 1453 | ancestor = firstChild, 1454 | child, 1455 | i = -1; 1456 | while (++i < n) { 1457 | child = children[i]; 1458 | firstWalk(child, previousChild); 1459 | ancestor = apportion(child, previousChild, ancestor); 1460 | previousChild = child; 1461 | } 1462 | d3_layout_treeShift(node); 1463 | var midpoint = .5 * (firstChild._tree.prelim + child._tree.prelim); 1464 | if (previousSibling) { 1465 | layout.prelim = previousSibling._tree.prelim + separation(node, previousSibling); 1466 | layout.mod = layout.prelim - midpoint; 1467 | } else { 1468 | layout.prelim = midpoint; 1469 | } 1470 | } else { 1471 | if (previousSibling) { 1472 | layout.prelim = previousSibling._tree.prelim + separation(node, previousSibling); 1473 | } 1474 | } 1475 | } 1476 | 1477 | function secondWalk(node, x) { 1478 | node.x = node._tree.prelim + x; 1479 | var children = node.children; 1480 | if (children && (n = children.length)) { 1481 | var i = -1, 1482 | n; 1483 | x += node._tree.mod; 1484 | while (++i < n) { 1485 | secondWalk(children[i], x); 1486 | } 1487 | } 1488 | } 1489 | 1490 | function apportion(node, previousSibling, ancestor) { 1491 | if (previousSibling) { 1492 | var vip = node, 1493 | vop = node, 1494 | vim = previousSibling, 1495 | vom = node.parent.children[0], 1496 | sip = vip._tree.mod, 1497 | sop = vop._tree.mod, 1498 | sim = vim._tree.mod, 1499 | som = vom._tree.mod, 1500 | shift; 1501 | while (vim = d3_layout_treeRight(vim), vip = d3_layout_treeLeft(vip), vim && vip) { 1502 | vom = d3_layout_treeLeft(vom); 1503 | vop = d3_layout_treeRight(vop); 1504 | vop._tree.ancestor = node; 1505 | shift = vim._tree.prelim + sim - vip._tree.prelim - sip + separation(vim, vip); 1506 | if (shift > 0) { 1507 | d3_layout_treeMove(d3_layout_treeAncestor(vim, node, ancestor), node, shift); 1508 | sip += shift; 1509 | sop += shift; 1510 | } 1511 | sim += vim._tree.mod; 1512 | sip += vip._tree.mod; 1513 | som += vom._tree.mod; 1514 | sop += vop._tree.mod; 1515 | } 1516 | if (vim && !d3_layout_treeRight(vop)) { 1517 | vop._tree.thread = vim; 1518 | vop._tree.mod += sim - sop; 1519 | } 1520 | if (vip && !d3_layout_treeLeft(vom)) { 1521 | vom._tree.thread = vip; 1522 | vom._tree.mod += sip - som; 1523 | ancestor = node; 1524 | } 1525 | } 1526 | return ancestor; 1527 | } 1528 | 1529 | // Initialize temporary layout variables. 1530 | d3_layout_treeVisitAfter(root, function(node, previousSibling) { 1531 | node._tree = { 1532 | ancestor: node, 1533 | prelim: 0, 1534 | mod: 0, 1535 | change: 0, 1536 | shift: 0, 1537 | number: previousSibling ? previousSibling._tree.number + 1 : 0 1538 | }; 1539 | }); 1540 | 1541 | // Compute the layout using Buchheim et al.'s algorithm. 1542 | firstWalk(root); 1543 | secondWalk(root, -root._tree.prelim); 1544 | 1545 | // Compute the left-most, right-most, and depth-most nodes for extents. 1546 | var left = d3_layout_treeSearch(root, d3_layout_treeLeftmost), 1547 | right = d3_layout_treeSearch(root, d3_layout_treeRightmost), 1548 | deep = d3_layout_treeSearch(root, d3_layout_treeDeepest), 1549 | x0 = left.x - separation(left, right) / 2, 1550 | x1 = right.x + separation(right, left) / 2, 1551 | y1 = deep.depth || 1; 1552 | 1553 | // Clear temporary layout variables; transform x and y. 1554 | d3_layout_treeVisitAfter(root, function(node) { 1555 | node.x = (node.x - x0) / (x1 - x0) * size[0]; 1556 | node.y = node.depth / y1 * size[1]; 1557 | delete node._tree; 1558 | }); 1559 | 1560 | return nodes; 1561 | } 1562 | 1563 | tree.separation = function(x) { 1564 | if (!arguments.length) return separation; 1565 | separation = x; 1566 | return tree; 1567 | }; 1568 | 1569 | tree.size = function(x) { 1570 | if (!arguments.length) return size; 1571 | size = x; 1572 | return tree; 1573 | }; 1574 | 1575 | return d3_layout_hierarchyRebind(tree, hierarchy); 1576 | }; 1577 | 1578 | function d3_layout_treeSeparation(a, b) { 1579 | return a.parent == b.parent ? 1 : 2; 1580 | } 1581 | 1582 | // function d3_layout_treeSeparationRadial(a, b) { 1583 | // return (a.parent == b.parent ? 1 : 2) / a.depth; 1584 | // } 1585 | 1586 | function d3_layout_treeLeft(node) { 1587 | var children = node.children; 1588 | return children && children.length ? children[0] : node._tree.thread; 1589 | } 1590 | 1591 | function d3_layout_treeRight(node) { 1592 | var children = node.children, 1593 | n; 1594 | return children && (n = children.length) ? children[n - 1] : node._tree.thread; 1595 | } 1596 | 1597 | function d3_layout_treeSearch(node, compare) { 1598 | var children = node.children; 1599 | if (children && (n = children.length)) { 1600 | var child, 1601 | n, 1602 | i = -1; 1603 | while (++i < n) { 1604 | if (compare(child = d3_layout_treeSearch(children[i], compare), node) > 0) { 1605 | node = child; 1606 | } 1607 | } 1608 | } 1609 | return node; 1610 | } 1611 | 1612 | function d3_layout_treeRightmost(a, b) { 1613 | return a.x - b.x; 1614 | } 1615 | 1616 | function d3_layout_treeLeftmost(a, b) { 1617 | return b.x - a.x; 1618 | } 1619 | 1620 | function d3_layout_treeDeepest(a, b) { 1621 | return a.depth - b.depth; 1622 | } 1623 | 1624 | function d3_layout_treeVisitAfter(node, callback) { 1625 | function visit(node, previousSibling) { 1626 | var children = node.children; 1627 | if (children && (n = children.length)) { 1628 | var child, 1629 | previousChild = null, 1630 | i = -1, 1631 | n; 1632 | while (++i < n) { 1633 | child = children[i]; 1634 | visit(child, previousChild); 1635 | previousChild = child; 1636 | } 1637 | } 1638 | callback(node, previousSibling); 1639 | } 1640 | visit(node, null); 1641 | } 1642 | 1643 | function d3_layout_treeShift(node) { 1644 | var shift = 0, 1645 | change = 0, 1646 | children = node.children, 1647 | i = children.length, 1648 | child; 1649 | while (--i >= 0) { 1650 | child = children[i]._tree; 1651 | child.prelim += shift; 1652 | child.mod += shift; 1653 | shift += child.shift + (change += child.change); 1654 | } 1655 | } 1656 | 1657 | function d3_layout_treeMove(ancestor, node, shift) { 1658 | ancestor = ancestor._tree; 1659 | node = node._tree; 1660 | var change = shift / (node.number - ancestor.number); 1661 | ancestor.change += change; 1662 | node.change -= change; 1663 | node.shift += shift; 1664 | node.prelim += shift; 1665 | node.mod += shift; 1666 | } 1667 | 1668 | function d3_layout_treeAncestor(vim, node, ancestor) { 1669 | return vim._tree.ancestor.parent == node.parent 1670 | ? vim._tree.ancestor 1671 | : ancestor; 1672 | } 1673 | // Squarified Treemaps by Mark Bruls, Kees Huizing, and Jarke J. van Wijk 1674 | // Modified to support a target aspect ratio by Jeff Heer 1675 | d3.layout.treemap = function() { 1676 | var hierarchy = d3.layout.hierarchy(), 1677 | round = Math.round, 1678 | size = [1, 1], // width, height 1679 | padding = null, 1680 | pad = d3_layout_treemapPadNull, 1681 | sticky = false, 1682 | stickies, 1683 | ratio = 0.5 * (1 + Math.sqrt(5)); // golden ratio 1684 | 1685 | // Compute the area for each child based on value & scale. 1686 | function scale(children, k) { 1687 | var i = -1, 1688 | n = children.length, 1689 | child, 1690 | area; 1691 | while (++i < n) { 1692 | area = (child = children[i]).value * (k < 0 ? 0 : k); 1693 | child.area = isNaN(area) || area <= 0 ? 0 : area; 1694 | } 1695 | } 1696 | 1697 | // Recursively arranges the specified node's children into squarified rows. 1698 | function squarify(node) { 1699 | var children = node.children; 1700 | if (children && children.length) { 1701 | var rect = pad(node), 1702 | row = [], 1703 | remaining = children.slice(), // copy-on-write 1704 | child, 1705 | best = Infinity, // the best row score so far 1706 | score, // the current row score 1707 | u = Math.min(rect.dx, rect.dy), // initial orientation 1708 | n; 1709 | scale(remaining, rect.dx * rect.dy / node.value); 1710 | row.area = 0; 1711 | while ((n = remaining.length) > 0) { 1712 | row.push(child = remaining[n - 1]); 1713 | row.area += child.area; 1714 | if ((score = worst(row, u)) <= best) { // continue with this orientation 1715 | remaining.pop(); 1716 | best = score; 1717 | } else { // abort, and try a different orientation 1718 | row.area -= row.pop().area; 1719 | position(row, u, rect, false); 1720 | u = Math.min(rect.dx, rect.dy); 1721 | row.length = row.area = 0; 1722 | best = Infinity; 1723 | } 1724 | } 1725 | if (row.length) { 1726 | position(row, u, rect, true); 1727 | row.length = row.area = 0; 1728 | } 1729 | children.forEach(squarify); 1730 | } 1731 | } 1732 | 1733 | // Recursively resizes the specified node's children into existing rows. 1734 | // Preserves the existing layout! 1735 | function stickify(node) { 1736 | var children = node.children; 1737 | if (children && children.length) { 1738 | var rect = pad(node), 1739 | remaining = children.slice(), // copy-on-write 1740 | child, 1741 | row = []; 1742 | scale(remaining, rect.dx * rect.dy / node.value); 1743 | row.area = 0; 1744 | while (child = remaining.pop()) { 1745 | row.push(child); 1746 | row.area += child.area; 1747 | if (child.z != null) { 1748 | position(row, child.z ? rect.dx : rect.dy, rect, !remaining.length); 1749 | row.length = row.area = 0; 1750 | } 1751 | } 1752 | children.forEach(stickify); 1753 | } 1754 | } 1755 | 1756 | // Computes the score for the specified row, as the worst aspect ratio. 1757 | function worst(row, u) { 1758 | var s = row.area, 1759 | r, 1760 | rmax = 0, 1761 | rmin = Infinity, 1762 | i = -1, 1763 | n = row.length; 1764 | while (++i < n) { 1765 | if (!(r = row[i].area)) continue; 1766 | if (r < rmin) rmin = r; 1767 | if (r > rmax) rmax = r; 1768 | } 1769 | s *= s; 1770 | u *= u; 1771 | return s 1772 | ? Math.max((u * rmax * ratio) / s, s / (u * rmin * ratio)) 1773 | : Infinity; 1774 | } 1775 | 1776 | // Positions the specified row of nodes. Modifies `rect`. 1777 | function position(row, u, rect, flush) { 1778 | var i = -1, 1779 | n = row.length, 1780 | x = rect.x, 1781 | y = rect.y, 1782 | v = u ? round(row.area / u) : 0, 1783 | o; 1784 | if (u == rect.dx) { // horizontal subdivision 1785 | if (flush || v > rect.dy) v = v ? rect.dy : 0; // over+underflow 1786 | while (++i < n) { 1787 | o = row[i]; 1788 | o.x = x; 1789 | o.y = y; 1790 | o.dy = v; 1791 | x += o.dx = v ? round(o.area / v) : 0; 1792 | } 1793 | o.z = true; 1794 | o.dx += rect.x + rect.dx - x; // rounding error 1795 | rect.y += v; 1796 | rect.dy -= v; 1797 | } else { // vertical subdivision 1798 | if (flush || v > rect.dx) v = v ? rect.dx : 0; // over+underflow 1799 | while (++i < n) { 1800 | o = row[i]; 1801 | o.x = x; 1802 | o.y = y; 1803 | o.dx = v; 1804 | y += o.dy = v ? round(o.area / v) : 0; 1805 | } 1806 | o.z = false; 1807 | o.dy += rect.y + rect.dy - y; // rounding error 1808 | rect.x += v; 1809 | rect.dx -= v; 1810 | } 1811 | } 1812 | 1813 | function treemap(d) { 1814 | var nodes = stickies || hierarchy(d), 1815 | root = nodes[0]; 1816 | root.x = 0; 1817 | root.y = 0; 1818 | root.dx = size[0]; 1819 | root.dy = size[1]; 1820 | if (stickies) hierarchy.revalue(root); 1821 | scale([root], root.dx * root.dy / root.value); 1822 | (stickies ? stickify : squarify)(root); 1823 | if (sticky) stickies = nodes; 1824 | return nodes; 1825 | } 1826 | 1827 | treemap.size = function(x) { 1828 | if (!arguments.length) return size; 1829 | size = x; 1830 | return treemap; 1831 | }; 1832 | 1833 | treemap.padding = function(x) { 1834 | if (!arguments.length) return padding; 1835 | 1836 | function padFunction(node) { 1837 | var p = x.call(treemap, node, node.depth); 1838 | return p == null 1839 | ? d3_layout_treemapPadNull(node) 1840 | : d3_layout_treemapPad(node, typeof p === "number" ? [p, p, p, p] : p); 1841 | } 1842 | 1843 | function padConstant(node) { 1844 | return d3_layout_treemapPad(node, x); 1845 | } 1846 | 1847 | var type; 1848 | pad = (padding = x) == null ? d3_layout_treemapPadNull 1849 | : (type = typeof x) === "function" ? padFunction 1850 | : type === "number" ? (x = [x, x, x, x], padConstant) 1851 | : padConstant; 1852 | return treemap; 1853 | }; 1854 | 1855 | treemap.round = function(x) { 1856 | if (!arguments.length) return round != Number; 1857 | round = x ? Math.round : Number; 1858 | return treemap; 1859 | }; 1860 | 1861 | treemap.sticky = function(x) { 1862 | if (!arguments.length) return sticky; 1863 | sticky = x; 1864 | stickies = null; 1865 | return treemap; 1866 | }; 1867 | 1868 | treemap.ratio = function(x) { 1869 | if (!arguments.length) return ratio; 1870 | ratio = x; 1871 | return treemap; 1872 | }; 1873 | 1874 | return d3_layout_hierarchyRebind(treemap, hierarchy); 1875 | }; 1876 | 1877 | function d3_layout_treemapPadNull(node) { 1878 | return {x: node.x, y: node.y, dx: node.dx, dy: node.dy}; 1879 | } 1880 | 1881 | function d3_layout_treemapPad(node, padding) { 1882 | var x = node.x + padding[3], 1883 | y = node.y + padding[0], 1884 | dx = node.dx - padding[1] - padding[3], 1885 | dy = node.dy - padding[0] - padding[2]; 1886 | if (dx < 0) { x += dx / 2; dx = 0; } 1887 | if (dy < 0) { y += dy / 2; dy = 0; } 1888 | return {x: x, y: y, dx: dx, dy: dy}; 1889 | } 1890 | })(); 1891 | -------------------------------------------------------------------------------- /iOSDeveloperRoadMap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fzhlee/ios-developer-roadmap/ca053742e40bc9666ac3e5fc7d90060ad2939aee/iOSDeveloperRoadMap.png -------------------------------------------------------------------------------- /iOSMap.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fzhlee/ios-developer-roadmap/ca053742e40bc9666ac3e5fc7d90060ad2939aee/iOSMap.jpg -------------------------------------------------------------------------------- /route.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 27 | iOS开发学习路线 - 互动教程网 28 | 29 | 30 | 31 | 32 | 33 | 34 |
35 | 36 |
37 | 178 | 179 | 180 | -------------------------------------------------------------------------------- /route.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "iOS开发学习路线", 3 | "children": [ 4 | { 5 | "name": "基本知识", 6 | "children": [ 7 | { 8 | "name": "常用网址", 9 | "children": [ 10 | { 11 | "name": "官方网址", 12 | "children": [ 13 | { 14 | "name": "开发者账号管理平台:https://developer.apple.com/" 15 | }, 16 | { 17 | "name": "Xcode下载地址:https://developer.apple.com/download/more/" 18 | }, 19 | { 20 | "name": "app管理平台:https://appstoreconnect.apple.com" 21 | } 22 | ] 23 | }, 24 | { 25 | "name": "第三方网址", 26 | "children": [ 27 | { 28 | "name": "Xcode互动教程:https://itunes.apple.com/cn/app/id1063100471" 29 | }, 30 | { 31 | "name": "Swift互动教程:https://itunes.apple.com/cn/app/id1320746678" 32 | }, 33 | { 34 | "name": "iOS开发中的神兵利器:https://itunes.apple.com/cn/app/id1209739676" 35 | }, 36 | { 37 | "name": "Objective-C应用开发互动教程:https://itunes.apple.com/cn/app/id838877136" 38 | }, 39 | { 40 | "name": "iOS开发1000题:https://github.com/fzhlee/iOS-1000-" 41 | } 42 | ] 43 | } 44 | ] 45 | }, 46 | { 47 | "name": "开发者账号类型", 48 | "children": [ 49 | { 50 | "name": "个人账号Individual:", 51 | "children": [ 52 | { 53 | "name": "99美元一年" 54 | }, 55 | { 56 | "name": "可以上架AppStore供全世界下载" 57 | }, 58 | { 59 | "name": "最多支持100台iOS测试设备" 60 | } 61 | ] 62 | }, 63 | { 64 | "name": "公司账号Company:", 65 | "children": [ 66 | { 67 | "name": "99美元一年" 68 | }, 69 | { 70 | "name": "需要填写公司的邓白氏编码" 71 | }, 72 | { 73 | "name": "可以上架AppStore供全世界下载" 74 | }, 75 | { 76 | "name": "最多支持100台iOS测试设备" 77 | }, 78 | { 79 | "name": "允许多个开发者协作开发" 80 | } 81 | ] 82 | }, 83 | { 84 | "name": "企业账号Enterprise:", 85 | "children": [ 86 | { 87 | "name": "299美元一年" 88 | }, 89 | { 90 | "name": "无法可以上架AppStore" 91 | }, 92 | { 93 | "name": "需要填写公司的邓白氏编码" 94 | }, 95 | { 96 | "name": "企业内部应用,不限制iOS安装设备的数量" 97 | } 98 | ] 99 | } 100 | ] 101 | }, 102 | { 103 | "name": "开发文件", 104 | "children": [ 105 | { 106 | "name": "Swift语言", 107 | "children": [ 108 | { 109 | "name": ".swift文件:源代码文件,属于Swift语言。" 110 | } 111 | ] 112 | }, 113 | { 114 | "name": "Objective-C语言", 115 | "children": [ 116 | { 117 | "name": ".h文件:头文件,包含类、函数和常数的声明,属于Objective-C语言。" 118 | }, 119 | { 120 | "name": ".m文件:源代码文件,用来实现头文件中的声明,属于Objective-C语言。" 121 | }, 122 | { 123 | "name": ".mm文件:源代码文件,可以包含Objective-C、C和C++代码。" 124 | }, 125 | { 126 | "name": ".cpp文件:源代码文件,主要存储C++代码。" 127 | } 128 | ] 129 | } 130 | 131 | ] 132 | }, 133 | { 134 | "name": "软件环境", 135 | "children": [ 136 | { 137 | "name": "开发软件", 138 | "children":[ 139 | { 140 | "name": "开发软件:Xcode" 141 | } 142 | ] 143 | }, 144 | { 145 | "name": "开发软件", 146 | "children":[ 147 | { 148 | "name": "开发语言:Objective-C:传统的iOS开发语言。" 149 | }, 150 | { 151 | "name": "开发语言:Swift:新推出的iOS开发语言,强烈推荐学习和使用!" 152 | } 153 | ] 154 | } 155 | 156 | ] 157 | }, 158 | { 159 | "name": "硬件环境", 160 | "children": [ 161 | { 162 | "name": "开发环境", 163 | "children": [ 164 | { 165 | "name": "iMac或iMac Pro:固定位置办公、大屏提高编码体验" 166 | }, 167 | { 168 | "name": "Macbook Air 或 Pro:方便移动工作、屏幕较小" 169 | }, 170 | { 171 | "name": "Mac Mini:成本较低" 172 | } 173 | ] 174 | }, 175 | { 176 | "name": "测试环境", 177 | "children": [ 178 | { 179 | "name": "iPhone[必选]" 180 | }, 181 | { 182 | "name": "iPad[可选,按需]" 183 | }, 184 | { 185 | "name": "Apple Watch[可选,按需]" 186 | } 187 | ] 188 | } 189 | ] 190 | } 191 | ] 192 | }, 193 | { 194 | "name": "开发语言", 195 | "children": [ 196 | { 197 | "name": "Objective-C", 198 | "children": [ 199 | 200 | { 201 | "name": "OC入门", 202 | "children": [ 203 | { 204 | "name": "数据类型" 205 | }, 206 | { 207 | "name": "基本语法" 208 | }, 209 | { 210 | "name": "变量和常量" 211 | }, 212 | { 213 | "name": "算术运算符" 214 | }, 215 | { 216 | "name": "关系运算符" 217 | }, 218 | { 219 | "name": "逻辑运算符" 220 | }, 221 | { 222 | "name": "位运算符" 223 | }, 224 | { 225 | "name": "赋值运算符" 226 | }, 227 | { 228 | "name": "for循环语句" 229 | }, 230 | { 231 | "name": "while循环语句" 232 | }, 233 | { 234 | "name": "break & continue & return" 235 | }, 236 | { 237 | "name": "if语句" 238 | }, 239 | { 240 | "name": "switch语句" 241 | } 242 | ] 243 | }, 244 | { 245 | "name": "OC基础", 246 | "children": [ 247 | { 248 | "name": "NSNumber" 249 | }, 250 | { 251 | "name": "NSString" 252 | }, 253 | { 254 | "name": "NSDate" 255 | }, 256 | { 257 | "name": "NSURL" 258 | }, 259 | { 260 | "name": "NSArray & NSMutableArray" 261 | }, 262 | { 263 | "name": "NSDictionary" 264 | }, 265 | { 266 | "name": "NSSet" 267 | }, 268 | { 269 | "name": "指针Pointer" 270 | } 271 | ] 272 | }, 273 | { 274 | "name": "OC进阶", 275 | "children": [ 276 | { 277 | "name": "预处理器" 278 | }, 279 | { 280 | "name": "Typedef" 281 | }, 282 | { 283 | "name": "Struct" 284 | }, 285 | { 286 | "name": "Class的实例方法" 287 | }, 288 | { 289 | "name": "Class的类方法" 290 | }, 291 | { 292 | "name": "Block" 293 | }, 294 | { 295 | "name": "动态绑定" 296 | }, 297 | { 298 | "name": "面向对象:多态" 299 | }, 300 | { 301 | "name": "面向对象:继承" 302 | }, 303 | { 304 | "name": "面向对象:数据封装" 305 | }, 306 | { 307 | "name": "Category" 308 | }, 309 | { 310 | "name": "Extension扩展" 311 | }, 312 | { 313 | "name": "Protocol协议" 314 | }, 315 | { 316 | "name": "内存管理MRR & ARM" 317 | } 318 | ] 319 | } 320 | ] 321 | }, 322 | { 323 | "name": "Swift", 324 | "children": [ 325 | { 326 | "name": "基础知识", 327 | "children": [ 328 | { 329 | "name": "常量和变量" 330 | }, 331 | { 332 | "name": "标识符" 333 | }, 334 | { 335 | "name": "表达式" 336 | }, 337 | { 338 | "name": "注释和输出" 339 | } 340 | ] 341 | }, 342 | { 343 | "name": "数据类型", 344 | "children": [ 345 | { 346 | "name": "布尔 & 整形 & 浮点 & 字符串" 347 | }, 348 | { 349 | "name": "元组" 350 | }, 351 | { 352 | "name": "数组" 353 | }, 354 | { 355 | "name": "字典" 356 | } 357 | ] 358 | }, 359 | { 360 | "name": "流程控制", 361 | "children": [ 362 | { 363 | "name": "for循环 & for-in循环" 364 | }, 365 | { 366 | "name": "switch语句" 367 | }, 368 | { 369 | "name": "while语句" 370 | }, 371 | { 372 | "name": "break & continue & forthrough" 373 | }, 374 | { 375 | "name": "if语句" 376 | } 377 | ] 378 | }, 379 | { 380 | "name": "函数", 381 | "children": [ 382 | { 383 | "name": "外部参数名" 384 | }, 385 | { 386 | "name": "可变参数" 387 | }, 388 | { 389 | "name": "输入输出函数" 390 | }, 391 | { 392 | "name": "函数作为参数" 393 | }, 394 | { 395 | "name": "多返回值" 396 | }, 397 | { 398 | "name": "嵌套函数" 399 | }, 400 | { 401 | "name": "公共函数" 402 | } 403 | ] 404 | }, 405 | { 406 | "name": "类与对象", 407 | "children": [ 408 | { 409 | "name": "枚举" 410 | }, 411 | { 412 | "name": "结构体" 413 | }, 414 | { 415 | "name": "类的引用特征" 416 | }, 417 | { 418 | "name": "willSet & didSet" 419 | }, 420 | { 421 | "name": "类的下村" 422 | }, 423 | { 424 | "name": "静态方法" 425 | }, 426 | { 427 | "name": "AnyObject & Any" 428 | } 429 | ] 430 | }, 431 | { 432 | "name": "Protocol", 433 | "children": [ 434 | { 435 | "name": "扩展属性和方法" 436 | }, 437 | { 438 | "name": "协议" 439 | }, 440 | { 441 | "name": "?和!可选链" 442 | }, 443 | { 444 | "name": "闭包" 445 | }, 446 | { 447 | "name": "泛型" 448 | } 449 | ] 450 | } 451 | ] 452 | } 453 | ] 454 | }, 455 | { 456 | "name": "iOS基础", 457 | "children": [ 458 | { 459 | "name": "UIView", 460 | "children": [ 461 | { 462 | "name": "MKAnnotationView" 463 | }, 464 | { 465 | "name": "MKMapView" 466 | }, 467 | { 468 | "name": "MKOverlayView" 469 | }, 470 | { 471 | "name": "MPVolumeView" 472 | }, 473 | { 474 | "name": "GLKView" 475 | }, 476 | { 477 | "name": "UIControl", 478 | "children": [ 479 | { 480 | "name": "UIRefreshControl" 481 | }, 482 | { 483 | "name": "UIButton" 484 | }, 485 | { 486 | "name": "UIDatePicker" 487 | }, 488 | { 489 | "name": "UISlider" 490 | }, 491 | { 492 | "name": "UITextField" 493 | }, 494 | { 495 | "name": "UISegmentedControl" 496 | }, 497 | { 498 | "name": "UISwitch" 499 | }, 500 | { 501 | "name": "UIPageControl" 502 | }, 503 | { 504 | "name": "UIStepper" 505 | }, 506 | { 507 | "name": "自定义Control" 508 | } 509 | ] 510 | }, 511 | { 512 | "name": "UINavigationBar" 513 | }, 514 | { 515 | "name": "UIProgressView" 516 | }, 517 | { 518 | "name": "UIActivityIndicatorView" 519 | }, 520 | { 521 | "name": "UITableViewCell" 522 | }, 523 | { 524 | "name": "UITabBar" 525 | }, 526 | { 527 | "name": "UILabel" 528 | }, 529 | { 530 | "name": "UIWindow" 531 | }, 532 | { 533 | "name": "UIScrollView", 534 | "children": [ 535 | { 536 | "name": "UITableView" 537 | }, 538 | { 539 | "name": "UICollectionView" 540 | }, 541 | { 542 | "name": "UITextView" 543 | } 544 | ] 545 | }, 546 | { 547 | "name": "WKWebview" 548 | }, 549 | { 550 | "name": "UIImageView" 551 | }, 552 | { 553 | "name": "UISearchBar" 554 | }, 555 | { 556 | "name": "UIAlertController" 557 | }, 558 | { 559 | "name": "UICollectionReusableView" 560 | }, 561 | { 562 | "name": "UIPickerView" 563 | }, 564 | { 565 | "name": "UIToolbar" 566 | } 567 | ] 568 | }, 569 | { 570 | "name": "iOS程序结构:AppDelegate & UIApplication" 571 | }, 572 | { 573 | "name": "Storyborad & XIB & SwiftUI" 574 | }, 575 | { 576 | "name": "IBOutlet & IBAction" 577 | }, 578 | { 579 | "name": "调试", 580 | "children": [ 581 | { 582 | "name": "po等命令" 583 | }, 584 | { 585 | "name": "NSLog" 586 | }, 587 | { 588 | "name": "print" 589 | }, 590 | { 591 | "name": "debugPrint" 592 | }, 593 | { 594 | "name": "dump" 595 | }, 596 | { 597 | "name": "断点调试", 598 | "children": [ 599 | { 600 | "name": "全局断点" 601 | }, 602 | { 603 | "name": "条件断点" 604 | }, 605 | { 606 | "name": "普通断点" 607 | } 608 | ] 609 | }, 610 | { 611 | "name": "NSZombieEnabled" 612 | }, 613 | { 614 | "name": "Console(lldb 命令)" 615 | }, 616 | { 617 | "name": "Profile(instruments)" 618 | }, 619 | { 620 | "name": "视图调试", 621 | "children": [ 622 | { 623 | "name": "Xcode UI Debug" 624 | }, 625 | { 626 | "name": "Debug View Hierarchy" 627 | }, 628 | { 629 | "name": "po recursiveDescription" 630 | } 631 | ] 632 | } 633 | ] 634 | }, 635 | { 636 | "name": "CocoaPods安装和使用" 637 | }, 638 | { 639 | "name": "沙盒机制" 640 | }, 641 | { 642 | "name": "设备旋转及屏幕适配" 643 | }, 644 | { 645 | "name": "UIView Animation & 核心动画" 646 | }, 647 | { 648 | "name": "Quartz2D && CoreGraphics" 649 | }, 650 | { 651 | "name": "UIViewController", 652 | "children": [ 653 | { 654 | "name": "EKEventViewController" 655 | }, 656 | { 657 | "name": "EKCalendarChooser" 658 | }, 659 | { 660 | "name": "MPMediaPickerController" 661 | }, 662 | { 663 | "name": "MPMoviePlayerViewController" 664 | }, 665 | { 666 | "name": "SLComposeViewController" 667 | }, 668 | { 669 | "name": "GLKViewController" 670 | }, 671 | { 672 | "name": "QLPreviewController" 673 | }, 674 | { 675 | "name": "UINavigationController", 676 | "children": [ 677 | { 678 | "name": "EKEventEditViewController" 679 | }, 680 | { 681 | "name": "GKMatchmakerViewController" 682 | }, 683 | { 684 | "name": "GKTurnBasedMatchmakerViewController" 685 | }, 686 | { 687 | "name": "GKFriendRequestComposeViewController" 688 | }, 689 | { 690 | "name": "ABPeoplePickerNavigationController" 691 | }, 692 | { 693 | "name": "MFMailComposeViewController" 694 | }, 695 | { 696 | "name": "MFMessageComposeViewController" 697 | }, 698 | { 699 | "name": "UIImagePickerController" 700 | }, 701 | { 702 | "name": "UIVideoEditorController" 703 | } 704 | ] 705 | }, 706 | { 707 | "name": "ABNewPersonViewController" 708 | }, 709 | { 710 | "name": "ABPersonViewController" 711 | }, 712 | { 713 | "name": "ABUnknownPersonViewController" 714 | }, 715 | { 716 | "name": "SKStoreProductViewController" 717 | }, 718 | { 719 | "name": "UITabBarController" 720 | }, 721 | { 722 | "name": "UICollectionViewController" 723 | }, 724 | { 725 | "name": "UITableViewController" 726 | }, 727 | { 728 | "name": "UIPageViewController" 729 | }, 730 | { 731 | "name": "UISplitViewController" 732 | }, 733 | { 734 | "name": "UIActivityViewController" 735 | }, 736 | { 737 | "name": "UIDocumentInteractionController" 738 | } 739 | ] 740 | }, 741 | { 742 | "name": "触摸事件 & 手势处理 & 加速计", 743 | "children": [ 744 | { 745 | "name": "触摸事件", 746 | "children": [ 747 | { 748 | "name": "UITouch" 749 | }, 750 | { 751 | "name": "UIEvent" 752 | }, 753 | { 754 | "name": "3DTouch" 755 | } 756 | ] 757 | }, 758 | { 759 | "name": "手势处理", 760 | "children": [ 761 | { 762 | "name": "UITapGestureRecognizer" 763 | }, 764 | { 765 | "name": "UILongPressGestureRecognizer" 766 | }, 767 | { 768 | "name": "UIRotationGestureRecognizer" 769 | }, 770 | { 771 | "name": "UIPinchGestureRecognizer" 772 | }, 773 | { 774 | "name": "UISwipeGestureRecognizer" 775 | }, 776 | { 777 | "name": "UIPanGestureRecognizer" 778 | } 779 | ] 780 | } 781 | ] 782 | }, 783 | { 784 | "name": "URL schemes" 785 | }, 786 | { 787 | "name": "本地化" 788 | }, 789 | { 790 | "name": "手机功能", 791 | "children": [ 792 | { 793 | "name": "加速计" 794 | }, 795 | { 796 | "name": "陀螺仪" 797 | }, 798 | { 799 | "name": "定位设备" 800 | }, 801 | { 802 | "name": "打电话" 803 | }, 804 | { 805 | "name": "短信" 806 | }, 807 | { 808 | "name": "邮件" 809 | }, 810 | { 811 | "name": "通讯录" 812 | } 813 | ] 814 | } 815 | ] 816 | }, 817 | { 818 | "name": "iOS高级", 819 | "children": [ 820 | { 821 | "name": "数据存储", 822 | "children": [ 823 | { 824 | "name": "Plist" 825 | }, 826 | { 827 | "name": "UserDefault" 828 | }, 829 | { 830 | "name": "NSKeyedArchiver" 831 | }, 832 | { 833 | "name": "SQLite数据库" 834 | }, 835 | { 836 | "name": "Core Data" 837 | }, 838 | { 839 | "name": "JSON" 840 | }, 841 | { 842 | "name": "XML" 843 | }, 844 | { 845 | "name": "KeyChain" 846 | } 847 | ] 848 | }, 849 | { 850 | "name": "RunLoop" 851 | }, 852 | { 853 | "name": "网络交互", 854 | "children": [ 855 | { 856 | "name": "Socket", 857 | "children": [ 858 | { 859 | "name": "GCDAsyncSocket" 860 | }, 861 | { 862 | "name": "WebSocket" 863 | }, 864 | { 865 | "name": "SIOSocket" 866 | }, 867 | { 868 | "name": "SocketRocket" 869 | } 870 | ] 871 | }, 872 | { 873 | "name": "CFNetwork" 874 | }, 875 | { 876 | "name": "NSStream" 877 | }, 878 | { 879 | "name": "AFNetworking" 880 | }, 881 | { 882 | "name": "NSURLSession" 883 | }, 884 | { 885 | "name": "Alamofire" 886 | } 887 | ] 888 | }, 889 | { 890 | "name": "WKWebview & Javascript交互", 891 | "children": [ 892 | { 893 | "name": "JavascriptCore.framework" 894 | }, 895 | { 896 | "name": "shouldStartLoadWithRequest&iframe" 897 | }, 898 | { 899 | "name": "WebViewJavascriptBridge" 900 | }, 901 | { 902 | "name": "WKScriptMessageHandler" 903 | }, 904 | { 905 | "name": "JSPatch热补丁" 906 | } 907 | ] 908 | }, 909 | { 910 | "name": "多线程", 911 | "children": [ 912 | { 913 | "name": "NSThread" 914 | }, 915 | { 916 | "name": "NSOperation" 917 | }, 918 | { 919 | "name": "GCD" 920 | } 921 | ] 922 | }, 923 | { 924 | "name": "多媒体", 925 | "children": [ 926 | { 927 | "name": "音频、音效及音频处理" 928 | }, 929 | { 930 | "name": "视频" 931 | }, 932 | { 933 | "name": "相机,相册" 934 | }, 935 | { 936 | "name": "流媒体" 937 | }, 938 | { 939 | "name": "滤镜" 940 | } 941 | ] 942 | }, 943 | { 944 | "name": "持续集成", 945 | "children": [ 946 | { 947 | "name": "代码仓库" 948 | }, 949 | { 950 | "name": "编译流程" 951 | }, 952 | { 953 | "name": "证书签名" 954 | }, 955 | { 956 | "name": "自动化" 957 | } 958 | ] 959 | }, 960 | { 961 | "name": "架构", 962 | "children": [ 963 | { 964 | "name": "解耦" 965 | }, 966 | { 967 | "name": "抽象" 968 | }, 969 | { 970 | "name": "响应式" 971 | }, 972 | { 973 | "name": "数据流" 974 | } 975 | ] 976 | }, 977 | { 978 | "name": "设计模式", 979 | "children": [ 980 | { 981 | "name": "MVC" 982 | }, 983 | { 984 | "name": "MVVM" 985 | }, 986 | { 987 | "name": "单例" 988 | }, 989 | { 990 | "name": "观察者" 991 | }, 992 | { 993 | "name": "代理" 994 | }, 995 | { 996 | "name": "通知" 997 | }, 998 | { 999 | "name": "观察者(KVO)" 1000 | }, 1001 | { 1002 | "name": "工厂模式" 1003 | } 1004 | ] 1005 | }, 1006 | { 1007 | "name": "安全机制", 1008 | "children": [ 1009 | { 1010 | "name": "HTTP验证", 1011 | "children": [ 1012 | { 1013 | "name": "HTTP Basic" 1014 | }, 1015 | { 1016 | "name": "HTTP Digest" 1017 | } 1018 | ] 1019 | }, 1020 | { 1021 | "name": "数据加密", 1022 | "children": [ 1023 | { 1024 | "name": "Hash", 1025 | "children": [ 1026 | { 1027 | "name": "MD5" 1028 | }, 1029 | { 1030 | "name": "sha1" 1031 | }, 1032 | { 1033 | "name": "sha256" 1034 | }, 1035 | { 1036 | "name": "sha512" 1037 | } 1038 | ] 1039 | }, 1040 | { 1041 | "name": "RSA" 1042 | }, 1043 | { 1044 | "name": "AES" 1045 | }, 1046 | { 1047 | "name": "3DES/DES" 1048 | }, 1049 | { 1050 | "name": "authCode" 1051 | }, 1052 | { 1053 | "name": "KeyChain" 1054 | } 1055 | ] 1056 | } 1057 | ] 1058 | }, 1059 | { 1060 | "name": "质量", 1061 | "children": [ 1062 | { 1063 | "name": "性能优化", 1064 | "children": [ 1065 | { 1066 | "name": "检测/量化" 1067 | }, 1068 | { 1069 | "name": "瓶颈" 1070 | }, 1071 | { 1072 | "name": "优化" 1073 | } 1074 | ] 1075 | }, 1076 | { 1077 | "name": "代码质量", 1078 | "children": [ 1079 | { 1080 | "name": "codereview" 1081 | }, 1082 | { 1083 | "name": "代码规范检测" 1084 | } 1085 | ] 1086 | }, 1087 | { 1088 | "name": "监控体系", 1089 | "children": [ 1090 | { 1091 | "name": "crash" 1092 | }, 1093 | { 1094 | "name": "卡顿" 1095 | }, 1096 | { 1097 | "name": "错误码监控" 1098 | }, 1099 | { 1100 | "name": "耗时监控" 1101 | }, 1102 | { 1103 | "name": "业务监控" 1104 | }, 1105 | { 1106 | "name": "出错补救", 1107 | "children": [ 1108 | { 1109 | "name": "配置" 1110 | }, 1111 | { 1112 | "name": "热修复" 1113 | } 1114 | ] 1115 | } 1116 | ] 1117 | }, 1118 | { 1119 | "name": "安全", 1120 | "children": [ 1121 | { 1122 | "name": "沙盒" 1123 | }, 1124 | { 1125 | "name": "加固" 1126 | }, 1127 | { 1128 | "name": "网络安全" 1129 | } 1130 | ] 1131 | }, 1132 | { 1133 | "name": "测试", 1134 | "children": [ 1135 | { 1136 | "name": "黑盒测试" 1137 | }, 1138 | { 1139 | "name": "白盒测试" 1140 | }, 1141 | { 1142 | "name": "自动化测试" 1143 | } 1144 | ] 1145 | } 1146 | ] 1147 | }, 1148 | { 1149 | "name": "打包与封装SDK", 1150 | "children": [ 1151 | { 1152 | "name": "App Thinning" 1153 | }, 1154 | { 1155 | "name": "封装动态库.dylib" 1156 | }, 1157 | { 1158 | "name": "封装静态库.a" 1159 | }, 1160 | { 1161 | "name": "封装.framework" 1162 | }, 1163 | { 1164 | "name": "封装.bundle" 1165 | } 1166 | ] 1167 | }, 1168 | { 1169 | "name": "Autolayout" 1170 | }, 1171 | { 1172 | "name": "Runtime" 1173 | }, 1174 | { 1175 | "name": "正则表达式", 1176 | "children": [ 1177 | { 1178 | "name": "NSPredicate" 1179 | }, 1180 | { 1181 | "name": "NSRegularExpression" 1182 | } 1183 | ] 1184 | }, 1185 | { 1186 | "name": "高级控件优化、自定义控制器容器、控件封装" 1187 | }, 1188 | { 1189 | "name": "开发者账号申请、真机调试、发布应用、内购、广告" 1190 | }, 1191 | { 1192 | "name": "Game Center & 社交服务" 1193 | }, 1194 | { 1195 | "name": "iCloud,Map kit & Core Location" 1196 | }, 1197 | { 1198 | "name": "推送机制" 1199 | }, 1200 | { 1201 | "name": "社会化分享/登陆" 1202 | }, 1203 | { 1204 | "name": "蓝牙" 1205 | }, 1206 | { 1207 | "name": "ARC及内存分析工具" 1208 | } 1209 | ] 1210 | }, 1211 | { 1212 | "name": "SwiftUI", 1213 | "children": [ 1214 | { 1215 | "name": "Control", 1216 | "children": [ 1217 | { 1218 | "name": "Text" 1219 | }, 1220 | { 1221 | "name": "TextField" 1222 | }, 1223 | { 1224 | "name": "Button" 1225 | }, 1226 | { 1227 | "name": "Spacer" 1228 | }, 1229 | { 1230 | "name": "Divider" 1231 | }, 1232 | { 1233 | "name": "Image" 1234 | }, 1235 | { 1236 | "name": "Picker" 1237 | }, 1238 | { 1239 | "name": "Slider" 1240 | }, 1241 | { 1242 | "name": "Stepper" 1243 | }, 1244 | { 1245 | "name": "Segment" 1246 | }, 1247 | { 1248 | "name": "Toggle" 1249 | }, 1250 | { 1251 | "name": "TabView" 1252 | }, 1253 | { 1254 | "name": "集成UIKit" 1255 | } 1256 | ] 1257 | }, 1258 | { 1259 | "name": "Animation", 1260 | "children": [ 1261 | { 1262 | "name": "scaleEffect" 1263 | }, 1264 | { 1265 | "name": "Opacity+linear" 1266 | }, 1267 | { 1268 | "name": "Offset+easeOut" 1269 | }, 1270 | { 1271 | "name": "RotationEffect+spring" 1272 | }, 1273 | { 1274 | "name": "组合动画" 1275 | }, 1276 | { 1277 | "name": "Repeating" 1278 | }, 1279 | { 1280 | "name": "AsymmetricTransition" 1281 | } 1282 | ] 1283 | }, 1284 | { 1285 | "name": "Gesture", 1286 | "children": [ 1287 | { 1288 | "name": "TapGesture" 1289 | }, 1290 | { 1291 | "name": "LongPressGesture" 1292 | }, 1293 | { 1294 | "name": "RotationGesture" 1295 | }, 1296 | { 1297 | "name": "DragGesture" 1298 | }, 1299 | { 1300 | "name": "组合手势" 1301 | } 1302 | ] 1303 | }, 1304 | { 1305 | "name": "Layout", 1306 | "children": [ 1307 | { 1308 | "name": "Group" 1309 | }, 1310 | { 1311 | "name": "HStack" 1312 | }, 1313 | { 1314 | "name": "VStack" 1315 | }, 1316 | { 1317 | "name": "VStack" 1318 | }, 1319 | { 1320 | "name": "List" 1321 | }, 1322 | { 1323 | "name": "ScrollView" 1324 | }, 1325 | { 1326 | "name": "Form" 1327 | } 1328 | ] 1329 | }, 1330 | { 1331 | "name": "DataFlow", 1332 | "children": [ 1333 | { 1334 | "name": "NavigationLink" 1335 | }, 1336 | { 1337 | "name": "PageNavigation" 1338 | }, 1339 | { 1340 | "name": "ObjectBinding" 1341 | }, 1342 | { 1343 | "name": "EnviromentObject" 1344 | }, 1345 | { 1346 | "name": "Modal" 1347 | }, 1348 | { 1349 | "name": "Alert" 1350 | }, 1351 | { 1352 | "name": "ActionSheet" 1353 | } 1354 | ] 1355 | }, 1356 | { 1357 | "name": "More", 1358 | "children": [ 1359 | { 1360 | "name": "Background" 1361 | }, 1362 | { 1363 | "name": "SizeCategory" 1364 | }, 1365 | { 1366 | "name": "PreviewDevice" 1367 | }, 1368 | { 1369 | "name": "onAppear-onDisappear" 1370 | }, 1371 | { 1372 | "name": "ViewModifier" 1373 | } 1374 | ] 1375 | } 1376 | ] 1377 | }, 1378 | { 1379 | "name": "热门技术", 1380 | "children": [ 1381 | { 1382 | "name": "React Native" 1383 | }, 1384 | { 1385 | "name": "支付" 1386 | }, 1387 | { 1388 | "name": "即时通讯" 1389 | }, 1390 | { 1391 | "name": "H5" 1392 | }, 1393 | { 1394 | "name": "机器学习" 1395 | }, 1396 | { 1397 | "name": "人脸识别" 1398 | }, 1399 | { 1400 | "name": "AR增强现实" 1401 | } 1402 | ] 1403 | }, 1404 | { 1405 | "name": "热门第三方库", 1406 | "children": [ 1407 | { 1408 | "name": "超级控件", 1409 | "children": [ 1410 | { 1411 | "name": "ActiveLabel" 1412 | }, 1413 | { 1414 | "name": "UICircularProgressRing" 1415 | }, 1416 | { 1417 | "name": "EZLoadingActivity" 1418 | }, 1419 | { 1420 | "name": "Toaster" 1421 | }, 1422 | { 1423 | "name": "PKHUD" 1424 | }, 1425 | { 1426 | "name": "Toaster" 1427 | }, 1428 | { 1429 | "name": "SweetAlert" 1430 | }, 1431 | { 1432 | "name": "SCLAlertView" 1433 | }, 1434 | { 1435 | "name": "JTAppleCalendar" 1436 | }, 1437 | { 1438 | "name": "CVCalendar" 1439 | }, 1440 | { 1441 | "name": "DateTimePicker" 1442 | }, 1443 | { 1444 | "name": "XLActionController" 1445 | }, 1446 | { 1447 | "name": "FaceAware" 1448 | } 1449 | ] 1450 | }, 1451 | { 1452 | "name": "表单 & 图表", 1453 | "children": [ 1454 | { 1455 | "name": "Charts" 1456 | }, 1457 | { 1458 | "name": "ScrollableGraphView" 1459 | } 1460 | ] 1461 | }, 1462 | { 1463 | "name": "网络和线程", 1464 | "children": [ 1465 | { 1466 | "name": "Alamofire" 1467 | }, 1468 | { 1469 | "name": "ObjectMapper" 1470 | }, 1471 | { 1472 | "name": "ReachabilitySwift" 1473 | }, 1474 | { 1475 | "name": "SwiftyStoreKit" 1476 | } 1477 | ] 1478 | }, 1479 | { 1480 | "name": "高级扩展", 1481 | "children": [ 1482 | { 1483 | "name": "Device" 1484 | }, 1485 | { 1486 | "name": "Arithmosophi" 1487 | }, 1488 | { 1489 | "name": "DateHelper" 1490 | }, 1491 | { 1492 | "name": "DynamicColor" 1493 | }, 1494 | { 1495 | "name": "Chameleon" 1496 | }, 1497 | { 1498 | "name": "EZSwiftExtensions" 1499 | }, 1500 | { 1501 | "name": "Alexandria" 1502 | }, 1503 | { 1504 | "name": "Kingfisher" 1505 | }, 1506 | { 1507 | "name": "IBLocalizable" 1508 | }, 1509 | { 1510 | "name": "MathParser" 1511 | } 1512 | ] 1513 | }, 1514 | { 1515 | "name": "智能布局", 1516 | "children": [ 1517 | { 1518 | "name": "SnapKit" 1519 | }, 1520 | { 1521 | "name": "Neon" 1522 | } 1523 | ] 1524 | }, 1525 | { 1526 | "name": "数据安全", 1527 | "children": [ 1528 | { 1529 | "name": "AEXML" 1530 | }, 1531 | { 1532 | "name": "Fuzi" 1533 | }, 1534 | { 1535 | "name": "SwiftJson" 1536 | }, 1537 | { 1538 | "name": "AERecord" 1539 | }, 1540 | { 1541 | "name": "SQLiteSwift" 1542 | }, 1543 | { 1544 | "name": "FileKit" 1545 | }, 1546 | { 1547 | "name": "FileBrowser" 1548 | }, 1549 | { 1550 | "name": "PDFGenerator" 1551 | }, 1552 | { 1553 | "name": "Zip" 1554 | }, 1555 | { 1556 | "name": "CryptoSwift" 1557 | } 1558 | ] 1559 | }, 1560 | { 1561 | "name": "多媒体", 1562 | "children": [ 1563 | { 1564 | "name": "AudioPlayer" 1565 | }, 1566 | { 1567 | "name": "MobilePlayer" 1568 | }, 1569 | { 1570 | "name": "Macaw" 1571 | }, 1572 | { 1573 | "name": "Spring" 1574 | }, 1575 | { 1576 | "name": "Cheetah" 1577 | }, 1578 | { 1579 | "name": "SwiftGif" 1580 | }, 1581 | { 1582 | "name": "AudioIndicatorBars" 1583 | } 1584 | ] 1585 | }, 1586 | { 1587 | "name": "哇啊WOW", 1588 | "children": [ 1589 | { 1590 | "name": "Appz" 1591 | }, 1592 | { 1593 | "name": "FontAwesome" 1594 | }, 1595 | { 1596 | "name": "PagingMenuController" 1597 | }, 1598 | { 1599 | "name": "TwicketSegmentedControl" 1600 | }, 1601 | { 1602 | "name": "RAMAnimatedTabBar" 1603 | }, 1604 | { 1605 | "name": "ENSideMenu" 1606 | }, 1607 | { 1608 | "name": "GuillotineMenu" 1609 | }, 1610 | { 1611 | "name": "BubbleTransition" 1612 | }, 1613 | { 1614 | "name": "ALCameraViewController" 1615 | }, 1616 | { 1617 | "name": "Chatto" 1618 | }, 1619 | { 1620 | "name": "PinterestSwift" 1621 | }, 1622 | { 1623 | "name": "preview-transition" 1624 | }, 1625 | { 1626 | "name": "Presentation" 1627 | }, 1628 | { 1629 | "name": "WWalkthrough" 1630 | }, 1631 | { 1632 | "name": "expanding-collection" 1633 | }, 1634 | { 1635 | "name": "FoldingCell" 1636 | } 1637 | ] 1638 | } 1639 | ] 1640 | }, 1641 | { 1642 | "name": "系统框架", 1643 | "children": [ 1644 | { 1645 | "name": "常用框架", 1646 | "children": [ 1647 | { 1648 | "name": "UIKit" 1649 | }, 1650 | { 1651 | "name": "Foundation" 1652 | }, 1653 | { 1654 | "name": "MapKit" 1655 | }, 1656 | { 1657 | "name": "MediaPlayer" 1658 | }, 1659 | { 1660 | "name": "MessageUI" 1661 | }, 1662 | { 1663 | "name": "QuartzCore" 1664 | }, 1665 | { 1666 | "name": "SystemConfiguration" 1667 | }, 1668 | { 1669 | "name": "GameKit" 1670 | }, 1671 | { 1672 | "name": "CFNetwork" 1673 | }, 1674 | { 1675 | "name": "AVFoundation" 1676 | }, 1677 | { 1678 | "name": "AudioToolbox" 1679 | }, 1680 | { 1681 | "name": "CoreText" 1682 | }, 1683 | { 1684 | "name": "CoreGraphics" 1685 | }, 1686 | { 1687 | "name": "CoreAnimation" 1688 | }, 1689 | { 1690 | "name": "CoreImage" 1691 | } 1692 | ] 1693 | }, 1694 | { 1695 | "name": "其它框架", 1696 | "children": [ 1697 | { 1698 | "name": "HomeKit" 1699 | }, 1700 | { 1701 | "name": "CloudKit" 1702 | }, 1703 | { 1704 | "name": "HealthKit" 1705 | }, 1706 | { 1707 | "name": "ResearchKit" 1708 | }, 1709 | { 1710 | "name": "SpriteKit" 1711 | }, 1712 | { 1713 | "name": "SceneKit" 1714 | } 1715 | ] 1716 | } 1717 | ] 1718 | }, 1719 | { 1720 | "name": "版本控制", 1721 | "children": [ 1722 | { 1723 | "name": "Git" 1724 | }, 1725 | { 1726 | "name": "GitHub" 1727 | }, 1728 | { 1729 | "name": "GitHub Desktop" 1730 | } 1731 | ] 1732 | }, 1733 | { 1734 | "name": "常用工具", 1735 | "children": [ 1736 | { 1737 | "name": "Application Loader" 1738 | }, 1739 | { 1740 | "name": "Charles" 1741 | }, 1742 | { 1743 | "name": "PostMan" 1744 | }, 1745 | { 1746 | "name": "MarkMan" 1747 | }, 1748 | { 1749 | "name": "F.lux" 1750 | }, 1751 | { 1752 | "name": "XMind" 1753 | }, 1754 | { 1755 | "name": "JSON Viewer" 1756 | }, 1757 | { 1758 | "name": "Texture Packer" 1759 | }, 1760 | { 1761 | "name": "Github Desktop" 1762 | }, 1763 | { 1764 | "name": "Sqlite Professional" 1765 | }, 1766 | { 1767 | "name": "Sublime Text" 1768 | }, 1769 | { 1770 | "name": "Cocos Creator" 1771 | }, 1772 | { 1773 | "name": "Cocos2d-x" 1774 | }, 1775 | { 1776 | "name": "Unity 3D" 1777 | } 1778 | ] 1779 | } 1780 | ] 1781 | } --------------------------------------------------------------------------------