概要 实例 介绍 源码

可拖拉收的树图

源文件:index.html

  1. <!DOCTYPE html>
  2. <meta charset="utf-8">
  3. <style type="text/css">
  4. .node {
  5. cursor: pointer;
  6. }
  7. .overlay{
  8. background-color:#fff;
  9. }
  10. .node circle {
  11. fill: #fff;
  12. stroke: steelblue;
  13. stroke-width: 1.5px;
  14. }
  15. .node text {
  16. font-size:10px;
  17. font-family:sans-serif;
  18. }
  19. .link {
  20. fill: none;
  21. stroke: #ccc;
  22. stroke-width: 1.5px;
  23. }
  24. .templink {
  25. fill: none;
  26. stroke: red;
  27. stroke-width: 3px;
  28. }
  29. .ghostCircle.show{
  30. display:block;
  31. }
  32. .ghostCircle, .activeDrag .ghostCircle{
  33. display: none;
  34. }
  35. </style>
  36. <script src="http://code.jquery.com/jquery-1.10.2.min.js"></script>
  37. <script src="http://d3js.org/d3.v3.min.js"></script>
  38. <script src="dndTree.js"></script>
  39. <body>
  40. <div id="tree-container"></div>
  41. </body>
  42. </html>

源文件:flare.json

  1. {
  2. "name": "flare",
  3. "children": [{
  4. "name": "analytics",
  5. "children": [{
  6. "name": "cluster",
  7. "children": [{
  8. "name": "AgglomerativeCluster",
  9. "size": 3938
  10. }, {
  11. "name": "CommunityStructure",
  12. "size": 3812
  13. }, {
  14. "name": "HierarchicalCluster",
  15. "size": 6714
  16. }, {
  17. "name": "MergeEdge",
  18. "size": 743
  19. }]
  20. }, {
  21. "name": "graph",
  22. "children": [{
  23. "name": "BetweennessCentrality",
  24. "size": 3534
  25. }, {
  26. "name": "LinkDistance",
  27. "size": 5731
  28. }, {
  29. "name": "MaxFlowMinCut",
  30. "size": 7840
  31. }, {
  32. "name": "ShortestPaths",
  33. "size": 5914
  34. }, {
  35. "name": "SpanningTree",
  36. "size": 3416
  37. }]
  38. }, {
  39. "name": "optimization",
  40. "children": [{
  41. "name": "AspectRatioBanker",
  42. "size": 7074
  43. }]
  44. }]
  45. }, {
  46. "name": "animate",
  47. "children": [{
  48. "name": "Easing",
  49. "size": 17010
  50. }, {
  51. "name": "FunctionSequence",
  52. "size": 5842
  53. }, {
  54. "name": "interpolate",
  55. "children": [{
  56. "name": "ArrayInterpolator",
  57. "size": 1983
  58. }, {
  59. "name": "ColorInterpolator",
  60. "size": 2047
  61. }, {
  62. "name": "DateInterpolator",
  63. "size": 1375
  64. }, {
  65. "name": "Interpolator",
  66. "size": 8746
  67. }, {
  68. "name": "MatrixInterpolator",
  69. "size": 2202
  70. }, {
  71. "name": "NumberInterpolator",
  72. "size": 1382
  73. }, {
  74. "name": "ObjectInterpolator",
  75. "size": 1629
  76. }, {
  77. "name": "PointInterpolator",
  78. "size": 1675
  79. }, {
  80. "name": "RectangleInterpolator",
  81. "size": 2042
  82. }]
  83. }, {
  84. "name": "ISchedulable",
  85. "size": 1041
  86. }, {
  87. "name": "Parallel",
  88. "size": 5176
  89. }, {
  90. "name": "Pause",
  91. "size": 449
  92. }, {
  93. "name": "Scheduler",
  94. "size": 5593
  95. }, {
  96. "name": "Sequence",
  97. "size": 5534
  98. }, {
  99. "name": "Transition",
  100. "size": 9201
  101. }, {
  102. "name": "Transitioner",
  103. "size": 19975
  104. }, {
  105. "name": "TransitionEvent",
  106. "size": 1116
  107. }, {
  108. "name": "Tween",
  109. "size": 6006
  110. }]
  111. }, {
  112. "name": "data",
  113. "children": [{
  114. "name": "converters",
  115. "children": [{
  116. "name": "Converters",
  117. "size": 721
  118. }, {
  119. "name": "DelimitedTextConverter",
  120. "size": 4294
  121. }, {
  122. "name": "GraphMLConverter",
  123. "size": 9800
  124. }, {
  125. "name": "IDataConverter",
  126. "size": 1314
  127. }, {
  128. "name": "JSONConverter",
  129. "size": 2220
  130. }]
  131. }, {
  132. "name": "DataField",
  133. "size": 1759
  134. }, {
  135. "name": "DataSchema",
  136. "size": 2165
  137. }, {
  138. "name": "DataSet",
  139. "size": 586
  140. }, {
  141. "name": "DataSource",
  142. "size": 3331
  143. }, {
  144. "name": "DataTable",
  145. "size": 772
  146. }, {
  147. "name": "DataUtil",
  148. "size": 3322
  149. }]
  150. }, {
  151. "name": "display",
  152. "children": [{
  153. "name": "DirtySprite",
  154. "size": 8833
  155. }, {
  156. "name": "LineSprite",
  157. "size": 1732
  158. }, {
  159. "name": "RectSprite",
  160. "size": 3623
  161. }, {
  162. "name": "TextSprite",
  163. "size": 10066
  164. }]
  165. }, {
  166. "name": "flex",
  167. "children": [{
  168. "name": "FlareVis",
  169. "size": 4116
  170. }]
  171. }, {
  172. "name": "physics",
  173. "children": [{
  174. "name": "DragForce",
  175. "size": 1082
  176. }, {
  177. "name": "GravityForce",
  178. "size": 1336
  179. }, {
  180. "name": "IForce",
  181. "size": 319
  182. }, {
  183. "name": "NBodyForce",
  184. "size": 10498
  185. }, {
  186. "name": "Particle",
  187. "size": 2822
  188. }, {
  189. "name": "Simulation",
  190. "size": 9983
  191. }, {
  192. "name": "Spring",
  193. "size": 2213
  194. }, {
  195. "name": "SpringForce",
  196. "size": 1681
  197. }]
  198. }, {
  199. "name": "query",
  200. "children": [{
  201. "name": "AggregateExpression",
  202. "size": 1616
  203. }, {
  204. "name": "And",
  205. "size": 1027
  206. }, {
  207. "name": "Arithmetic",
  208. "size": 3891
  209. }, {
  210. "name": "Average",
  211. "size": 891
  212. }, {
  213. "name": "BinaryExpression",
  214. "size": 2893
  215. }, {
  216. "name": "Comparison",
  217. "size": 5103
  218. }, {
  219. "name": "CompositeExpression",
  220. "size": 3677
  221. }, {
  222. "name": "Count",
  223. "size": 781
  224. }, {
  225. "name": "DateUtil",
  226. "size": 4141
  227. }, {
  228. "name": "Distinct",
  229. "size": 933
  230. }, {
  231. "name": "Expression",
  232. "size": 5130
  233. }, {
  234. "name": "ExpressionIterator",
  235. "size": 3617
  236. }, {
  237. "name": "Fn",
  238. "size": 3240
  239. }, {
  240. "name": "If",
  241. "size": 2732
  242. }, {
  243. "name": "IsA",
  244. "size": 2039
  245. }, {
  246. "name": "Literal",
  247. "size": 1214
  248. }, {
  249. "name": "Match",
  250. "size": 3748
  251. }, {
  252. "name": "Maximum",
  253. "size": 843
  254. }, {
  255. "name": "methods",
  256. "children": [{
  257. "name": "add",
  258. "size": 593
  259. }, {
  260. "name": "and",
  261. "size": 330
  262. }, {
  263. "name": "average",
  264. "size": 287
  265. }, {
  266. "name": "count",
  267. "size": 277
  268. }, {
  269. "name": "distinct",
  270. "size": 292
  271. }, {
  272. "name": "div",
  273. "size": 595
  274. }, {
  275. "name": "eq",
  276. "size": 594
  277. }, {
  278. "name": "fn",
  279. "size": 460
  280. }, {
  281. "name": "gt",
  282. "size": 603
  283. }, {
  284. "name": "gte",
  285. "size": 625
  286. }, {
  287. "name": "iff",
  288. "size": 748
  289. }, {
  290. "name": "isa",
  291. "size": 461
  292. }, {
  293. "name": "lt",
  294. "size": 597
  295. }, {
  296. "name": "lte",
  297. "size": 619
  298. }, {
  299. "name": "max",
  300. "size": 283
  301. }, {
  302. "name": "min",
  303. "size": 283
  304. }, {
  305. "name": "mod",
  306. "size": 591
  307. }, {
  308. "name": "mul",
  309. "size": 603
  310. }, {
  311. "name": "neq",
  312. "size": 599
  313. }, {
  314. "name": "not",
  315. "size": 386
  316. }, {
  317. "name": "or",
  318. "size": 323
  319. }, {
  320. "name": "orderby",
  321. "size": 307
  322. }, {
  323. "name": "range",
  324. "size": 772
  325. }, {
  326. "name": "select",
  327. "size": 296
  328. }, {
  329. "name": "stddev",
  330. "size": 363
  331. }, {
  332. "name": "sub",
  333. "size": 600
  334. }, {
  335. "name": "sum",
  336. "size": 280
  337. }, {
  338. "name": "update",
  339. "size": 307
  340. }, {
  341. "name": "variance",
  342. "size": 335
  343. }, {
  344. "name": "where",
  345. "size": 299
  346. }, {
  347. "name": "xor",
  348. "size": 354
  349. }, {
  350. "name": "_",
  351. "size": 264
  352. }]
  353. }, {
  354. "name": "Minimum",
  355. "size": 843
  356. }, {
  357. "name": "Not",
  358. "size": 1554
  359. }, {
  360. "name": "Or",
  361. "size": 970
  362. }, {
  363. "name": "Query",
  364. "size": 13896
  365. }, {
  366. "name": "Range",
  367. "size": 1594
  368. }, {
  369. "name": "StringUtil",
  370. "size": 4130
  371. }, {
  372. "name": "Sum",
  373. "size": 791
  374. }, {
  375. "name": "Variable",
  376. "size": 1124
  377. }, {
  378. "name": "Variance",
  379. "size": 1876
  380. }, {
  381. "name": "Xor",
  382. "size": 1101
  383. }]
  384. }, {
  385. "name": "scale",
  386. "children": [{
  387. "name": "IScaleMap",
  388. "size": 2105
  389. }, {
  390. "name": "LinearScale",
  391. "size": 1316
  392. }, {
  393. "name": "LogScale",
  394. "size": 3151
  395. }, {
  396. "name": "OrdinalScale",
  397. "size": 3770
  398. }, {
  399. "name": "QuantileScale",
  400. "size": 2435
  401. }, {
  402. "name": "QuantitativeScale",
  403. "size": 4839
  404. }, {
  405. "name": "RootScale",
  406. "size": 1756
  407. }, {
  408. "name": "Scale",
  409. "size": 4268
  410. }, {
  411. "name": "ScaleType",
  412. "size": 1821
  413. }, {
  414. "name": "TimeScale",
  415. "size": 5833
  416. }]
  417. }, {
  418. "name": "util",
  419. "children": [{
  420. "name": "Arrays",
  421. "size": 8258
  422. }, {
  423. "name": "Colors",
  424. "size": 10001
  425. }, {
  426. "name": "Dates",
  427. "size": 8217
  428. }, {
  429. "name": "Displays",
  430. "size": 12555
  431. }, {
  432. "name": "Filter",
  433. "size": 2324
  434. }, {
  435. "name": "Geometry",
  436. "size": 10993
  437. }, {
  438. "name": "heap",
  439. "children": [{
  440. "name": "FibonacciHeap",
  441. "size": 9354
  442. }, {
  443. "name": "HeapNode",
  444. "size": 1233
  445. }]
  446. }, {
  447. "name": "IEvaluable",
  448. "size": 335
  449. }, {
  450. "name": "IPredicate",
  451. "size": 383
  452. }, {
  453. "name": "IValueProxy",
  454. "size": 874
  455. }, {
  456. "name": "math",
  457. "children": [{
  458. "name": "DenseMatrix",
  459. "size": 3165
  460. }, {
  461. "name": "IMatrix",
  462. "size": 2815
  463. }, {
  464. "name": "SparseMatrix",
  465. "size": 3366
  466. }]
  467. }, {
  468. "name": "Maths",
  469. "size": 17705
  470. }, {
  471. "name": "Orientation",
  472. "size": 1486
  473. }, {
  474. "name": "palette",
  475. "children": [{
  476. "name": "ColorPalette",
  477. "size": 6367
  478. }, {
  479. "name": "Palette",
  480. "size": 1229
  481. }, {
  482. "name": "ShapePalette",
  483. "size": 2059
  484. }, {
  485. "name": "SizePalette",
  486. "size": 2291
  487. }]
  488. }, {
  489. "name": "Property",
  490. "size": 5559
  491. }, {
  492. "name": "Shapes",
  493. "size": 19118
  494. }, {
  495. "name": "Sort",
  496. "size": 6887
  497. }, {
  498. "name": "Stats",
  499. "size": 6557
  500. }, {
  501. "name": "Strings",
  502. "size": 22026
  503. }]
  504. }, {
  505. "name": "vis",
  506. "children": [{
  507. "name": "axis",
  508. "children": [{
  509. "name": "Axes",
  510. "size": 1302
  511. }, {
  512. "name": "Axis",
  513. "size": 24593
  514. }, {
  515. "name": "AxisGridLine",
  516. "size": 652
  517. }, {
  518. "name": "AxisLabel",
  519. "size": 636
  520. }, {
  521. "name": "CartesianAxes",
  522. "size": 6703
  523. }]
  524. }, {
  525. "name": "controls",
  526. "children": [{
  527. "name": "AnchorControl",
  528. "size": 2138
  529. }, {
  530. "name": "ClickControl",
  531. "size": 3824
  532. }, {
  533. "name": "Control",
  534. "size": 1353
  535. }, {
  536. "name": "ControlList",
  537. "size": 4665
  538. }, {
  539. "name": "DragControl",
  540. "size": 2649
  541. }, {
  542. "name": "ExpandControl",
  543. "size": 2832
  544. }, {
  545. "name": "HoverControl",
  546. "size": 4896
  547. }, {
  548. "name": "IControl",
  549. "size": 763
  550. }, {
  551. "name": "PanZoomControl",
  552. "size": 5222
  553. }, {
  554. "name": "SelectionControl",
  555. "size": 7862
  556. }, {
  557. "name": "TooltipControl",
  558. "size": 8435
  559. }]
  560. }, {
  561. "name": "data",
  562. "children": [{
  563. "name": "Data",
  564. "size": 20544
  565. }, {
  566. "name": "DataList",
  567. "size": 19788
  568. }, {
  569. "name": "DataSprite",
  570. "size": 10349
  571. }, {
  572. "name": "EdgeSprite",
  573. "size": 3301
  574. }, {
  575. "name": "NodeSprite",
  576. "size": 19382
  577. }, {
  578. "name": "render",
  579. "children": [{
  580. "name": "ArrowType",
  581. "size": 698
  582. }, {
  583. "name": "EdgeRenderer",
  584. "size": 5569
  585. }, {
  586. "name": "IRenderer",
  587. "size": 353
  588. }, {
  589. "name": "ShapeRenderer",
  590. "size": 2247
  591. }]
  592. }, {
  593. "name": "ScaleBinding",
  594. "size": 11275
  595. }, {
  596. "name": "Tree",
  597. "size": 7147
  598. }, {
  599. "name": "TreeBuilder",
  600. "size": 9930
  601. }]
  602. }, {
  603. "name": "events",
  604. "children": [{
  605. "name": "DataEvent",
  606. "size": 2313
  607. }, {
  608. "name": "SelectionEvent",
  609. "size": 1880
  610. }, {
  611. "name": "TooltipEvent",
  612. "size": 1701
  613. }, {
  614. "name": "VisualizationEvent",
  615. "size": 1117
  616. }]
  617. }, {
  618. "name": "legend",
  619. "children": [{
  620. "name": "Legend",
  621. "size": 20859
  622. }, {
  623. "name": "LegendItem",
  624. "size": 4614
  625. }, {
  626. "name": "LegendRange",
  627. "size": 10530
  628. }]
  629. }, {
  630. "name": "operator",
  631. "children": [{
  632. "name": "distortion",
  633. "children": [{
  634. "name": "BifocalDistortion",
  635. "size": 4461
  636. }, {
  637. "name": "Distortion",
  638. "size": 6314
  639. }, {
  640. "name": "FisheyeDistortion",
  641. "size": 3444
  642. }]
  643. }, {
  644. "name": "encoder",
  645. "children": [{
  646. "name": "ColorEncoder",
  647. "size": 3179
  648. }, {
  649. "name": "Encoder",
  650. "size": 4060
  651. }, {
  652. "name": "PropertyEncoder",
  653. "size": 4138
  654. }, {
  655. "name": "ShapeEncoder",
  656. "size": 1690
  657. }, {
  658. "name": "SizeEncoder",
  659. "size": 1830
  660. }]
  661. }, {
  662. "name": "filter",
  663. "children": [{
  664. "name": "FisheyeTreeFilter",
  665. "size": 5219
  666. }, {
  667. "name": "GraphDistanceFilter",
  668. "size": 3165
  669. }, {
  670. "name": "VisibilityFilter",
  671. "size": 3509
  672. }]
  673. }, {
  674. "name": "IOperator",
  675. "size": 1286
  676. }, {
  677. "name": "label",
  678. "children": [{
  679. "name": "Labeler",
  680. "size": 9956
  681. }, {
  682. "name": "RadialLabeler",
  683. "size": 3899
  684. }, {
  685. "name": "StackedAreaLabeler",
  686. "size": 3202
  687. }]
  688. }, {
  689. "name": "layout",
  690. "children": [{
  691. "name": "AxisLayout",
  692. "size": 6725
  693. }, {
  694. "name": "BundledEdgeRouter",
  695. "size": 3727
  696. }, {
  697. "name": "CircleLayout",
  698. "size": 9317
  699. }, {
  700. "name": "CirclePackingLayout",
  701. "size": 12003
  702. }, {
  703. "name": "DendrogramLayout",
  704. "size": 4853
  705. }, {
  706. "name": "ForceDirectedLayout",
  707. "size": 8411
  708. }, {
  709. "name": "IcicleTreeLayout",
  710. "size": 4864
  711. }, {
  712. "name": "IndentedTreeLayout",
  713. "size": 3174
  714. }, {
  715. "name": "Layout",
  716. "size": 7881
  717. }, {
  718. "name": "NodeLinkTreeLayout",
  719. "size": 12870
  720. }, {
  721. "name": "PieLayout",
  722. "size": 2728
  723. }, {
  724. "name": "RadialTreeLayout",
  725. "size": 12348
  726. }, {
  727. "name": "RandomLayout",
  728. "size": 870
  729. }, {
  730. "name": "StackedAreaLayout",
  731. "size": 9121
  732. }, {
  733. "name": "TreeMapLayout",
  734. "size": 9191
  735. }]
  736. }, {
  737. "name": "Operator",
  738. "size": 2490
  739. }, {
  740. "name": "OperatorList",
  741. "size": 5248
  742. }, {
  743. "name": "OperatorSequence",
  744. "size": 4190
  745. }, {
  746. "name": "OperatorSwitch",
  747. "size": 2581
  748. }, {
  749. "name": "SortOperator",
  750. "size": 2023
  751. }]
  752. }, {
  753. "name": "Visualization",
  754. "size": 16540
  755. }]
  756. }]
  757. }

源文件:dndTree.js

  1. /*Copyright (c) 2013-2016, Rob Schmuecker
  2. All rights reserved.
  3. Redistribution and use in source and binary forms, with or without
  4. modification, are permitted provided that the following conditions are met:
  5. * Redistributions of source code must retain the above copyright notice, this
  6. list of conditions and the following disclaimer.
  7. * Redistributions in binary form must reproduce the above copyright notice,
  8. this list of conditions and the following disclaimer in the documentation
  9. and/or other materials provided with the distribution.
  10. * The name Rob Schmuecker may not be used to endorse or promote products
  11. derived from this software without specific prior written permission.
  12. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  13. AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  14. IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  15. DISCLAIMED. IN NO EVENT SHALL MICHAEL BOSTOCK BE LIABLE FOR ANY DIRECT,
  16. INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
  17. BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  18. DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
  19. OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  20. NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
  21. EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.*/
  22. // Get JSON data
  23. treeJSON = d3.json("flare.json", function(error, treeData) {
  24. // Calculate total nodes, max label length
  25. var totalNodes = 0;
  26. var maxLabelLength = 0;
  27. // variables for drag/drop
  28. var selectedNode = null;
  29. var draggingNode = null;
  30. // panning variables
  31. var panSpeed = 200;
  32. var panBoundary = 20; // Within 20px from edges will pan when dragging.
  33. // Misc. variables
  34. var i = 0;
  35. var duration = 750;
  36. var root;
  37. // size of the diagram
  38. var viewerWidth = $(document).width();
  39. var viewerHeight = $(document).height();
  40. var tree = d3.layout.tree()
  41. .size([viewerHeight, viewerWidth]);
  42. // define a d3 diagonal projection for use by the node paths later on.
  43. var diagonal = d3.svg.diagonal()
  44. .projection(function(d) {
  45. return [d.y, d.x];
  46. });
  47. // A recursive helper function for performing some setup by walking through all nodes
  48. function visit(parent, visitFn, childrenFn) {
  49. if (!parent) return;
  50. visitFn(parent);
  51. var children = childrenFn(parent);
  52. if (children) {
  53. var count = children.length;
  54. for (var i = 0; i < count; i++) {
  55. visit(children[i], visitFn, childrenFn);
  56. }
  57. }
  58. }
  59. // Call visit function to establish maxLabelLength
  60. visit(treeData, function(d) {
  61. totalNodes++;
  62. maxLabelLength = Math.max(d.name.length, maxLabelLength);
  63. }, function(d) {
  64. return d.children && d.children.length > 0 ? d.children : null;
  65. });
  66. // sort the tree according to the node names
  67. function sortTree() {
  68. tree.sort(function(a, b) {
  69. return b.name.toLowerCase() < a.name.toLowerCase() ? 1 : -1;
  70. });
  71. }
  72. // Sort the tree initially incase the JSON isn't in a sorted order.
  73. sortTree();
  74. // TODO: Pan function, can be better implemented.
  75. function pan(domNode, direction) {
  76. var speed = panSpeed;
  77. if (panTimer) {
  78. clearTimeout(panTimer);
  79. translateCoords = d3.transform(svgGroup.attr("transform"));
  80. if (direction == 'left' || direction == 'right') {
  81. translateX = direction == 'left' ? translateCoords.translate[0] + speed : translateCoords.translate[0] - speed;
  82. translateY = translateCoords.translate[1];
  83. } else if (direction == 'up' || direction == 'down') {
  84. translateX = translateCoords.translate[0];
  85. translateY = direction == 'up' ? translateCoords.translate[1] + speed : translateCoords.translate[1] - speed;
  86. }
  87. scaleX = translateCoords.scale[0];
  88. scaleY = translateCoords.scale[1];
  89. scale = zoomListener.scale();
  90. svgGroup.transition().attr("transform", "translate(" + translateX + "," + translateY + ")scale(" + scale + ")");
  91. d3.select(domNode).select('g.node').attr("transform", "translate(" + translateX + "," + translateY + ")");
  92. zoomListener.scale(zoomListener.scale());
  93. zoomListener.translate([translateX, translateY]);
  94. panTimer = setTimeout(function() {
  95. pan(domNode, speed, direction);
  96. }, 50);
  97. }
  98. }
  99. // Define the zoom function for the zoomable tree
  100. function zoom() {
  101. svgGroup.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
  102. }
  103. // define the zoomListener which calls the zoom function on the "zoom" event constrained within the scaleExtents
  104. var zoomListener = d3.behavior.zoom().scaleExtent([0.1, 3]).on("zoom", zoom);
  105. function initiateDrag(d, domNode) {
  106. draggingNode = d;
  107. d3.select(domNode).select('.ghostCircle').attr('pointer-events', 'none');
  108. d3.selectAll('.ghostCircle').attr('class', 'ghostCircle show');
  109. d3.select(domNode).attr('class', 'node activeDrag');
  110. svgGroup.selectAll("g.node").sort(function(a, b) { // select the parent and sort the path's
  111. if (a.id != draggingNode.id) return 1; // a is not the hovered element, send "a" to the back
  112. else return -1; // a is the hovered element, bring "a" to the front
  113. });
  114. // if nodes has children, remove the links and nodes
  115. if (nodes.length > 1) {
  116. // remove link paths
  117. links = tree.links(nodes);
  118. nodePaths = svgGroup.selectAll("path.link")
  119. .data(links, function(d) {
  120. return d.target.id;
  121. }).remove();
  122. // remove child nodes
  123. nodesExit = svgGroup.selectAll("g.node")
  124. .data(nodes, function(d) {
  125. return d.id;
  126. }).filter(function(d, i) {
  127. if (d.id == draggingNode.id) {
  128. return false;
  129. }
  130. return true;
  131. }).remove();
  132. }
  133. // remove parent link
  134. parentLink = tree.links(tree.nodes(draggingNode.parent));
  135. svgGroup.selectAll('path.link').filter(function(d, i) {
  136. if (d.target.id == draggingNode.id) {
  137. return true;
  138. }
  139. return false;
  140. }).remove();
  141. dragStarted = null;
  142. }
  143. // define the baseSvg, attaching a class for styling and the zoomListener
  144. var baseSvg = d3.select("#tree-container").append("svg")
  145. .attr("width", viewerWidth)
  146. .attr("height", viewerHeight)
  147. .attr("class", "overlay")
  148. .call(zoomListener);
  149. // Define the drag listeners for drag/drop behaviour of nodes.
  150. dragListener = d3.behavior.drag()
  151. .on("dragstart", function(d) {
  152. if (d == root) {
  153. return;
  154. }
  155. dragStarted = true;
  156. nodes = tree.nodes(d);
  157. d3.event.sourceEvent.stopPropagation();
  158. // it's important that we suppress the mouseover event on the node being dragged. Otherwise it will absorb the mouseover event and the underlying node will not detect it d3.select(this).attr('pointer-events', 'none');
  159. })
  160. .on("drag", function(d) {
  161. if (d == root) {
  162. return;
  163. }
  164. if (dragStarted) {
  165. domNode = this;
  166. initiateDrag(d, domNode);
  167. }
  168. // get coords of mouseEvent relative to svg container to allow for panning
  169. relCoords = d3.mouse($('svg').get(0));
  170. if (relCoords[0] < panBoundary) {
  171. panTimer = true;
  172. pan(this, 'left');
  173. } else if (relCoords[0] > ($('svg').width() - panBoundary)) {
  174. panTimer = true;
  175. pan(this, 'right');
  176. } else if (relCoords[1] < panBoundary) {
  177. panTimer = true;
  178. pan(this, 'up');
  179. } else if (relCoords[1] > ($('svg').height() - panBoundary)) {
  180. panTimer = true;
  181. pan(this, 'down');
  182. } else {
  183. try {
  184. clearTimeout(panTimer);
  185. } catch (e) {
  186. }
  187. }
  188. d.x0 += d3.event.dy;
  189. d.y0 += d3.event.dx;
  190. var node = d3.select(this);
  191. node.attr("transform", "translate(" + d.y0 + "," + d.x0 + ")");
  192. updateTempConnector();
  193. }).on("dragend", function(d) {
  194. if (d == root) {
  195. return;
  196. }
  197. domNode = this;
  198. if (selectedNode) {
  199. // now remove the element from the parent, and insert it into the new elements children
  200. var index = draggingNode.parent.children.indexOf(draggingNode);
  201. if (index > -1) {
  202. draggingNode.parent.children.splice(index, 1);
  203. }
  204. if (typeof selectedNode.children !== 'undefined' || typeof selectedNode._children !== 'undefined') {
  205. if (typeof selectedNode.children !== 'undefined') {
  206. selectedNode.children.push(draggingNode);
  207. } else {
  208. selectedNode._children.push(draggingNode);
  209. }
  210. } else {
  211. selectedNode.children = [];
  212. selectedNode.children.push(draggingNode);
  213. }
  214. // Make sure that the node being added to is expanded so user can see added node is correctly moved
  215. expand(selectedNode);
  216. sortTree();
  217. endDrag();
  218. } else {
  219. endDrag();
  220. }
  221. });
  222. function endDrag() {
  223. selectedNode = null;
  224. d3.selectAll('.ghostCircle').attr('class', 'ghostCircle');
  225. d3.select(domNode).attr('class', 'node');
  226. // now restore the mouseover event or we won't be able to drag a 2nd time
  227. d3.select(domNode).select('.ghostCircle').attr('pointer-events', '');
  228. updateTempConnector();
  229. if (draggingNode !== null) {
  230. update(root);
  231. centerNode(draggingNode);
  232. draggingNode = null;
  233. }
  234. }
  235. // Helper functions for collapsing and expanding nodes.
  236. function collapse(d) {
  237. if (d.children) {
  238. d._children = d.children;
  239. d._children.forEach(collapse);
  240. d.children = null;
  241. }
  242. }
  243. function expand(d) {
  244. if (d._children) {
  245. d.children = d._children;
  246. d.children.forEach(expand);
  247. d._children = null;
  248. }
  249. }
  250. var overCircle = function(d) {
  251. selectedNode = d;
  252. updateTempConnector();
  253. };
  254. var outCircle = function(d) {
  255. selectedNode = null;
  256. updateTempConnector();
  257. };
  258. // Function to update the temporary connector indicating dragging affiliation
  259. var updateTempConnector = function() {
  260. var data = [];
  261. if (draggingNode !== null && selectedNode !== null) {
  262. // have to flip the source coordinates since we did this for the existing connectors on the original tree
  263. data = [{
  264. source: {
  265. x: selectedNode.y0,
  266. y: selectedNode.x0
  267. },
  268. target: {
  269. x: draggingNode.y0,
  270. y: draggingNode.x0
  271. }
  272. }];
  273. }
  274. var link = svgGroup.selectAll(".templink").data(data);
  275. link.enter().append("path")
  276. .attr("class", "templink")
  277. .attr("d", d3.svg.diagonal())
  278. .attr('pointer-events', 'none');
  279. link.attr("d", d3.svg.diagonal());
  280. link.exit().remove();
  281. };
  282. // Function to center node when clicked/dropped so node doesn't get lost when collapsing/moving with large amount of children.
  283. function centerNode(source) {
  284. scale = zoomListener.scale();
  285. x = -source.y0;
  286. y = -source.x0;
  287. x = x * scale + viewerWidth / 2;
  288. y = y * scale + viewerHeight / 2;
  289. d3.select('g').transition()
  290. .duration(duration)
  291. .attr("transform", "translate(" + x + "," + y + ")scale(" + scale + ")");
  292. zoomListener.scale(scale);
  293. zoomListener.translate([x, y]);
  294. }
  295. // Toggle children function
  296. function toggleChildren(d) {
  297. if (d.children) {
  298. d._children = d.children;
  299. d.children = null;
  300. } else if (d._children) {
  301. d.children = d._children;
  302. d._children = null;
  303. }
  304. return d;
  305. }
  306. // Toggle children on click.
  307. function click(d) {
  308. if (d3.event.defaultPrevented) return; // click suppressed
  309. d = toggleChildren(d);
  310. update(d);
  311. centerNode(d);
  312. }
  313. function update(source) {
  314. // Compute the new height, function counts total children of root node and sets tree height accordingly.
  315. // This prevents the layout looking squashed when new nodes are made visible or looking sparse when nodes are removed
  316. // This makes the layout more consistent.
  317. var levelWidth = [1];
  318. var childCount = function(level, n) {
  319. if (n.children && n.children.length > 0) {
  320. if (levelWidth.length <= level + 1) levelWidth.push(0);
  321. levelWidth[level + 1] += n.children.length;
  322. n.children.forEach(function(d) {
  323. childCount(level + 1, d);
  324. });
  325. }
  326. };
  327. childCount(0, root);
  328. var newHeight = d3.max(levelWidth) * 25; // 25 pixels per line
  329. tree = tree.size([newHeight, viewerWidth]);
  330. // Compute the new tree layout.
  331. var nodes = tree.nodes(root).reverse(),
  332. links = tree.links(nodes);
  333. // Set widths between levels based on maxLabelLength.
  334. nodes.forEach(function(d) {
  335. d.y = (d.depth * (maxLabelLength * 10)); //maxLabelLength * 10px
  336. // alternatively to keep a fixed scale one can set a fixed depth per level
  337. // Normalize for fixed-depth by commenting out below line
  338. // d.y = (d.depth * 500); //500px per level.
  339. });
  340. // Update the nodes…
  341. node = svgGroup.selectAll("g.node")
  342. .data(nodes, function(d) {
  343. return d.id || (d.id = ++i);
  344. });
  345. // Enter any new nodes at the parent's previous position.
  346. var nodeEnter = node.enter().append("g")
  347. .call(dragListener)
  348. .attr("class", "node")
  349. .attr("transform", function(d) {
  350. return "translate(" + source.y0 + "," + source.x0 + ")";
  351. })
  352. .on('click', click);
  353. nodeEnter.append("circle")
  354. .attr('class', 'nodeCircle')
  355. .attr("r", 0)
  356. .style("fill", function(d) {
  357. return d._children ? "lightsteelblue" : "#fff";
  358. });
  359. nodeEnter.append("text")
  360. .attr("x", function(d) {
  361. return d.children || d._children ? -10 : 10;
  362. })
  363. .attr("dy", ".35em")
  364. .attr('class', 'nodeText')
  365. .attr("text-anchor", function(d) {
  366. return d.children || d._children ? "end" : "start";
  367. })
  368. .text(function(d) {
  369. return d.name;
  370. })
  371. .style("fill-opacity", 0);
  372. // phantom node to give us mouseover in a radius around it
  373. nodeEnter.append("circle")
  374. .attr('class', 'ghostCircle')
  375. .attr("r", 30)
  376. .attr("opacity", 0.2) // change this to zero to hide the target area
  377. .style("fill", "red")
  378. .attr('pointer-events', 'mouseover')
  379. .on("mouseover", function(node) {
  380. overCircle(node);
  381. })
  382. .on("mouseout", function(node) {
  383. outCircle(node);
  384. });
  385. // Update the text to reflect whether node has children or not.
  386. node.select('text')
  387. .attr("x", function(d) {
  388. return d.children || d._children ? -10 : 10;
  389. })
  390. .attr("text-anchor", function(d) {
  391. return d.children || d._children ? "end" : "start";
  392. })
  393. .text(function(d) {
  394. return d.name;
  395. });
  396. // Change the circle fill depending on whether it has children and is collapsed
  397. node.select("circle.nodeCircle")
  398. .attr("r", 4.5)
  399. .style("fill", function(d) {
  400. return d._children ? "lightsteelblue" : "#fff";
  401. });
  402. // Transition nodes to their new position.
  403. var nodeUpdate = node.transition()
  404. .duration(duration)
  405. .attr("transform", function(d) {
  406. return "translate(" + d.y + "," + d.x + ")";
  407. });
  408. // Fade the text in
  409. nodeUpdate.select("text")
  410. .style("fill-opacity", 1);
  411. // Transition exiting nodes to the parent's new position.
  412. var nodeExit = node.exit().transition()
  413. .duration(duration)
  414. .attr("transform", function(d) {
  415. return "translate(" + source.y + "," + source.x + ")";
  416. })
  417. .remove();
  418. nodeExit.select("circle")
  419. .attr("r", 0);
  420. nodeExit.select("text")
  421. .style("fill-opacity", 0);
  422. // Update the links…
  423. var link = svgGroup.selectAll("path.link")
  424. .data(links, function(d) {
  425. return d.target.id;
  426. });
  427. // Enter any new links at the parent's previous position.
  428. link.enter().insert("path", "g")
  429. .attr("class", "link")
  430. .attr("d", function(d) {
  431. var o = {
  432. x: source.x0,
  433. y: source.y0
  434. };
  435. return diagonal({
  436. source: o,
  437. target: o
  438. });
  439. });
  440. // Transition links to their new position.
  441. link.transition()
  442. .duration(duration)
  443. .attr("d", diagonal);
  444. // Transition exiting nodes to the parent's new position.
  445. link.exit().transition()
  446. .duration(duration)
  447. .attr("d", function(d) {
  448. var o = {
  449. x: source.x,
  450. y: source.y
  451. };
  452. return diagonal({
  453. source: o,
  454. target: o
  455. });
  456. })
  457. .remove();
  458. // Stash the old positions for transition.
  459. nodes.forEach(function(d) {
  460. d.x0 = d.x;
  461. d.y0 = d.y;
  462. });
  463. }
  464. // Append a group which holds all nodes and which the zoom Listener can act upon.
  465. var svgGroup = baseSvg.append("g");
  466. // Define the root
  467. root = treeData;
  468. root.x0 = viewerHeight / 2;
  469. root.y0 = 0;
  470. // Layout the tree initially and center on the root node.
  471. update(root);
  472. centerNode(root);
  473. });