d3-flame-graph
Advanced tools
Comparing version 0.4.2 to 1.0.0
@@ -27,8 +27,9 @@ { | ||
"dependencies": { | ||
"d3": "~3.5.5", | ||
"d3-tip": "~0.6.7" | ||
"d3": "~4", | ||
"d3-tip": "~0.7.1", | ||
"lodash": "~3.10.1" | ||
}, | ||
"resolutions": { | ||
"d3": "3.5.5" | ||
"d3": "4.8.0" | ||
} | ||
} |
{ | ||
"name": "d3-flame-graph", | ||
"version": "0.4.2", | ||
"version": "1.0.0", | ||
"description": "A d3.js library to produce flame graphs.", | ||
@@ -5,0 +5,0 @@ "main": "src/d3.flame.js", |
@@ -21,4 +21,6 @@ # d3-flame-graph | ||
Click [here](http://spiermar.github.io/d3-flame-graph/) to check the demo! | ||
Click [here](http://spiermar.github.io/d3-flame-graph/) to check the fully-featured demo! | ||
Click [here](http://bl.ocks.org/spiermar/4509343495f8d6e214cb) to check the simplified demo on bl.ocks.org! | ||
## Getting Started | ||
@@ -43,3 +45,3 @@ | ||
``` | ||
```html | ||
<script type="text/javascript" src="bower_components/d3/d3.js"></script> | ||
@@ -65,3 +67,3 @@ <script type="text/javascript" src="bower_components/d3-flame-graph/dist/d3.layout.flame.js"></script> | ||
``` | ||
```js | ||
{ | ||
@@ -78,2 +80,23 @@ "name": "<name>", | ||
### Interacting with entries | ||
Internally, the data is transformed into a d3 **hierarchy**. | ||
Functions like `onClick`, `label` and `zoom` expose individual entries as hierarchy Nodes, which wrap the provided data and add more properties: | ||
``` | ||
{ | ||
"data": <original user-provided object>, | ||
"parent": <another hierarchy node>, | ||
"children": [ | ||
<hierarchy node> | ||
], | ||
"x1": <double>, // x2 - x1 is the size of this node, as a fraction of the root. | ||
"x2": <double> | ||
} | ||
``` | ||
**This is a breaking change from previous versions of d3-flame-graph, which were based on version 3 of the d3 library*** | ||
See [d3-hierarchy](https://github.com/d3/d3-hierarchy#hierarchy). | ||
## API Reference | ||
@@ -107,3 +130,3 @@ | ||
``` | ||
```js | ||
.attr('class', 'd3-flame-graph-tip') | ||
@@ -122,28 +145,26 @@ ``` | ||
Specifies the transition easing function. The default easing function is "cubic-in-out". If ease is not specified, returns the flameGraph object. The following easing types are supported: | ||
Specifies the transition easing function. The default easing function is `d3.easeCubic`. | ||
* linear - the identity function, t. | ||
* poly(k) - raises t to the specified power k (e.g., 3). | ||
* quad - equivalent to poly(2). | ||
* cubic - equivalent to poly(3). | ||
* sin - applies the trigonometric function sin. | ||
* exp - raises 2 to a power based on t. | ||
* circle - the quarter circle. | ||
* elastic(a, p) - simulates an elastic band; may extend slightly beyond 0 and 1. | ||
* back(s) - simulates backing into a parking space. | ||
* bounce - simulates a bouncy collision. | ||
See [d3-ease](https://github.com/d3/d3-ease). | ||
These built-in types may be extended using a variety of modes: | ||
<a name="sort" href="#sort">#</a> flameGraph.<b>sort</b>(<i>[enabled]</i>) | ||
* in - the identity function. | ||
* out - reverses the easing direction to [1,0]. | ||
* in-out - copies and mirrors the easing function from [0,.5] and [.5,1]. | ||
* out-in - copies and mirrors the easing function from [1,.5] and [.5,0]. | ||
Enables/disables sorting of children frames. Defaults to <i>true</i> if not set to sort in ascending order by frame's name. If set to a function, the function takes two frames (a,b) and returns -1 if frame a is less than b, 1 if greater, or 0 if equal. If a value is specified, it will enable/disable sorting, otherwise it will return the flameGraph object. | ||
See [d3.ease](https://github.com/mbostock/d3/wiki/Transitions#d3_ease). | ||
<a name="resetZoom" href="#resetZoom">#</a> flameGraph.<b>resetZoom</b>() | ||
<a name="sort" href="#sort">#</a> flameGraph.<b>sort</b>(<i>[enabled]</i>) | ||
Resets the zoom so that everything is visible. | ||
Enables/disables sorting of children frames. Defaults to <i>true</i> if not set to sort in ascending order by frame's name. If set to a function, the function takes two frames (a,b) and returns -1 if frame a is less than b, 1 if greater, or 0 if equal. If a value is specified, it will enable/disable sorting, otherwise it will return the flameGraph object. | ||
<a name="onClick" href="#onClick">#</a> flameGraph.<b>onClick</b>(<i>[function]</i>) | ||
Adds a function that will be called when the user clicks on a frame. Example: | ||
```js | ||
flameGraph.onClick(function (d) { | ||
console.info("You clicked on frame "+ d.data.name); | ||
}); | ||
``` | ||
If called with no arguments, `onClick` will return the click handler. | ||
## Issues | ||
@@ -150,0 +171,0 @@ |
@@ -13,4 +13,6 @@ (function() { | ||
transitionDuration = 750, | ||
transitionEase = "cubic-in-out", // tooltip offset | ||
sort = true; | ||
transitionEase = d3.easeCubic, // tooltip offset | ||
sort = true, | ||
reversed = false, // reverse the graph direction | ||
clickHandler = null; | ||
@@ -23,6 +25,8 @@ var tip = d3.tip() | ||
var labelFormat = function(d) { | ||
return d.name + " (" + d3.round(100 * d.dx, 3) + "%, " + d.value + " samples)"; | ||
} | ||
var svg; | ||
var label = function(d) { | ||
return d.data.name + " (" + d3.format(".3f")(100 * (d.x1 - d.x0), 3) + "%, " + d.data.value + " samples)"; | ||
}; | ||
function setDetails(t) { | ||
@@ -34,14 +38,10 @@ var details = document.getElementById("details"); | ||
function label(d) { | ||
if (!d.dummy) { | ||
return labelFormat(d); | ||
} else { | ||
return ""; | ||
} | ||
} | ||
function name(d) { | ||
return d.name; | ||
return d.data.name; | ||
} | ||
var colorMapper = function(d) { | ||
return d.highlight ? "#E600E6" : colorHash(d.data.name); | ||
}; | ||
function generateHash(name) { | ||
@@ -80,29 +80,7 @@ // Return a vector (0.0->1.0) that is a hash of the input string. | ||
function augment(data) { | ||
// Augment partitioning layout with "dummy" nodes so that internal nodes' | ||
// values dictate their width. Annoying, but seems to be least painful | ||
// option. https://github.com/mbostock/d3/pull/574 | ||
if (data.children && (data.children.length > 0)) { | ||
data.children.forEach(augment); | ||
var childValues = 0; | ||
data.children.forEach(function(child) { | ||
childValues += child.value; | ||
}); | ||
if (childValues < data.value) { | ||
data.children.push( | ||
{ | ||
"name": "", | ||
"value": data.value - childValues, | ||
"dummy": true | ||
} | ||
); | ||
} | ||
} | ||
} | ||
function hide(d) { | ||
if(!d.original) { | ||
d.original = d.value; | ||
if(!d.data.original) { | ||
d.data.original = d.data.value; | ||
} | ||
d.value = 0; | ||
d.data.value = 0; | ||
if(d.children) { | ||
@@ -114,5 +92,5 @@ d.children.forEach(hide); | ||
function show(d) { | ||
d.fade = false; | ||
if(d.original) { | ||
d.value = d.original; | ||
d.data.fade = false; | ||
if(d.data.original) { | ||
d.data.value = d.data.original; | ||
} | ||
@@ -146,3 +124,3 @@ if(d.children) { | ||
if(d.parent) { | ||
d.parent.fade = true; | ||
d.parent.data.fade = true; | ||
fadeAncestors(d.parent); | ||
@@ -165,2 +143,5 @@ } | ||
update(); | ||
if (typeof clickHandler === 'function') { | ||
clickHandler(d); | ||
} | ||
} | ||
@@ -170,15 +151,23 @@ | ||
var re = new RegExp(term), | ||
label = d.name; | ||
searchResults = []; | ||
if(d.children) { | ||
d.children.forEach(function(child) { | ||
searchTree(child, term); | ||
}); | ||
function searchInner(d) { | ||
var label = d.data.name; | ||
if (d.children) { | ||
d.children.forEach(function (child) { | ||
searchInner(child); | ||
}); | ||
} | ||
if (label.match(re)) { | ||
d.highlight = true; | ||
searchResults.push(d); | ||
} else { | ||
d.highlight = false; | ||
} | ||
} | ||
if (label.match(re)) { | ||
d.highlight = true; | ||
} else { | ||
d.highlight = false; | ||
} | ||
searchInner(d); | ||
return searchResults; | ||
} | ||
@@ -190,3 +179,3 @@ | ||
d.children.forEach(function(child) { | ||
clear(child, term); | ||
clear(child); | ||
}); | ||
@@ -200,3 +189,3 @@ } | ||
} else if (sort) { | ||
return d3.ascending(a.name, b.name); | ||
return d3.ascending(a.data.name, b.data.name); | ||
} else { | ||
@@ -207,24 +196,36 @@ return 0; | ||
var partition = d3.layout.partition() | ||
.sort(doSort) | ||
.value(function(d) {return d.v || d.value;}) | ||
.children(function(d) {return d.c || d.children;}); | ||
var partition = d3.partition(); | ||
function update() { | ||
selection.each(function(data) { | ||
selection.each(function(root) { | ||
var x = d3.scaleLinear().range([0, w]), | ||
y = d3.scaleLinear().range([0, c]); | ||
var x = d3.scale.linear().range([0, w]), | ||
y = d3.scale.linear().range([0, c]); | ||
root.sort(doSort); | ||
root.sum(function(d) { | ||
if (d.fade) { | ||
return 0; | ||
} | ||
// The node's self value is its total value minus all children. | ||
var v = d.v || d.value || 0; | ||
if (d.children) { | ||
for (var i = 0; i < d.children.length; i++) { | ||
v -= d.children[i].value; | ||
} | ||
} | ||
return v; | ||
}); | ||
partition(root); | ||
var nodes = partition(data); | ||
var kx = w / (root.x1 - root.x0); | ||
function width(d) { return (d.x1 - d.x0) * kx; } | ||
var kx = w / data.dx; | ||
var g = d3.select(this).select("svg").selectAll("g").data(root.descendants()); | ||
var g = d3.select(this).select("svg").selectAll("g").data(nodes); | ||
g.transition() | ||
.duration(transitionDuration) | ||
.ease(transitionEase) | ||
.attr("transform", function(d) { return "translate(" + x(d.x) + "," + (h - y(d.depth) - c) + ")"; }); | ||
.attr("transform", function(d) { return "translate(" + x(d.x0) + "," | ||
+ (reversed ? y(d.depth) : (h - y(d.depth) - c)) + ")"; }); | ||
@@ -234,10 +235,10 @@ g.select("rect").transition() | ||
.ease(transitionEase) | ||
.attr("width", function(d) { return d.dx * kx; }); | ||
.attr("width", width); | ||
var node = g.enter() | ||
.append("svg:g") | ||
.attr("transform", function(d) { return "translate(" + x(d.x) + "," + (h - y(d.depth) - c) + ")"; }); | ||
.attr("transform", function(d) { return "translate(" + x(d.x0) + "," | ||
+ (reversed ? y(d.depth) : (h - y(d.depth) - c)) + ")"; }); | ||
node.append("svg:rect") | ||
.attr("width", function(d) { return d.dx * kx; }); | ||
node.append("svg:rect").attr("width", width); | ||
@@ -250,11 +251,13 @@ if (!tooltip) | ||
g.attr("width", function(d) { return d.dx * kx; }) | ||
// Now we have to re-select to see the new elements (why?). | ||
g = d3.select(this).select("svg").selectAll("g").data(root.descendants()); | ||
g.attr("width", width) | ||
.attr("height", function(d) { return c; }) | ||
.attr("name", function(d) { return d.name; }) | ||
.attr("class", function(d) { return d.fade ? "frame fade" : "frame"; }); | ||
.attr("name", function(d) { return d.data.name; }) | ||
.attr("class", function(d) { return d.data.fade ? "frame fade" : "frame"; }); | ||
g.select("rect") | ||
.attr("height", function(d) { return c; }) | ||
.attr("fill", function(d) {return d.highlight ? "#E600E6" : colorHash(d.name); }) | ||
.style("visibility", function(d) {return d.dummy ? "hidden" : "visible";}); | ||
.attr("fill", function(d) { return colorMapper(d); }); | ||
@@ -266,7 +269,7 @@ if (!tooltip) | ||
g.select("foreignObject") | ||
.attr("width", function(d) { return d.dx * kx; }) | ||
.attr("width", width) | ||
.attr("height", function(d) { return c; }) | ||
.select("div") | ||
.attr("class", "label") | ||
.style("display", function(d) { return (d.dx * kx < 35) || d.dummy ? "none" : "block";}) | ||
.style("display", function(d) { return (width(d) < 35) ? "none" : "block";}) | ||
.text(name); | ||
@@ -276,16 +279,10 @@ | ||
g.exit().remove(); | ||
g.on('mouseover', function(d) { | ||
if(!d.dummy) { | ||
if (tooltip) tip.show(d); | ||
setDetails(label(d)); | ||
} | ||
if (tooltip) tip.show(d); | ||
setDetails(label(d)); | ||
}).on('mouseout', function(d) { | ||
if(!d.dummy) { | ||
if (tooltip) tip.hide(d); | ||
setDetails(""); | ||
} | ||
if (tooltip) tip.hide(d); | ||
setDetails(""); | ||
}); | ||
@@ -295,6 +292,30 @@ }); | ||
function merge(data, samples) { | ||
samples.forEach(function (sample) { | ||
var node = _.find(data, function (element) { | ||
return element.name === sample.name; | ||
}); | ||
if (node) { | ||
if (node.original) { | ||
node.original += sample.value; | ||
} else { | ||
node.value += sample.value; | ||
} | ||
if (sample.children) { | ||
if (!node.children) { | ||
node.children = []; | ||
} | ||
merge(node.children, sample.children) | ||
} | ||
} else { | ||
data.push(sample); | ||
} | ||
}); | ||
} | ||
function chart(s) { | ||
var root = d3.hierarchy(s.datum(), function(d) { return d.c || d.children; }); | ||
selection = s.datum(root); | ||
selection = s; | ||
if (!arguments.length) return chart; | ||
@@ -304,26 +325,22 @@ | ||
var svg = d3.select(this) | ||
.append("svg:svg") | ||
.attr("width", w) | ||
.attr("height", h) | ||
.attr("class", "partition d3-flame-graph") | ||
.call(tip); | ||
if (!svg) { | ||
svg = d3.select(this) | ||
.append("svg:svg") | ||
.attr("width", w) | ||
.attr("height", h) | ||
.attr("class", "partition d3-flame-graph") | ||
.call(tip); | ||
svg.append("svg:text") | ||
.attr("class", "title") | ||
.attr("text-anchor", "middle") | ||
.attr("y", "25") | ||
.attr("x", w/2) | ||
.attr("fill", "#808080") | ||
.text(title); | ||
svg.append("svg:text") | ||
.attr("class", "title") | ||
.attr("text-anchor", "middle") | ||
.attr("y", "25") | ||
.attr("x", w/2) | ||
.attr("fill", "#808080") | ||
.text(title); | ||
} | ||
}); | ||
augment(data); | ||
// "creative" fix for node ordering when partition is called for the first time | ||
partition(data); | ||
// first draw | ||
update(); | ||
}); | ||
// first draw | ||
update(); | ||
} | ||
@@ -382,13 +399,21 @@ | ||
chart.reversed = function (_) { | ||
if (!arguments.length) { return reversed; } | ||
reversed = _; | ||
return chart; | ||
}; | ||
chart.label = function(_) { | ||
if (!arguments.length) { return labelFormat; } | ||
labelFormat = _; | ||
if (!arguments.length) { return label; } | ||
label = _; | ||
return chart; | ||
} | ||
}; | ||
chart.search = function(term) { | ||
var searchResults = []; | ||
selection.each(function(data) { | ||
searchTree(data, term); | ||
searchResults = searchTree(data, term); | ||
update(); | ||
}); | ||
return searchResults; | ||
}; | ||
@@ -403,4 +428,36 @@ | ||
chart.zoomTo = function(d) { | ||
zoom(d); | ||
}; | ||
chart.resetZoom = function() { | ||
selection.each(function (data) { | ||
zoom(data); // zoom to root | ||
}); | ||
}; | ||
chart.onClick = function(_) { | ||
if (!arguments.length) { | ||
return clickHandler; | ||
} | ||
clickHandler = _; | ||
return chart; | ||
}; | ||
chart.merge = function(samples) { | ||
var newRoot; // Need to re-create hierarchy after data changes. | ||
selection.each(function (root) { | ||
merge([root.data], [samples]); | ||
newRoot = d3.hierarchy(root.data, function(d) { return d.c || d.children; }); | ||
}); | ||
selection = selection.datum(newRoot); | ||
update(); | ||
} | ||
chart.color = function(_) { | ||
if (!arguments.length) { return colorMapper; } | ||
colorMapper = _; | ||
return chart; | ||
}; | ||
return chart; | ||
@@ -407,0 +464,0 @@ } |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
11126228
14
4063
0
199
1