Repository: fzaninotto/DependencyWheel Branch: master Commit: c7a1f4a1672d Files: 7 Total size: 21.6 KB Directory structure: gitextract_bh_4ohqv/ ├── LICENSE ├── README.md ├── data/ │ └── composer.json ├── index.html ├── js/ │ ├── composerBuilder.js │ └── d3.dependencyWheel.js └── package.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: LICENSE ================================================ Copyright (c) 2013 Francois Zaninotto Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ Dependency Wheel ================ This experiment visualizes package dependencies using an interactive disc. Each disc section represents a dependency, and links between arcs materialize these dependencies. All rendering is done client-side, in JavaScript. Built with d3.js, published with the MIT open-source license. Interact with DependencyWheels, see examples, and build your own at [http://fzaninotto.github.com/DependencyWheel](http://fzaninotto.github.com/DependencyWheel).  ================================================ FILE: data/composer.json ================================================ { "name": "sylius/sylius", "type": "project", "description": "Modern ecommerce for Symfony2", "license": "MIT", "homepage": "http://sylius.org", "authors": [ { "name": "Paweł Jędrzejewski", "homepage": "http://pjedrzejewski.com" }, { "name": "Sylius project", "homepage": "http://sylius.org" }, { "name": "Community contributions", "homepage": "http://github.com/Sylius/Sylius/contributors" } ], "require": { "php": ">=5.3.3", "athari/yalinqo": "*@dev", "doctrine/doctrine-bundle": "1.2.*@dev", "doctrine/doctrine-fixtures-bundle": "2.2.*", "doctrine/orm": "~2.3", "friendsofsymfony/rest-bundle": "0.13.*", "friendsofsymfony/user-bundle": "2.0.*@dev", "fzaninotto/faker": "1.2.*", "incenteev/composer-parameter-handler": "~2.0", "jms/serializer-bundle": "0.12.*", "jms/translation-bundle": "1.1.*", "knplabs/knp-menu-bundle": "*@dev", "knplabs/knp-gaufrette-bundle": "*@dev", "knplabs/knp-snappy-bundle": "*@dev", "liip/doctrine-cache-bundle": "*", "liip/imagine-bundle": "0.9.*", "mathiasverraes/money": "*@dev", "omnipay/omnipay": "*@dev", "sensio/distribution-bundle": "2.3.*", "stof/doctrine-extensions-bundle": "1.1.*", "symfony/assetic-bundle": "2.3.*", "symfony/intl": "~2.3", "symfony/monolog-bundle": "2.3.*", "symfony/swiftmailer-bundle": "2.3.*", "symfony/symfony": "~2.3", "twig/extensions": "1.0.*", "white-october/pagerfanta-bundle": "1.0.*@dev" }, "require-dev": { "behat/behat": "2.4.*@stable", "behat/symfony2-extension": "*", "behat/mink-extension": "*", "behat/mink-browserkit-driver": "*", "phpspec/phpspec": "2.0.*@dev", "behat/mink-selenium2-driver": "*" }, "scripts": { "post-install-cmd": [ "Incenteev\\ParameterHandler\\ScriptHandler::buildParameters", "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::buildBootstrap", "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::clearCache", "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::installAssets", "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::installRequirementsFile" ], "post-update-cmd": [ "Incenteev\\ParameterHandler\\ScriptHandler::buildParameters", "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::buildBootstrap", "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::clearCache", "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::installAssets", "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::installRequirementsFile" ] }, "autoload": { "psr-0": { "Sylius\\": "src/" } }, "config": { "bin-dir": "bin" }, "extra": { "symfony-app-dir": "app", "symfony-web-dir": "web", "incenteev-parameters": { "file": "app/config/parameters.yml" } } } ================================================ FILE: index.html ================================================
Modern development use package managers (composer, npm, bundler, etc.). Applications depend on a large number of packages, which depend themselves on other packages. The Dependency Wheel visualization attempts to reveal the entire dependency tree of any PHP library using Composer for dependency management. Each chord in the disc represents a dependency. Try hovering on packages to mask other dependencies. All rendering is done client-side, in JavaScript. Built with d3.js
Do you want to try it out with another project? Paste the composer.json and composer.lock of any PHP project below to see its Dependency Wheel:
vendor/ directory is actually useful?composer.json?DependencyWheel tries to answer these needs. Also, it tries to make dependencies look beautiful, but that's another story.
To create a DependencyWheel, include the d3.dependencyWheel.js file together with d3.js, just like in this page. Create a new instance of the dependency wheel chart constructor, then make a d3 selection using a CSS selector (of the div where the wheel should be inserted), attach dependency data, and call the chart on the selection.
var chart = d3.chart.dependencyWheel();
d3.select('#chart_placeholder')
.datum(data)
.call(chart);
The data must be a matrix of dependencies. The first item must be the main package. For instance, if the main package depends on packages A and B, and package A also depends on package B, you should build the data as follows:
var data = {
packageNames: ['Main', 'A', 'B'],
matrix: [[0, 1, 1], // Main depends on A and B
[0, 0, 1], // A depends on B
[0, 0, 0]] // B doesn't depend on A or Main
};
For more information about the matrix format, check the d3 Chord Layout documentation.
DependencyWheel comes with a utility to transform data from composer.json and composer.lock files into a matrix and a list of package names. You first need to include the composerBuilder.js script provided in this repository, and then call:
var data = buildMatrixFromComposerJsonAndLock(composerjson, composerlock);
d3.select('#chart_placeholder')
.datum(data)
.call(chart);
DependencyWheel follows the d3.js reusable charts pattern to let you customize the chart at will:
var chart = d3.chart.dependencyWheel() .width(700) // also used for height, since the wheel is in a a square .margin(150) // used to display package names .padding(.02); // separating groups in the wheel
The best way to share your DependencyWheels is to use GitHub Pages, just like this very page. So just fork the fzaninotto/DependencyWheel repository, add your own JSON data under the data/ directory, commit the code, and push to the gh-pages branch. GitHub will publish the result for you.
All this work is open-source, published by François Zaninotto under the MIT license. Sponsored by marmelab.
================================================
FILE: js/composerBuilder.js
================================================
var buildMatrixFromComposerJson = function(composerjson) {
var n = Object.keys(composerjson.require).length;
var matrix = [];
matrix[0] = [0];
var increment = 0;
// only the first element depends on others
for (var i=1; i < n; i++) {
matrix[0][i] = 1 + increment;
increment += 0.001;
matrix[i] = Array.apply(null, new Array(n)).map(Number.prototype.valueOf,0);
matrix[i][0] = 1;
}
var packageNames = Object.keys(composerjson.require);
packageNames.unshift(composerjson.name);
return {
matrix: matrix,
packageNames: packageNames
}
};
var buildMatrixFromComposerJsonAndLock = function(composerjson, composerlock) {
var packages = composerlock.packages;
composerjson.isMain = true;
packages.unshift(composerjson);
var indexByName = {};
var packageNames = {};
var matrix = [];
var n = 0;
var replaces = {};
// List the replacements
packages.forEach(function(p) {
if (!p.replace) return;
for (replaced in p.replace) {
replaces[replaced] = p.name;
}
});
// update required packages with replacements
packages.forEach(function(p) {
for (packageName in p.require) {
if (packageName in replaces) {
p.require[replaces[packageName]] = p.require[packageName];
delete p.require[packageName];
}
}
});
// Compute a unique index for each package name.
packages.forEach(function(p) {
packageName = p.name;
if (!(packageName in indexByName)) {
packageNames[n] = packageName;
indexByName[packageName] = n++;
}
});
// Construct a square matrix counting package requires.
packages.forEach(function(p) {
var source = indexByName[p.name];
var row = matrix[source];
if (!row) {
row = matrix[source] = [];
for (var i = -1; ++i < n;) row[i] = 0;
}
for (packageName in p.require) {
row[indexByName[packageName]]++;
}
});
// add small increment to equally weighted dependencies to force order
matrix.forEach(function(row, index) {
var increment = 0.001;
for (var i = -1; ++i < n;) {
var ii = (i + index) % n;
if (row[ii] == 1) {
row[ii] += increment;
increment += 0.001;
}
}
});
return {
matrix: matrix,
packageNames: packageNames
}
};
================================================
FILE: js/d3.dependencyWheel.js
================================================
d3.chart = d3.chart || {};
/**
* Dependency wheel chart for d3.js
*
* Usage:
* var chart = d3.chart.dependencyWheel();
* d3.select('#chart_placeholder')
* .datum({
* packageNames: [the name of the packages in the matrix],
* matrix: [your dependency matrix]
* })
* .call(chart);
*
* // Data must be a matrix of dependencies. The first item must be the main package.
* // For instance, if the main package depends on packages A and B, and package A
* // also depends on package B, you should build the data as follows:
*
* var data = {
* packageNames: ['Main', 'A', 'B'],
* matrix: [[0, 1, 1], // Main depends on A and B
* [0, 0, 1], // A depends on B
* [0, 0, 0]] // B doesn't depend on A or Main
* };
*
* // You can customize the chart width, margin (used to display package names),
* // and padding (separating groups in the wheel)
* var chart = d3.chart.dependencyWheel().width(700).margin(150).padding(.02);
*
* @author François Zaninotto
* @license MIT
* @see https://github.com/fzaninotto/DependencyWheel for complete source and license
*/
d3.chart.dependencyWheel = function(options) {
var width = 700;
var margin = 150;
var padding = 0.02;
function chart(selection) {
selection.each(function(data) {
var matrix = data.matrix;
var packageNames = data.packageNames;
var radius = width / 2 - margin;
// create the layout
var chord = d3.chord()
.padAngle(padding)
.sortSubgroups(d3.descending);
// Select the svg element, if it exists.
var svg = d3.select(this).selectAll("svg").data([data]);
// Otherwise, create the skeletal chart.
var gEnter = svg.enter().append("svg:svg")
.attr("width", width)
.attr("height", width)
.attr("class", "dependencyWheel")
.append("g")
.attr("transform", "translate(" + (width / 2) + "," + (width / 2) + ")");
var arc = d3.arc()
.innerRadius(radius)
.outerRadius(radius + 20);
var fill = function(d) {
if (d.index === 0) return '#ccc';
return "hsl(" + parseInt(((packageNames[d.index][0].charCodeAt() - 97) / 26) * 360, 10) + ",90%,70%)";
};
// Returns an event handler for fading a given chord group.
var fade = function(opacity) {
return function(g, i) {
gEnter.selectAll(".chord")
.filter(function(d) {
return d.source.index != i && d.target.index != i;
})
.transition()
.style("opacity", opacity);
var groups = [];
gEnter.selectAll(".chord")
.filter(function(d) {
if (d.source.index == i) {
groups.push(d.target.index);
}
if (d.target.index == i) {
groups.push(d.source.index);
}
});
groups.push(i);
var length = groups.length;
gEnter.selectAll('.group')
.filter(function(d) {
for (var i = 0; i < length; i++) {
if(groups[i] == d.index) return false;
}
return true;
})
.transition()
.style("opacity", opacity);
};
};
var chordResult = chord(matrix);
var rootGroup = chordResult.groups[0];
var rotation = - (rootGroup.endAngle - rootGroup.startAngle) / 2 * (180 / Math.PI);
var g = gEnter.selectAll("g.group")
.data(chordResult.groups)
.enter().append("svg:g")
.attr("class", "group")
.attr("transform", function(d) {
return "rotate(" + rotation + ")";
});
g.append("svg:path")
.style("fill", fill)
.style("stroke", fill)
.attr("d", arc)
.style("cursor", "pointer")
.on("mouseover", fade(0.1))
.on("mouseout", fade(1));
g.append("svg:text")
.each(function(d) { d.angle = (d.startAngle + d.endAngle) / 2; })
.attr("dy", ".35em")
.attr("text-anchor", function(d) { return d.angle > Math.PI ? "end" : null; })
.attr("transform", function(d) {
return "rotate(" + (d.angle * 180 / Math.PI - 90) + ")" +
"translate(" + (radius + 26) + ")" +
(d.angle > Math.PI ? "rotate(180)" : "");
})
.style("cursor", "pointer")
.text(function(d) { return packageNames[d.index]; })
.on("mouseover", fade(0.1))
.on("mouseout", fade(1));
gEnter.selectAll("path.chord")
.data(chordResult)
.enter().append("svg:path")
.attr("class", "chord")
.style("stroke", function(d) { return d3.rgb(fill(d.source)).darker(); })
.style("fill", function(d) { return fill(d.source); })
.attr("d", d3.ribbon().radius(radius))
.attr("transform", function(d) {
return "rotate(" + rotation + ")";
})
.style("opacity", 1);
});
}
chart.width = function(value) {
if (!arguments.length) return width;
width = value;
return chart;
};
chart.margin = function(value) {
if (!arguments.length) return margin;
margin = value;
return chart;
};
chart.padding = function(value) {
if (!arguments.length) return padding;
padding = value;
return chart;
};
return chart;
};
================================================
FILE: package.json
================================================
{
"name": "d3-dependency-wheel",
"version": "1.1.0",
"description": "Dependency Wheel",
"main": "js/d3.dependencyWheel.js",
"repository": {
"type": "git",
"url": "git+https://github.com/fzaninotto/DependencyWheel.git"
},
"license": "MIT",
"bugs": {
"url": "https://github.com/fzaninotto/DependencyWheel/issues"
},
"homepage": "https://github.com/fzaninotto/DependencyWheel#readme"
}