暂无描述

d3_flame_graph.go 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711
  1. // A D3.js plugin that produces flame graphs from hierarchical data.
  2. // https://github.com/spiermar/d3-flame-graph
  3. // Version 1.0.11
  4. // See LICENSE file for license details
  5. package d3flamegraph
  6. // JSSource returns the d3.flameGraph.js file
  7. const JSSource = `
  8. /**!
  9. *
  10. * Copyright 2017 Martin Spier <spiermar@gmail.com>
  11. *
  12. * Licensed under the Apache License, Version 2.0 (the "License");
  13. * you may not use this file except in compliance with the License.
  14. * You may obtain a copy of the License at
  15. *
  16. * http://www.apache.org/licenses/LICENSE-2.0
  17. *
  18. * Unless required by applicable law or agreed to in writing, software
  19. * distributed under the License is distributed on an "AS IS" BASIS,
  20. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  21. * See the License for the specific language governing permissions and
  22. * limitations under the License.
  23. *
  24. */
  25. (function() {
  26. 'use strict';
  27. /*jshint eqnull:true */
  28. // https://tc39.github.io/ecma262/#sec-array.prototype.find
  29. if (!Array.prototype.find) {
  30. Object.defineProperty(Array.prototype, 'find', {
  31. value: function(predicate) {
  32. // 1. Let O be ? ToObject(this value).
  33. if (this == null) {
  34. throw new TypeError('"this" is null or not defined');
  35. }
  36. var o = Object(this);
  37. // 2. Let len be ? ToLength(? Get(O, "length")).
  38. var len = o.length >>> 0;
  39. // 3. If IsCallable(predicate) is false, throw a TypeError exception.
  40. if (typeof predicate !== 'function') {
  41. throw new TypeError('predicate must be a function');
  42. }
  43. // 4. If thisArg was supplied, let T be thisArg; else let T be undefined.
  44. var thisArg = arguments[1];
  45. // 5. Let k be 0.
  46. var k = 0;
  47. // 6. Repeat, while k < len
  48. while (k < len) {
  49. // a. Let Pk be ! ToString(k).
  50. // b. Let kValue be ? Get(O, Pk).
  51. // c. Let testResult be ToBoolean(? Call(predicate, T, « kValue, k, O »)).
  52. // d. If testResult is true, return kValue.
  53. var kValue = o[k];
  54. if (predicate.call(thisArg, kValue, k, o)) {
  55. return kValue;
  56. }
  57. // e. Increase k by 1.
  58. k++;
  59. }
  60. // 7. Return undefined.
  61. return undefined;
  62. }
  63. });
  64. }
  65. if (!Array.prototype.filter)
  66. Array.prototype.filter = function(func, thisArg) {
  67. if ( ! ((typeof func === 'function') && this) )
  68. throw new TypeError();
  69. var len = this.length >>> 0,
  70. res = new Array(len), // preallocate array
  71. c = 0, i = -1;
  72. if (thisArg === undefined)
  73. while (++i !== len)
  74. // checks to see if the key was set
  75. if (i in this)
  76. if (func(t[i], i, t))
  77. res[c++] = t[i];
  78. else
  79. while (++i !== len)
  80. // checks to see if the key was set
  81. if (i in this)
  82. if (func.call(thisArg, t[i], i, t))
  83. res[c++] = t[i];
  84. res.length = c; // shrink down array to proper size
  85. return res;
  86. };
  87. /*jshint eqnull:false */
  88. // Node/CommonJS - require D3
  89. if (typeof(module) !== 'undefined' && typeof(exports) !== 'undefined' && typeof(d3) == 'undefined') {
  90. d3 = require('d3');
  91. }
  92. // Node/CommonJS - require d3-tip
  93. if (typeof(module) !== 'undefined' && typeof(exports) !== 'undefined' && typeof(d3.tip) == 'undefined') {
  94. d3.tip = require('d3-tip');
  95. }
  96. function flameGraph() {
  97. var w = 960, // graph width
  98. h = null, // graph height
  99. c = 18, // cell height
  100. selection = null, // selection
  101. tooltip = true, // enable tooltip
  102. title = "", // graph title
  103. transitionDuration = 750,
  104. transitionEase = d3.easeCubic, // tooltip offset
  105. sort = false,
  106. reversed = false, // reverse the graph direction
  107. clickHandler = null,
  108. minFrameSize = 0,
  109. details = null;
  110. var tip = d3.tip()
  111. .direction("s")
  112. .offset([8, 0])
  113. .attr('class', 'd3-flame-graph-tip')
  114. .html(function(d) { return label(d); });
  115. var svg;
  116. function name(d) {
  117. return d.data.n || d.data.name;
  118. }
  119. function children(d) {
  120. return d.c || d.children;
  121. }
  122. function value(d) {
  123. return d.v || d.value;
  124. }
  125. var label = function(d) {
  126. return name(d) + " (" + d3.format(".3f")(100 * (d.x1 - d.x0), 3) + "%, " + value(d) + " samples)";
  127. };
  128. function setDetails(t) {
  129. if (details)
  130. details.innerHTML = t;
  131. }
  132. var colorMapper = function(d) {
  133. return d.highlight ? "#E600E6" : colorHash(name(d));
  134. };
  135. function generateHash(name) {
  136. // Return a vector (0.0->1.0) that is a hash of the input string.
  137. // The hash is computed to favor early characters over later ones, so
  138. // that strings with similar starts have similar vectors. Only the first
  139. // 6 characters are considered.
  140. var hash = 0, weight = 1, max_hash = 0, mod = 10, max_char = 6;
  141. if (name) {
  142. for (var i = 0; i < name.length; i++) {
  143. if (i > max_char) { break; }
  144. hash += weight * (name.charCodeAt(i) % mod);
  145. max_hash += weight * (mod - 1);
  146. weight *= 0.70;
  147. }
  148. if (max_hash > 0) { hash = hash / max_hash; }
  149. }
  150. return hash;
  151. }
  152. function colorHash(name) {
  153. // Return an rgb() color string that is a hash of the provided name,
  154. // and with a warm palette.
  155. var vector = 0;
  156. if (name) {
  157. var nameArr = name.split('` + "`" + `');
  158. if (nameArr.length > 1) {
  159. name = nameArr[nameArr.length -1]; // drop module name if present
  160. }
  161. name = name.split('(')[0]; // drop extra info
  162. vector = generateHash(name);
  163. }
  164. var r = 200 + Math.round(55 * vector);
  165. var g = 0 + Math.round(230 * (1 - vector));
  166. var b = 0 + Math.round(55 * (1 - vector));
  167. return "rgb(" + r + "," + g + "," + b + ")";
  168. }
  169. function hide(d) {
  170. d.data.hide = true;
  171. if(children(d)) {
  172. children(d).forEach(hide);
  173. }
  174. }
  175. function show(d) {
  176. d.data.fade = false;
  177. d.data.hide = false;
  178. if(children(d)) {
  179. children(d).forEach(show);
  180. }
  181. }
  182. function getSiblings(d) {
  183. var siblings = [];
  184. if (d.parent) {
  185. var me = d.parent.children.indexOf(d);
  186. siblings = d.parent.children.slice(0);
  187. siblings.splice(me, 1);
  188. }
  189. return siblings;
  190. }
  191. function hideSiblings(d) {
  192. var siblings = getSiblings(d);
  193. siblings.forEach(function(s) {
  194. hide(s);
  195. });
  196. if(d.parent) {
  197. hideSiblings(d.parent);
  198. }
  199. }
  200. function fadeAncestors(d) {
  201. if(d.parent) {
  202. d.parent.data.fade = true;
  203. fadeAncestors(d.parent);
  204. }
  205. }
  206. function getRoot(d) {
  207. if(d.parent) {
  208. return getRoot(d.parent);
  209. }
  210. return d;
  211. }
  212. function zoom(d) {
  213. tip.hide(d);
  214. hideSiblings(d);
  215. show(d);
  216. fadeAncestors(d);
  217. update();
  218. if (typeof clickHandler === 'function') {
  219. clickHandler(d);
  220. }
  221. }
  222. function searchTree(d, term) {
  223. var re = new RegExp(term),
  224. searchResults = [];
  225. function searchInner(d) {
  226. var label = name(d);
  227. if (children(d)) {
  228. children(d).forEach(function (child) {
  229. searchInner(child);
  230. });
  231. }
  232. if (label.match(re)) {
  233. d.highlight = true;
  234. searchResults.push(d);
  235. } else {
  236. d.highlight = false;
  237. }
  238. }
  239. searchInner(d);
  240. return searchResults;
  241. }
  242. function clear(d) {
  243. d.highlight = false;
  244. if(children(d)) {
  245. children(d).forEach(function(child) {
  246. clear(child);
  247. });
  248. }
  249. }
  250. function doSort(a, b) {
  251. if (typeof sort === 'function') {
  252. return sort(a, b);
  253. } else if (sort) {
  254. return d3.ascending(name(a), name(b));
  255. }
  256. }
  257. var partition = d3.partition();
  258. function filterNodes(root) {
  259. var nodeList = root.descendants();
  260. if (minFrameSize > 0) {
  261. var kx = w / (root.x1 - root.x0);
  262. nodeList = nodeList.filter(function(el) {
  263. return ((el.x1 - el.x0) * kx) > minFrameSize;
  264. });
  265. }
  266. return nodeList;
  267. }
  268. function update() {
  269. selection.each(function(root) {
  270. var x = d3.scaleLinear().range([0, w]),
  271. y = d3.scaleLinear().range([0, c]);
  272. if (sort) root.sort(doSort);
  273. root.sum(function(d) {
  274. if (d.fade || d.hide) {
  275. return 0;
  276. }
  277. // The node's self value is its total value minus all children.
  278. var v = value(d);
  279. if (children(d)) {
  280. var c = children(d);
  281. for (var i = 0; i < c.length; i++) {
  282. v -= value(c[i]);
  283. }
  284. }
  285. return v;
  286. });
  287. partition(root);
  288. var kx = w / (root.x1 - root.x0);
  289. function width(d) { return (d.x1 - d.x0) * kx; }
  290. var descendants = filterNodes(root);
  291. var g = d3.select(this).select("svg").selectAll("g").data(descendants, function(d) { return d.id; });
  292. g.transition()
  293. .duration(transitionDuration)
  294. .ease(transitionEase)
  295. .attr("transform", function(d) { return "translate(" + x(d.x0) + "," + (reversed ? y(d.depth) : (h - y(d.depth) - c)) + ")"; });
  296. g.select("rect")
  297. .attr("width", width);
  298. var node = g.enter()
  299. .append("svg:g")
  300. .attr("transform", function(d) { return "translate(" + x(d.x0) + "," + (reversed ? y(d.depth) : (h - y(d.depth) - c)) + ")"; });
  301. node.append("svg:rect")
  302. .transition()
  303. .delay(transitionDuration / 2)
  304. .attr("width", width);
  305. if (!tooltip)
  306. node.append("svg:title");
  307. node.append("foreignObject")
  308. .append("xhtml:div");
  309. // Now we have to re-select to see the new elements (why?).
  310. g = d3.select(this).select("svg").selectAll("g").data(descendants, function(d) { return d.id; });
  311. g.attr("width", width)
  312. .attr("height", function(d) { return c; })
  313. .attr("name", function(d) { return name(d); })
  314. .attr("class", function(d) { return d.data.fade ? "frame fade" : "frame"; });
  315. g.select("rect")
  316. .attr("height", function(d) { return c; })
  317. .attr("fill", function(d) { return colorMapper(d); });
  318. if (!tooltip)
  319. g.select("title")
  320. .text(label);
  321. g.select("foreignObject")
  322. .attr("width", width)
  323. .attr("height", function(d) { return c; })
  324. .select("div")
  325. .attr("class", "d3-flame-graph-label")
  326. .style("display", function(d) { return (width(d) < 35) ? "none" : "block";})
  327. .transition()
  328. .delay(transitionDuration)
  329. .text(name);
  330. g.on('click', zoom);
  331. g.exit()
  332. .remove();
  333. g.on('mouseover', function(d) {
  334. if (tooltip) tip.show(d);
  335. setDetails(label(d));
  336. }).on('mouseout', function(d) {
  337. if (tooltip) tip.hide(d);
  338. setDetails("");
  339. });
  340. });
  341. }
  342. function merge(data, samples) {
  343. samples.forEach(function (sample) {
  344. var node = data.find(function (element) {
  345. return (element.name === sample.name);
  346. });
  347. if (node) {
  348. if (node.original) {
  349. node.original += sample.value;
  350. } else {
  351. node.value += sample.value;
  352. }
  353. if (sample.children) {
  354. if (!node.children) {
  355. node.children = [];
  356. }
  357. merge(node.children, sample.children);
  358. }
  359. } else {
  360. data.push(sample);
  361. }
  362. });
  363. }
  364. function s4() {
  365. return Math.floor((1 + Math.random()) * 0x10000)
  366. .toString(16)
  367. .substring(1);
  368. }
  369. function injectIds(node) {
  370. node.id = s4() + "-" + s4() + "-" + "-" + s4() + "-" + s4();
  371. var children = node.c || node.children || [];
  372. for (var i = 0; i < children.length; i++) {
  373. injectIds(children[i]);
  374. }
  375. }
  376. function chart(s) {
  377. var root = d3.hierarchy(
  378. s.datum(), function(d) { return children(d); }
  379. );
  380. injectIds(root);
  381. selection = s.datum(root);
  382. if (!arguments.length) return chart;
  383. if (!h) {
  384. h = (root.height + 2) * c;
  385. }
  386. selection.each(function(data) {
  387. if (!svg) {
  388. svg = d3.select(this)
  389. .append("svg:svg")
  390. .attr("width", w)
  391. .attr("height", h)
  392. .attr("class", "partition d3-flame-graph")
  393. .call(tip);
  394. svg.append("svg:text")
  395. .attr("class", "title")
  396. .attr("text-anchor", "middle")
  397. .attr("y", "25")
  398. .attr("x", w/2)
  399. .attr("fill", "#808080")
  400. .text(title);
  401. }
  402. });
  403. // first draw
  404. update();
  405. }
  406. chart.height = function (_) {
  407. if (!arguments.length) { return h; }
  408. h = _;
  409. return chart;
  410. };
  411. chart.width = function (_) {
  412. if (!arguments.length) { return w; }
  413. w = _;
  414. return chart;
  415. };
  416. chart.cellHeight = function (_) {
  417. if (!arguments.length) { return c; }
  418. c = _;
  419. return chart;
  420. };
  421. chart.tooltip = function (_) {
  422. if (!arguments.length) { return tooltip; }
  423. if (typeof _ === "function") {
  424. tip = _;
  425. }
  426. tooltip = !!_;
  427. return chart;
  428. };
  429. chart.title = function (_) {
  430. if (!arguments.length) { return title; }
  431. title = _;
  432. return chart;
  433. };
  434. chart.transitionDuration = function (_) {
  435. if (!arguments.length) { return transitionDuration; }
  436. transitionDuration = _;
  437. return chart;
  438. };
  439. chart.transitionEase = function (_) {
  440. if (!arguments.length) { return transitionEase; }
  441. transitionEase = _;
  442. return chart;
  443. };
  444. chart.sort = function (_) {
  445. if (!arguments.length) { return sort; }
  446. sort = _;
  447. return chart;
  448. };
  449. chart.reversed = function (_) {
  450. if (!arguments.length) { return reversed; }
  451. reversed = _;
  452. return chart;
  453. };
  454. chart.label = function(_) {
  455. if (!arguments.length) { return label; }
  456. label = _;
  457. return chart;
  458. };
  459. chart.search = function(term) {
  460. var searchResults = [];
  461. selection.each(function(data) {
  462. searchResults = searchTree(data, term);
  463. update();
  464. });
  465. return searchResults;
  466. };
  467. chart.clear = function() {
  468. selection.each(function(data) {
  469. clear(data);
  470. update();
  471. });
  472. };
  473. chart.zoomTo = function(d) {
  474. zoom(d);
  475. };
  476. chart.resetZoom = function() {
  477. selection.each(function (data) {
  478. zoom(data); // zoom to root
  479. });
  480. };
  481. chart.onClick = function(_) {
  482. if (!arguments.length) {
  483. return clickHandler;
  484. }
  485. clickHandler = _;
  486. return chart;
  487. };
  488. chart.merge = function(samples) {
  489. var newRoot; // Need to re-create hierarchy after data changes.
  490. selection.each(function (root) {
  491. merge([root.data], [samples]);
  492. newRoot = d3.hierarchy(root.data, function(d) { return children(d); });
  493. injectIds(newRoot);
  494. });
  495. selection = selection.datum(newRoot);
  496. update();
  497. };
  498. chart.color = function(_) {
  499. if (!arguments.length) { return colorMapper; }
  500. colorMapper = _;
  501. return chart;
  502. };
  503. chart.minFrameSize = function (_) {
  504. if (!arguments.length) { return minFrameSize; }
  505. minFrameSize = _;
  506. return chart;
  507. };
  508. chart.details = function (_) {
  509. if (!arguments.length) { return details; }
  510. details = _;
  511. return chart;
  512. };
  513. return chart;
  514. }
  515. // Node/CommonJS exports
  516. if (typeof(module) !== 'undefined' && typeof(exports) !== 'undefined') {
  517. module.exports = flameGraph;
  518. } else {
  519. d3.flameGraph = flameGraph;
  520. }
  521. })();
  522. `
  523. // CSSSource returns the d3.flameGraph.css file
  524. const CSSSource = `
  525. .d3-flame-graph rect {
  526. stroke: #EEEEEE;
  527. fill-opacity: .8;
  528. }
  529. .d3-flame-graph rect:hover {
  530. stroke: #474747;
  531. stroke-width: 0.5;
  532. cursor: pointer;
  533. }
  534. .d3-flame-graph-label {
  535. pointer-events: none;
  536. white-space: nowrap;
  537. text-overflow: ellipsis;
  538. overflow: hidden;
  539. font-size: 12px;
  540. font-family: Verdana;
  541. margin-left: 4px;
  542. margin-right: 4px;
  543. line-height: 1.5;
  544. padding: 0 0 0;
  545. font-weight: 400;
  546. color: black;
  547. text-align: left;
  548. }
  549. .d3-flame-graph .fade {
  550. opacity: 0.6 !important;
  551. }
  552. .d3-flame-graph .title {
  553. font-size: 20px;
  554. font-family: Verdana;
  555. }
  556. .d3-flame-graph-tip {
  557. line-height: 1;
  558. font-family: Verdana;
  559. font-size: 12px;
  560. padding: 12px;
  561. background: rgba(0, 0, 0, 0.8);
  562. color: #fff;
  563. border-radius: 2px;
  564. pointer-events: none;
  565. }
  566. /* Creates a small triangle extender for the tooltip */
  567. .d3-flame-graph-tip:after {
  568. box-sizing: border-box;
  569. display: inline;
  570. font-size: 10px;
  571. width: 100%;
  572. line-height: 1;
  573. color: rgba(0, 0, 0, 0.8);
  574. position: absolute;
  575. pointer-events: none;
  576. }
  577. /* Northward tooltips */
  578. .d3-flame-graph-tip.n:after {
  579. content: "\25BC";
  580. margin: -1px 0 0 0;
  581. top: 100%;
  582. left: 0;
  583. text-align: center;
  584. }
  585. /* Eastward tooltips */
  586. .d3-flame-graph-tip.e:after {
  587. content: "\25C0";
  588. margin: -4px 0 0 0;
  589. top: 50%;
  590. left: -8px;
  591. }
  592. /* Southward tooltips */
  593. .d3-flame-graph-tip.s:after {
  594. content: "\25B2";
  595. margin: 0 0 1px 0;
  596. top: -8px;
  597. left: 0;
  598. text-align: center;
  599. }
  600. /* Westward tooltips */
  601. .d3-flame-graph-tip.w:after {
  602. content: "\25B6";
  603. margin: -4px 0 0 -1px;
  604. top: 50%;
  605. left: 100%;
  606. }
  607. `