diff --git a/.bowerrc b/.bowerrc
new file mode 100644
index 0000000..966fb07
--- /dev/null
+++ b/.bowerrc
@@ -0,0 +1,3 @@
+{
+ "directory": "bower"
+}
\ No newline at end of file
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..5760be5
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,12 @@
+# http://editorconfig.org
+root = true
+
+[*]
+indent_style = space
+indent_size = 2
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+
+[*.md]
+trim_trailing_whitespace = false
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..a1994cb
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,18 @@
+._*
+.~lock.*
+.buildpath
+.DS_Store
+.idea
+.project
+.settings
+
+# Ignore node stuff
+node_modules/
+npm-debug.log
+libpeerconnection.log
+
+# OS-specific
+.DS_Store
+
+# Bower components
+bower
diff --git a/.jshintrc b/.jshintrc
new file mode 100644
index 0000000..433ed80
--- /dev/null
+++ b/.jshintrc
@@ -0,0 +1,62 @@
+{
+ "bitwise": true,
+ "camelcase": true,
+ "curly": true,
+ "eqeqeq": true,
+ "es3": false,
+ "forin": true,
+ "freeze": true,
+ "immed": true,
+ "indent": 2,
+ "latedef": "nofunc",
+ "newcap": true,
+ "noarg": true,
+ "noempty": true,
+ "nonbsp": true,
+ "nonew": true,
+ "plusplus": false,
+ "quotmark": "single",
+ "undef": true,
+ "unused": false,
+ "strict": false,
+ "maxparams": 10,
+ "maxdepth": 5,
+ "maxstatements": 40,
+ "maxcomplexity": 8,
+ "maxlen": 1200,
+
+ "asi": false,
+ "boss": false,
+ "debug": false,
+ "eqnull": true,
+ "esnext": false,
+ "evil": false,
+ "expr": false,
+ "funcscope": false,
+ "globalstrict": false,
+ "iterator": false,
+ "lastsemic": false,
+ "laxbreak": false,
+ "laxcomma": false,
+ "loopfunc": true,
+ "maxerr": false,
+ "moz": false,
+ "multistr": false,
+ "notypeof": false,
+ "proto": false,
+ "scripturl": false,
+ "shadow": false,
+ "sub": true,
+ "supernew": false,
+ "validthis": false,
+ "noyield": false,
+
+ "browser": true,
+ "node": true,
+
+ "globals": {
+ "angular": false,
+ "$": false,
+ "d3": false
+ }
+}
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..31285dc
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,7 @@
+language: node_js
+node_js:
+- '4'
+before_install:
+- npm install -g gulp gulp-cli bower
+- bower install
+script: npm run ci
diff --git a/.yo-rc.json b/.yo-rc.json
new file mode 100644
index 0000000..a673f4f
--- /dev/null
+++ b/.yo-rc.json
@@ -0,0 +1,35 @@
+{
+ "generator-angular-package": {
+ "props": {
+ "moduleName": "angular-bubbletree",
+ "description": "Angular bubble tree chart using d3",
+ "gitUrl": "http://amgadfahmi.git",
+ "keywords": "[\"d3\",\"angular\",\"chart\",\"responsive\",\"related\",\"bubble\"]",
+ "license": "MIT",
+ "author": {
+ "name": "Amgad Fahmi",
+ "email": "amgadnady@gmail.com"
+ },
+ "yourModule": {
+ "original": "angular-bubbletree",
+ "camelized": "angularBubbletree",
+ "dasherized": "angular-bubbletree",
+ "slugified": "angular-bubbletree",
+ "parts": [
+ "angular",
+ "bubbletree"
+ ]
+ },
+ "includeModuleDirectives": true,
+ "includeModuleFilters": true,
+ "includeModuleServices": true,
+ "includeModuleControllers": true,
+ "includeAngularModuleResource": false,
+ "includeAngularModuleCookies": false,
+ "includeAngularModuleSanitize": false,
+ "moduleDirectory": "modules/angular-bubbletree",
+ "moduleUnitTestDirectory": "test/unit/angular-bubbletree",
+ "moduleUnitE2eDirectory": "test/e2e/angular-bubbletree"
+ }
+ }
+}
\ No newline at end of file
diff --git a/README.md b/README.md
index e69de29..e0b83b9 100644
--- a/README.md
+++ b/README.md
@@ -0,0 +1,97 @@
+# angular-bubbletree
+Master 0.1.0: [![Master Status](https://travis-ci.org/amgadfahmi/angular-bubbletree.svg?branch=master)](https://travis-ci.org/amgadfahmi/js-binarysearch) Development 0.1.0: [![Development Status](https://travis-ci.org/amgadfahmi/angular-bubbletree.svg?branch=development)](https://travis-ci.org/amgadfahmi/js-binarysearch) [![Dependency Status](https://david-dm.org/amgadfahmi/angular-bubbletree.svg)](https://david-dm.org/amgadfahmi/angular-bubbletree)
+
+![angular d3 chart bubble tree](https://amgadfahmi.files.wordpress.com/2016/06/angular-bubbletree-chart.png "angular d3 chart bubble tree")
+
+
+
+## Installation
+
+[![bower](https://amgadfahmi.files.wordpress.com/2016/05/bower.png "Javascript Binary Search")](http://bower.io/search/?q=angular-bubbletree)
+
+You can install the package using Bower
+```
+$ bower install --save js-binarysearch
+```
+Or download the project and find the module in `dist/` folder
+
+## Usage
+The directive is simple, define the directive in your html as following
+
+```html
+ //optional
+
+```
+And in your controller define the following
+```javascript
+//this very important to communicate with the directive
+//and be able to call internal functionalities
+ $scope.chart = {};
+ //load you data in anyway you want
+ $http.get('data.json').then(function(result) {
+ //once data is ready pass it to the bind function
+ $scope.chart.bind( result.data);
+ });
+```
+
+## Parameters
+
+```
+parameter condition default description
+id required NA defines the control id in the dom
+control required NA refernece to scope object in order to communicate with the directive
+width optional 1000 determine the svg container width
+height optional 400 determine the svg container height
+link-distance optional 100 determine the distanaation between each node
+fill-style optional gradient changes the chart bubbles layout (solid - gradient)
+parent-color optional #336699 color of any parent node (works only with solid style)
+child-color optional #e0e0eb color of any child node (works only with solid style)
+collapsed-color optional #19334d color of any collapsed parent node (works only with solid style)
+link-color optional #9ecae1 color of link between nodes
+enable-tooltip optional true show small tooltip beside the node(very basic)
+text-x optional 1.45em determine the x coordinates of the node text according to its node
+text-y optional 3.45em determine the y coordinates of the node text according to its node
+scale-score optional 10 determine the scaling factor for node sizes(1 too small - 30 is big)
+```
+
+## Extra info
+
+![angular d3 chart bubble tree](https://amgadfahmi.files.wordpress.com/2016/06/angular-bubbletree-chart2.png "angular d3 chart bubble tree")
+
+You can override the default following classes in order to change the look of component
+```css
+.btNode circle {
+}
+.btNode text {
+}
+.btLink {
+}
+div.btTooltip{
+}
+```
+
+## License
+
+Copyright [2016] Amgad Fahmi
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
\ No newline at end of file
diff --git a/bower.json b/bower.json
new file mode 100644
index 0000000..2d06799
--- /dev/null
+++ b/bower.json
@@ -0,0 +1,45 @@
+{
+ "name": "angular-bubbletree",
+ "version": "0.1.0",
+ "description": "Angular bubble tree chart using d3",
+ "keywords": [
+ "d3",
+ "angular",
+ "chart",
+ "responsive",
+ "related",
+ "bubble"
+ ],
+ "authors": [
+ {
+ "name": "Amgad Fahmi",
+ "email": "amgadnady@gmail.com"
+ }
+ ],
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/amgadfahmi/angular-bubbletree.git"
+ },
+ "license": "ALv2",
+ "main": [
+ "dist/angular-bubbletree.js",
+ "dist/angular-bubbletree.css"
+ ],
+ "ignore": [
+ "*.*",
+ "bower",
+ "modules",
+ "node_modules",
+ "src",
+ "test"
+ ],
+ "dependencies": {
+ "d3": "^3.5.17"
+ },
+ "devDependencies": {
+ "angular-mocks": ">=1.2.0",
+ "angular-scenario": ">=1.2.0",
+ "angular": ">=1.2.0",
+ "d3": "^3.5.17"
+ }
+}
diff --git a/data.json b/data.json
new file mode 100644
index 0000000..b34eefd
--- /dev/null
+++ b/data.json
@@ -0,0 +1,65 @@
+{
+ "name": "flare",
+ "children": [{
+ "name": "analytics",
+ "children": [{
+ "name": "cluster",
+ "children": [{
+ "name": "Agglomerative Cluster",
+ "size": 3938
+ }, {
+ "name": "Community Structure",
+ "size": 3812
+ }, {
+ "name": "Hierarchical Cluster",
+ "size": 6714
+ }, {
+ "name": "Merge Edge",
+ "size": 700
+ }]
+ }, {
+ "name": "graph",
+ "children": [{
+ "name": "Betweenness Centrality",
+ "size": 3534
+ }, {
+ "name": "Link Distance",
+ "size": 5731
+ }, {
+ "name": "Max Flow MinCut",
+ "size": 7840
+ }, {
+ "name": "Shortest Paths",
+ "size": 5914
+ }, {
+ "name": "Spanning Tree",
+ "size": 3416
+ }]
+ }, {
+ "name": "optimization",
+ "children": [{
+ "name": "Aspect Ratio Banker",
+ "size": 7074,
+ "children": [{
+ "name": "Betweenness Centrality",
+ "size": 3534
+ }, {
+ "name": "Link Distance",
+ "size": 5731
+ }, {
+ "name": "Max Flow MinCut",
+ "size": 7840,
+ "children": [{
+ "name": "Betweenness Centrality",
+ "size": 534
+ }, {
+ "name": "Link Distance",
+ "size": 1731
+ }
+ ]
+ }
+ ]
+ }]
+ }]
+ }]
+}
\ No newline at end of file
diff --git a/dist/angular-bubbletree.css b/dist/angular-bubbletree.css
new file mode 100644
index 0000000..b95713e
--- /dev/null
+++ b/dist/angular-bubbletree.css
@@ -0,0 +1,43 @@
+.svg-container {
+ /*border: 4px solid red;*/
+ width: 99%;
+ height: 600px;
+ background: #fff;
+ margin: 0 auto;
+}
+
+.btNode circle {
+ cursor: pointer;
+ stroke: #3182bd;
+ stroke-width: 1.5px;
+}
+
+.btNode text {
+ font: 10px sans-serif;
+ pointer-events: none;
+ text-anchor: middle;
+}
+
+.btLink {
+ fill: none;
+ stroke: #9ecae1;
+ stroke-width: 1.5px;
+}
+
+div.btTooltip {
+ position: absolute;
+ text-align: center;
+ vertical-align: middle;
+ width: 100px;
+ height: 38px;
+ padding: 2px;
+ font: 11px sans-serif;
+ color: #153851;
+ background: lightsteelblue;
+ border-radius: 8px;
+ pointer-events: none;
+ border-style: solid;
+ border-color: #8cb3d9;
+ border-width: 0.01em;
+ box-shadow: 2px 2px 5px #888888;
+}
\ No newline at end of file
diff --git a/dist/angular-bubbletree.js b/dist/angular-bubbletree.js
new file mode 100644
index 0000000..3a3af84
--- /dev/null
+++ b/dist/angular-bubbletree.js
@@ -0,0 +1,327 @@
+(function (angular) {
+
+ // Create all modules and define dependencies to make sure they exist
+ // and are loaded in the correct order to satisfy dependency injection
+ // before all nested files are concatenated by Gulp
+
+ // Config
+ angular.module('angular-bubbletree.config', [])
+ .value('angular-bubbletree.config', {
+ debug: true
+ });
+
+ // Modules
+
+ angular.module('angular-bubbletree.directives', []);
+
+
+ angular.module('angular-bubbletree.filters', []);
+
+
+ angular.module('angular-bubbletree.services', []);
+
+
+ angular.module('angular-bubbletree.controllers', []);
+
+ angular.module('angular-bubbletree',
+ [
+ 'angular-bubbletree.config',
+ 'angular-bubbletree.directives',
+ 'angular-bubbletree.filters',
+ 'angular-bubbletree.services',
+ 'angular-bubbletree.controllers'
+ ]);
+
+})(angular);
+
+(function() {
+ 'use strict';
+ angular.module('angular-bubbletree').directive('bubbletree', bubbletree);
+ bubbletree.$inject = ['$log', 'bubbletreeSvc'];
+
+ function bubbletree(log, svc) {
+
+ return {
+ name: 'bubbletree',
+ scope: {
+ datasource: '=',
+ control: '='
+ },
+ restrict: 'E',
+ replace: true,
+ transclude: false,
+ link: function postLink(scope, element, attrs) {
+ var width = '100%',
+ height = '100%',
+ force, svg, div, link, node,
+ datasource, scaledData = [];
+
+ scope.internalCtrl = scope.control || {};
+
+ scope.internalCtrl.bind = function(data) {
+ log.debug('Binding bubble tree chart');
+ if (data) {
+ datasource = data;
+ render();
+ log.debug('Binding completed');
+ }
+ };
+
+ function initialize() {
+ var dummy;
+ scope.params = {};
+ dummy = attrs.id ? scope.params.id = attrs.id : scope.params.id = 'bubbletree';
+ dummy = attrs.width ? scope.params.width = attrs.width : scope.params.width = 1000;
+ dummy = attrs.height ? scope.params.height = attrs.height : scope.params.height = 400;
+ dummy = attrs.linkDistance ? scope.params.linkDistance = attrs.linkDistance : scope.params.linkDistance = 100;
+ dummy = attrs.enableTooltip ? scope.params.enableTooltip = scope.$eval(attrs.enableTooltip) : scope.params.enableTooltip = true;
+ dummy = attrs.fillStyle && ['solid', 'gradient'].indexOf(attrs.fillStyle) >= 0 ? scope.params.fillStyle = attrs.fillStyle : scope.params.fillStyle = 'gradient';
+ dummy = attrs.parentColor ? scope.params.parentColor = attrs.parentColor : scope.params.parentColor = '#336699';
+ dummy = attrs.childColor ? scope.params.childColor = attrs.childColor : scope.params.childColor = '#e0e0eb';
+ dummy = attrs.collapsedColor ? scope.params.collapsedColor = attrs.collapsedColor : scope.params.collapsedColor = '#19334d';
+ dummy = attrs.linkColor ? scope.params.linkColor = attrs.linkColor : scope.params.linkColor = '#9ecae1';
+ dummy = attrs.textX ? scope.params.textX = attrs.textX : scope.params.textX = '1.45em';
+ dummy = attrs.textY ? scope.params.textY = attrs.textY : scope.params.textY = '3.45em';
+ dummy = attrs.scaleScore ? scope.params.scaleScore = attrs.scaleScore : scope.params.scaleScore = 10;
+
+
+ force = d3.layout.force()
+ .linkDistance(scope.params.linkDistance)
+ .charge(-120)
+ .gravity(0.01)
+ .size([scope.params.width, scope.params.height])
+ .on('tick', tick);
+
+ svg = d3.select('#' + attrs.id)
+ .append('svg')
+ .attr('width', width)
+ .attr('height', height);
+
+ link = svg.selectAll('.btLink');
+ node = svg.selectAll('.btNode');
+
+ if (scope.params.enableTooltip) {
+ div = d3.select('#' + attrs.id).append('div')
+ .attr('class', 'btTooltip')
+ .style('opacity', 0);
+ }
+
+ }
+
+ initialize();
+
+
+ function render() {
+ var nodes = svc.flatten(datasource, scaledData),
+ links = d3.layout.tree().links(nodes);
+
+ // Restart the force layout.
+ force
+ .nodes(nodes)
+ .links(links)
+ .start();
+
+ // Update links.
+ link = link.data(links, function(d) {
+ return d.target.id;
+ });
+
+ link.exit().remove();
+
+ link.enter().insert('line', '.btNode')
+ .attr('class', 'btLink');
+ if (scope.params.linkColor) {
+ link.attr('style', 'stroke:' + scope.params.linkColor + ';');
+ }
+
+ // Update nodes.
+ node = node.data(nodes, function(d) {
+ return d.id;
+ });
+
+ node.exit().remove();
+
+ var nodeEnter = node.enter().append('g')
+ .attr('class', 'btNode')
+ .on('click', click)
+ .call(force.drag);
+
+
+ nodeEnter.append('circle')
+ .on('mouseover', function(d) {
+ if (scope.params.enableTooltip) {
+ div.transition()
+ .duration(1000)
+ .style('opacity', 0.9);
+
+ var pageX = d3.event.pageX;
+ var pageY = d3.event.pageY;
+ var data = d;
+ // http.get('exampleTooltip.html').then(function(result) {
+ // var element = angular.element('
');
+ // element.append(result.data);
+ // var newScope = scope.$new();
+ // newScope.node = data;
+ // newScope.test = 'i am data';
+ // var compiled = compile(element)(newScope);
+ // console.log(compiled);
+ // // newScope.$digest();
+ // div.html(compiled[0].innerHTML)
+ // .style('left', (pageX - 120) + 'px')
+ // .style('top', (pageY - 12) + 'px');
+ // });
+ var size = data.size ? data.size : '';
+ div.html(data.name + ' ' + size)
+ .style('left', (pageX - 120) + 'px')
+ .style('top', (pageY - 12) + 'px');
+ }
+
+ })
+ .on('mouseout', function(d) {
+ if (scope.params.enableTooltip) {
+ div.transition()
+ .duration(500)
+ .style('opacity', 0);
+ }
+ })
+ .attr('r', function(d) {
+ // console.log(svc.linearScale(d.size, datasource));
+ var min = d3.min(scaledData);
+ var max = d3.max(scaledData);
+ return svc.linearScale(d.size, datasource, min, max, scope.params.scaleScore) || 10;
+ });
+
+ nodeEnter.append('text')
+ .attr('dy', scope.params.textY)
+ .attr('dx', scope.params.textX)
+ .text(function(d) {
+ return d.name;
+ });
+
+ node.select('circle')
+ .style('fill', function(d) {
+ if (scope.params.fillStyle === 'solid') {
+ return svc.colorSolid(d, scope.params);
+ } else {
+ return svc.colorGradient(svg, d, scope.params);
+ }
+
+ });
+ }
+
+ function tick() {
+ link.attr('x1', function(d) {
+ return d.source.x;
+ })
+ .attr('y1', function(d) {
+ return d.source.y;
+ })
+ .attr('x2', function(d) {
+ return d.target.x;
+ })
+ .attr('y2', function(d) {
+ return d.target.y;
+ });
+
+ node.attr('transform', function(d) {
+ return 'translate(' + d.x + ',' + d.y + ')';
+ });
+ }
+
+
+ // Toggle children on click.
+ function click(d) {
+ if (d3.event.defaultPrevented) {
+ return;
+ } // ignore drag
+ if (d.children) {
+ d._children = d.children;
+ d.children = null;
+ } else {
+ d.children = d._children;
+ d._children = null;
+ }
+ render();
+ }
+
+ }
+
+ };
+ }
+
+})();
+(function() {
+ 'use strict';
+ angular.module('angular-bubbletree').service('bubbletreeSvc', bubbletreeSvc);
+ bubbletreeSvc.$inject = ['$log'];
+
+ function bubbletreeSvc(log) {
+ var service = {};
+
+ service.linearScale = function(value, scaledData, min, max, scale) {
+ var scaled = d3.scale.linear()
+ .domain([min, max])
+ .range([1 * scale, 2 * scale]);
+ return scaled(value);
+ };
+
+ // Returns a list of all nodes under the datasource.
+ service.flatten = function(datasource, scaledData) {
+ var nodes = [],
+ i = 0;
+
+ function recurse(node) {
+ if (node.size) {
+ scaledData.push(node.size);
+ }
+ if (node.children) {
+ node.children.forEach(recurse);
+ }
+ if (!node.id) {
+ node.id = ++i;
+ }
+ nodes.push(node);
+ }
+ recurse(datasource);
+ return nodes;
+ };
+
+ service.colorSolid = function(d, params) {
+ return d._children ? params.collapsedColor //'#19334d' // collapsed package
+ : d.children ? params.parentColor //'#336699' // expanded package
+ : params.childColor; //'#e0e0eb'; // leaf node
+ };
+
+ service.colorGradient = function(svg, d) {
+ var color = '#fff',
+ id = 'btGradient';
+
+ // Define the gradient
+ var gradient = svg.append('svg:defs')
+ .append('svg:linearGradient')
+ .attr('id', id)
+ .attr('x1', '0%')
+ .attr('y1', '0%')
+ .attr('x2', '100%')
+ .attr('y2', '100%')
+ .attr('spreadMethod', 'pad');
+
+ // Define the gradient colors
+ gradient.append('svg:stop')
+ .attr('offset', '0%')
+ .attr('stop-color', '#999')
+ .attr('stop-opacity', 1);
+
+ gradient.append('svg:stop')
+ .attr('offset', '100%')
+ .attr('stop-color', color)
+ .attr('stop-opacity', 1);
+
+ return 'url(#' + id + ')';
+ };
+
+ return service;
+
+ }
+
+})();
\ No newline at end of file
diff --git a/dist/angular-bubbletree.min.js b/dist/angular-bubbletree.min.js
new file mode 100644
index 0000000..c1c841a
--- /dev/null
+++ b/dist/angular-bubbletree.min.js
@@ -0,0 +1 @@
+!function(e){e.module("angular-bubbletree.config",[]).value("angular-bubbletree.config",{debug:!0}),e.module("angular-bubbletree.directives",[]),e.module("angular-bubbletree.filters",[]),e.module("angular-bubbletree.services",[]),e.module("angular-bubbletree.controllers",[]),e.module("angular-bubbletree",["angular-bubbletree.config","angular-bubbletree.directives","angular-bubbletree.filters","angular-bubbletree.services","angular-bubbletree.controllers"])}(angular),function(){"use strict";function e(e,t){return{name:"bubbletree",scope:{datasource:"=",control:"="},restrict:"E",replace:!0,transclude:!1,link:function(r,a,l){function n(){var e;r.params={},e=l.id?r.params.id=l.id:r.params.id="bubbletree",e=l.width?r.params.width=l.width:r.params.width=1e3,e=l.height?r.params.height=l.height:r.params.height=400,e=l.linkDistance?r.params.linkDistance=l.linkDistance:r.params.linkDistance=100,e=l.enableTooltip?r.params.enableTooltip=r.$eval(l.enableTooltip):r.params.enableTooltip=!0,e=l.fillStyle&&["solid","gradient"].indexOf(l.fillStyle)>=0?r.params.fillStyle=l.fillStyle:r.params.fillStyle="gradient",e=l.parentColor?r.params.parentColor=l.parentColor:r.params.parentColor="#336699",e=l.childColor?r.params.childColor=l.childColor:r.params.childColor="#e0e0eb",e=l.collapsedColor?r.params.collapsedColor=l.collapsedColor:r.params.collapsedColor="#19334d",e=l.linkColor?r.params.linkColor=l.linkColor:r.params.linkColor="#9ecae1",e=l.textX?r.params.textX=l.textX:r.params.textX="1.45em",e=l.textY?r.params.textY=l.textY:r.params.textY="3.45em",e=l.scaleScore?r.params.scaleScore=l.scaleScore:r.params.scaleScore=10,c=d3.layout.force().linkDistance(r.params.linkDistance).charge(-120).gravity(.01).size([r.params.width,r.params.height]).on("tick",i),u=d3.select("#"+l.id).append("svg").attr("width",f).attr("height",g),p=u.selectAll(".btLink"),b=u.selectAll(".btNode"),r.params.enableTooltip&&(d=d3.select("#"+l.id).append("div").attr("class","btTooltip").style("opacity",0))}function o(){var e=t.flatten(m,h),a=d3.layout.tree().links(e);c.nodes(e).links(a).start(),p=p.data(a,function(e){return e.target.id}),p.exit().remove(),p.enter().insert("line",".btNode").attr("class","btLink"),r.params.linkColor&&p.attr("style","stroke:"+r.params.linkColor+";"),b=b.data(e,function(e){return e.id}),b.exit().remove();var l=b.enter().append("g").attr("class","btNode").on("click",s).call(c.drag);l.append("circle").on("mouseover",function(e){if(r.params.enableTooltip){d.transition().duration(1e3).style("opacity",.9);var t=d3.event.pageX,a=d3.event.pageY,l=e,n=l.size?l.size:"";d.html(l.name+" "+n).style("left",t-120+"px").style("top",a-12+"px")}}).on("mouseout",function(e){r.params.enableTooltip&&d.transition().duration(500).style("opacity",0)}).attr("r",function(e){var a=d3.min(h),l=d3.max(h);return t.linearScale(e.size,m,a,l,r.params.scaleScore)||10}),l.append("text").attr("dy",r.params.textY).attr("dx",r.params.textX).text(function(e){return e.name}),b.select("circle").style("fill",function(e){return"solid"===r.params.fillStyle?t.colorSolid(e,r.params):t.colorGradient(u,e,r.params)})}function i(){p.attr("x1",function(e){return e.source.x}).attr("y1",function(e){return e.source.y}).attr("x2",function(e){return e.target.x}).attr("y2",function(e){return e.target.y}),b.attr("transform",function(e){return"translate("+e.x+","+e.y+")"})}function s(e){d3.event.defaultPrevented||(e.children?(e._children=e.children,e.children=null):(e.children=e._children,e._children=null),o())}var c,u,d,p,b,m,f="100%",g="100%",h=[];r.internalCtrl=r.control||{},r.internalCtrl.bind=function(t){e.debug("Binding bubble tree chart"),t&&(m=t,o(),e.debug("Binding completed"))},n()}}}angular.module("angular-bubbletree").directive("bubbletree",e),e.$inject=["$log","bubbletreeSvc"]}(),function(){"use strict";function e(e){var t={};return t.linearScale=function(e,t,r,a,l){var n=d3.scale.linear().domain([r,a]).range([1*l,2*l]);return n(e)},t.flatten=function(e,t){function r(e){e.size&&t.push(e.size),e.children&&e.children.forEach(r),e.id||(e.id=++l),a.push(e)}var a=[],l=0;return r(e),a},t.colorSolid=function(e,t){return e._children?t.collapsedColor:e.children?t.parentColor:t.childColor},t.colorGradient=function(e,t){var r="#fff",a="btGradient",l=e.append("svg:defs").append("svg:linearGradient").attr("id",a).attr("x1","0%").attr("y1","0%").attr("x2","100%").attr("y2","100%").attr("spreadMethod","pad");return l.append("svg:stop").attr("offset","0%").attr("stop-color","#999").attr("stop-opacity",1),l.append("svg:stop").attr("offset","100%").attr("stop-color",r).attr("stop-opacity",1),"url(#"+a+")"},t}angular.module("angular-bubbletree").service("bubbletreeSvc",e),e.$inject=["$log"]}();
\ No newline at end of file
diff --git a/example.html b/example.html
new file mode 100644
index 0000000..2da9337
--- /dev/null
+++ b/example.html
@@ -0,0 +1,53 @@
+
+
+
+Angular Bubble Tree
+
+
+
+
+
+
+
+
+
+
+
Angular Bubble Tree
+
+
+Click the following button to lead the data from data.json and bind the chart
Load
+If you are facing an issues or have an enquiry, please submit a case on ithub issue
here
+
+
+
+
+
+
+
diff --git a/exampleTooltip.html b/exampleTooltip.html
new file mode 100644
index 0000000..76cba33
--- /dev/null
+++ b/exampleTooltip.html
@@ -0,0 +1,4 @@
+Soon
+{{node.name}}
+{{test}}
+end
\ No newline at end of file
diff --git a/gulpfile.js b/gulpfile.js
new file mode 100644
index 0000000..9062920
--- /dev/null
+++ b/gulpfile.js
@@ -0,0 +1,151 @@
+var gulp = require('gulp');
+var Server = require('karma').Server;
+var concat = require('gulp-concat');
+var uglify = require('gulp-uglify');
+var rename = require('gulp-rename');
+var path = require('path');
+var plumber = require('gulp-plumber');
+var runSequence = require('run-sequence');
+var jshint = require('gulp-jshint');
+var concatCss = require('gulp-concat-css');
+var templateCache = require('gulp-angular-templatecache');
+var eventStream = require('event-stream');
+var webserver = require('gulp-webserver');
+
+/**
+ * File patterns
+ **/
+
+// Root directory
+var rootDirectory = path.resolve('./');
+
+// Source directory for build process
+var sourceDirectory = path.join(rootDirectory, './modules');
+
+var sourceFiles = [
+
+ // Make sure module files are handled first
+ path.join(sourceDirectory, '/**/*.module.js'),
+
+ // Then add all JavaScript files
+ path.join(sourceDirectory, '/**/*.js')
+];
+
+var stylesheets = [
+ path.join(sourceDirectory, '/**/*.css')
+];
+
+var templates = [
+ path.join(sourceDirectory, '/**/*.tpl.html')
+];
+
+var lintFiles = [
+ 'gulpfile.js',
+ // Karma configuration
+ 'karma-*.conf.js'
+].concat(sourceFiles);
+
+function getTemplateCache() {
+ return gulp.src(templates)
+ .pipe(plumber())
+ .pipe(templateCache({
+ module: 'angular-bubbletree.directives'
+ }));
+}
+
+gulp.task('build', function() {
+ return eventStream.merge(gulp.src(sourceFiles), getTemplateCache())
+ .pipe(plumber())
+ .pipe(concat('angular-bubbletree.js'))
+ .pipe(gulp.dest('./dist/'))
+ .pipe(uglify())
+ .pipe(rename('angular-bubbletree.min.js'))
+ .pipe(gulp.dest('./dist'));
+});
+
+gulp.task('build-stylesheets', function() {
+ return gulp.src(stylesheets)
+ .pipe(plumber())
+ .pipe(concatCss('angular-bubbletree.css'))
+ .pipe(gulp.dest('./dist'));
+});
+
+/**
+ * Process
+ */
+gulp.task('process-all', function(done) {
+ runSequence('jshint', 'test-src', 'build', done);
+});
+
+/**
+ * Watch task
+ */
+gulp.task('watch', function() {
+
+ // Watch JavaScript files
+ gulp.watch([sourceFiles, templates], ['process-all']);
+ gulp.watch([stylesheets], ['build-stylesheets']);
+});
+
+/**
+ * Validate source JavaScript
+ */
+gulp.task('jshint', function() {
+ gulp.src(lintFiles)
+ .pipe(plumber())
+ .pipe(jshint())
+ .pipe(jshint.reporter('jshint-stylish'))
+ .pipe(jshint.reporter('fail'));
+});
+
+/**
+ * Run test once and exit
+ */
+gulp.task('test-src', function(done) {
+ new Server({
+ configFile: __dirname + '/karma-src.conf.js',
+ singleRun: true
+ }, done).start();
+
+});
+
+/**
+ * Run test once and exit
+ */
+gulp.task('test-dist-concatenated', function(done) {
+ new Server({
+ configFile: __dirname + '/karma-dist-concatenated.conf.js',
+ singleRun: true
+ }, done).start();
+});
+
+/**
+ * Run test once and exit
+ */
+gulp.task('test-dist-minified', function(done) {
+ new Server({
+ configFile: __dirname + '/karma-dist-minified.conf.js',
+ singleRun: true
+ }, done).start();
+
+ // karma.start({
+ // configFile: __dirname + '/karma-dist-minified.conf.js',
+ // singleRun: true
+ // }, done);
+});
+
+gulp.task('webserver', function() {
+ gulp.src('./')
+ .pipe(webserver({
+ host: 'localhost',
+ port: 8000,
+ open: 'http://localhost:8000/example.html',
+ livereload: true,
+ directoryListing: true
+ // open: true
+ }));
+});
+
+gulp.task('default', function() {
+ runSequence('process-all', 'build-stylesheets', 'watch', 'webserver');
+});
\ No newline at end of file
diff --git a/karma-dist-concatenated.conf.js b/karma-dist-concatenated.conf.js
new file mode 100644
index 0000000..f35f207
--- /dev/null
+++ b/karma-dist-concatenated.conf.js
@@ -0,0 +1,77 @@
+// Karma configuration
+// Generated on Thu Aug 21 2014 10:24:39 GMT+0200 (CEST)
+
+module.exports = function(config) {
+ config.set({
+
+ // base path that will be used to resolve all patterns (eg. files, exclude)
+ basePath: '',
+
+
+ // frameworks to use
+ // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
+ frameworks: ['mocha', 'chai-jquery', 'jquery-1.8.3', 'sinon-chai'],
+
+ plugins: [
+ 'karma-mocha',
+ 'karma-chai',
+ 'karma-sinon-chai',
+ 'karma-chrome-launcher',
+ 'karma-phantomjs-launcher',
+ 'karma-jquery',
+ 'karma-chai-jquery'
+ ],
+
+ // list of files / patterns to load in the browser
+ files: [
+ 'bower/angular/angular.js',
+ 'bower/angular-mocks/angular-mocks.js',
+ 'dist/angular-bubbletree.js',
+ 'test/unit/**/*.js'
+ ],
+
+
+ // list of files to exclude
+ exclude: [
+ ],
+
+
+ // preprocess matching files before serving them to the browser
+ // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
+ preprocessors: {
+ },
+
+
+ // test results reporter to use
+ // possible values: 'dots', 'progress'
+ // available reporters: https://npmjs.org/browse/keyword/karma-reporter
+ reporters: ['progress'],
+
+
+ // web server port
+ port: 9876,
+
+
+ // enable / disable colors in the output (reporters and logs)
+ colors: true,
+
+
+ // level of logging
+ // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
+ logLevel: config.LOG_INFO,
+
+
+ // enable / disable watching file and executing tests whenever any file changes
+ autoWatch: true,
+
+
+ // start these browsers
+ // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
+ browsers: ['PhantomJS'],
+
+
+ // Continuous Integration mode
+ // if true, Karma captures browsers, runs the tests and exits
+ singleRun: false
+ });
+};
diff --git a/karma-dist-minified.conf.js b/karma-dist-minified.conf.js
new file mode 100644
index 0000000..62c5686
--- /dev/null
+++ b/karma-dist-minified.conf.js
@@ -0,0 +1,77 @@
+// Karma configuration
+// Generated on Thu Aug 21 2014 10:24:39 GMT+0200 (CEST)
+
+module.exports = function(config) {
+ config.set({
+
+ // base path that will be used to resolve all patterns (eg. files, exclude)
+ basePath: '',
+
+
+ // frameworks to use
+ // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
+ frameworks: ['mocha', 'chai-jquery', 'jquery-1.8.3', 'sinon-chai'],
+
+ plugins: [
+ 'karma-mocha',
+ 'karma-chai',
+ 'karma-sinon-chai',
+ 'karma-chrome-launcher',
+ 'karma-phantomjs-launcher',
+ 'karma-jquery',
+ 'karma-chai-jquery'
+ ],
+
+ // list of files / patterns to load in the browser
+ files: [
+ 'bower/angular/angular.js',
+ 'bower/angular-mocks/angular-mocks.js',
+ 'dist/angular-bubbletree.min.js',
+ 'test/unit/**/*.js'
+ ],
+
+
+ // list of files to exclude
+ exclude: [
+ ],
+
+
+ // preprocess matching files before serving them to the browser
+ // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
+ preprocessors: {
+ },
+
+
+ // test results reporter to use
+ // possible values: 'dots', 'progress'
+ // available reporters: https://npmjs.org/browse/keyword/karma-reporter
+ reporters: ['progress'],
+
+
+ // web server port
+ port: 9876,
+
+
+ // enable / disable colors in the output (reporters and logs)
+ colors: true,
+
+
+ // level of logging
+ // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
+ logLevel: config.LOG_INFO,
+
+
+ // enable / disable watching file and executing tests whenever any file changes
+ autoWatch: true,
+
+
+ // start these browsers
+ // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
+ browsers: ['PhantomJS'],
+
+
+ // Continuous Integration mode
+ // if true, Karma captures browsers, runs the tests and exits
+ singleRun: false
+ });
+};
diff --git a/karma-src.conf.js b/karma-src.conf.js
new file mode 100644
index 0000000..49fc31d
--- /dev/null
+++ b/karma-src.conf.js
@@ -0,0 +1,79 @@
+// Karma configuration
+// Generated on Thu Aug 21 2014 10:24:39 GMT+0200 (CEST)
+
+module.exports = function(config) {
+ config.set({
+
+ // base path that will be used to resolve all patterns (eg. files, exclude)
+ basePath: '',
+
+
+ // frameworks to use
+ // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
+ frameworks: ['mocha', 'chai-jquery', 'jquery-1.8.3', 'sinon-chai'],
+
+ plugins: [
+ 'karma-mocha',
+ 'karma-chai',
+ 'karma-sinon-chai',
+ 'karma-chrome-launcher',
+ 'karma-phantomjs-launcher',
+ 'karma-jquery',
+ 'karma-chai-jquery',
+ 'karma-spec-reporter'
+ ],
+
+ // list of files / patterns to load in the browser
+ files: [
+ 'bower/angular/angular.js',
+ 'bower/angular-mocks/angular-mocks.js',
+ 'modules/**/*.module.js',
+ 'modules/**/*.js',
+ 'test/unit/**/*.js'
+ ],
+
+
+ // list of files to exclude
+ exclude: [
+ ],
+
+
+ // preprocess matching files before serving them to the browser
+ // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
+ preprocessors: {
+ },
+
+
+ // test results reporter to use
+ // possible values: 'dots', 'progress'
+ // available reporters: https://npmjs.org/browse/keyword/karma-reporter
+ reporters: ['progress', 'spec'],
+
+
+ // web server port
+ port: 9876,
+
+
+ // enable / disable colors in the output (reporters and logs)
+ colors: true,
+
+
+ // level of logging
+ // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
+ logLevel: config.LOG_INFO,
+
+
+ // enable / disable watching file and executing tests whenever any file changes
+ autoWatch: true,
+
+
+ // start these browsers
+ // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
+ browsers: ['PhantomJS'],
+
+
+ // Continuous Integration mode
+ // if true, Karma captures browsers, runs the tests and exits
+ singleRun: false
+ });
+};
diff --git a/modules/angular-bubbletree/angular-bubbletree.module.js b/modules/angular-bubbletree/angular-bubbletree.module.js
new file mode 100644
index 0000000..7364c80
--- /dev/null
+++ b/modules/angular-bubbletree/angular-bubbletree.module.js
@@ -0,0 +1,35 @@
+(function (angular) {
+
+ // Create all modules and define dependencies to make sure they exist
+ // and are loaded in the correct order to satisfy dependency injection
+ // before all nested files are concatenated by Gulp
+
+ // Config
+ angular.module('angular-bubbletree.config', [])
+ .value('angular-bubbletree.config', {
+ debug: true
+ });
+
+ // Modules
+
+ angular.module('angular-bubbletree.directives', []);
+
+
+ angular.module('angular-bubbletree.filters', []);
+
+
+ angular.module('angular-bubbletree.services', []);
+
+
+ angular.module('angular-bubbletree.controllers', []);
+
+ angular.module('angular-bubbletree',
+ [
+ 'angular-bubbletree.config',
+ 'angular-bubbletree.directives',
+ 'angular-bubbletree.filters',
+ 'angular-bubbletree.services',
+ 'angular-bubbletree.controllers'
+ ]);
+
+})(angular);
diff --git a/modules/angular-bubbletree/directives/bubbletree.css b/modules/angular-bubbletree/directives/bubbletree.css
new file mode 100644
index 0000000..b5293b5
--- /dev/null
+++ b/modules/angular-bubbletree/directives/bubbletree.css
@@ -0,0 +1,45 @@
+.svg-container {
+ /*border: 4px solid red;*/
+ width: 99%;
+ height: 600px;
+ background: #fff;
+ margin: 0 auto;
+}
+
+
+
+.btNode circle {
+ cursor: pointer;
+ stroke: #3182bd;
+ stroke-width: 1.5px;
+}
+
+.btNode text {
+ font: 10px sans-serif;
+ pointer-events: none;
+ text-anchor: middle;
+}
+
+.btLink {
+ fill: none;
+ stroke: #9ecae1;
+ stroke-width: 1.5px;
+}
+
+div.btTooltip {
+ position: absolute;
+ text-align: center;
+ vertical-align: middle;
+ width: 100px;
+ height: 38px;
+ padding: 2px;
+ font: 11px sans-serif;
+ color: #153851;
+ background: lightsteelblue;
+ border-radius: 8px;
+ pointer-events: none;
+ border-style: solid;
+ border-color: #8cb3d9;
+ border-width: 0.01em;
+ box-shadow: 2px 2px 5px #888888;
+}
\ No newline at end of file
diff --git a/modules/angular-bubbletree/directives/bubbletree.directive.js b/modules/angular-bubbletree/directives/bubbletree.directive.js
new file mode 100644
index 0000000..9dab119
--- /dev/null
+++ b/modules/angular-bubbletree/directives/bubbletree.directive.js
@@ -0,0 +1,216 @@
+(function() {
+ 'use strict';
+ angular.module('angular-bubbletree').directive('bubbletree', bubbletree);
+ bubbletree.$inject = ['$log', 'bubbletreeSvc'];
+
+ function bubbletree(log, svc) {
+
+ return {
+ name: 'bubbletree',
+ scope: {
+ datasource: '=',
+ control: '='
+ },
+ restrict: 'E',
+ replace: true,
+ transclude: false,
+ link: function postLink(scope, element, attrs) {
+ var width = '100%',
+ height = '100%',
+ force, svg, div, link, node,
+ datasource, scaledData = [];
+
+ scope.internalCtrl = scope.control || {};
+
+ scope.internalCtrl.bind = function(data) {
+ log.debug('Binding bubble tree chart');
+ if (data) {
+ datasource = data;
+ render();
+ log.debug('Binding completed');
+ }
+ };
+
+ function initialize() {
+ var dummy;
+ scope.params = {};
+ dummy = attrs.id ? scope.params.id = attrs.id : scope.params.id = 'bubbletree';
+ dummy = attrs.width ? scope.params.width = attrs.width : scope.params.width = 1000;
+ dummy = attrs.height ? scope.params.height = attrs.height : scope.params.height = 400;
+ dummy = attrs.linkDistance ? scope.params.linkDistance = attrs.linkDistance : scope.params.linkDistance = 100;
+ dummy = attrs.enableTooltip ? scope.params.enableTooltip = scope.$eval(attrs.enableTooltip) : scope.params.enableTooltip = true;
+ dummy = attrs.fillStyle && ['solid', 'gradient'].indexOf(attrs.fillStyle) >= 0 ? scope.params.fillStyle = attrs.fillStyle : scope.params.fillStyle = 'gradient';
+ dummy = attrs.parentColor ? scope.params.parentColor = attrs.parentColor : scope.params.parentColor = '#336699';
+ dummy = attrs.childColor ? scope.params.childColor = attrs.childColor : scope.params.childColor = '#e0e0eb';
+ dummy = attrs.collapsedColor ? scope.params.collapsedColor = attrs.collapsedColor : scope.params.collapsedColor = '#19334d';
+ dummy = attrs.linkColor ? scope.params.linkColor = attrs.linkColor : scope.params.linkColor = '#9ecae1';
+ dummy = attrs.textX ? scope.params.textX = attrs.textX : scope.params.textX = '1.45em';
+ dummy = attrs.textY ? scope.params.textY = attrs.textY : scope.params.textY = '3.45em';
+ dummy = attrs.scaleScore ? scope.params.scaleScore = attrs.scaleScore : scope.params.scaleScore = 10;
+
+
+ force = d3.layout.force()
+ .linkDistance(scope.params.linkDistance)
+ .charge(-120)
+ .gravity(0.01)
+ .size([scope.params.width, scope.params.height])
+ .on('tick', tick);
+
+ svg = d3.select('#' + attrs.id)
+ .append('svg')
+ .attr('width', width)
+ .attr('height', height);
+
+ link = svg.selectAll('.btLink');
+ node = svg.selectAll('.btNode');
+
+ if (scope.params.enableTooltip) {
+ div = d3.select('#' + attrs.id).append('div')
+ .attr('class', 'btTooltip')
+ .style('opacity', 0);
+ }
+
+ }
+
+ initialize();
+
+
+ function render() {
+ var nodes = svc.flatten(datasource, scaledData),
+ links = d3.layout.tree().links(nodes);
+
+ // Restart the force layout.
+ force
+ .nodes(nodes)
+ .links(links)
+ .start();
+
+ // Update links.
+ link = link.data(links, function(d) {
+ return d.target.id;
+ });
+
+ link.exit().remove();
+
+ link.enter().insert('line', '.btNode')
+ .attr('class', 'btLink');
+ if (scope.params.linkColor) {
+ link.attr('style', 'stroke:' + scope.params.linkColor + ';');
+ }
+
+ // Update nodes.
+ node = node.data(nodes, function(d) {
+ return d.id;
+ });
+
+ node.exit().remove();
+
+ var nodeEnter = node.enter().append('g')
+ .attr('class', 'btNode')
+ .on('click', click)
+ .call(force.drag);
+
+
+ nodeEnter.append('circle')
+ .on('mouseover', function(d) {
+ if (scope.params.enableTooltip) {
+ div.transition()
+ .duration(1000)
+ .style('opacity', 0.9);
+
+ var pageX = d3.event.pageX;
+ var pageY = d3.event.pageY;
+ var data = d;
+ // http.get('exampleTooltip.html').then(function(result) {
+ // var element = angular.element('
');
+ // element.append(result.data);
+ // var newScope = scope.$new();
+ // newScope.node = data;
+ // newScope.test = 'i am data';
+ // var compiled = compile(element)(newScope);
+ // console.log(compiled);
+ // // newScope.$digest();
+ // div.html(compiled[0].innerHTML)
+ // .style('left', (pageX - 120) + 'px')
+ // .style('top', (pageY - 12) + 'px');
+ // });
+ var size = data.size ? data.size : '';
+ div.html(data.name + ' ' + size)
+ .style('left', (pageX - 120) + 'px')
+ .style('top', (pageY - 12) + 'px');
+ }
+
+ })
+ .on('mouseout', function(d) {
+ if (scope.params.enableTooltip) {
+ div.transition()
+ .duration(500)
+ .style('opacity', 0);
+ }
+ })
+ .attr('r', function(d) {
+ // console.log(svc.linearScale(d.size, datasource));
+ var min = d3.min(scaledData);
+ var max = d3.max(scaledData);
+ return svc.linearScale(d.size, datasource, min, max, scope.params.scaleScore) || 10;
+ });
+
+ nodeEnter.append('text')
+ .attr('dy', scope.params.textY)
+ .attr('dx', scope.params.textX)
+ .text(function(d) {
+ return d.name;
+ });
+
+ node.select('circle')
+ .style('fill', function(d) {
+ if (scope.params.fillStyle === 'solid') {
+ return svc.colorSolid(d, scope.params);
+ } else {
+ return svc.colorGradient(svg, d, scope.params);
+ }
+
+ });
+ }
+
+ function tick() {
+ link.attr('x1', function(d) {
+ return d.source.x;
+ })
+ .attr('y1', function(d) {
+ return d.source.y;
+ })
+ .attr('x2', function(d) {
+ return d.target.x;
+ })
+ .attr('y2', function(d) {
+ return d.target.y;
+ });
+
+ node.attr('transform', function(d) {
+ return 'translate(' + d.x + ',' + d.y + ')';
+ });
+ }
+
+
+ // Toggle children on click.
+ function click(d) {
+ if (d3.event.defaultPrevented) {
+ return;
+ } // ignore drag
+ if (d.children) {
+ d._children = d.children;
+ d.children = null;
+ } else {
+ d.children = d._children;
+ d._children = null;
+ }
+ render();
+ }
+
+ }
+
+ };
+ }
+
+})();
\ No newline at end of file
diff --git a/modules/angular-bubbletree/services/bubbletree.service.js b/modules/angular-bubbletree/services/bubbletree.service.js
new file mode 100644
index 0000000..bee438e
--- /dev/null
+++ b/modules/angular-bubbletree/services/bubbletree.service.js
@@ -0,0 +1,75 @@
+(function() {
+ 'use strict';
+ angular.module('angular-bubbletree').service('bubbletreeSvc', bubbletreeSvc);
+ bubbletreeSvc.$inject = ['$log'];
+
+ function bubbletreeSvc(log) {
+ var service = {};
+
+ service.linearScale = function(value, scaledData, min, max, scale) {
+ var scaled = d3.scale.linear()
+ .domain([min, max])
+ .range([1 * scale, 2 * scale]);
+ return scaled(value);
+ };
+
+ // Returns a list of all nodes under the datasource.
+ service.flatten = function(datasource, scaledData) {
+ var nodes = [],
+ i = 0;
+
+ function recurse(node) {
+ if (node.size) {
+ scaledData.push(node.size);
+ }
+ if (node.children) {
+ node.children.forEach(recurse);
+ }
+ if (!node.id) {
+ node.id = ++i;
+ }
+ nodes.push(node);
+ }
+ recurse(datasource);
+ return nodes;
+ };
+
+ service.colorSolid = function(d, params) {
+ return d._children ? params.collapsedColor //'#19334d' // collapsed package
+ : d.children ? params.parentColor //'#336699' // expanded package
+ : params.childColor; //'#e0e0eb'; // leaf node
+ };
+
+ service.colorGradient = function(svg, d) {
+ var color = '#fff',
+ id = 'btGradient';
+
+ // Define the gradient
+ var gradient = svg.append('svg:defs')
+ .append('svg:linearGradient')
+ .attr('id', id)
+ .attr('x1', '0%')
+ .attr('y1', '0%')
+ .attr('x2', '100%')
+ .attr('y2', '100%')
+ .attr('spreadMethod', 'pad');
+
+ // Define the gradient colors
+ gradient.append('svg:stop')
+ .attr('offset', '0%')
+ .attr('stop-color', '#999')
+ .attr('stop-opacity', 1);
+
+ gradient.append('svg:stop')
+ .attr('offset', '100%')
+ .attr('stop-color', color)
+ .attr('stop-opacity', 1);
+
+ return 'url(#' + id + ')';
+ };
+
+ return service;
+
+ }
+
+})();
\ No newline at end of file
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..5e4a215
--- /dev/null
+++ b/package.json
@@ -0,0 +1,65 @@
+{
+ "name": "angular-bubbletree",
+ "version": "0.1.0",
+ "description": "Angular bubble tree chart using d3",
+ "keywords": [
+ "d3",
+ "angular",
+ "chart",
+ "responsive",
+ "related",
+ "bubble"
+ ],
+ "author": {
+ "name": "Amgad Fahmi",
+ "email": "amgadnady@gmail.com"
+ },
+ "scripts": {
+ "ci": "gulp test-src"
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/amgadfahmi/angular-bubbletree.git"
+ },
+ "license": "ALv2",
+ "dependencies": {
+ "d3": "^3.5.17"
+ },
+ "devDependencies": {
+ "chai": "^3.5.0",
+ "chai-jquery": "^2.0.0",
+ "event-stream": "^3.3.2",
+ "gulp": "^3.8.7",
+ "gulp-angular-templatecache": "^1.8.0",
+ "gulp-concat": "^2.3.4",
+ "gulp-concat-css": "^2.2.0",
+ "gulp-jshint": "^2.0.1",
+ "gulp-plumber": "^1.1.0",
+ "gulp-rename": "^1.2.0",
+ "gulp-scss": "^1.3.15",
+ "gulp-uglify": "^1.5.3",
+ "gulp-webserver": "^0.9.1",
+ "jshint": "^2.9.2",
+ "jshint-stylish": "^2.2.0",
+ "karma": "^0.13.22",
+ "karma-chai": "^0.1.0",
+ "karma-chai-jquery": "^1.0.0",
+ "karma-chrome-launcher": "^1.0.1",
+ "karma-jasmine": "^1.0.2",
+ "karma-jquery": "^0.1.0",
+ "karma-mocha": "^1.0.1",
+ "karma-ng-html2js-preprocessor": "^1.0.0",
+ "karma-phantomjs-launcher": "^1.0.0",
+ "karma-sinon-chai": "^1.2.0",
+ "karma-spec-reporter": "^0.0.26",
+ "mocha": "^2.5.3",
+ "ng-html2js": "^2.0.0",
+ "phantomjs-prebuilt": "^2.1.7",
+ "run-sequence": "^1.0.2",
+ "sinon": "^1.10.3",
+ "sinon-chai": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=0.8.0"
+ }
+}
diff --git a/test/unit/angular-bubbletree/angular-bubbletreeSpec.js b/test/unit/angular-bubbletree/angular-bubbletreeSpec.js
new file mode 100644
index 0000000..e170161
--- /dev/null
+++ b/test/unit/angular-bubbletree/angular-bubbletreeSpec.js
@@ -0,0 +1,48 @@
+'use strict';
+
+describe('initialize', function() {
+
+ var module;
+ var dependencies;
+ dependencies = [];
+
+ var hasModule = function(module) {
+ return dependencies.indexOf(module) >= 0;
+ };
+
+ beforeEach(function() {
+
+ // Get module
+ module = angular.module('angular-bubbletree');
+ dependencies = module.requires;
+ });
+
+ it('should load config module', function() {
+ expect(hasModule('angular-bubbletree.config')).to.be.ok;
+ });
+
+
+ it('should load filters module', function() {
+ expect(hasModule('angular-bubbletree.filters')).to.be.ok;
+ });
+
+
+
+ it('should load directives module', function() {
+ expect(hasModule('angular-bubbletree.directives')).to.be.ok;
+ });
+
+
+
+ it('should load services module', function() {
+ expect(hasModule('angular-bubbletree.services')).to.be.ok;
+ });
+
+
+
+ it('should load controllers module', function() {
+ expect(hasModule('angular-bubbletree.controllers')).to.be.ok;
+ });
+
+
+});